269 lines
10 KiB
Bash
269 lines
10 KiB
Bash
#!/bin/bash
|
||
###############################################################################
|
||
# AdGuard Shield - Offense-Cleanup-Worker
|
||
# Räumt abgelaufene Offense-Zähler (progressive Sperren) automatisch auf.
|
||
# Entfernt .offenses-Dateien, deren letztes Vergehen länger als
|
||
# PROGRESSIVE_BAN_RESET_AFTER zurückliegt.
|
||
# Wird als Hintergrundprozess vom Hauptscript gestartet.
|
||
#
|
||
# Autor: Patrick Asmus
|
||
# E-Mail: support@techniverse.net
|
||
# Datum: 2026-04-16
|
||
# Lizenz: MIT
|
||
###############################################################################
|
||
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
CONFIG_FILE="${SCRIPT_DIR}/adguard-shield.conf"
|
||
|
||
# ─── Konfiguration laden ───────────────────────────────────────────────────────
|
||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||
echo "FEHLER: Konfigurationsdatei nicht gefunden: $CONFIG_FILE" >&2
|
||
exit 1
|
||
fi
|
||
# shellcheck source=adguard-shield.conf
|
||
source "$CONFIG_FILE"
|
||
|
||
# ─── Niedrigste Priorität setzen (CPU + I/O) ─────────────────────────────────
|
||
# Stellt sicher, dass der Worker auch bei manuellem Start nie andere Dienste
|
||
# verdrängt. nice 19 = niedrigste CPU-Priorität, ionice idle = nur bei freier I/O.
|
||
renice -n 19 $$ >/dev/null 2>&1 || true
|
||
ionice -c 3 -p $$ >/dev/null 2>&1 || true
|
||
|
||
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
||
WORKER_PID_FILE="/var/run/adguard-offense-cleanup-worker.pid"
|
||
|
||
# ─── Prüfintervall ───────────────────────────────────────────────────────────
|
||
# Prüft einmal pro Stunde – das ist völlig ausreichend für diese Aufgabe
|
||
OFFENSE_CLEANUP_INTERVAL=3600
|
||
|
||
# ─── Logging (eigene Funktion, nutzt gleiche Log-Datei) ───────────────────────
|
||
declare -A LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
|
||
|
||
log() {
|
||
local level="$1"
|
||
shift
|
||
local message="$*"
|
||
local configured_level="${LOG_LEVEL:-INFO}"
|
||
|
||
if [[ ${LOG_LEVELS[$level]:-1} -ge ${LOG_LEVELS[$configured_level]:-1} ]]; then
|
||
local timestamp
|
||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||
local log_entry="[$timestamp] [$level] [OFFENSE-CLEANUP] $message"
|
||
echo "$log_entry" | tee -a "$LOG_FILE" >&2
|
||
fi
|
||
}
|
||
|
||
# ─── Hilfsfunktionen ─────────────────────────────────────────────────────────
|
||
format_duration() {
|
||
local seconds="$1"
|
||
if [[ "$seconds" -eq 0 ]]; then
|
||
echo "PERMANENT"
|
||
return
|
||
fi
|
||
if [[ "$seconds" -ge 86400 ]]; then
|
||
echo "$((seconds / 86400))d $((seconds % 86400 / 3600))h"
|
||
elif [[ "$seconds" -ge 3600 ]]; then
|
||
echo "$((seconds / 3600))h $((seconds % 3600 / 60))m"
|
||
elif [[ "$seconds" -ge 60 ]]; then
|
||
echo "$((seconds / 60))m $((seconds % 60))s"
|
||
else
|
||
echo "${seconds}s"
|
||
fi
|
||
}
|
||
|
||
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
||
init_directories() {
|
||
mkdir -p "${STATE_DIR}"
|
||
mkdir -p "$(dirname "$LOG_FILE")"
|
||
}
|
||
|
||
# ─── Abgelaufene Offense-Zähler aufräumen ────────────────────────────────────
|
||
cleanup_expired_offenses() {
|
||
local reset_after="${PROGRESSIVE_BAN_RESET_AFTER:-86400}"
|
||
local now
|
||
now=$(date '+%s')
|
||
local cleaned=0
|
||
|
||
local batch_count=0
|
||
for offense_file in "${STATE_DIR}"/*.offenses; do
|
||
[[ -f "$offense_file" ]] || continue
|
||
|
||
local last_offense_epoch client_ip offense_level
|
||
last_offense_epoch=$(grep '^LAST_OFFENSE_EPOCH=' "$offense_file" | cut -d= -f2 || true)
|
||
client_ip=$(grep '^CLIENT_IP=' "$offense_file" | cut -d= -f2 || true)
|
||
offense_level=$(grep '^OFFENSE_LEVEL=' "$offense_file" | cut -d= -f2 || true)
|
||
|
||
# Kein Zeitstempel vorhanden → überspringen
|
||
if [[ -z "$last_offense_epoch" ]]; then
|
||
log "DEBUG" "Offense-Datei ohne Zeitstempel übersprungen: $offense_file"
|
||
continue
|
||
fi
|
||
|
||
local elapsed=$((now - last_offense_epoch))
|
||
|
||
if [[ $elapsed -gt $reset_after ]]; then
|
||
log "INFO" "Offense-Zähler abgelaufen: $client_ip (Stufe $offense_level, letztes Vergehen vor $(format_duration $elapsed)) → entfernt"
|
||
rm -f "$offense_file"
|
||
cleaned=$((cleaned + 1))
|
||
fi
|
||
|
||
# Alle 10 Dateien kurz pausieren, um I/O-Bursts zu vermeiden
|
||
batch_count=$((batch_count + 1))
|
||
if (( batch_count % 10 == 0 )); then
|
||
sleep 0.1
|
||
fi
|
||
done
|
||
|
||
if [[ $cleaned -gt 0 ]]; then
|
||
log "INFO" "Offense-Cleanup: $cleaned abgelaufene Zähler entfernt"
|
||
else
|
||
log "DEBUG" "Offense-Cleanup: keine abgelaufenen Zähler gefunden"
|
||
fi
|
||
}
|
||
|
||
# ─── PID-Management ──────────────────────────────────────────────────────────
|
||
write_pid() {
|
||
echo $$ > "$WORKER_PID_FILE"
|
||
}
|
||
|
||
cleanup() {
|
||
log "INFO" "Offense-Cleanup-Worker wird beendet..."
|
||
rm -f "$WORKER_PID_FILE"
|
||
exit 0
|
||
}
|
||
|
||
check_already_running() {
|
||
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||
local old_pid
|
||
old_pid=$(cat "$WORKER_PID_FILE")
|
||
if kill -0 "$old_pid" 2>/dev/null; then
|
||
log "DEBUG" "Offense-Cleanup-Worker läuft bereits (PID: $old_pid)"
|
||
return 1
|
||
else
|
||
rm -f "$WORKER_PID_FILE"
|
||
fi
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
# ─── Status anzeigen ─────────────────────────────────────────────────────────
|
||
show_status() {
|
||
echo "═══════════════════════════════════════════════════════════════"
|
||
echo " Offense-Cleanup-Worker - Status"
|
||
echo "═══════════════════════════════════════════════════════════════"
|
||
echo ""
|
||
|
||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" != "true" ]]; then
|
||
echo " ⚠️ Progressive Sperren sind deaktiviert"
|
||
echo " Aktivieren: PROGRESSIVE_BAN_ENABLED=true in $CONFIG_FILE"
|
||
echo ""
|
||
return
|
||
fi
|
||
|
||
# Worker-Prozess Status
|
||
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||
local pid
|
||
pid=$(cat "$WORKER_PID_FILE")
|
||
if kill -0 "$pid" 2>/dev/null; then
|
||
echo " 🟢 Worker läuft (PID: $pid)"
|
||
else
|
||
echo " 🔴 Worker nicht aktiv (veraltete PID-Datei)"
|
||
fi
|
||
else
|
||
echo " 🔴 Worker nicht aktiv"
|
||
fi
|
||
|
||
echo ""
|
||
echo " Reset-Zeitraum: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}")"
|
||
echo " Prüfintervall: $(format_duration "$OFFENSE_CLEANUP_INTERVAL")"
|
||
|
||
# Aktuelle Offense-Dateien zählen
|
||
local total=0
|
||
local expired=0
|
||
local now
|
||
now=$(date '+%s')
|
||
local reset_after="${PROGRESSIVE_BAN_RESET_AFTER:-86400}"
|
||
|
||
for offense_file in "${STATE_DIR}"/*.offenses; do
|
||
[[ -f "$offense_file" ]] || continue
|
||
total=$((total + 1))
|
||
local last_epoch
|
||
last_epoch=$(grep '^LAST_OFFENSE_EPOCH=' "$offense_file" | cut -d= -f2 || true)
|
||
if [[ -n "$last_epoch" && $((now - last_epoch)) -gt $reset_after ]]; then
|
||
expired=$((expired + 1))
|
||
fi
|
||
done
|
||
|
||
echo ""
|
||
echo " Offense-Zähler gesamt: $total"
|
||
echo " Davon abgelaufen: $expired"
|
||
echo ""
|
||
echo "═══════════════════════════════════════════════════════════════"
|
||
}
|
||
|
||
# ─── Hauptschleife ──────────────────────────────────────────────────────────
|
||
main_loop() {
|
||
init_directories
|
||
|
||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||
log "INFO" "Offense-Cleanup-Worker gestartet"
|
||
log "INFO" " Reset-Zeitraum: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}")"
|
||
log "INFO" " Prüfintervall: $(format_duration "$OFFENSE_CLEANUP_INTERVAL")"
|
||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||
|
||
while true; do
|
||
cleanup_expired_offenses
|
||
sleep "$OFFENSE_CLEANUP_INTERVAL"
|
||
done
|
||
}
|
||
|
||
# ─── Signal-Handler ──────────────────────────────────────────────────────────
|
||
trap cleanup SIGTERM SIGINT SIGHUP
|
||
|
||
# ─── Kommandozeilen-Argumente ────────────────────────────────────────────────
|
||
case "${1:-start}" in
|
||
start)
|
||
if ! check_already_running; then
|
||
exit 0
|
||
fi
|
||
write_pid
|
||
main_loop
|
||
;;
|
||
stop)
|
||
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||
kill "$(cat "$WORKER_PID_FILE")" 2>/dev/null || true
|
||
rm -f "$WORKER_PID_FILE"
|
||
echo "Offense-Cleanup-Worker gestoppt"
|
||
else
|
||
echo "Offense-Cleanup-Worker läuft nicht"
|
||
fi
|
||
;;
|
||
run-once)
|
||
init_directories
|
||
log "INFO" "Einmaliger Offense-Cleanup..."
|
||
cleanup_expired_offenses
|
||
log "INFO" "Cleanup abgeschlossen"
|
||
;;
|
||
status)
|
||
init_directories
|
||
show_status
|
||
;;
|
||
*)
|
||
cat << USAGE
|
||
AdGuard Shield - Offense-Cleanup-Worker
|
||
|
||
Nutzung: $0 {start|stop|run-once|status}
|
||
|
||
Befehle:
|
||
start Startet den Worker (Dauerbetrieb)
|
||
stop Stoppt den Worker
|
||
run-once Einmaliger Cleanup-Durchlauf
|
||
status Zeigt Status und aktuelle Offense-Zähler
|
||
|
||
Konfiguration: $CONFIG_FILE
|
||
USAGE
|
||
;;
|
||
esac
|