Files
kill_unwanted/kill_unwanted.sh
2026-08-16 13:37:42 +02:00

294 lines
6.5 KiB
Bash
Executable File

#!/bin/bash
# === SETTINGS ===
EXLUDE_USR=(root jhub nginx)
RESCAN_S=3
MAX_CPU_P=50
MAX_RAM_GB=32
IMMUNITY_TIME_S=15
HARDKILL_DELAY_S=10
NERDFONT=0
COLOR=0
KILL=0
HARDKILL=0
# === === ===
# ansi
UNDERLINE="\e[4m"
BOLD="\e[1m"
BLINK="\e[5m"
ORANGE="\e[38;5;208m"
RED="\033[31m"
GREEN="\033[32m"
RESET="\033[0m"
# icons
ICON_RAM=" "
ICON_CPU=" "
ICON_MAG="󰍉"
ICON_SKULL=" "
SIGTERM=15
SIGKILL=9
SCANNED_PIDS=()
print_banner() {
cat <<"EOF"
_ _ _ _ _ _
| | (_) | | | | | |
| | ___| | |_____ _ _ _ ____ ____ _ _ __ | |_ ___ __| |
| |/ / | | |_____| | | | '_ \ \ /\ / / _` | '_ \| __/ _ \/ _` |
| <| | | | | |_| | | | \ V V / (_| | | | | || __/ (_| |
|_|\_\_|_|_| \__,_|_| |_|\_/\_/ \__,_|_| |_|\__\___|\__,_|
EOF
}
show_help() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Options:
-m, --memory [GiB]
Set maximum allowed RAM usage.
If no value is given, prints current value.
Default: ${MAX_RAM_GB} GiB
-t, --time [SECONDS]
Set process immunity time.
If no value is given, prints current value.
Default: ${IMMUNITY_TIME_S} s
-u, --utilization [PERCENT]
Set maximum allowed CPU usage.
If no value is given, prints current value.
Default: ${MAX_CPU_P} %
-r, --refresh [SECONDS]
Set time between rescans.
If no value is given, prints current value.
Default: ${RESCAN_S} s
-k, --kill
Send SIGTERM to detected processes.
-h, --hardkill [SECONDS]
Send SIGTERM and after a delay
SIGKILL to detected processes.
Optionally a delay can be specified.
Default: ${HARDKILL_DELAY_S} s
-n, --nerdfonts
Enable Nerd Font icons.
-c, --color
Enable colored output.
-h, --help
Display this help message and exit.
Examples:
$(basename "$0") --memory 8
$(basename "$0") -t 60 -u 10 -m 1 -r 2 -c
$(basename "$0") --utilization -c
$(basename "$0") -m -u -r 2 -n -c
EOF
}
kill_ps() {
VICTIM_PID="${1}"
if [[ "$KILL" == "0" ]]; then
return
fi
echo "${ICON_SKULL}killing"
kill "-$SIGTERM" "${VICTIM_PID}"
if [[ "$HARDKILL" == "0" ]]; then
return
fi
(
sleep ${HARDKILL_DELAY_S};
if kill -0 "${VICTIM_PID}" > /dev/zero; then
# still alive
echo -e "${ICON_SKULL}Terminating (${VICTIM_PID})"
kill "-${SIGKILL}" "${VICTIM_PID}"
else
echo "{ICON_SKULL}Died successfully ($VICTIM_PID)"
fi
) &
}
# === ARG PARSING ===
print_banner
echo ""
while [ "$#" -gt 0 ]; do
case "$1" in
-m|--memory)
if [[ -n $2 && "$2" != -* ]]; then
MAX_RAM_GB="${2}"
echo -e "- Maximum allowed RAM set to ${2}GiB"
shift
else
echo -e "- Maximum allowed RAM usage: ${MAX_RAM_GB}GiB"
fi
;;
-t|--time)
if [[ -n $2 && "$2" != -* ]]; then
IMMUNITY_TIME_S="${2}"
echo -e "- Process immunity time set to ${2}s"
shift
else
echo -e "- Process immunity time: ${IMMUNITY_TIME_S}s"
fi
;;
-u|--utilization)
if [[ -n $2 && "$2" != -* ]]; then
MAX_CPU_P="${2}"
echo -e "- Maximum allowed CPU usage set to ${2}%"
shift
else
echo -e "- Maximum allowed CPU usage: ${MAX_CPU_P}%"
fi
;;
-r|--refresh)
if [[ -n $2 && "$2" != -* ]]; then
RESCAN_S="${2}"
shift
else
echo -e "- Rescan time set to: ${RESCAN_S}s"
fi
;;
-k|--kill)
KILL=1
echo "- Killing enabled"
;;
-h|--hardkill)
KILL=1
HARDKILL=1
if [[ -n $2 && "$2" != -* ]]; then
HARDKILL_DELAY_S="${2}"
shift
fi
echo -e "- Hardkill enabled with delay: ${HARDKILL_DELAY_S}s"
;;
-n|--nerdfonts)
# enable nerdfont icons
NERDFONT=1
;;
-c|--color)
# enable color
COLOR=1
;;
--help)
show_help
exit 0
;;
*)
echo "! ERROR: unknown command: ${1}"
exit 0
esac
shift
done
# setting icons according to input
if [[ "$NERDFONT" == "0" ]]; then
ICON_RAM=""
ICON_CPU=""
ICON_MAG=""
ICON_SKULL=""
fi
# setting ansi according to input
if [[ "$COLOR" == "0" ]]; then
UNDERLINE=""
BOLD=""
BLINK=""
ORANGE=""
RED=""
GREEN=""
RESET=""
fi
sleep 0.4
echo -e "${GREEN}${UNDERLINE}\n${ICON_MAG} starting scan..${RESET}"
# === SCAN ===
while true; do
#echo "SCANNED: ${SCANNED_PIDS[@]}"
while read -r PID TIME USER MEM CPU; do
# === Check if process should be excluded ===
skip=0
MEM_GB=$(awk "BEGIN {print $MEM/1024/1024}") # KiB -> GiB
for user_i in "${EXLUDE_USR[@]}"; do
if [[ "$user_i" == "$USER" ]]; then
skip=1
break
fi
done
for element in "${SCANNED_PIDS[@]}"; do
if [[ "$PID" == "$element" ]]; then
skip=1
break
fi
done
if [[ "$TIME" -lt "$IMMUNITY_TIME_S" ]]; then
skip=1
fi
if [[ $skip -eq 1 ]]; then
continue
fi
# === Check if ps should be killed ===
NAME="$(ps -p $PID -o args=)"
KILL_PREFIX="-> \"${RED}${NAME}${RESET}\" (PID: ${PID})"
should_die=0
# check memory usage
if awk "BEGIN {exit !(${MEM_GB} > ${MAX_RAM_GB})}"; then
# kill ps
echo -e "${KILL_PREFIX} - ${ICON_RAM}RAM usage too high (${ORANGE}${MEM_GB}GiB${RESET} > ${MAX_RAM_GB}GiB)"
should_die=1
fi
# check cpu usage
if awk "BEGIN {exit !(${CPU} > ${MAX_CPU_P})}"; then
# kill ps
echo -e "${KILL_PREFIX} - ${ICON_CPU}CPU usage too high (${ORANGE}${CPU}%${RESET} > ${MAX_CPU_P}%)"
should_die=1
fi
if [[ "$should_die" == 1 ]]; then
SCANNED_PIDS+=($PID)
kill_ps "$PID"
continue
fi
# next ps
done < <(ps -eo pid,etimes,user,rss,%cpu --no-headers | tr -s " ")
## no process found - repeat
sleep ${RESCAN_S}
done