From 01a99489ab723ab5cc9c35137f7eb9efd6a43c76 Mon Sep 17 00:00:00 2001 From: scriptos Date: Sun, 12 Apr 2026 14:17:59 +0200 Subject: [PATCH 01/10] =?UTF-8?q?Hier=20soll=20in=20wenigen=20Worten=20dri?= =?UTF-8?q?n=20stehen,=20was=20ge=C3=A4ndert=20wurde,=20hinzugef=C3=BCgt?= =?UTF-8?q?=20wurde=20oder=20entfernt=20wurde.=20Du=20kannst=20Formate=20w?= =?UTF-8?q?ie=20=E2=80=9Cfix=E2=80=9D,=20=E2=80=9Cfeat=E2=80=9D,=20?= =?UTF-8?q?=E2=80=9Cupdate=E2=80=9D=20und=20co=20verwenden.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +- adguard-shield-watchdog.service | 7 ++ adguard-shield-watchdog.sh | 166 ++++++++++++++++++++++++++++++ adguard-shield-watchdog.timer | 11 ++ adguard-shield.service | 4 +- docs/architektur.md | 9 +- docs/befehle.md | 30 +++++- docs/benachrichtigungen.md | 18 ++++ docs/tipps-und-troubleshooting.md | 35 +++++++ docs/update.md | 5 +- install.sh | 52 +++++++++- uninstall.sh | 19 +++- 12 files changed, 350 insertions(+), 16 deletions(-) create mode 100644 adguard-shield-watchdog.service create mode 100644 adguard-shield-watchdog.sh create mode 100644 adguard-shield-watchdog.timer diff --git a/README.md b/README.md index cdbacee..dd67622 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Wenn ein Client eine bestimmte Domain zu oft anfragt (z.B. >30x pro Minute), wir - Whitelist für vertrauenswürdige IPs - Dry-Run Modus zum gefahrlosen Testen - Benachrichtigungen (Discord, Slack, Gotify, Ntfy) +- **Watchdog** — automatischer Health Check alle 5 Minuten mit Recovery und Benachrichtigung bei Service-Ausfall - systemd Service für dauerhaften Betrieb ## Voraussetzungen @@ -97,6 +98,10 @@ sudo /opt/adguard-shield/report-generator.sh send # Report jetzt sudo /opt/adguard-shield/report-generator.sh status # Report-Status anzeigen sudo /opt/adguard-shield/report-generator.sh install # Cron-Job einrichten sudo journalctl -u adguard-shield -f # Logs live verfolgen + +# Watchdog (automatischer Health Check) +sudo systemctl status adguard-shield-watchdog.timer # Watchdog-Status +sudo systemctl list-timers adguard-shield-watchdog.timer # Nächste Ausführung ``` ## Projektstruktur @@ -105,6 +110,9 @@ sudo journalctl -u adguard-shield -f # Logs live ver ├── adguard-shield.sh # Haupt-Monitor-Script ├── adguard-shield.conf # Konfiguration ├── adguard-shield.service # systemd Unit +├── adguard-shield-watchdog.sh # Watchdog Health-Check-Script +├── adguard-shield-watchdog.service # systemd Watchdog-Unit (oneshot) +├── adguard-shield-watchdog.timer # systemd Timer (alle 5 Min.) ├── external-blocklist-worker.sh # Externer Blocklist-Worker ├── external-whitelist-worker.sh # Externer Whitelist-Worker (DynDNS-Auflösung) ├── iptables-helper.sh # Manuelle iptables-Verwaltung @@ -134,7 +142,7 @@ sudo journalctl -u adguard-shield -f # Logs live ver | [Befehle](docs/befehle.md) | Vollständige Befehlsreferenz für Installer, Monitor, iptables-Helper und systemd | | [Benachrichtigungen](docs/benachrichtigungen.md) | Setup für Discord, Slack, Gotify, Ntfy | | [E-Mail Report](docs/report.md) | Periodische Statistik-Reports per E-Mail (HTML/TXT) | -| [Tipps & Troubleshooting](docs/tipps-und-troubleshooting.md) | Best Practices, häufige Probleme, Deinstallation | +| [Tipps & Troubleshooting](docs/tipps-und-troubleshooting.md) | Best Practices, häufige Probleme, Watchdog, Deinstallation | ## Lizenz diff --git a/adguard-shield-watchdog.service b/adguard-shield-watchdog.service new file mode 100644 index 0000000..0267685 --- /dev/null +++ b/adguard-shield-watchdog.service @@ -0,0 +1,7 @@ +[Unit] +Description=AdGuard Shield - Watchdog Health Check +Documentation=https://git.techniverse.net/scriptos/adguard-shield + +[Service] +Type=oneshot +ExecStart=/opt/adguard-shield/adguard-shield-watchdog.sh diff --git a/adguard-shield-watchdog.sh b/adguard-shield-watchdog.sh new file mode 100644 index 0000000..9c3f60e --- /dev/null +++ b/adguard-shield-watchdog.sh @@ -0,0 +1,166 @@ +#!/bin/bash +############################################################################### +# AdGuard Shield - Watchdog +# Prüft ob der Hauptservice läuft und startet ihn bei Bedarf neu. +# Wird über adguard-shield-watchdog.timer alle 5 Minuten ausgeführt. +# +# Autor: Patrick Asmus +# E-Mail: support@techniverse.net +# Lizenz: MIT +############################################################################### + +set -euo pipefail + +INSTALL_DIR="/opt/adguard-shield" +CONFIG_FILE="${INSTALL_DIR}/adguard-shield.conf" +SERVICE_NAME="adguard-shield.service" +LOG_FILE="/var/log/adguard-shield.log" +WATCHDOG_STATE_FILE="/var/lib/adguard-shield/watchdog.state" + +# ─── Logging ────────────────────────────────────────────────────────────────── +log() { + local level="$1" + shift + local message="$*" + local timestamp + timestamp="$(date '+%Y-%m-%d %H:%M:%S')" + local log_entry="[$timestamp] [WATCHDOG] [$level] $message" + + echo "$log_entry" | tee -a "$LOG_FILE" +} + +# ─── Benachrichtigung senden ────────────────────────────────────────────────── +send_watchdog_notification() { + local action="$1" # "recovery" oder "failure" + local detail="$2" + + # Konfiguration laden für Benachrichtigungs-Einstellungen + if [[ ! -f "$CONFIG_FILE" ]]; then + return + fi + source "$CONFIG_FILE" + + if [[ "${NOTIFY_ENABLED:-false}" != "true" ]]; then + return + fi + + local my_hostname + my_hostname=$(hostname) + local title message + + if [[ "$action" == "recovery" ]]; then + title="🔄 AdGuard Shield Watchdog" + message="🔄 AdGuard Shield Watchdog auf ${my_hostname} +--- +Der Service war ausgefallen und wurde automatisch neu gestartet. +${detail}" + elif [[ "$action" == "failure" ]]; then + title="🚨 AdGuard Shield Watchdog" + message="🚨 AdGuard Shield Watchdog auf ${my_hostname} +--- +Der Service konnte NICHT automatisch neu gestartet werden! +Manuelles Eingreifen erforderlich. +${detail}" + fi + + case "${NOTIFY_TYPE:-}" in + discord) + local json_payload + json_payload=$(jq -nc --arg msg "$message" '{content: $msg}') + curl -s -H "Content-Type: application/json" \ + -d "$json_payload" \ + "$NOTIFY_WEBHOOK_URL" &>/dev/null || true + ;; + slack) + local json_payload + json_payload=$(jq -nc --arg msg "$message" '{text: $msg}') + curl -s -H "Content-Type: application/json" \ + -d "$json_payload" \ + "$NOTIFY_WEBHOOK_URL" &>/dev/null || true + ;; + gotify) + curl -s -X POST "$NOTIFY_WEBHOOK_URL" \ + -F "title=${title}" \ + -F "message=${message}" \ + -F "priority=5" &>/dev/null || true + ;; + ntfy) + if [[ -n "${NTFY_TOPIC:-}" ]]; then + local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}" + local auth_args=() + if [[ -n "${NTFY_TOKEN:-}" ]]; then + auth_args=(-H "Authorization: Bearer ${NTFY_TOKEN}") + fi + curl -s \ + -H "Title: ${title}" \ + -H "Priority: ${NTFY_PRIORITY:-5}" \ + -H "Tags: warning,watchdog" \ + "${auth_args[@]}" \ + -d "$message" \ + "${ntfy_url}/${NTFY_TOPIC}" &>/dev/null || true + fi + ;; + generic) + local json_payload + json_payload=$(jq -nc --arg msg "$message" --arg act "watchdog_${action}" \ + '{message: $msg, action: $act}') + curl -s -H "Content-Type: application/json" \ + -d "$json_payload" \ + "$NOTIFY_WEBHOOK_URL" &>/dev/null || true + ;; + esac +} + +# ─── Hauptlogik ────────────────────────────────────────────────────────────── +main() { + # Verzeichnis für State-Datei sicherstellen + mkdir -p "$(dirname "$WATCHDOG_STATE_FILE")" + + # Prüfen ob der Service aktiv ist + if systemctl is-active --quiet "$SERVICE_NAME"; then + # Service läuft – falls vorher ausgefallen war, Status zurücksetzen + if [[ -f "$WATCHDOG_STATE_FILE" ]]; then + rm -f "$WATCHDOG_STATE_FILE" + fi + exit 0 + fi + + # Service läuft NICHT – Recovery versuchen + log "WARN" "Service $SERVICE_NAME ist nicht aktiv – starte Recovery..." + + # Zähler für fehlgeschlagene Recovery-Versuche + local fail_count=0 + if [[ -f "$WATCHDOG_STATE_FILE" ]]; then + fail_count=$(cat "$WATCHDOG_STATE_FILE" 2>/dev/null || echo "0") + fi + + # systemd reset-failed damit StartLimit zurückgesetzt wird + systemctl reset-failed "$SERVICE_NAME" 2>/dev/null || true + + # Service starten + if systemctl start "$SERVICE_NAME" 2>/dev/null; then + # Kurz warten und prüfen ob er auch wirklich läuft + sleep 3 + if systemctl is-active --quiet "$SERVICE_NAME"; then + log "INFO" "Service $SERVICE_NAME erfolgreich neu gestartet (Watchdog Recovery)" + send_watchdog_notification "recovery" "Versuch: $((fail_count + 1))" + rm -f "$WATCHDOG_STATE_FILE" + exit 0 + fi + fi + + # Start fehlgeschlagen + fail_count=$((fail_count + 1)) + echo "$fail_count" > "$WATCHDOG_STATE_FILE" + log "ERROR" "Service $SERVICE_NAME konnte nicht gestartet werden (Fehlversuch: $fail_count)" + + # Bei jedem 3. Fehlversuch eine Benachrichtigung senden (Spam vermeiden) + if [[ $((fail_count % 3)) -eq 1 ]]; then + send_watchdog_notification "failure" "Fehlversuche: $fail_count +Letzter Fehler: $(systemctl status "$SERVICE_NAME" 2>&1 | tail -5)" + fi + + exit 1 +} + +main diff --git a/adguard-shield-watchdog.timer b/adguard-shield-watchdog.timer new file mode 100644 index 0000000..a1d60d1 --- /dev/null +++ b/adguard-shield-watchdog.timer @@ -0,0 +1,11 @@ +[Unit] +Description=AdGuard Shield - Watchdog Timer +Documentation=https://git.techniverse.net/scriptos/adguard-shield + +[Timer] +OnBootSec=2min +OnUnitActiveSec=5min +AccuracySec=30s + +[Install] +WantedBy=timers.target diff --git a/adguard-shield.service b/adguard-shield.service index a876d30..6f2638f 100644 --- a/adguard-shield.service +++ b/adguard-shield.service @@ -4,7 +4,7 @@ Documentation=https://git.techniverse.net/scriptos/adguard-shield After=network.target AdGuardHome.service Wants=AdGuardHome.service StartLimitBurst=5 -StartLimitIntervalSec=60 +StartLimitIntervalSec=300 [Service] Type=simple @@ -14,7 +14,7 @@ ExecReload=/bin/kill -HUP $MAINPID # Neustart-Verhalten Restart=on-failure -RestartSec=10 +RestartSec=30 # Sicherheits-Hardening ProtectSystem=full diff --git a/docs/architektur.md b/docs/architektur.md index c2d6ada..f54141a 100644 --- a/docs/architektur.md +++ b/docs/architektur.md @@ -132,13 +132,16 @@ Das ermöglicht: ├── adguard-shield.sh # Haupt-Monitor-Script ├── adguard-shield.conf # Konfiguration (chmod 600) ├── adguard-shield.conf.old # Backup der Konfig nach Update +├── adguard-shield-watchdog.sh # Watchdog Health-Check-Script ├── iptables-helper.sh # iptables Verwaltung ├── external-blocklist-worker.sh # Externer Blocklist-Worker ├── external-whitelist-worker.sh # Externer Whitelist-Worker (DNS-Auflösung) └── unban-expired.sh # Cron-basiertes Entsperren /etc/systemd/system/ -└── adguard-shield.service # systemd Service (Autostart aktiv) +├── adguard-shield.service # systemd Service (Autostart aktiv) +├── adguard-shield-watchdog.service # systemd Watchdog-Unit (oneshot) +└── adguard-shield-watchdog.timer # systemd Timer (alle 5 Min.) /var/lib/adguard-shield/ ├── *.ban # State-Dateien aktiver Sperren @@ -157,8 +160,8 @@ Der Installer (`install.sh`) bietet ein interaktives Menü und folgende Funktion | Befehl | Beschreibung | |--------|--------------| -| `install` | Vollständige Neuinstallation (Abhängigkeiten, Dateien, Konfiguration, Service) | -| `update` | Update mit automatischer Konfigurations-Migration und Service-Neustart | +| `install` | Vollständige Neuinstallation (Abhängigkeiten, Dateien, Konfiguration, Service, Watchdog) | +| `update` | Update mit automatischer Konfigurations-Migration, Watchdog-Aktivierung und Service-Neustart | | `uninstall` | Deinstallation mit optionalem Behalten der Konfiguration | | `status` | Installationsstatus, Version und Service-Status anzeigen | | `--help` | Hilfe und Befehlsübersicht | diff --git a/docs/befehle.md b/docs/befehle.md index c8c606f..a036f9d 100644 --- a/docs/befehle.md +++ b/docs/befehle.md @@ -42,8 +42,9 @@ Beim Update passiert automatisch: 2. Die bestehende Konfiguration wird als `adguard-shield.conf.old` gesichert 3. Neue Konfigurationsparameter werden automatisch zur bestehenden Konfig hinzugefügt 4. Bestehende Einstellungen bleiben **immer** erhalten -5. Der systemd Service wird per `daemon-reload` neu geladen -6. Der Service wird automatisch neu gestartet (falls er lief) +5. Der systemd Service und Watchdog-Timer werden per `daemon-reload` neu geladen +6. Der Watchdog-Timer wird automatisch aktiviert (falls noch nicht aktiv) +7. Der Service wird automatisch neu gestartet (falls er lief) ### API-Verbindungstest nach Installation @@ -86,6 +87,31 @@ sudo systemctl disable adguard-shield > **Hinweis:** Der Service wird bei der Installation automatisch für den Autostart beim Booten aktiviert. Nach einem Update wird der Service automatisch neu gestartet — ein manueller Neustart ist nicht nötig. +## Watchdog (automatischer Health Check) + +Der Watchdog prüft alle 5 Minuten ob der Hauptservice läuft und startet ihn bei Bedarf automatisch neu. Er wird als systemd Timer betrieben und bei der Installation automatisch aktiviert. + +```bash +# Watchdog-Status +sudo systemctl status adguard-shield-watchdog.timer + +# Nächste geplante Ausführung anzeigen +sudo systemctl list-timers adguard-shield-watchdog.timer + +# Watchdog aktivieren / deaktivieren +sudo systemctl enable adguard-shield-watchdog.timer +sudo systemctl disable adguard-shield-watchdog.timer + +# Watchdog starten / stoppen +sudo systemctl start adguard-shield-watchdog.timer +sudo systemctl stop adguard-shield-watchdog.timer + +# Watchdog-Logs anzeigen +sudo journalctl -u adguard-shield-watchdog.service --no-pager -n 20 +``` + +> **Hinweis:** Der Watchdog sendet automatisch Benachrichtigungen (falls `NOTIFY_ENABLED=true`), wenn er den Service wiederbeleben muss oder die Recovery fehlschlägt. + ## Monitor — Verwaltungsbefehle Die folgenden Befehle dienen der **Verwaltung und Diagnose** und können jederzeit ausgeführt werden, auch während der Service läuft: diff --git a/docs/benachrichtigungen.md b/docs/benachrichtigungen.md index 96ff2e9..418ce36 100644 --- a/docs/benachrichtigungen.md +++ b/docs/benachrichtigungen.md @@ -123,6 +123,24 @@ Bei Sperren aus der **externen Blocklist** werden Benachrichtigungen separat üb > 🔴 AdGuard Shield v0.7.0 wurde auf dns1 gestoppt. +### Watchdog — Service wiederhergestellt +**Überschrift:** 🔄 AdGuard Shield Watchdog + +> 🔄 AdGuard Shield Watchdog auf dns1 +> --- +> Der Service war ausgefallen und wurde automatisch neu gestartet. +> Versuch: 1 + +### Watchdog — Recovery fehlgeschlagen +**Überschrift:** 🚨 AdGuard Shield Watchdog + +> 🚨 AdGuard Shield Watchdog auf dns1 +> --- +> Der Service konnte NICHT automatisch neu gestartet werden! +> Manuelles Eingreifen erforderlich. +> Fehlversuche: 1 +> Letzter Fehler: (systemd Statusausgabe) + ### Sperre (Ban) **Überschrift:** 🚨 🛡️ AdGuard Shield diff --git a/docs/tipps-und-troubleshooting.md b/docs/tipps-und-troubleshooting.md index a4e6d4c..23f0663 100644 --- a/docs/tipps-und-troubleshooting.md +++ b/docs/tipps-und-troubleshooting.md @@ -193,6 +193,37 @@ sudo rm -f /var/run/adguard-shield.pid sudo systemctl start adguard-shield ``` +### Service ist ausgefallen und startet nicht mehr + +Wenn systemd das Restart-Limit erreicht hat (z.B. `"Start request repeated too quickly"`), hilft der **Watchdog** — er prüft alle 5 Minuten ob der Service läuft und startet ihn automatisch neu. + +**Watchdog-Status prüfen:** +```bash +# Timer-Status anzeigen +sudo systemctl status adguard-shield-watchdog.timer + +# Letzte Watchdog-Ausführungen anzeigen +sudo systemctl list-timers adguard-shield-watchdog.timer + +# Watchdog-Logs prüfen +sudo journalctl -u adguard-shield-watchdog.service --no-pager -n 20 +``` + +**Manuelles Recovery (sofort):** +```bash +# systemd-Fehlerzähler zurücksetzen und Service starten +sudo systemctl reset-failed adguard-shield.service +sudo systemctl start adguard-shield.service +``` + +**Watchdog nachträglich aktivieren:** +```bash +sudo systemctl enable adguard-shield-watchdog.timer +sudo systemctl start adguard-shield-watchdog.timer +``` + +> **Hinweis:** Der Watchdog sendet automatisch eine Benachrichtigung (falls `NOTIFY_ENABLED=true`), wenn er den Service wiederbeleben muss oder die Recovery fehlschlägt. + ## Update durchführen ```bash @@ -229,9 +260,13 @@ Oder manuell: ```bash sudo systemctl stop adguard-shield sudo systemctl disable adguard-shield +sudo systemctl stop adguard-shield-watchdog.timer +sudo systemctl disable adguard-shield-watchdog.timer sudo /opt/adguard-shield/iptables-helper.sh remove sudo rm -rf /opt/adguard-shield sudo rm -f /etc/systemd/system/adguard-shield.service +sudo rm -f /etc/systemd/system/adguard-shield-watchdog.service +sudo rm -f /etc/systemd/system/adguard-shield-watchdog.timer sudo systemctl daemon-reload ``` diff --git a/docs/update.md b/docs/update.md index c313554..0636259 100644 --- a/docs/update.md +++ b/docs/update.md @@ -35,8 +35,9 @@ Das Update-Script macht automatisch folgendes: 2. **Scripts aktualisieren** — Alle `.sh`-Dateien werden nach `/opt/adguard-shield/` kopiert 3. **Konfigurations-Migration** — Neue Parameter werden automatisch zur bestehenden Konfiguration hinzugefügt, bestehende Einstellungen bleiben **unverändert** 4. **Backup erstellen** — Die alte Konfiguration wird als `adguard-shield.conf.old` gesichert -5. **Service aktualisieren** — Die systemd Service-Datei wird aktualisiert und `daemon-reload` ausgeführt -6. **Service neustarten** — Der Service wird automatisch neu gestartet (falls er vorher lief) +5. **Service aktualisieren** — Die systemd Service-Datei und Watchdog-Dateien werden aktualisiert und `daemon-reload` ausgeführt +6. **Watchdog aktivieren** — Der Watchdog-Timer wird automatisch aktiviert (falls noch nicht aktiv) +7. **Service neustarten** — Der Service wird automatisch neu gestartet (falls er vorher lief) ### 3. Neue Parameter prüfen (optional) diff --git a/install.sh b/install.sh index d8eac1a..5223822 100644 --- a/install.sh +++ b/install.sh @@ -125,6 +125,13 @@ print_help() { echo -e " ${CYAN}sudo /opt/adguard-shield/report-generator.sh install${NC} # Cron-Job einrichten" echo -e " ${CYAN}sudo /opt/adguard-shield/report-generator.sh remove${NC} # Cron-Job entfernen" echo "" + echo -e "${BOLD}Watchdog-Befehle:${NC}" + echo -e " ${CYAN}sudo systemctl status adguard-shield-watchdog.timer${NC} # Watchdog-Status" + echo -e " ${CYAN}sudo systemctl list-timers adguard-shield-watchdog.timer${NC} # Nächste Ausführung" + echo -e " ${CYAN}sudo systemctl enable adguard-shield-watchdog.timer${NC} # Watchdog aktivieren" + echo -e " ${CYAN}sudo systemctl disable adguard-shield-watchdog.timer${NC} # Watchdog deaktivieren" + echo -e " ${CYAN}sudo journalctl -u adguard-shield-watchdog.service${NC} # Watchdog-Logs" + echo "" echo -e "${BOLD}Voraussetzungen:${NC}" echo " - Linux Server (Debian/Ubuntu empfohlen)" echo " - Root-Zugriff (sudo)" @@ -248,6 +255,7 @@ install_files() { cp "$SCRIPT_DIR/external-blocklist-worker.sh" "$INSTALL_DIR/" cp "$SCRIPT_DIR/external-whitelist-worker.sh" "$INSTALL_DIR/" cp "$SCRIPT_DIR/report-generator.sh" "$INSTALL_DIR/" + cp "$SCRIPT_DIR/adguard-shield-watchdog.sh" "$INSTALL_DIR/" cp "$SCRIPT_DIR/uninstall.sh" "$INSTALL_DIR/" # Templates kopieren @@ -262,6 +270,7 @@ install_files() { chmod +x "$INSTALL_DIR/external-blocklist-worker.sh" chmod +x "$INSTALL_DIR/external-whitelist-worker.sh" chmod +x "$INSTALL_DIR/report-generator.sh" + chmod +x "$INSTALL_DIR/adguard-shield-watchdog.sh" chmod +x "$INSTALL_DIR/uninstall.sh" echo -e " ✅ Dateien installiert" @@ -356,18 +365,22 @@ install_service() { echo -e "${YELLOW}Installiere systemd Service...${NC}" cp "$SCRIPT_DIR/adguard-shield.service" "$SERVICE_FILE" + cp "$SCRIPT_DIR/adguard-shield-watchdog.service" /etc/systemd/system/adguard-shield-watchdog.service + cp "$SCRIPT_DIR/adguard-shield-watchdog.timer" /etc/systemd/system/adguard-shield-watchdog.timer systemctl daemon-reload - echo -e " ✅ Service-Datei installiert" + echo -e " ✅ Service-Dateien installiert (inkl. Watchdog)" echo "" # Interaktiv: Autostart beim Booten? read -rep " Soll AdGuard Shield beim Booten automatisch starten? [J/n]: " autostart if [[ "${autostart,,}" != "n" ]]; then systemctl enable adguard-shield.service - echo -e " ✅ Autostart aktiviert" + systemctl enable adguard-shield-watchdog.timer + echo -e " ✅ Autostart aktiviert (inkl. Watchdog-Timer)" else systemctl disable adguard-shield.service 2>/dev/null || true + systemctl disable adguard-shield-watchdog.timer 2>/dev/null || true echo -e " ℹ️ Autostart nicht aktiviert" echo -e " ${YELLOW}Später aktivieren mit: sudo systemctl enable adguard-shield${NC}" fi @@ -500,6 +513,15 @@ print_summary() { echo " Konfiguration: $INSTALL_DIR/adguard-shield.conf" echo " Service: adguard-shield.service ($svc_status)" echo " Autostart: $autostart_status" + + # Watchdog-Status + local watchdog_status="deaktiviert" + if systemctl is-active adguard-shield-watchdog.timer &>/dev/null 2>&1; then + watchdog_status="aktiv ✅" + elif systemctl is-enabled adguard-shield-watchdog.timer &>/dev/null 2>&1; then + watchdog_status="aktiviert (Timer nicht gestartet)" + fi + echo " Watchdog: $watchdog_status" echo " Log-Datei: /var/log/adguard-shield.log" echo "" echo " Nützliche Befehle:" @@ -579,6 +601,15 @@ do_status() { echo -e " ❌ Konfiguration: fehlt!" fi + # Watchdog-Status + if systemctl is-active adguard-shield-watchdog.timer &>/dev/null 2>&1; then + echo -e " ✅ Watchdog-Timer: aktiv" + elif systemctl is-enabled adguard-shield-watchdog.timer &>/dev/null 2>&1; then + echo -e " ⚠️ Watchdog-Timer: aktiviert aber nicht gestartet" + else + echo -e " ❌ Watchdog-Timer: nicht installiert/deaktiviert" + fi + echo "" } @@ -618,7 +649,8 @@ do_install() { read -rep " Soll der AdGuard Shield Service jetzt gestartet werden? [J/n]: " start_now if [[ "${start_now,,}" != "n" ]]; then systemctl start adguard-shield - echo -e " ✅ Service gestartet" + systemctl start adguard-shield-watchdog.timer 2>/dev/null || true + echo -e " ✅ Service gestartet (inkl. Watchdog-Timer)" else echo -e " ℹ️ Service nicht gestartet" echo -e " ${YELLOW}Später starten mit: sudo systemctl start adguard-shield${NC}" @@ -651,18 +683,28 @@ do_update() { # Service-Datei aktualisieren echo -e "${YELLOW}Aktualisiere systemd Service...${NC}" cp "$SCRIPT_DIR/adguard-shield.service" "$SERVICE_FILE" + cp "$SCRIPT_DIR/adguard-shield-watchdog.service" /etc/systemd/system/adguard-shield-watchdog.service + cp "$SCRIPT_DIR/adguard-shield-watchdog.timer" /etc/systemd/system/adguard-shield-watchdog.timer systemctl daemon-reload - echo -e " ✅ Service-Datei aktualisiert" + echo -e " ✅ Service-Dateien aktualisiert (inkl. Watchdog)" echo "" # Interaktiv: Autostart beim Booten? if systemctl is-enabled adguard-shield &>/dev/null; then echo -e " ℹ️ Autostart ist bereits aktiviert" + # Watchdog-Timer auch aktivieren falls noch nicht aktiv + if ! systemctl is-enabled adguard-shield-watchdog.timer &>/dev/null 2>&1; then + systemctl enable adguard-shield-watchdog.timer + systemctl start adguard-shield-watchdog.timer + echo -e " ✅ Watchdog-Timer aktiviert" + fi else read -rep " Soll AdGuard Shield beim Booten automatisch starten? [J/n]: " autostart if [[ "${autostart,,}" != "n" ]]; then systemctl enable adguard-shield.service - echo -e " ✅ Autostart aktiviert" + systemctl enable adguard-shield-watchdog.timer + systemctl start adguard-shield-watchdog.timer + echo -e " ✅ Autostart aktiviert (inkl. Watchdog-Timer)" else echo -e " ℹ️ Autostart bleibt deaktiviert" fi diff --git a/uninstall.sh b/uninstall.sh index 473f86f..23faa72 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -13,6 +13,8 @@ # INSTALL_DIR ergibt sich aus dem Verzeichnis, in dem dieses Script liegt INSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SERVICE_FILE="/etc/systemd/system/adguard-shield.service" +WATCHDOG_SERVICE_FILE="/etc/systemd/system/adguard-shield-watchdog.service" +WATCHDOG_TIMER_FILE="/etc/systemd/system/adguard-shield-watchdog.timer" # Farben RED='\033[0;31m' @@ -79,6 +81,16 @@ do_uninstall() { fi echo "" + # Watchdog-Timer stoppen und deaktivieren + if systemctl is-active adguard-shield-watchdog.timer &>/dev/null 2>&1; then + systemctl stop adguard-shield-watchdog.timer + echo " ✅ Watchdog-Timer gestoppt" + fi + if systemctl is-enabled adguard-shield-watchdog.timer &>/dev/null 2>&1; then + systemctl disable adguard-shield-watchdog.timer + echo " ✅ Watchdog-Timer deaktiviert" + fi + # Service stoppen und deaktivieren if systemctl is-active adguard-shield &>/dev/null; then systemctl stop adguard-shield @@ -90,9 +102,13 @@ do_uninstall() { fi if [[ -f "$SERVICE_FILE" ]]; then rm -f "$SERVICE_FILE" - systemctl daemon-reload echo " ✅ Service-Datei entfernt" fi + rm -f "$WATCHDOG_SERVICE_FILE" "$WATCHDOG_TIMER_FILE" + if [[ -f "$WATCHDOG_SERVICE_FILE" ]] || [[ -f "$WATCHDOG_TIMER_FILE" ]]; then + echo " ✅ Watchdog-Dateien entfernt" + fi + systemctl daemon-reload # iptables Chain aufräumen if [[ -f "$INSTALL_DIR/iptables-helper.sh" ]]; then @@ -108,6 +124,7 @@ do_uninstall() { rm -f "$INSTALL_DIR/external-blocklist-worker.sh" rm -f "$INSTALL_DIR/external-whitelist-worker.sh" rm -f "$INSTALL_DIR/report-generator.sh" + rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh" rm -f "$INSTALL_DIR/uninstall.sh" rm -rf "$INSTALL_DIR/templates" echo " ✅ Scripts entfernt (Konfiguration und Logs behalten)" From 77a5ebb14453d4168197ea80a290a3fac6ddbab5 Mon Sep 17 00:00:00 2001 From: scriptos Date: Sun, 12 Apr 2026 14:19:08 +0200 Subject: [PATCH 02/10] docs: Projektstruktur entfernt --- README.md | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/README.md b/README.md index dd67622..d43ccbb 100644 --- a/README.md +++ b/README.md @@ -104,35 +104,6 @@ sudo systemctl status adguard-shield-watchdog.timer # Watchdog-Sta sudo systemctl list-timers adguard-shield-watchdog.timer # Nächste Ausführung ``` -## Projektstruktur - -``` -├── adguard-shield.sh # Haupt-Monitor-Script -├── adguard-shield.conf # Konfiguration -├── adguard-shield.service # systemd Unit -├── adguard-shield-watchdog.sh # Watchdog Health-Check-Script -├── adguard-shield-watchdog.service # systemd Watchdog-Unit (oneshot) -├── adguard-shield-watchdog.timer # systemd Timer (alle 5 Min.) -├── external-blocklist-worker.sh # Externer Blocklist-Worker -├── external-whitelist-worker.sh # Externer Whitelist-Worker (DynDNS-Auflösung) -├── iptables-helper.sh # Manuelle iptables-Verwaltung -├── unban-expired.sh # Cron-basiertes Entsperren -├── report-generator.sh # E-Mail Report Generator -├── install.sh # Installer / Updater -├── uninstall.sh # Uninstaller (wird ins Installationsverzeichnis kopiert) -├── templates/ -│ ├── report.html # HTML-Report-Template -│ └── report.txt # TXT-Report-Template -├── README.md -└── docs/ - ├── architektur.md # Architektur & Funktionsweise - ├── konfiguration.md # Alle Parameter erklärt + Konfig-Migration - ├── befehle.md # Vollständige Befehlsreferenz inkl. Installer - ├── benachrichtigungen.md # Webhook-Setup (Discord, Slack, Gotify, Ntfy) - ├── report.md # E-Mail Report Setup & Konfiguration - └── tipps-und-troubleshooting.md -``` - ## Dokumentation | Dokument | Inhalt | From 606a28ed8e207de21dfd9a7cbdef2c674f8aa8eb Mon Sep 17 00:00:00 2001 From: scriptos Date: Sun, 12 Apr 2026 14:28:37 +0200 Subject: [PATCH 03/10] =?UTF-8?q?docs:=20README=20f=C3=BCr=20docs-Verzeich?= =?UTF-8?q?nis=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..62855dd --- /dev/null +++ b/docs/README.md @@ -0,0 +1,15 @@ +# Dokumentation + +Hier findest du die vollständige Dokumentation zu AdGuard Shield. + +## Inhaltsverzeichnis + +| Dokument | Beschreibung | +|---|---| +| [Architektur & Funktionsweise](architektur.md) | Überblick über den Systemaufbau, Datenfluss und die internen Komponenten | +| [Befehle & Nutzung](befehle.md) | Alle verfügbaren Befehle des Installers, des Hauptskripts und des Watchdogs | +| [Konfiguration](konfiguration.md) | Beschreibung aller Konfigurationsparameter in `adguard-shield.conf` | +| [Webhook-Benachrichtigungen](benachrichtigungen.md) | Einrichtung von Push-Benachrichtigungen über Telegram, Discord, Gotify u.a. | +| [E-Mail Report](report.md) | Konfiguration des automatischen Statistik-Reports per E-Mail | +| [Update-Anleitung](update.md) | Schritt-für-Schritt-Anleitung zum Aktualisieren einer bestehenden Installation | +| [Tipps & Troubleshooting](tipps-und-troubleshooting.md) | Best Practices, häufige Probleme und deren Lösungen | From 0af79e7a2886aa14de3c38daaca7931e905b156f Mon Sep 17 00:00:00 2001 From: scriptos Date: Sun, 12 Apr 2026 14:35:47 +0200 Subject: [PATCH 04/10] Release: Version v0.7.1 --- adguard-shield.sh | 2 +- docs/benachrichtigungen.md | 4 ++-- install.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/adguard-shield.sh b/adguard-shield.sh index 3e2abb3..4a840cb 100644 --- a/adguard-shield.sh +++ b/adguard-shield.sh @@ -8,7 +8,7 @@ # Lizenz: MIT ############################################################################### -VERSION="v0.7.0" +VERSION="v0.7.1" set -euo pipefail diff --git a/docs/benachrichtigungen.md b/docs/benachrichtigungen.md index 418ce36..96fa15c 100644 --- a/docs/benachrichtigungen.md +++ b/docs/benachrichtigungen.md @@ -116,12 +116,12 @@ Bei Sperren aus der **externen Blocklist** werden Benachrichtigungen separat üb ### Service gestartet **Überschrift:** ✅ AdGuard Shield -> 🟢 AdGuard Shield v0.7.0 wurde auf dns1 gestartet. +> 🟢 AdGuard Shield v0.7.1 wurde auf dns1 gestartet. ### Service gestoppt **Überschrift:** 🚨 🛡️ AdGuard Shield -> 🔴 AdGuard Shield v0.7.0 wurde auf dns1 gestoppt. +> 🔴 AdGuard Shield v0.7.1 wurde auf dns1 gestoppt. ### Watchdog — Service wiederhergestellt **Überschrift:** 🔄 AdGuard Shield Watchdog diff --git a/install.sh b/install.sh index 5223822..54e3888 100644 --- a/install.sh +++ b/install.sh @@ -6,7 +6,7 @@ # Lizenz: MIT ############################################################################### -VERSION="v0.7.0" +VERSION="v0.7.1" set -euo pipefail From 535be66b553b30abe3e11ab2956da6527813f041 Mon Sep 17 00:00:00 2001 From: scriptos Date: Tue, 14 Apr 2026 20:30:37 +0200 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20GeoIP-L=C3=A4nderfilter=20mit=20M?= =?UTF-8?q?axMind=20Auto-Download?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 + adguard-shield.conf | 50 +++ adguard-shield.sh | 100 ++++- docs/architektur.md | 7 +- docs/befehle.md | 43 ++ docs/konfiguration.md | 87 ++++ geoip-worker.sh | 932 ++++++++++++++++++++++++++++++++++++++++++ install.sh | 9 + uninstall.sh | 2 + 9 files changed, 1231 insertions(+), 3 deletions(-) create mode 100644 geoip-worker.sh diff --git a/README.md b/README.md index d43ccbb..7db1893 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Wenn ein Client eine bestimmte Domain zu oft anfragt (z.B. >30x pro Minute), wir - Automatisches Entsperren nach konfigurierbarer Dauer - **Externe Blocklisten** — IP-Adressen von externen Textdateien (URLs) laden und automatisch sperren - **Externe Whitelisten** — Domains/IPs aus externen Listen laden und automatisch whitelisten (ideal für DynDNS) +- **GeoIP-Länderfilter** — Länder sperren oder erlauben (Blocklist/Allowlist), mit automatischem MaxMind-DB-Download - **AbuseIPDB Reporting** — permanent gesperrte IPs automatisch an AbuseIPDB melden - **E-Mail Reports** — periodische Statistik-Reports als HTML oder TXT (täglich, wöchentlich, zweiwöchentlich, monatlich) - **Ban-History** — lückenlose Protokollierung aller Sperren/Entsperrungen mit Zeitstempel @@ -94,6 +95,9 @@ sudo /opt/adguard-shield/adguard-shield.sh blocklist-status # Externe Blocklis sudo /opt/adguard-shield/adguard-shield.sh blocklist-sync # Blocklisten manuell synchronisieren sudo /opt/adguard-shield/adguard-shield.sh whitelist-status # Externe Whitelisten Status sudo /opt/adguard-shield/adguard-shield.sh whitelist-sync # Whitelisten manuell synchronisieren +sudo /opt/adguard-shield/adguard-shield.sh geoip-status # GeoIP-Status anzeigen +sudo /opt/adguard-shield/adguard-shield.sh geoip-sync # GeoIP einmalig prüfen +sudo /opt/adguard-shield/adguard-shield.sh geoip-lookup IP # GeoIP-Lookup einer IP sudo /opt/adguard-shield/report-generator.sh send # Report jetzt senden sudo /opt/adguard-shield/report-generator.sh status # Report-Status anzeigen sudo /opt/adguard-shield/report-generator.sh install # Cron-Job einrichten diff --git a/adguard-shield.conf b/adguard-shield.conf index f261a06..abc1212 100644 --- a/adguard-shield.conf +++ b/adguard-shield.conf @@ -207,6 +207,56 @@ ABUSEIPDB_API_KEY="" # Siehe: https://www.abuseipdb.com/categories ABUSEIPDB_CATEGORIES="4" +# --- GeoIP-basierte Länderfilter (optional) --- +# Sperrt oder erlaubt DNS-Anfragen basierend auf dem Herkunftsland der Client-IP. +# Alle Lookups erfolgen LOKAL über eine Datenbank – es werden keine Online-API-Calls gemacht. +# +# Einfachster Weg (empfohlen): +# sudo apt install geoip-bin geoip-database +# → Damit funktioniert GeoIP sofort, ohne Account oder API-Key. +# +# Für genauere/aktuellere Daten (optional): +# Kostenlosen MaxMind-Account erstellen (https://www.maxmind.com/en/geolite2/signup) +# und License-Key unter GEOIP_LICENSE_KEY eintragen. Die GeoLite2-Datenbank +# wird dann automatisch heruntergeladen und alle 24 Stunden aktualisiert. +# Alternativ kann ein bereits vorhandener DB-Pfad über GEOIP_MMDB_PATH angegeben werden. +GEOIP_ENABLED=false + +# Modus: "blocklist" = nur gelistete Länder sperren +# "allowlist" = nur gelistete Länder erlauben (alle anderen werden gesperrt) +GEOIP_MODE="blocklist" + +# Kommagetrennte Liste von ISO 3166-1 Alpha-2 Ländercodes +# Blocklist-Modus: Diese Länder werden gesperrt +# Allowlist-Modus: NUR diese Länder werden erlaubt (Rest wird gesperrt) +# Beispiel: "CN,RU,KP,IR" oder "DE,AT,CH" +GEOIP_COUNTRIES="" + +# Wie oft die GeoIP-Prüfung durchgeführt wird (in Sekunden, 0 = bei jedem Check-Intervall) +# Empfohlen: Gleicher Wert wie CHECK_INTERVAL oder höher +GEOIP_CHECK_INTERVAL=0 + +# Benachrichtigungen bei GeoIP-Sperren senden? +GEOIP_NOTIFY=true + +# Lokale IPs und private Netze von GeoIP-Prüfung ausnehmen (empfohlen: true) +GEOIP_SKIP_PRIVATE=true + +# MaxMind GeoLite2 License-Key (optional, für automatischen Download & Update) +# Kostenloser Account: https://www.maxmind.com/en/geolite2/signup +# License-Key erstellen: Account → Manage License Keys → Generate New License Key +# Wenn gesetzt, wird die GeoLite2-Country-Datenbank automatisch heruntergeladen +# und alle 24 Stunden aktualisiert. Die DB wird lokal gespeichert unter: +# /geoip/GeoLite2-Country.mmdb +GEOIP_LICENSE_KEY="" + +# Pfad zur MaxMind GeoLite2 .mmdb-Datenbank (optional) +# Wenn leer UND GEOIP_LICENSE_KEY gesetzt → automatischer Download (s.o.) +# Wenn leer UND GEOIP_LICENSE_KEY leer → Fallback auf geoiplookup (apt install geoip-bin) +# Wenn gesetzt → diese Datei wird direkt verwendet (kein automatischer Download) +# Beispiel: "/usr/share/GeoIP/GeoLite2-Country.mmdb" +GEOIP_MMDB_PATH="" + # --- Erweiterte Einstellungen --- # Pfad zur State-Datei (speichert aktive Sperren) STATE_DIR="/var/lib/adguard-shield" diff --git a/adguard-shield.sh b/adguard-shield.sh index 4a840cb..ea9f4e6 100644 --- a/adguard-shield.sh +++ b/adguard-shield.sh @@ -330,6 +330,7 @@ cleanup() { fi stop_blocklist_worker stop_whitelist_worker + stop_geoip_worker rm -f "$PID_FILE" exit 0 } @@ -987,6 +988,17 @@ show_status() { echo "" fi + # GeoIP-Filter Info + if [[ "${GEOIP_ENABLED:-false}" == "true" ]]; then + local geoip_mode_label + [[ "${GEOIP_MODE:-blocklist}" == "blocklist" ]] && geoip_mode_label="Blocklist" || geoip_mode_label="Allowlist" + echo " 🌍 GeoIP-Filter: AKTIV" + echo " Modus: ${geoip_mode_label}" + echo " Länder: ${GEOIP_COUNTRIES:-}" + echo " Sperrdauer: PERMANENT (Auto-Unban bei Änderung der Länderliste)" + echo "" + fi + # Aktive Sperren local ban_count=0 if [[ -d "$STATE_DIR" ]]; then @@ -1212,6 +1224,39 @@ stop_whitelist_worker() { fi } +# ─── GeoIP-Worker starten ──────────────────────────────────────────────────── +start_geoip_worker() { + if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then + log "DEBUG" "GeoIP-Worker ist deaktiviert" + return + fi + + local worker_script="${SCRIPT_DIR}/geoip-worker.sh" + if [[ ! -f "$worker_script" ]]; then + log "WARN" "GeoIP-Worker Script nicht gefunden: $worker_script" + return + fi + + log "INFO" "Starte GeoIP-Worker im Hintergrund..." + bash "$worker_script" start & + GEOIP_WORKER_PID=$! + log "INFO" "GeoIP-Worker gestartet (PID: $GEOIP_WORKER_PID)" +} + +# ─── GeoIP-Worker stoppen ──────────────────────────────────────────────────── +stop_geoip_worker() { + local worker_pid_file="/var/run/adguard-geoip-worker.pid" + if [[ -f "$worker_pid_file" ]]; then + local wpid + wpid=$(cat "$worker_pid_file") + if kill -0 "$wpid" 2>/dev/null; then + log "INFO" "Stoppe GeoIP-Worker (PID: $wpid)..." + kill "$wpid" 2>/dev/null || true + rm -f "$worker_pid_file" + fi + fi +} + # ─── Hauptschleife ────────────────────────────────────────────────────────── main_loop() { log "INFO" "═══════════════════════════════════════════════════════════" @@ -1238,6 +1283,11 @@ main_loop() { else log "INFO" " AbuseIPDB Reporting: deaktiviert" fi + if [[ "${GEOIP_ENABLED:-false}" == "true" ]]; then + log "INFO" " GeoIP-Filter: AKTIV (Modus: ${GEOIP_MODE:-blocklist}, Länder: ${GEOIP_COUNTRIES:-})" + else + log "INFO" " GeoIP-Filter: deaktiviert" + fi log "INFO" "═══════════════════════════════════════════════════════════" # Service-Start-Benachrichtigung senden @@ -1251,6 +1301,9 @@ main_loop() { # Whitelist-Worker als Hintergrundprozess starten start_whitelist_worker + # GeoIP-Worker als Hintergrundprozess starten + start_geoip_worker + while true; do # Abgelaufene Sperren prüfen check_expired_bans @@ -1344,6 +1397,47 @@ case "${1:-start}" in echo "Whitelist-Worker nicht gefunden" fi ;; + geoip-status) + init_directories + _worker_script="${SCRIPT_DIR}/geoip-worker.sh" + if [[ -f "$_worker_script" ]]; then + bash "$_worker_script" status + else + echo "GeoIP-Worker nicht gefunden" + fi + ;; + geoip-sync) + init_directories + setup_iptables_chain + _worker_script="${SCRIPT_DIR}/geoip-worker.sh" + if [[ -f "$_worker_script" ]]; then + bash "$_worker_script" sync + else + echo "GeoIP-Worker nicht gefunden" + fi + ;; + geoip-flush) + init_directories + _worker_script="${SCRIPT_DIR}/geoip-worker.sh" + if [[ -f "$_worker_script" ]]; then + bash "$_worker_script" flush + else + echo "GeoIP-Worker nicht gefunden" + fi + ;; + geoip-lookup) + if [[ -z "${2:-}" ]]; then + echo "Nutzung: $0 geoip-lookup " >&2 + exit 1 + fi + init_directories + _worker_script="${SCRIPT_DIR}/geoip-worker.sh" + if [[ -f "$_worker_script" ]]; then + bash "$_worker_script" lookup "$2" + else + echo "GeoIP-Worker nicht gefunden" + fi + ;; status) init_directories show_status @@ -1407,7 +1501,7 @@ Service-Steuerung (empfohlen): sudo systemctl restart adguard-shield sudo systemctl status adguard-shield -Nutzung: $0 {status|history|flush|unban|reset-offenses|test|dry-run|blocklist-status|blocklist-sync|blocklist-flush|whitelist-status|whitelist-sync|whitelist-flush} +Nutzung: $0 {status|history|flush|unban|reset-offenses|test|dry-run|blocklist-status|blocklist-sync|blocklist-flush|whitelist-status|whitelist-sync|whitelist-flush|geoip-status|geoip-sync|geoip-flush|geoip-lookup} Verwaltungsbefehle: status Zeigt aktive Sperren, Regeln und Wiederholungstäter @@ -1423,6 +1517,10 @@ Verwaltungsbefehle: whitelist-status Zeigt Status der externen Whitelisten whitelist-sync Einmalige Synchronisation der externen Whitelisten whitelist-flush Entfernt alle aufgelösten Whitelist-IPs + geoip-status Zeigt Status der GeoIP-Länderfilter + geoip-sync Einmalige GeoIP-Prüfung aller aktiven Clients + geoip-flush Alle GeoIP-Sperren aufheben + geoip-lookup IP GeoIP-Lookup für eine einzelne IP-Adresse Interne Befehle (nicht direkt verwenden — nur über systemd): start Startet den Monitor im Vordergrund diff --git a/docs/architektur.md b/docs/architektur.md index f54141a..c3ea517 100644 --- a/docs/architektur.md +++ b/docs/architektur.md @@ -136,7 +136,9 @@ Das ermöglicht: ├── iptables-helper.sh # iptables Verwaltung ├── external-blocklist-worker.sh # Externer Blocklist-Worker ├── external-whitelist-worker.sh # Externer Whitelist-Worker (DNS-Auflösung) -└── unban-expired.sh # Cron-basiertes Entsperren +├── geoip-worker.sh # GeoIP-Länderfilter-Worker +├── unban-expired.sh # Cron-basiertes Entsperren +└── geoip/ # Auto-Download MaxMind GeoLite2 DB (optional) /etc/systemd/system/ ├── adguard-shield.service # systemd Service (Autostart aktiv) @@ -147,7 +149,8 @@ Das ermöglicht: ├── *.ban # State-Dateien aktiver Sperren ├── *.offenses # Offense-Zähler (Progressive Sperren) ├── external-blocklist/ # Cache für externe Blocklisten -└── external-whitelist/ # Cache für externe Whitelisten + aufgelöste IPs +├── external-whitelist/ # Cache für externe Whitelisten + aufgelöste IPs +└── geoip-cache/ # Cache für GeoIP-Lookups (24h) /var/log/ ├── adguard-shield.log # Laufzeit-Log diff --git a/docs/befehle.md b/docs/befehle.md index a036f9d..82cda84 100644 --- a/docs/befehle.md +++ b/docs/befehle.md @@ -239,6 +239,49 @@ sudo /opt/adguard-shield/external-blocklist-worker.sh status sudo /opt/adguard-shield/external-blocklist-worker.sh flush ``` +## GeoIP-Worker (Länderfilter) + +Der GeoIP-Worker prüft Client-IPs auf ihr Herkunftsland und sperrt/erlaubt sie basierend auf der Konfiguration: + +```bash +# GeoIP-Status anzeigen (Modus, Länder, aktive Sperren, verfügbare Tools) +sudo /opt/adguard-shield/adguard-shield.sh geoip-status + +# Einmalige GeoIP-Prüfung aller aktiven Clients +sudo /opt/adguard-shield/adguard-shield.sh geoip-sync + +# Alle GeoIP-Sperren aufheben +sudo /opt/adguard-shield/adguard-shield.sh geoip-flush + +# GeoIP-Lookup für eine einzelne IP +sudo /opt/adguard-shield/adguard-shield.sh geoip-lookup 8.8.8.8 +``` + +Der Worker kann auch standalone gesteuert werden: + +```bash +# Worker manuell starten (normalerweise automatisch per Hauptscript) +sudo /opt/adguard-shield/geoip-worker.sh start + +# Worker stoppen +sudo /opt/adguard-shield/geoip-worker.sh stop + +# Einmalige Synchronisation +sudo /opt/adguard-shield/geoip-worker.sh sync + +# Status anzeigen +sudo /opt/adguard-shield/geoip-worker.sh status + +# IP nachschlagen +sudo /opt/adguard-shield/geoip-worker.sh lookup 1.2.3.4 + +# Alle GeoIP-Sperren aufheben +sudo /opt/adguard-shield/geoip-worker.sh flush + +# GeoIP-Lookup-Cache leeren +sudo /opt/adguard-shield/geoip-worker.sh flush-cache +``` + ## E-Mail Report ```bash diff --git a/docs/konfiguration.md b/docs/konfiguration.md index 7903779..89a7c06 100644 --- a/docs/konfiguration.md +++ b/docs/konfiguration.md @@ -255,6 +255,93 @@ Der Report an AbuseIPDB enthält (auf Englisch): - **Bei Subdomain-Flood:** `DNS flooding on our DNS server: 85x *.microsoft.com in 60s (random subdomain attack). Banned by Adguard Shield 🔗 https://tnvs.de/as` Die Kategorie `4` (DDoS Attack) wird standardmäßig verwendet. Weitere Kategorien können kommagetrennt angegeben werden (z.B. `"4,15"`). + +### GeoIP-basierte Länderfilter + +Ermöglicht das Sperren oder Erlauben von DNS-Anfragen basierend auf dem Herkunftsland der Client-IP. Unterstützt zwei Modi: + +- **Blocklist-Modus:** Nur die gelisteten Länder werden gesperrt (alle anderen erlaubt) +- **Allowlist-Modus:** Nur die gelisteten Länder werden erlaubt (alle anderen gesperrt) + +| Parameter | Standard | Beschreibung | +|-----------|----------|--------------| +| `GEOIP_ENABLED` | `false` | GeoIP-Filter aktivieren | +| `GEOIP_MODE` | `blocklist` | Modus: `blocklist` oder `allowlist` | +| `GEOIP_COUNTRIES` | *(leer)* | ISO 3166-1 Alpha-2 Ländercodes (kommagetrennt), z.B. `CN,RU,KP,IR` | +| `GEOIP_CHECK_INTERVAL` | `0` | Prüfintervall in Sekunden (`0` = nutzt `CHECK_INTERVAL`) | +| `GEOIP_NOTIFY` | `true` | Benachrichtigungen bei GeoIP-Sperren senden | +| `GEOIP_SKIP_PRIVATE` | `true` | Private/lokale IPs von der GeoIP-Prüfung ausnehmen | +| `GEOIP_LICENSE_KEY` | *(leer)* | MaxMind License-Key für automatischen DB-Download (kostenlos) | +| `GEOIP_MMDB_PATH` | *(leer)* | Manueller Pfad zur MaxMind GeoLite2 Datenbank (überschreibt Auto-Download) | + +#### Voraussetzungen + +Es muss mindestens eines der folgenden GeoIP-Tools installiert sein: + +1. **Automatischer MaxMind-Download** (empfohlen): + ```bash + # Kostenlosen Account erstellen: https://www.maxmind.com/en/geolite2/signup + # License-Key generieren und in adguard-shield.conf eintragen: + GEOIP_LICENSE_KEY="dein_license_key_hier" + ``` + Die GeoLite2-Country-Datenbank wird automatisch heruntergeladen und alle 24 Stunden aktualisiert. + Es wird zusätzlich `mmdbinspect` oder `mmdblookup` benötigt: + ```bash + sudo apt install mmdb-bin # für mmdblookup + ``` + +2. **geoiplookup** (einfachster Einstieg, weniger genau): + ```bash + sudo apt install geoip-bin geoip-database + ``` + +3. **Manueller MaxMind-Pfad** (eigene Datenbank): + ```bash + # mmdbinspect oder mmdblookup installieren + # Datenbank manuell herunterladen: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data + GEOIP_MMDB_PATH="/usr/share/GeoIP/GeoLite2-Country.mmdb" + ``` + +> **Priorität:** `GEOIP_MMDB_PATH` (manuell) → Auto-Download-DB → `geoiplookup` (Legacy-Fallback) + +#### Beispiel: Bestimmte Länder sperren (Blocklist) + +```bash +GEOIP_ENABLED=true +GEOIP_MODE="blocklist" +GEOIP_COUNTRIES="CN,RU,KP,IR" +GEOIP_LICENSE_KEY="dein_maxmind_license_key" # optional, für Auto-Download +``` + +→ Alle Anfragen aus China, Russland, Nordkorea und Iran werden permanent gesperrt. + +#### Beispiel: Nur bestimmte Länder erlauben (Allowlist) + +```bash +GEOIP_ENABLED=true +GEOIP_MODE="allowlist" +GEOIP_COUNTRIES="DE,AT,CH" +``` + +→ Nur Anfragen aus Deutschland, Österreich und der Schweiz werden erlaubt. Alle anderen Länder werden gesperrt. + +> **Hinweis:** Private IP-Adressen (10.x.x.x, 192.168.x.x, etc.) und Whitelist-IPs werden niemals durch GeoIP gesperrt. GeoIP-Sperren sind **immer permanent**. + +> **Auto-Unban:** Wird ein Land aus `GEOIP_COUNTRIES` entfernt oder der Modus (`GEOIP_MODE`) geändert, werden die nicht mehr zutreffenden Sperren beim nächsten Sync **automatisch aufgehoben**. Dasselbe gilt, wenn GeoIP komplett deaktiviert wird (`GEOIP_ENABLED=false`). + +> **Tipp:** GeoIP-Lookups werden für 24 Stunden gecacht. Mit `geoip-flush-cache` kann der Cache manuell geleert werden. + +> **Auto-Download:** Ist `GEOIP_LICENSE_KEY` gesetzt, wird die GeoLite2-Country-Datenbank automatisch nach `/geoip/` heruntergeladen und alle 24 Stunden aktualisiert. Bei einem Update wird der Download im Hintergrund durchgeführt — der Worker läuft während des Downloads normal weiter. Ein manuell gesetzter `GEOIP_MMDB_PATH` hat immer Vorrang vor der automatisch heruntergeladenen Datenbank. + +#### GeoIP-Befehle + +| Befehl | Beschreibung | +|--------|--------------| +| `adguard-shield.sh geoip-status` | Zeigt GeoIP-Status, aktive Sperren und verfügbare Tools | +| `adguard-shield.sh geoip-sync` | Einmalige GeoIP-Prüfung aller aktiven Clients | +| `adguard-shield.sh geoip-flush` | Alle GeoIP-Sperren aufheben | +| `adguard-shield.sh geoip-lookup ` | GeoIP-Lookup einer einzelnen IP-Adresse | + #### Externe Blocklist einrichten 1. Erstelle eine Textdatei auf einem Webserver. Pro Zeile ein Eintrag — IPv4, IPv6, CIDR oder Hostname: diff --git a/geoip-worker.sh b/geoip-worker.sh new file mode 100644 index 0000000..e2f63ff --- /dev/null +++ b/geoip-worker.sh @@ -0,0 +1,932 @@ +#!/bin/bash +############################################################################### +# AdGuard Shield - GeoIP Worker +# Prüft Client-IPs auf Herkunftsland und sperrt/erlaubt basierend auf Konfig. +# Wird als Hintergrundprozess vom Hauptscript gestartet. +# +# Autor: Patrick Asmus +# E-Mail: support@techniverse.net +# 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" + +# ─── Worker PID-File ────────────────────────────────────────────────────────── +WORKER_PID_FILE="/var/run/adguard-geoip-worker.pid" + +# ─── GeoIP Cache ────────────────────────────────────────────────────────────── +GEOIP_CACHE_DIR="${STATE_DIR}/geoip-cache" + +# ─── MaxMind Auto-Download Verzeichnis ──────────────────────────────────────── +GEOIP_DB_DIR="${SCRIPT_DIR}/geoip" +GEOIP_AUTO_DB="${GEOIP_DB_DIR}/GeoLite2-Country.mmdb" +GEOIP_DB_UPDATE_INTERVAL=86400 # 24 Stunden (fest) + +# ─── 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] [GEOIP-WORKER] $message" + echo "$log_entry" | tee -a "$LOG_FILE" >&2 + fi +} + +# ─── Ban-History ───────────────────────────────────────────────────────────── +log_ban_history() { + local action="$1" + local client_ip="$2" + local country="${3:-}" + local reason="${4:-geoip}" + local timestamp + timestamp="$(date '+%Y-%m-%d %H:%M:%S')" + + if [[ ! -f "$BAN_HISTORY_FILE" ]]; then + echo "# AdGuard Shield - Ban History" > "$BAN_HISTORY_FILE" + echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | PROTOKOLL | GRUND" >> "$BAN_HISTORY_FILE" + echo "#──────────────────────────────────────────────────────────────────────────────────────────────────" >> "$BAN_HISTORY_FILE" + fi + + local duration="permanent" + + printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \ + "$timestamp" "$action" "$client_ip" "Land: ${country:-?}" "-" "$duration" "-" "$reason" \ + >> "$BAN_HISTORY_FILE" +} + +# ─── Verzeichnisse erstellen ────────────────────────────────────────────────── +init_directories() { + mkdir -p "$GEOIP_CACHE_DIR" + mkdir -p "$GEOIP_DB_DIR" + mkdir -p "$STATE_DIR" + mkdir -p "$(dirname "$LOG_FILE")" +} + +# ─── Private IP-Adressen erkennen ──────────────────────────────────────────── +is_private_ip() { + local ip="$1" + + # IPv6 Loopback und Link-Local + if [[ "$ip" == "::1" || "$ip" == fe80:* || "$ip" == fc00:* || "$ip" == fd00:* ]]; then + return 0 + fi + + # IPv4 private Bereiche + if [[ "$ip" =~ ^10\. || "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. || "$ip" =~ ^192\.168\. || "$ip" =~ ^127\. || "$ip" == "0.0.0.0" ]]; then + return 0 + fi + + # IPv4 CGNAT + if [[ "$ip" =~ ^100\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\. ]]; then + return 0 + fi + + return 1 +} + +# ─── Whitelist Prüfung ─────────────────────────────────────────────────────── +is_whitelisted() { + local ip="$1" + IFS=',' read -ra wl_entries <<< "$WHITELIST" + for entry in "${wl_entries[@]}"; do + entry=$(echo "$entry" | xargs) # trim + if [[ "$ip" == "$entry" ]]; then + return 0 + fi + done + + # Externe Whitelist prüfen + local ext_wl_file="${EXTERNAL_WHITELIST_CACHE_DIR:-/var/lib/adguard-shield/external-whitelist}/resolved_ips.txt" + if [[ -f "$ext_wl_file" ]] && grep -qxF "$ip" "$ext_wl_file" 2>/dev/null; then + return 0 + fi + + return 1 +} + +# ─── MaxMind GeoLite2 Auto-Download & Update ───────────────────────────────── +# Lädt die GeoLite2-Country.mmdb herunter, wenn GEOIP_LICENSE_KEY gesetzt ist +# und kein eigener GEOIP_MMDB_PATH angegeben wurde. +# Aktualisiert automatisch alle 24 Stunden. +update_maxmind_db() { + local license_key="${GEOIP_LICENSE_KEY:-}" + + # Kein License-Key → nichts zu tun + if [[ -z "$license_key" ]]; then + return 0 + fi + + # User hat eigenen Pfad gesetzt → kein Auto-Download + if [[ -n "${GEOIP_MMDB_PATH:-}" ]]; then + return 0 + fi + + # Prüfen ob Update nötig (alle 24h) + if [[ -f "$GEOIP_AUTO_DB" ]]; then + local db_age + db_age=$(( $(date '+%s') - $(stat -c '%Y' "$GEOIP_AUTO_DB" 2>/dev/null || stat -f '%m' "$GEOIP_AUTO_DB" 2>/dev/null || echo "0") )) + if [[ "$db_age" -lt "$GEOIP_DB_UPDATE_INTERVAL" ]]; then + log "DEBUG" "MaxMind DB ist aktuell (Alter: $((db_age / 3600))h, nächstes Update in $(( (GEOIP_DB_UPDATE_INTERVAL - db_age) / 3600 ))h)" + return 0 + fi + log "INFO" "MaxMind DB ist älter als 24h – starte Update..." + else + log "INFO" "MaxMind DB nicht vorhanden – starte Erstdownload..." + fi + + # Download-URL zusammenbauen (MaxMind Permalink) + local download_url="https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${license_key}&suffix=tar.gz" + local tmp_file="${GEOIP_DB_DIR}/GeoLite2-Country.tar.gz" + local tmp_extract="${GEOIP_DB_DIR}/extract_tmp" + + # Herunterladen + local http_code + http_code=$(curl -s -o "$tmp_file" -w "%{http_code}" \ + --connect-timeout 10 \ + --max-time 60 \ + "$download_url" 2>/dev/null) || true + + if [[ "$http_code" != "200" ]]; then + rm -f "$tmp_file" + case "$http_code" in + 401) log "ERROR" "MaxMind Download fehlgeschlagen: Ungültiger License-Key (HTTP 401)" ;; + 403) log "ERROR" "MaxMind Download fehlgeschlagen: Zugriff verweigert (HTTP 403) – License-Key prüfen" ;; + *) log "ERROR" "MaxMind Download fehlgeschlagen (HTTP ${http_code:-timeout})" ;; + esac + return 1 + fi + + # Entpacken + rm -rf "$tmp_extract" + mkdir -p "$tmp_extract" + + if ! tar -xzf "$tmp_file" -C "$tmp_extract" 2>/dev/null; then + log "ERROR" "MaxMind DB: tar-Archiv konnte nicht entpackt werden" + rm -f "$tmp_file" + rm -rf "$tmp_extract" + return 1 + fi + + # .mmdb Datei finden und verschieben + local mmdb_file + mmdb_file=$(find "$tmp_extract" -name 'GeoLite2-Country.mmdb' -type f 2>/dev/null | head -1) + + if [[ -z "$mmdb_file" || ! -f "$mmdb_file" ]]; then + log "ERROR" "MaxMind DB: GeoLite2-Country.mmdb nicht im Archiv gefunden" + rm -f "$tmp_file" + rm -rf "$tmp_extract" + return 1 + fi + + mv "$mmdb_file" "$GEOIP_AUTO_DB" + rm -f "$tmp_file" + rm -rf "$tmp_extract" + + log "INFO" "MaxMind GeoLite2-Country DB erfolgreich aktualisiert: $GEOIP_AUTO_DB" + return 0 +} + +# ─── Effektiven MMDB-Pfad ermitteln ────────────────────────────────────────── +# Priorität: GEOIP_MMDB_PATH (User) > Auto-Download > leer (Fallback auf geoiplookup) +resolve_mmdb_path() { + # User hat eigenen Pfad gesetzt + if [[ -n "${GEOIP_MMDB_PATH:-}" && -f "${GEOIP_MMDB_PATH:-}" ]]; then + echo "$GEOIP_MMDB_PATH" + return 0 + fi + + # Auto-Download DB vorhanden + if [[ -f "$GEOIP_AUTO_DB" ]]; then + echo "$GEOIP_AUTO_DB" + return 0 + fi + + # Kein MMDB verfügbar + echo "" + return 1 +} + +# ─── GeoIP Lookup ──────────────────────────────────────────────────────────── +# Gibt den ISO 3166-1 Alpha-2 Ländercode zurück (z.B. "DE", "US", "CN") +# Nutzt Cache um wiederholte Lookups zu vermeiden +geoip_lookup() { + local ip="$1" + local cache_file="${GEOIP_CACHE_DIR}/${ip//[:\/]/_}.country" + + # Cache prüfen (max 24 Stunden alt) + if [[ -f "$cache_file" ]]; then + local cache_age + cache_age=$(( $(date '+%s') - $(stat -c '%Y' "$cache_file" 2>/dev/null || stat -f '%m' "$cache_file" 2>/dev/null || echo "0") )) + if [[ "$cache_age" -lt 86400 ]]; then + cat "$cache_file" + return 0 + fi + fi + + local country_code="" + + # Effektiven MMDB-Pfad ermitteln (User-Pfad oder Auto-Download) + local effective_mmdb + effective_mmdb=$(resolve_mmdb_path 2>/dev/null) || true + + # Methode 1: MaxMind mmdbinspect (bevorzugt, genauer) + if [[ -n "$effective_mmdb" && -f "$effective_mmdb" ]] && command -v mmdbinspect &>/dev/null; then + country_code=$(mmdbinspect -db "$effective_mmdb" -ip "$ip" 2>/dev/null \ + | jq -r '.[0].Records[0].Record.country.iso_code // empty' 2>/dev/null || true) + fi + + # Methode 2: geoiplookup (GeoIP Legacy) + if [[ -z "$country_code" ]] && command -v geoiplookup &>/dev/null; then + if [[ "$ip" == *:* ]]; then + # IPv6 + if command -v geoiplookup6 &>/dev/null; then + country_code=$(geoiplookup6 "$ip" 2>/dev/null \ + | grep -oP '(?<=: )[A-Z]{2}(?=,)' | head -1 || true) + fi + else + # IPv4 + country_code=$(geoiplookup "$ip" 2>/dev/null \ + | grep -oP '(?<=: )[A-Z]{2}(?=,)' | head -1 || true) + fi + fi + + # Methode 3: mmdblookup (libmaxminddb) + if [[ -z "$country_code" && -n "$effective_mmdb" && -f "$effective_mmdb" ]] && command -v mmdblookup &>/dev/null; then + country_code=$(mmdblookup --file "$effective_mmdb" --ip "$ip" country iso_code 2>/dev/null \ + | grep -oP '"[A-Z]{2}"' | tr -d '"' | head -1 || true) + fi + + if [[ -n "$country_code" ]]; then + echo "$country_code" > "$cache_file" + echo "$country_code" + return 0 + fi + + # Unbekannt – nicht cachen (könnte temporärer Fehler sein) + echo "" + return 1 +} + +# ─── GeoIP Prüfung: Soll eine IP gesperrt werden? ──────────────────────────── +# Return 0 = sperren, Return 1 = erlauben +should_block_by_geoip() { + local country_code="$1" + local mode="${GEOIP_MODE:-blocklist}" + local countries="${GEOIP_COUNTRIES:-}" + + [[ -z "$country_code" || -z "$countries" ]] && return 1 + + # Länder-Liste in Array umwandeln + IFS=',' read -ra country_list <<< "$countries" + + local found=false + for c in "${country_list[@]}"; do + c=$(echo "$c" | xargs | tr '[:lower:]' '[:upper:]') # trim + uppercase + if [[ "$country_code" == "$c" ]]; then + found=true + break + fi + done + + if [[ "$mode" == "blocklist" ]]; then + # Blocklist-Modus: Sperren wenn Land in der Liste + [[ "$found" == "true" ]] && return 0 || return 1 + elif [[ "$mode" == "allowlist" ]]; then + # Allowlist-Modus: Sperren wenn Land NICHT in der Liste + [[ "$found" == "true" ]] && return 1 || return 0 + fi + + return 1 +} + +# ─── IP via iptables sperren ───────────────────────────────────────────────── +ban_ip_geoip() { + local client_ip="$1" + local country_code="$2" + local mode="${GEOIP_MODE:-blocklist}" + + # Prüfen ob bereits gesperrt + local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban" + if [[ -f "$state_file" ]]; then + log "DEBUG" "GeoIP: $client_ip ist bereits gesperrt" + return 0 + fi + + # GeoIP-Sperren sind immer permanent + local ban_until=0 + local ban_until_display="PERMANENT" + + local reason_text + if [[ "$mode" == "blocklist" ]]; then + reason_text="geoip-blocklist (Land: $country_code)" + else + reason_text="geoip-allowlist (Land: $country_code)" + fi + + log "WARN" "GeoIP SPERRE: $client_ip (Land: $country_code, Modus: $mode) PERMANENT" + + # iptables Regel setzen + if [[ "$client_ip" == *:* ]]; then + ip6tables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true + else + iptables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true + fi + + # State-Datei erstellen + cat > "$state_file" << EOF +CLIENT_IP=$client_ip +DOMAIN=GeoIP:${country_code} +COUNT=- +BAN_TIME=$(date '+%Y-%m-%d %H:%M:%S') +BAN_UNTIL_EPOCH=0 +BAN_UNTIL=PERMANENT +BAN_DURATION=0 +OFFENSE_LEVEL=0 +IS_PERMANENT=true +REASON=geoip +PROTOCOL=- +GEOIP_COUNTRY=$country_code +GEOIP_MODE=$mode +EOF + + # Ban-History + log_ban_history "BAN" "$client_ip" "$country_code" "$reason_text" + + # Benachrichtigung senden + if [[ "${GEOIP_NOTIFY:-true}" == "true" && "${NOTIFY_ENABLED:-false}" == "true" ]]; then + send_geoip_notification "ban" "$client_ip" "$country_code" "PERMANENT" "$mode" + fi +} + +# ─── GeoIP Benachrichtigung ────────────────────────────────────────────────── +send_geoip_notification() { + local action="$1" + local client_ip="$2" + local country_code="$3" + local duration="${4:-PERMANENT}" + local mode="${5:-blocklist}" + local my_hostname + my_hostname=$(hostname) + + local title="🌍 🛡️ AdGuard Shield" + local mode_label + [[ "$mode" == "blocklist" ]] && mode_label="Blocklist" || mode_label="Allowlist" + + local message="🌍 AdGuard Shield GeoIP-Sperre auf ${my_hostname} +--- +IP: ${client_ip} +Land: ${country_code} +Modus: ${mode_label} +Dauer: ${duration} + +Whois: https://www.whois.com/whois/${client_ip} +AbuseIPDB: https://www.abuseipdb.com/check/${client_ip}" + + case "${NOTIFY_TYPE:-}" in + discord) + local json_payload + json_payload=$(jq -nc --arg msg "$message" '{content: $msg}') + curl -s -H "Content-Type: application/json" \ + -d "$json_payload" \ + "$NOTIFY_WEBHOOK_URL" &>/dev/null & + ;; + slack) + local json_payload + json_payload=$(jq -nc --arg msg "$message" '{text: $msg}') + curl -s -H "Content-Type: application/json" \ + -d "$json_payload" \ + "$NOTIFY_WEBHOOK_URL" &>/dev/null & + ;; + gotify) + local clean_message + clean_message=$(echo "$message" | sed 's/\*\*//g') + curl -s -X POST "$NOTIFY_WEBHOOK_URL" \ + -F "title=${title}" \ + -F "message=${clean_message}" \ + -F "priority=5" &>/dev/null & + ;; + ntfy) + local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}" + local -a curl_args=( + -s -X POST + "${ntfy_url}/${NTFY_TOPIC}" + -H "Title: 🛡️ AdGuard Shield GeoIP" + -H "Priority: ${NTFY_PRIORITY:-4}" + -H "Tags: globe_with_meridians,ban" + -d "$message" + ) + [[ -n "${NTFY_TOKEN:-}" ]] && curl_args+=(-H "Authorization: Bearer ${NTFY_TOKEN}") + curl "${curl_args[@]}" &>/dev/null & + ;; + generic) + local json_payload + json_payload=$(jq -nc --arg msg "$message" --arg cl "$client_ip" --arg cc "$country_code" \ + '{message: $msg, action: "geoip-ban", client: $cl, country: $cc}') + curl -s -H "Content-Type: application/json" \ + -d "$json_payload" \ + "$NOTIFY_WEBHOOK_URL" &>/dev/null & + ;; + esac +} + +# ─── iptables Chain Setup ──────────────────────────────────────────────────── +setup_iptables_chain() { + if ! iptables -n -L "$IPTABLES_CHAIN" &>/dev/null; then + iptables -N "$IPTABLES_CHAIN" + for port in $BLOCKED_PORTS; do + iptables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN" + iptables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN" + done + fi + if ! ip6tables -n -L "$IPTABLES_CHAIN" &>/dev/null; then + ip6tables -N "$IPTABLES_CHAIN" + for port in $BLOCKED_PORTS; do + ip6tables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN" + ip6tables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN" + done + fi +} + +# ─── GeoIP-Tools Verfügbarkeit prüfen ──────────────────────────────────────── +check_geoip_tools() { + # Effektiven MMDB-Pfad ermitteln + local effective_mmdb + effective_mmdb=$(resolve_mmdb_path 2>/dev/null) || true + + # mmdbinspect + MMDB + if [[ -n "$effective_mmdb" && -f "$effective_mmdb" ]]; then + if command -v mmdbinspect &>/dev/null; then + echo "mmdbinspect" + return 0 + elif command -v mmdblookup &>/dev/null; then + echo "mmdblookup" + return 0 + fi + fi + + # geoiplookup (Legacy GeoIP) + if command -v geoiplookup &>/dev/null; then + echo "geoiplookup" + return 0 + fi + + echo "none" + return 1 +} + +# ─── Client-IPs aus AdGuard API extrahieren ────────────────────────────────── +get_active_clients() { + local response + response=$(curl -s -u "${ADGUARD_USER}:${ADGUARD_PASS}" \ + --connect-timeout 5 \ + --max-time 10 \ + -k "${ADGUARD_URL}/control/querylog?limit=${API_QUERY_LIMIT:-500}&response_status=all" 2>/dev/null) + + if [[ -z "$response" || "$response" == "null" ]]; then + log "ERROR" "Keine Antwort von AdGuard Home API" + return 1 + fi + + # Eindeutige Client-IPs extrahieren + echo "$response" | jq -r '.data // [] | [.[].client // .[].client_info.ip] | unique | .[]' 2>/dev/null | sort -u +} + +# ─── Auto-Unban: GeoIP-Sperren aufheben bei Konfigurationsänderung ──────────── +# Prüft alle bestehenden GeoIP-Sperren und hebt sie auf, wenn: +# - Das Land nicht mehr in GEOIP_COUNTRIES steht +# - Der Modus gewechselt wurde (blocklist ↔ allowlist) +# - GeoIP deaktiviert wurde +auto_unban_geoip() { + local unban_count=0 + + for f in "${STATE_DIR}"/*.ban; do + [[ -f "$f" ]] || continue + + local reason + reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true) + [[ "$reason" != "geoip" ]] && continue + + local client_ip country_code old_mode + client_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true) + country_code=$(grep '^GEOIP_COUNTRY=' "$f" | cut -d= -f2 || true) + old_mode=$(grep '^GEOIP_MODE=' "$f" | cut -d= -f2 || true) + + local should_unban=false + + # GeoIP deaktiviert → alle GeoIP-Sperren aufheben + if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then + should_unban=true + # Modus gewechselt → alle GeoIP-Sperren aufheben und neu prüfen + elif [[ -n "$old_mode" && "$old_mode" != "${GEOIP_MODE:-blocklist}" ]]; then + should_unban=true + # Prüfen ob das Land nach aktueller Konfiguration noch gesperrt sein soll + elif [[ -n "$country_code" ]] && ! should_block_by_geoip "$country_code"; then + should_unban=true + fi + + if [[ "$should_unban" == "true" ]]; then + log "INFO" "GeoIP Auto-Unban: $client_ip (Land: ${country_code:-?}, war: ${old_mode:-?})" + + # iptables Regel entfernen + if [[ "$client_ip" == *:* ]]; then + ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true + else + iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true + fi + + rm -f "$f" + log_ban_history "UNBAN" "$client_ip" "$country_code" "geoip-auto-unban" + unban_count=$((unban_count + 1)) + fi + done + + if [[ $unban_count -gt 0 ]]; then + log "INFO" "GeoIP Auto-Unban: $unban_count Sperren aufgehoben (Länderliste/Modus geändert)" + fi +} + +# ─── Einmaliger GeoIP-Sync ────────────────────────────────────────────────── +sync_geoip() { + # Auto-Unban zuerst: bestehende Sperren prüfen, die nicht mehr zur Config passen + auto_unban_geoip + + if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then + log "INFO" "GeoIP ist deaktiviert" + return 0 + fi + + # MaxMind DB automatisch herunterladen/aktualisieren (falls License-Key gesetzt) + update_maxmind_db || true + + local countries="${GEOIP_COUNTRIES:-}" + if [[ -z "$countries" ]]; then + log "WARN" "GeoIP: Keine Länder konfiguriert (GEOIP_COUNTRIES ist leer)" + return 0 + fi + + local tool + tool=$(check_geoip_tools) || { + log "ERROR" "GeoIP: Kein GeoIP-Tool verfügbar. Installiere geoip-bin oder mmdbinspect." + return 1 + } + log "INFO" "GeoIP-Sync gestartet (Tool: $tool, Modus: ${GEOIP_MODE:-blocklist}, Länder: $countries)" + + # Client-IPs aus der API holen + local clients + clients=$(get_active_clients) || { + log "ERROR" "GeoIP: Konnte aktive Clients nicht ermitteln" + return 1 + } + + local checked=0 + local blocked=0 + local skipped=0 + + while IFS= read -r client_ip; do + [[ -z "$client_ip" || "$client_ip" == "null" ]] && continue + + # Private IPs überspringen + if [[ "${GEOIP_SKIP_PRIVATE:-true}" == "true" ]] && is_private_ip "$client_ip"; then + log "DEBUG" "GeoIP: Private IP übersprungen: $client_ip" + skipped=$((skipped + 1)) + continue + fi + + # Whitelist prüfen + if is_whitelisted "$client_ip"; then + log "DEBUG" "GeoIP: Whitelisted IP übersprungen: $client_ip" + skipped=$((skipped + 1)) + continue + fi + + # Bereits gesperrt? + local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban" + if [[ -f "$state_file" ]]; then + skipped=$((skipped + 1)) + continue + fi + + checked=$((checked + 1)) + + # GeoIP Lookup + local country_code + country_code=$(geoip_lookup "$client_ip") || true + + if [[ -z "$country_code" ]]; then + log "DEBUG" "GeoIP: Kein Ergebnis für $client_ip" + continue + fi + + log "DEBUG" "GeoIP: $client_ip → $country_code" + + # Prüfen ob gesperrt werden soll + if should_block_by_geoip "$country_code"; then + ban_ip_geoip "$client_ip" "$country_code" + blocked=$((blocked + 1)) + fi + done <<< "$clients" + + log "INFO" "GeoIP-Sync abgeschlossen: $checked geprüft, $blocked gesperrt, $skipped übersprungen" +} + +# ─── Worker-Hauptschleife ──────────────────────────────────────────────────── +start_worker() { + if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then + log "DEBUG" "GeoIP-Worker ist deaktiviert" + return 0 + fi + + # PID schreiben + echo $$ > "$WORKER_PID_FILE" + trap 'rm -f "$WORKER_PID_FILE"; exit 0' SIGTERM SIGINT SIGHUP + + local interval="${GEOIP_CHECK_INTERVAL:-0}" + [[ "$interval" -le 0 ]] && interval="${CHECK_INTERVAL:-10}" + + log "INFO" "GeoIP-Worker gestartet (PID: $$, Intervall: ${interval}s)" + + # Beim Start: MaxMind DB herunterladen/aktualisieren (falls License-Key gesetzt) + update_maxmind_db || true + + while true; do + sync_geoip + sleep "$interval" + done +} + +# ─── Status anzeigen ───────────────────────────────────────────────────────── +show_status() { + echo "═══════════════════════════════════════════════════════════════" + echo " AdGuard Shield - GeoIP Status" + echo "═══════════════════════════════════════════════════════════════" + echo "" + + if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then + echo " ℹ️ GeoIP ist deaktiviert" + echo "" + return + fi + + echo " Modus: ${GEOIP_MODE:-blocklist}" + echo " Länder: ${GEOIP_COUNTRIES:-}" + echo " Sperrdauer: PERMANENT (Auto-Unban bei Änderung der Länderliste)" + echo " Private IPs überspringen: ${GEOIP_SKIP_PRIVATE:-true}" + echo "" + + # MaxMind DB Info + local eff_mmdb + eff_mmdb=$(resolve_mmdb_path) + if [[ -n "${GEOIP_MMDB_PATH:-}" ]]; then + echo " MMDB-Pfad: ${GEOIP_MMDB_PATH} (manuell konfiguriert)" + elif [[ -n "${GEOIP_LICENSE_KEY:-}" ]]; then + echo " MMDB-Pfad: ${GEOIP_AUTO_DB} (Auto-Download)" + if [[ -f "${GEOIP_AUTO_DB}" ]]; then + local db_age db_age_h + db_age=$(( $(date +%s) - $(stat -c %Y "${GEOIP_AUTO_DB}" 2>/dev/null || echo 0) )) + db_age_h=$(( db_age / 3600 )) + echo " DB-Alter: ${db_age_h}h (Update alle 24h)" + else + echo " DB-Status: ⚠️ Noch nicht heruntergeladen" + fi + elif [[ -n "$eff_mmdb" ]]; then + echo " MMDB-Pfad: ${eff_mmdb}" + else + echo " MMDB-Pfad: (Fallback auf geoiplookup)" + fi + echo " License-Key: $(if [[ -n "${GEOIP_LICENSE_KEY:-}" ]]; then echo "✅ konfiguriert"; else echo "❌ nicht gesetzt (kein Auto-Download)"; fi)" + echo "" + + # GeoIP Tools prüfen + echo " GeoIP Tools:" + local tool + tool=$(check_geoip_tools 2>/dev/null) || tool="none" + case "$tool" in + mmdbinspect) echo " ✅ mmdbinspect mit MaxMind DB" ;; + mmdblookup) echo " ✅ mmdblookup mit MaxMind DB" ;; + geoiplookup) echo " ✅ geoiplookup (Legacy GeoIP)" ;; + none) echo " ❌ Kein GeoIP-Tool gefunden!" ;; + esac + echo "" + + # Worker-Status + if [[ -f "$WORKER_PID_FILE" ]]; then + local wpid + wpid=$(cat "$WORKER_PID_FILE") + if kill -0 "$wpid" 2>/dev/null; then + echo " Worker: Läuft (PID: $wpid)" + else + echo " Worker: Abgestürzt (PID: $wpid existiert nicht mehr)" + fi + else + echo " Worker: Nicht gestartet" + fi + echo "" + + # GeoIP-Sperren anzeigen + local geoip_bans=0 + if [[ -d "$STATE_DIR" ]]; then + for f in "${STATE_DIR}"/*.ban; do + [[ -f "$f" ]] || continue + local reason + reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true) + if [[ "$reason" == "geoip" ]]; then + geoip_bans=$((geoip_bans + 1)) + local s_ip s_country s_until + s_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true) + s_country=$(grep '^GEOIP_COUNTRY=' "$f" | cut -d= -f2 || true) + s_until=$(grep '^BAN_UNTIL=' "$f" | cut -d= -f2 || true) + echo " 🌍 $s_ip → Land: ${s_country:-?} (bis: ${s_until:-?})" + fi + done + fi + + if [[ $geoip_bans -eq 0 ]]; then + echo " Keine aktiven GeoIP-Sperren" + else + echo "" + echo " Gesamt: $geoip_bans aktive GeoIP-Sperren" + fi + + # Cache-Statistik + if [[ -d "$GEOIP_CACHE_DIR" ]]; then + local cache_count + cache_count=$(find "$GEOIP_CACHE_DIR" -name '*.country' -type f 2>/dev/null | wc -l) + echo "" + echo " Cache: $cache_count IP-Lookups zwischengespeichert" + fi + + echo "" + echo "═══════════════════════════════════════════════════════════════" +} + +# ─── Einzelne IP nachschlagen ──────────────────────────────────────────────── +lookup_ip() { + local ip="$1" + + local eff_mmdb + eff_mmdb=$(resolve_mmdb_path) + + local tool + tool=$(check_geoip_tools 2>/dev/null) || tool="none" + + if [[ "$tool" == "none" ]]; then + echo "❌ Kein GeoIP-Tool verfügbar." + echo " Installiere geoip-bin: sudo apt install geoip-bin geoip-database" + echo " Oder mmdbinspect mit MaxMind GeoLite2 DB" + return 1 + fi + + local country_code + country_code=$(geoip_lookup "$ip") || true + + if [[ -z "$country_code" ]]; then + echo "IP: $ip → Land: unbekannt (kein GeoIP-Ergebnis)" + return 1 + fi + + echo "IP: $ip → Land: $country_code (Tool: $tool)" + [[ -n "$eff_mmdb" ]] && echo " MMDB: $eff_mmdb" + + # Prüfen ob diese IP gesperrt werden würde + if [[ "${GEOIP_ENABLED:-false}" == "true" && -n "${GEOIP_COUNTRIES:-}" ]]; then + if should_block_by_geoip "$country_code"; then + echo "→ Würde GESPERRT werden (Modus: ${GEOIP_MODE:-blocklist}, Länder: ${GEOIP_COUNTRIES})" + else + echo "→ Würde ERLAUBT werden (Modus: ${GEOIP_MODE:-blocklist}, Länder: ${GEOIP_COUNTRIES})" + fi + fi +} + +# ─── Cache leeren ──────────────────────────────────────────────────────────── +flush_cache() { + if [[ -d "$GEOIP_CACHE_DIR" ]]; then + local count + count=$(find "$GEOIP_CACHE_DIR" -name '*.country' -type f 2>/dev/null | wc -l) + rm -f "${GEOIP_CACHE_DIR}"/*.country 2>/dev/null || true + echo "✅ GeoIP-Cache geleert ($count Einträge entfernt)" + log "INFO" "GeoIP-Cache geleert ($count Einträge)" + else + echo "ℹ️ GeoIP-Cache-Verzeichnis existiert nicht" + fi +} + +# ─── GeoIP-Sperren aufheben ───────────────────────────────────────────────── +flush_geoip_bans() { + local count=0 + if [[ -d "$STATE_DIR" ]]; then + for f in "${STATE_DIR}"/*.ban; do + [[ -f "$f" ]] || continue + local reason + reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true) + if [[ "$reason" == "geoip" ]]; then + local client_ip + client_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true) + + # iptables Regel entfernen + if [[ "$client_ip" == *:* ]]; then + ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true + else + iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true + fi + + rm -f "$f" + log_ban_history "UNBAN" "$client_ip" "" "geoip-flush" + count=$((count + 1)) + fi + done + fi + + echo "✅ $count GeoIP-Sperren aufgehoben" + log "INFO" "$count GeoIP-Sperren aufgehoben (flush)" +} + +# ─── Hauptprogramm ────────────────────────────────────────────────────────── +case "${1:-help}" in + start) + init_directories + setup_iptables_chain + start_worker + ;; + sync) + init_directories + setup_iptables_chain + sync_geoip + ;; + status) + init_directories + show_status + ;; + lookup) + if [[ -z "${2:-}" ]]; then + echo "Nutzung: $0 lookup " >&2 + exit 1 + fi + init_directories + lookup_ip "$2" + ;; + flush) + init_directories + flush_geoip_bans + ;; + flush-cache) + init_directories + flush_cache + ;; + stop) + if [[ -f "$WORKER_PID_FILE" ]]; then + local wpid + wpid=$(cat "$WORKER_PID_FILE") + if kill -0 "$wpid" 2>/dev/null; then + kill "$wpid" 2>/dev/null || true + rm -f "$WORKER_PID_FILE" + echo "GeoIP-Worker gestoppt" + else + rm -f "$WORKER_PID_FILE" + echo "GeoIP-Worker war nicht aktiv" + fi + else + echo "GeoIP-Worker läuft nicht" + fi + ;; + *) + cat << USAGE +AdGuard Shield - GeoIP Worker + +Nutzung: $0 {start|stop|sync|status|lookup|flush|flush-cache} + +Befehle: + start Startet den GeoIP-Worker (Hintergrundprozess) + stop Stoppt den GeoIP-Worker + sync Einmalige GeoIP-Prüfung aller aktiven Clients + status Zeigt GeoIP-Status und aktive Sperren + lookup GeoIP-Lookup für eine einzelne IP + flush Alle GeoIP-Sperren aufheben + flush-cache GeoIP-Lookup-Cache leeren + +Konfiguration in: $CONFIG_FILE + GEOIP_ENABLED=true/false + GEOIP_MODE=blocklist/allowlist + GEOIP_COUNTRIES="CN,RU,..." + +USAGE + exit 0 + ;; +esac diff --git a/install.sh b/install.sh index 54e3888..0edc84b 100644 --- a/install.sh +++ b/install.sh @@ -132,11 +132,18 @@ print_help() { echo -e " ${CYAN}sudo systemctl disable adguard-shield-watchdog.timer${NC} # Watchdog deaktivieren" echo -e " ${CYAN}sudo journalctl -u adguard-shield-watchdog.service${NC} # Watchdog-Logs" echo "" + echo -e "${BOLD}GeoIP-Befehle:${NC}" + echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-status${NC} # GeoIP-Status anzeigen" + echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-sync${NC} # Einmalige GeoIP-Prüfung" + echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-flush${NC} # Alle GeoIP-Sperren aufheben" + echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-lookup IP${NC} # GeoIP-Lookup einer IP" + echo "" echo -e "${BOLD}Voraussetzungen:${NC}" echo " - Linux Server (Debian/Ubuntu empfohlen)" echo " - Root-Zugriff (sudo)" echo " - AdGuard Home installiert und erreichbar" echo " - Pakete: curl, jq, iptables, gawk (werden bei Installation automatisch installiert)" + echo " - GeoIP (optional): geoip-bin + geoip-database oder MaxMind GeoLite2 DB" echo "" echo -e "${BOLD}Dokumentation:${NC}" echo " https://git.techniverse.net/scriptos/adguard-shield" @@ -257,6 +264,7 @@ install_files() { cp "$SCRIPT_DIR/report-generator.sh" "$INSTALL_DIR/" cp "$SCRIPT_DIR/adguard-shield-watchdog.sh" "$INSTALL_DIR/" cp "$SCRIPT_DIR/uninstall.sh" "$INSTALL_DIR/" + cp "$SCRIPT_DIR/geoip-worker.sh" "$INSTALL_DIR/" # Templates kopieren mkdir -p "$INSTALL_DIR/templates" @@ -272,6 +280,7 @@ install_files() { chmod +x "$INSTALL_DIR/report-generator.sh" chmod +x "$INSTALL_DIR/adguard-shield-watchdog.sh" chmod +x "$INSTALL_DIR/uninstall.sh" + chmod +x "$INSTALL_DIR/geoip-worker.sh" echo -e " ✅ Dateien installiert" echo "" diff --git a/uninstall.sh b/uninstall.sh index 23faa72..f734c93 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -125,8 +125,10 @@ do_uninstall() { rm -f "$INSTALL_DIR/external-whitelist-worker.sh" rm -f "$INSTALL_DIR/report-generator.sh" rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh" + rm -f "$INSTALL_DIR/geoip-worker.sh" rm -f "$INSTALL_DIR/uninstall.sh" rm -rf "$INSTALL_DIR/templates" + rm -rf "$INSTALL_DIR/geoip" echo " ✅ Scripts entfernt (Konfiguration und Logs behalten)" echo "" echo -e "${YELLOW} Konfiguration verbleibt in: $INSTALL_DIR/adguard-shield.conf${NC}" From 0da5d01641077af176df0ca6645c006027db9435 Mon Sep 17 00:00:00 2001 From: scriptos Date: Tue, 14 Apr 2026 20:44:06 +0200 Subject: [PATCH 06/10] =?UTF-8?q?update:=20Konfigurationsdatei=20aufr?= =?UTF-8?q?=C3=A4umen=20=E2=80=93=20Kommentare=20gek=C3=BCrzt,=20Verweis?= =?UTF-8?q?=20auf=20Doku=20erg=C3=A4nzt,=20fehlende=20Variable=20EXTERNAL?= =?UTF-8?q?=5FWHITELIST=5FCACHE=5FDIR=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adguard-shield.conf | 271 +++++++++----------------------------------- 1 file changed, 51 insertions(+), 220 deletions(-) diff --git a/adguard-shield.conf b/adguard-shield.conf index abc1212..537ebe3 100644 --- a/adguard-shield.conf +++ b/adguard-shield.conf @@ -1,271 +1,102 @@ ############################################################################### # AdGuard Shield - Konfigurationsdatei -# Schutz vor übermäßigen DNS-Anfragen einzelner Clients +# Ausführliche Dokumentation: docs/konfiguration.md ############################################################################### -# --- AdGuard Home API Einstellungen --- -# URL der AdGuard Home Web-Oberfläche (ohne trailing slash) +# --- AdGuard Home API --- ADGUARD_URL="https://dns1.domain.com" - -# AdGuard Home Zugangsdaten (Web-UI Login) ADGUARD_USER="admin" ADGUARD_PASS='changeme' -# --- Rate-Limit Einstellungen --- -# Maximale Anfragen pro Domain pro Client innerhalb des Zeitfensters -RATE_LIMIT_MAX_REQUESTS=30 +# --- Rate-Limit --- +RATE_LIMIT_MAX_REQUESTS=30 # Max. Anfragen pro Domain/Client im Zeitfenster +RATE_LIMIT_WINDOW=60 # Zeitfenster in Sekunden +CHECK_INTERVAL=10 # Prüfintervall in Sekunden -# Zeitfenster in Sekunden (60 = 1 Minute) -RATE_LIMIT_WINDOW=60 - -# Wie oft das Script die Logs prüft (in Sekunden) -CHECK_INTERVAL=10 - -# --- Subdomain-Flood-Erkennung (Random Subdomain Attack) --- -# Erkennt Bots/Clients die massenhaft zufällige Subdomains einer Domain abfragen -# Beispiel: abc123.microsoft.com, xyz456.microsoft.com, ... -# Dabei wird pro Client gezählt, wie viele EINDEUTIGE Subdomains einer -# Basisdomain (z.B. microsoft.com) im Zeitfenster aufgerufen werden. - -# Subdomain-Flood-Erkennung aktivieren +# --- Subdomain-Flood-Erkennung --- SUBDOMAIN_FLOOD_ENABLED=true - -# Maximale Anzahl eindeutiger Subdomains pro Basisdomain pro Client im Zeitfenster -# Beispiel: 50 = ein Client darf max. 50 verschiedene Subdomains von microsoft.com abfragen -SUBDOMAIN_FLOOD_MAX_UNIQUE=50 - -# Zeitfenster in Sekunden für die Subdomain-Flood-Erkennung (60 = 1 Minute) -SUBDOMAIN_FLOOD_WINDOW=60 +SUBDOMAIN_FLOOD_MAX_UNIQUE=50 # Max. eindeutige Subdomains pro Basisdomain/Client +SUBDOMAIN_FLOOD_WINDOW=60 # Zeitfenster in Sekunden # --- Sperr-Einstellungen --- -# Wie lange ein Client gesperrt wird (in Sekunden, 3600 = 1 Stunde) -BAN_DURATION=3600 - -# iptables Chain-Name für die Sperren +BAN_DURATION=3600 # Basis-Sperrdauer in Sekunden IPTABLES_CHAIN="ADGUARD_SHIELD" - -# Welche Ports gesperrt werden sollen (IPv4 + IPv6) -# Port 53 = DNS (UDP + TCP) -# Port 443 = DNS-over-HTTPS (DoH) -# Port 853 = DNS-over-TLS (tls://...:853) / DNS-over-QUIC (quic://...:853) -# Hinweis: Das verwendete Protokoll (DNS/DoH/DoT/DoQ) wird automatisch -# aus der AdGuard Home API erkannt und in Logs/History angezeigt. -BLOCKED_PORTS="53 443 853" +BLOCKED_PORTS="53 443 853" # DNS(53), DoH(443), DoT/DoQ(853) # --- Whitelist --- -# IP-Adressen die NIEMALS gesperrt werden (kommagetrennt) -# Lokale Netze und wichtige Server hier eintragen +# IPs die niemals gesperrt werden (kommagetrennt) WHITELIST="127.0.0.1,::1" # --- Logging --- -# Log-Datei Pfad LOG_FILE="/var/log/adguard-shield.log" - -# Log-Level: DEBUG, INFO, WARN, ERROR -LOG_LEVEL="INFO" - -# Maximale Größe der Log-Datei in MB (danach wird rotiert) -LOG_MAX_SIZE_MB=50 - -# Ban-History Datei (protokolliert alle Sperren & Entsperrungen dauerhaft) +LOG_LEVEL="INFO" # DEBUG, INFO, WARN, ERROR +LOG_MAX_SIZE_MB=50 # Max. Größe in MB (danach Rotation) BAN_HISTORY_FILE="/var/log/adguard-shield-bans.log" +BAN_HISTORY_RETENTION_DAYS=0 # 0 = unbegrenzt -# Maximale Aufbewahrungsdauer der Ban-History in Tagen -# 0 = unbegrenzt (niemals automatisch löschen) -# Beispiel: 90 = Einträge älter als 90 Tage werden beim nächsten Report entfernt -BAN_HISTORY_RETENTION_DAYS=0 - -# --- Benachrichtigungen (optional) --- -# Aktiviert Benachrichtigungen bei Sperren/Entsperrungen +# --- Benachrichtigungen --- NOTIFY_ENABLED=false +NOTIFY_TYPE="ntfy" # ntfy, discord, slack, gotify, generic +NOTIFY_WEBHOOK_URL="" # Webhook-URL (nicht für ntfy) -# Benachrichtigungs-Typ: "ntfy", "discord", "slack", "gotify", "generic" -NOTIFY_TYPE="ntfy" - -# Webhook-URL (nur für discord, slack, gotify, generic – bei ntfy nicht nötig) -# Discord: https://discord.com/api/webhooks/xxx/yyy -# Gotify: https://gotify.example.com/message?token=xxx -NOTIFY_WEBHOOK_URL="" - -# --- Ntfy Einstellungen (nur bei NOTIFY_TYPE="ntfy") --- -# Server-URL der Ntfy-Instanz (ohne trailing slash) +# Ntfy-Einstellungen (nur bei NOTIFY_TYPE="ntfy") NTFY_SERVER_URL="https://ntfy.sh" - -# Topic-Name für die Benachrichtigungen NTFY_TOPIC="" - -# Optionaler Access-Token (leer lassen wenn nicht benötigt) NTFY_TOKEN="" +NTFY_PRIORITY="4" # 1=min, 3=default, 5=max -# Priorität der Ntfy-Nachrichten (1=min, 3=default, 5=max) -NTFY_PRIORITY="4" - -# --- E-Mail Report (optional) --- -# Regelmäßiger Statistik-Report per E-Mail -# Voraussetzung: Ein funktionierender Mail-Transport (z.B. msmtp) -# Anleitung für msmtp: https://www.cleveradmin.de/blog/2024/12/linux-einfach-emails-versenden-mit-msmtp/ +# --- E-Mail Report --- REPORT_ENABLED=false - -# Report-Intervall: "daily", "weekly", "biweekly", "monthly" -# daily = täglich um die konfigurierte Uhrzeit -# weekly = wöchentlich am Montag -# biweekly = alle zwei Wochen am Montag -# monthly = monatlich am 1. des Monats -REPORT_INTERVAL="weekly" - -# Uhrzeit für den Report-Versand (Format: HH:MM, 24h) +REPORT_INTERVAL="weekly" # daily, weekly, biweekly, monthly REPORT_TIME="08:00" - -# E-Mail-Empfänger REPORT_EMAIL_TO="admin@example.com" - -# E-Mail-Absender REPORT_EMAIL_FROM="adguard-shield@example.com" - -# E-Mail-Format: "html" oder "txt" -REPORT_FORMAT="html" - -# Mail-Befehl (z.B. "msmtp", "sendmail", "mail") +REPORT_FORMAT="html" # html, txt REPORT_MAIL_CMD="msmtp" +REPORT_BUSIEST_DAY_RANGE=30 # Tage für "Aktivster Tag" (0 = nur Berichtszeitraum) -# Zeitraum für "Aktivster Tag" im Report (in Tagen) -# Bestimmt, über wie viele Tage zurück der aktivste Tag ermittelt wird. -# 30 = Aktivster Tag der letzten 30 Tage (empfohlen) -# 0 = Nur innerhalb des Berichtszeitraums (altes Verhalten) -REPORT_BUSIEST_DAY_RANGE=30 - -# --- Externe Whitelist (optional) --- -# Ermöglicht das Einbinden externer Whitelist-Dateien mit Domains/IPs. -# Domains werden regelmäßig per DNS aufgelöst (ideal für dynamische IPs/DynDNS). +# --- Externe Whitelist --- +# Externe Whitelist-Dateien mit Domains/IPs; Domains werden per DNS aufgelöst EXTERNAL_WHITELIST_ENABLED=false +EXTERNAL_WHITELIST_URLS="" # URL(s) kommagetrennt +EXTERNAL_WHITELIST_INTERVAL=300 # Prüfintervall in Sekunden +EXTERNAL_WHITELIST_CACHE_DIR="/var/lib/adguard-shield/external-whitelist" -# URL(s) zu externen Textdateien mit Domains/IPs (eine pro Zeile) -# Mehrere URLs kommagetrennt angeben -# Beispiel: "https://example.com/whitelist.txt,https://other.com/trusted-hosts.txt" -EXTERNAL_WHITELIST_URLS="" - -# Wie oft die externe Whitelist geprüft und Domains neu aufgelöst werden (in Sekunden, 300 = 5 Minuten) -# Kürzere Intervalle empfohlen bei vielen DynDNS-Einträgen -EXTERNAL_WHITELIST_INTERVAL=300 - -# --- Externe Blocklist (optional) --- -# Aktiviert den externen Blocklist-Worker +# --- Externe Blocklist --- EXTERNAL_BLOCKLIST_ENABLED=false - -# URL(s) zu externen Textdateien mit IP-Adressen (eine IP pro Zeile) -# Mehrere URLs kommagetrennt angeben -# Beispiel: "https://example.com/blocklist.txt,https://other.com/bad-ips.txt" -EXTERNAL_BLOCKLIST_URLS="" - -# Wie oft die externe Blocklist geprüft wird (in Sekunden, 300 = 5 Minuten) -EXTERNAL_BLOCKLIST_INTERVAL=300 - -# Sperrdauer für externe Blocklist-IPs in Sekunden (0 = permanent bis IP aus Liste entfernt) -EXTERNAL_BLOCKLIST_BAN_DURATION=0 - -# Automatisch IPs entsperren die aus der externen Liste entfernt wurden? +EXTERNAL_BLOCKLIST_URLS="" # URL(s) kommagetrennt +EXTERNAL_BLOCKLIST_INTERVAL=300 # Prüfintervall in Sekunden +EXTERNAL_BLOCKLIST_BAN_DURATION=0 # 0 = permanent bis IP aus Liste entfernt EXTERNAL_BLOCKLIST_AUTO_UNBAN=true - -# Benachrichtigungen bei Blocklist-Sperren senden? -# Bei Listen mit vielen IPs empfiehlt sich false, da sonst beim Sync -# hunderte Benachrichtigungen auf einmal verschickt werden. -EXTERNAL_BLOCKLIST_NOTIFY=false - -# Lokaler Cache-Pfad für die heruntergeladene Blocklist +EXTERNAL_BLOCKLIST_NOTIFY=false # Bei großen Listen auf false lassen EXTERNAL_BLOCKLIST_CACHE_DIR="/var/lib/adguard-shield/external-blocklist" # --- Progressive Sperren (Recidive) --- -# Wiederholungstäter werden stufenweise länger gesperrt (wie bei fail2ban) -# Aktiviert das progressive Sperrsystem +# Wiederholungstäter werden stufenweise länger gesperrt PROGRESSIVE_BAN_ENABLED=true +PROGRESSIVE_BAN_MULTIPLIER=2 # Multiplikator pro Stufe (2 = Verdopplung) +PROGRESSIVE_BAN_MAX_LEVEL=5 # Ab dieser Stufe permanent sperren (0 = nie) +PROGRESSIVE_BAN_RESET_AFTER=86400 # Zähler-Reset nach X Sekunden ohne Vergehen -# Multiplikator pro Wiederholung (2 = Verdopplung der Sperrdauer) -# Stufe 1: BAN_DURATION × 1 (Standard-Sperrdauer) -# Stufe 2: BAN_DURATION × 2 -# Stufe 3: BAN_DURATION × 4 -# Stufe 4: BAN_DURATION × 8 ... usw. -PROGRESSIVE_BAN_MULTIPLIER=2 - -# Ab dieser Stufe wird die IP permanent gesperrt (0 = nie permanent sperren) -# Beispiel: 5 = nach dem 5. Vergehen wird die IP dauerhaft gesperrt -PROGRESSIVE_BAN_MAX_LEVEL=5 - -# Nach wie vielen Sekunden ohne erneutes Vergehen wird der Zähler zurückgesetzt -# (86400 = 24 Stunden, 604800 = 7 Tage) -PROGRESSIVE_BAN_RESET_AFTER=86400 - -# --- AbuseIPDB Reporting (optional) --- -# Meldet permanent gesperrte IPs automatisch an AbuseIPDB -# Nur bei PERMANENTEN Sperren wird ein Report gesendet. +# --- AbuseIPDB Reporting --- +# Meldet nur permanent gesperrte IPs an AbuseIPDB ABUSEIPDB_ENABLED=false - -# AbuseIPDB API-Key (https://www.abuseipdb.com/account/api) ABUSEIPDB_API_KEY="" +ABUSEIPDB_CATEGORIES="4" # 4 = DDoS Attack (siehe abuseipdb.com/categories) -# Kategorien für den Report (kommagetrennt) -# 4 = DDoS Attack -# Siehe: https://www.abuseipdb.com/categories -ABUSEIPDB_CATEGORIES="4" - -# --- GeoIP-basierte Länderfilter (optional) --- -# Sperrt oder erlaubt DNS-Anfragen basierend auf dem Herkunftsland der Client-IP. -# Alle Lookups erfolgen LOKAL über eine Datenbank – es werden keine Online-API-Calls gemacht. -# -# Einfachster Weg (empfohlen): -# sudo apt install geoip-bin geoip-database -# → Damit funktioniert GeoIP sofort, ohne Account oder API-Key. -# -# Für genauere/aktuellere Daten (optional): -# Kostenlosen MaxMind-Account erstellen (https://www.maxmind.com/en/geolite2/signup) -# und License-Key unter GEOIP_LICENSE_KEY eintragen. Die GeoLite2-Datenbank -# wird dann automatisch heruntergeladen und alle 24 Stunden aktualisiert. -# Alternativ kann ein bereits vorhandener DB-Pfad über GEOIP_MMDB_PATH angegeben werden. +# --- GeoIP-basierte Länderfilter --- +# Sperrt/erlaubt DNS-Anfragen nach Herkunftsland (lokale DB, keine Online-API) GEOIP_ENABLED=false - -# Modus: "blocklist" = nur gelistete Länder sperren -# "allowlist" = nur gelistete Länder erlauben (alle anderen werden gesperrt) -GEOIP_MODE="blocklist" - -# Kommagetrennte Liste von ISO 3166-1 Alpha-2 Ländercodes -# Blocklist-Modus: Diese Länder werden gesperrt -# Allowlist-Modus: NUR diese Länder werden erlaubt (Rest wird gesperrt) -# Beispiel: "CN,RU,KP,IR" oder "DE,AT,CH" -GEOIP_COUNTRIES="" - -# Wie oft die GeoIP-Prüfung durchgeführt wird (in Sekunden, 0 = bei jedem Check-Intervall) -# Empfohlen: Gleicher Wert wie CHECK_INTERVAL oder höher -GEOIP_CHECK_INTERVAL=0 - -# Benachrichtigungen bei GeoIP-Sperren senden? +GEOIP_MODE="blocklist" # blocklist oder allowlist +GEOIP_COUNTRIES="" # ISO 3166-1 Alpha-2 Codes, z.B. "CN,RU,KP,IR" +GEOIP_CHECK_INTERVAL=0 # 0 = nutzt CHECK_INTERVAL GEOIP_NOTIFY=true - -# Lokale IPs und private Netze von GeoIP-Prüfung ausnehmen (empfohlen: true) -GEOIP_SKIP_PRIVATE=true - -# MaxMind GeoLite2 License-Key (optional, für automatischen Download & Update) -# Kostenloser Account: https://www.maxmind.com/en/geolite2/signup -# License-Key erstellen: Account → Manage License Keys → Generate New License Key -# Wenn gesetzt, wird die GeoLite2-Country-Datenbank automatisch heruntergeladen -# und alle 24 Stunden aktualisiert. Die DB wird lokal gespeichert unter: -# /geoip/GeoLite2-Country.mmdb -GEOIP_LICENSE_KEY="" - -# Pfad zur MaxMind GeoLite2 .mmdb-Datenbank (optional) -# Wenn leer UND GEOIP_LICENSE_KEY gesetzt → automatischer Download (s.o.) -# Wenn leer UND GEOIP_LICENSE_KEY leer → Fallback auf geoiplookup (apt install geoip-bin) -# Wenn gesetzt → diese Datei wird direkt verwendet (kein automatischer Download) -# Beispiel: "/usr/share/GeoIP/GeoLite2-Country.mmdb" -GEOIP_MMDB_PATH="" +GEOIP_SKIP_PRIVATE=true # Private IPs ausnehmen +GEOIP_LICENSE_KEY="" # MaxMind GeoLite2 Key (optional, für Auto-Download) +GEOIP_MMDB_PATH="" # Manueller DB-Pfad (optional, hat Vorrang) # --- Erweiterte Einstellungen --- -# Pfad zur State-Datei (speichert aktive Sperren) STATE_DIR="/var/lib/adguard-shield" - -# Pfad zum PID-File PID_FILE="/var/run/adguard-shield.pid" - -# Anzahl der API-Einträge die pro Abfrage geholt werden (max 5000) -API_QUERY_LIMIT=500 - -# Dry-Run Modus: true = nur loggen, nicht sperren (zum Testen) -DRY_RUN=false +API_QUERY_LIMIT=500 # API-Einträge pro Abfrage (max 5000) +DRY_RUN=false # true = nur loggen, nicht sperren From 2a1d8ae975d730ab25a78cb9d720bebc7118fb1d Mon Sep 17 00:00:00 2001 From: scriptos Date: Tue, 14 Apr 2026 21:01:51 +0200 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20Offense-Cleanup-Worker=20f=C3=BCr?= =?UTF-8?q?=20automatisches=20Aufr=C3=A4umen=20abgelaufener=20Offense-Z?= =?UTF-8?q?=C3=A4hler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- adguard-shield.conf | 8 +- adguard-shield.sh | 40 ++++++ docs/architektur.md | 1 + docs/befehle.md | 20 +++ docs/konfiguration.md | 2 +- install.sh | 7 ++ offense-cleanup-worker.sh | 255 ++++++++++++++++++++++++++++++++++++++ uninstall.sh | 1 + 9 files changed, 330 insertions(+), 6 deletions(-) create mode 100644 offense-cleanup-worker.sh diff --git a/README.md b/README.md index 7db1893..c757be0 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Wenn ein Client eine bestimmte Domain zu oft anfragt (z.B. >30x pro Minute), wir - Automatische Erkennung und Sperre bei Rate-Limit-Verstößen - **Subdomain-Flood-Erkennung** — erkennt Random-Subdomain-Attacken (z.B. `abc123.microsoft.com`, `xyz456.microsoft.com`, ...) -- **Progressive Sperren (Recidive)** — Wiederholungstäter werden stufenweise länger gesperrt (wie bei fail2ban) +- **Progressive Sperren (Recidive)** — Wiederholungstäter werden stufenweise länger gesperrt (wie bei fail2ban), mit automatischem Cleanup abgelaufener Zähler - Unterstützt **alle DNS-Protokolle**: DNS (53), DoH (443), DoT (853), DoQ (784/853/8853) - **IPv4 + IPv6** - Eigene iptables Chain — greift nicht in bestehende Regeln ein diff --git a/adguard-shield.conf b/adguard-shield.conf index 537ebe3..aae7ca0 100644 --- a/adguard-shield.conf +++ b/adguard-shield.conf @@ -21,7 +21,7 @@ SUBDOMAIN_FLOOD_WINDOW=60 # Zeitfenster in Sekunden # --- Sperr-Einstellungen --- BAN_DURATION=3600 # Basis-Sperrdauer in Sekunden IPTABLES_CHAIN="ADGUARD_SHIELD" -BLOCKED_PORTS="53 443 853" # DNS(53), DoH(443), DoT/DoQ(853) +BLOCKED_PORTS="53 443 853" # DNS(53), DoH(443), DoT/DoQ(853) # --- Whitelist --- # IPs die niemals gesperrt werden (kommagetrennt) @@ -88,12 +88,12 @@ ABUSEIPDB_CATEGORIES="4" # 4 = DDoS Attack (siehe abuseipdb.com/categ # Sperrt/erlaubt DNS-Anfragen nach Herkunftsland (lokale DB, keine Online-API) GEOIP_ENABLED=false GEOIP_MODE="blocklist" # blocklist oder allowlist -GEOIP_COUNTRIES="" # ISO 3166-1 Alpha-2 Codes, z.B. "CN,RU,KP,IR" +GEOIP_COUNTRIES="" # ISO 3166-1 Alpha-2 Codes, z.B. "CN,RU,KP,IR" GEOIP_CHECK_INTERVAL=0 # 0 = nutzt CHECK_INTERVAL GEOIP_NOTIFY=true GEOIP_SKIP_PRIVATE=true # Private IPs ausnehmen -GEOIP_LICENSE_KEY="" # MaxMind GeoLite2 Key (optional, für Auto-Download) -GEOIP_MMDB_PATH="" # Manueller DB-Pfad (optional, hat Vorrang) +GEOIP_LICENSE_KEY="" # MaxMind GeoLite2 Key (optional, für Auto-Download) +GEOIP_MMDB_PATH="" # Manueller DB-Pfad (optional, hat Vorrang) # --- Erweiterte Einstellungen --- STATE_DIR="/var/lib/adguard-shield" diff --git a/adguard-shield.sh b/adguard-shield.sh index ea9f4e6..7eff70f 100644 --- a/adguard-shield.sh +++ b/adguard-shield.sh @@ -331,6 +331,7 @@ cleanup() { stop_blocklist_worker stop_whitelist_worker stop_geoip_worker + stop_offense_cleanup_worker rm -f "$PID_FILE" exit 0 } @@ -1257,6 +1258,39 @@ stop_geoip_worker() { fi } +# ─── Offense-Cleanup-Worker starten ────────────────────────────────────────── +start_offense_cleanup_worker() { + if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" != "true" ]]; then + log "DEBUG" "Offense-Cleanup-Worker ist deaktiviert (Progressive Sperren inaktiv)" + return + fi + + local worker_script="${SCRIPT_DIR}/offense-cleanup-worker.sh" + if [[ ! -f "$worker_script" ]]; then + log "WARN" "Offense-Cleanup-Worker Script nicht gefunden: $worker_script" + return + fi + + log "INFO" "Starte Offense-Cleanup-Worker im Hintergrund..." + bash "$worker_script" start & + OFFENSE_CLEANUP_WORKER_PID=$! + log "INFO" "Offense-Cleanup-Worker gestartet (PID: $OFFENSE_CLEANUP_WORKER_PID)" +} + +# ─── Offense-Cleanup-Worker stoppen ────────────────────────────────────────── +stop_offense_cleanup_worker() { + local worker_pid_file="/var/run/adguard-offense-cleanup-worker.pid" + if [[ -f "$worker_pid_file" ]]; then + local wpid + wpid=$(cat "$worker_pid_file") + if kill -0 "$wpid" 2>/dev/null; then + log "INFO" "Stoppe Offense-Cleanup-Worker (PID: $wpid)..." + kill "$wpid" 2>/dev/null || true + rm -f "$worker_pid_file" + fi + fi +} + # ─── Hauptschleife ────────────────────────────────────────────────────────── main_loop() { log "INFO" "═══════════════════════════════════════════════════════════" @@ -1288,6 +1322,9 @@ main_loop() { else log "INFO" " GeoIP-Filter: deaktiviert" fi + if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then + log "INFO" " Offense-Cleanup: AKTIV (Reset: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}"), Prüfintervall: 1h)" + fi log "INFO" "═══════════════════════════════════════════════════════════" # Service-Start-Benachrichtigung senden @@ -1304,6 +1341,9 @@ main_loop() { # GeoIP-Worker als Hintergrundprozess starten start_geoip_worker + # Offense-Cleanup-Worker als Hintergrundprozess starten + start_offense_cleanup_worker + while true; do # Abgelaufene Sperren prüfen check_expired_bans diff --git a/docs/architektur.md b/docs/architektur.md index c3ea517..904613f 100644 --- a/docs/architektur.md +++ b/docs/architektur.md @@ -137,6 +137,7 @@ Das ermöglicht: ├── external-blocklist-worker.sh # Externer Blocklist-Worker ├── external-whitelist-worker.sh # Externer Whitelist-Worker (DNS-Auflösung) ├── geoip-worker.sh # GeoIP-Länderfilter-Worker +├── offense-cleanup-worker.sh # Automatisches Aufräumen abgelaufener Offense-Zähler ├── unban-expired.sh # Cron-basiertes Entsperren └── geoip/ # Auto-Download MaxMind GeoLite2 DB (optional) diff --git a/docs/befehle.md b/docs/befehle.md index 82cda84..22c890d 100644 --- a/docs/befehle.md +++ b/docs/befehle.md @@ -282,6 +282,26 @@ sudo /opt/adguard-shield/geoip-worker.sh flush sudo /opt/adguard-shield/geoip-worker.sh flush-cache ``` +## Offense-Cleanup-Worker + +Der Offense-Cleanup-Worker räumt abgelaufene Offense-Zähler (progressive Sperren) automatisch auf. Er startet automatisch mit dem Hauptservice, wenn progressive Sperren aktiviert sind, und prüft stündlich ob Zähler aufgeräumt werden können. + +Der Worker kann auch standalone gesteuert werden: + +```bash +# Worker manuell starten (normalerweise automatisch per Hauptscript) +sudo /opt/adguard-shield/offense-cleanup-worker.sh start + +# Worker stoppen +sudo /opt/adguard-shield/offense-cleanup-worker.sh stop + +# Einmaliger Cleanup-Durchlauf +sudo /opt/adguard-shield/offense-cleanup-worker.sh run-once + +# Status anzeigen (aktive/abgelaufene Zähler) +sudo /opt/adguard-shield/offense-cleanup-worker.sh status +``` + ## E-Mail Report ```bash diff --git a/docs/konfiguration.md b/docs/konfiguration.md index 89a7c06..85f0c41 100644 --- a/docs/konfiguration.md +++ b/docs/konfiguration.md @@ -100,7 +100,7 @@ Wiederholungstäter werden wie bei fail2ban stufenweise länger gesperrt. Wird e | 4. Mal | 4 | 8 Stunden | 3600 × 8 | | 5. Mal | 5 | **PERMANENT** | Max-Stufe erreicht | -> **Hinweis:** Der Offense-Zähler wird automatisch zurückgesetzt, wenn eine IP für den konfigurierten Zeitraum (`PROGRESSIVE_BAN_RESET_AFTER`) kein erneutes Vergehen begeht. Permanente Sperren werden **nicht** automatisch aufgehoben – sie müssen manuell mit `unban` oder `flush` entfernt werden. +> **Hinweis:** Abgelaufene Offense-Zähler werden automatisch vom **Offense-Cleanup-Worker** aufgeräumt, der stündlich prüft, ob das letzte Vergehen einer IP länger als `PROGRESSIVE_BAN_RESET_AFTER` zurückliegt. Der Worker startet automatisch zusammen mit dem Hauptservice, wenn progressive Sperren aktiviert sind. Manuelles Zurücksetzen ist jederzeit mit `reset-offenses` möglich. Permanente Sperren werden **nicht** automatisch aufgehoben – sie müssen manuell mit `unban` oder `flush` entfernt werden. ### Logging diff --git a/install.sh b/install.sh index 0edc84b..2cf9fef 100644 --- a/install.sh +++ b/install.sh @@ -265,6 +265,7 @@ install_files() { cp "$SCRIPT_DIR/adguard-shield-watchdog.sh" "$INSTALL_DIR/" cp "$SCRIPT_DIR/uninstall.sh" "$INSTALL_DIR/" cp "$SCRIPT_DIR/geoip-worker.sh" "$INSTALL_DIR/" + cp "$SCRIPT_DIR/offense-cleanup-worker.sh" "$INSTALL_DIR/" # Templates kopieren mkdir -p "$INSTALL_DIR/templates" @@ -281,6 +282,7 @@ install_files() { chmod +x "$INSTALL_DIR/adguard-shield-watchdog.sh" chmod +x "$INSTALL_DIR/uninstall.sh" chmod +x "$INSTALL_DIR/geoip-worker.sh" + chmod +x "$INSTALL_DIR/offense-cleanup-worker.sh" echo -e " ✅ Dateien installiert" echo "" @@ -810,8 +812,13 @@ do_uninstall() { rm -f "$INSTALL_DIR/unban-expired.sh" rm -f "$INSTALL_DIR/external-blocklist-worker.sh" rm -f "$INSTALL_DIR/external-whitelist-worker.sh" + rm -f "$INSTALL_DIR/offense-cleanup-worker.sh" + rm -f "$INSTALL_DIR/geoip-worker.sh" rm -f "$INSTALL_DIR/report-generator.sh" + rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh" + rm -f "$INSTALL_DIR/uninstall.sh" rm -rf "$INSTALL_DIR/templates" + rm -rf "$INSTALL_DIR/geoip" echo " ✅ Scripts entfernt (Konfiguration und Logs behalten)" else rm -rf "$INSTALL_DIR" diff --git a/offense-cleanup-worker.sh b/offense-cleanup-worker.sh new file mode 100644 index 0000000..b160854 --- /dev/null +++ b/offense-cleanup-worker.sh @@ -0,0 +1,255 @@ +#!/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-14 +# 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" + +# ─── 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 + + 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 + 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 diff --git a/uninstall.sh b/uninstall.sh index f734c93..751891e 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -123,6 +123,7 @@ do_uninstall() { rm -f "$INSTALL_DIR/unban-expired.sh" rm -f "$INSTALL_DIR/external-blocklist-worker.sh" rm -f "$INSTALL_DIR/external-whitelist-worker.sh" + rm -f "$INSTALL_DIR/offense-cleanup-worker.sh" rm -f "$INSTALL_DIR/report-generator.sh" rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh" rm -f "$INSTALL_DIR/geoip-worker.sh" From 83075f27827eb22ce56024e7060478833d201bbc Mon Sep 17 00:00:00 2001 From: scriptos Date: Tue, 14 Apr 2026 21:06:52 +0200 Subject: [PATCH 08/10] Release: Version v0.8.0 --- adguard-shield.sh | 2 +- docs/benachrichtigungen.md | 4 ++-- install.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/adguard-shield.sh b/adguard-shield.sh index 7eff70f..4dc26c2 100644 --- a/adguard-shield.sh +++ b/adguard-shield.sh @@ -8,7 +8,7 @@ # Lizenz: MIT ############################################################################### -VERSION="v0.7.1" +VERSION="v0.8.0" set -euo pipefail diff --git a/docs/benachrichtigungen.md b/docs/benachrichtigungen.md index 96fa15c..580305c 100644 --- a/docs/benachrichtigungen.md +++ b/docs/benachrichtigungen.md @@ -116,12 +116,12 @@ Bei Sperren aus der **externen Blocklist** werden Benachrichtigungen separat üb ### Service gestartet **Überschrift:** ✅ AdGuard Shield -> 🟢 AdGuard Shield v0.7.1 wurde auf dns1 gestartet. +> 🟢 AdGuard Shield v0.8.0 wurde auf dns1 gestartet. ### Service gestoppt **Überschrift:** 🚨 🛡️ AdGuard Shield -> 🔴 AdGuard Shield v0.7.1 wurde auf dns1 gestoppt. +> 🔴 AdGuard Shield v0.8.0 wurde auf dns1 gestoppt. ### Watchdog — Service wiederhergestellt **Überschrift:** 🔄 AdGuard Shield Watchdog diff --git a/install.sh b/install.sh index 2cf9fef..2c4b6e3 100644 --- a/install.sh +++ b/install.sh @@ -6,7 +6,7 @@ # Lizenz: MIT ############################################################################### -VERSION="v0.7.1" +VERSION="v0.8.0" set -euo pipefail From 633331748fa1de43731a20dfc04fa77c6ac24013 Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Thu, 16 Apr 2026 21:33:47 +0200 Subject: [PATCH 09/10] =?UTF-8?q?fix:=20Offense-Cleanup-Worker=20mit=20nie?= =?UTF-8?q?drigster=20CPU/IO-Priorit=C3=A4t=20ausf=C3=BChren?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adguard-shield.sh | 4 ++-- docs/architektur.md | 2 +- docs/befehle.md | 2 +- docs/konfiguration.md | 2 +- offense-cleanup-worker.sh | 15 ++++++++++++++- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/adguard-shield.sh b/adguard-shield.sh index 4dc26c2..b5b9907 100644 --- a/adguard-shield.sh +++ b/adguard-shield.sh @@ -1271,8 +1271,8 @@ start_offense_cleanup_worker() { return fi - log "INFO" "Starte Offense-Cleanup-Worker im Hintergrund..." - bash "$worker_script" start & + log "INFO" "Starte Offense-Cleanup-Worker im Hintergrund (nice 19, idle I/O)..." + nice -n 19 ionice -c 3 bash "$worker_script" start & OFFENSE_CLEANUP_WORKER_PID=$! log "INFO" "Offense-Cleanup-Worker gestartet (PID: $OFFENSE_CLEANUP_WORKER_PID)" } diff --git a/docs/architektur.md b/docs/architektur.md index 904613f..34637a6 100644 --- a/docs/architektur.md +++ b/docs/architektur.md @@ -137,7 +137,7 @@ Das ermöglicht: ├── external-blocklist-worker.sh # Externer Blocklist-Worker ├── external-whitelist-worker.sh # Externer Whitelist-Worker (DNS-Auflösung) ├── geoip-worker.sh # GeoIP-Länderfilter-Worker -├── offense-cleanup-worker.sh # Automatisches Aufräumen abgelaufener Offense-Zähler +├── offense-cleanup-worker.sh # Aufräumen abgelaufener Offense-Zähler (nice 19, idle I/O) ├── unban-expired.sh # Cron-basiertes Entsperren └── geoip/ # Auto-Download MaxMind GeoLite2 DB (optional) diff --git a/docs/befehle.md b/docs/befehle.md index 22c890d..385cc1b 100644 --- a/docs/befehle.md +++ b/docs/befehle.md @@ -284,7 +284,7 @@ sudo /opt/adguard-shield/geoip-worker.sh flush-cache ## Offense-Cleanup-Worker -Der Offense-Cleanup-Worker räumt abgelaufene Offense-Zähler (progressive Sperren) automatisch auf. Er startet automatisch mit dem Hauptservice, wenn progressive Sperren aktiviert sind, und prüft stündlich ob Zähler aufgeräumt werden können. +Der Offense-Cleanup-Worker räumt abgelaufene Offense-Zähler (progressive Sperren) automatisch auf. Er startet automatisch mit dem Hauptservice, wenn progressive Sperren aktiviert sind, und prüft stündlich ob Zähler aufgeräumt werden können. Der Worker läuft mit niedrigster CPU- und I/O-Priorität (`nice 19`, `ionice idle`), um den DNS-Dienst nicht zu beeinträchtigen. Der Worker kann auch standalone gesteuert werden: diff --git a/docs/konfiguration.md b/docs/konfiguration.md index 85f0c41..4bd4889 100644 --- a/docs/konfiguration.md +++ b/docs/konfiguration.md @@ -100,7 +100,7 @@ Wiederholungstäter werden wie bei fail2ban stufenweise länger gesperrt. Wird e | 4. Mal | 4 | 8 Stunden | 3600 × 8 | | 5. Mal | 5 | **PERMANENT** | Max-Stufe erreicht | -> **Hinweis:** Abgelaufene Offense-Zähler werden automatisch vom **Offense-Cleanup-Worker** aufgeräumt, der stündlich prüft, ob das letzte Vergehen einer IP länger als `PROGRESSIVE_BAN_RESET_AFTER` zurückliegt. Der Worker startet automatisch zusammen mit dem Hauptservice, wenn progressive Sperren aktiviert sind. Manuelles Zurücksetzen ist jederzeit mit `reset-offenses` möglich. Permanente Sperren werden **nicht** automatisch aufgehoben – sie müssen manuell mit `unban` oder `flush` entfernt werden. +> **Hinweis:** Abgelaufene Offense-Zähler werden automatisch vom **Offense-Cleanup-Worker** aufgeräumt, der stündlich prüft, ob das letzte Vergehen einer IP länger als `PROGRESSIVE_BAN_RESET_AFTER` zurückliegt. Der Worker startet automatisch zusammen mit dem Hauptservice, wenn progressive Sperren aktiviert sind. Er läuft mit niedrigster CPU- und I/O-Priorität (`nice 19`, `ionice idle`), sodass andere Dienste nicht beeinträchtigt werden. Manuelles Zurücksetzen ist jederzeit mit `reset-offenses` möglich. Permanente Sperren werden **nicht** automatisch aufgehoben – sie müssen manuell mit `unban` oder `flush` entfernt werden. ### Logging diff --git a/offense-cleanup-worker.sh b/offense-cleanup-worker.sh index b160854..ff40f9e 100644 --- a/offense-cleanup-worker.sh +++ b/offense-cleanup-worker.sh @@ -8,7 +8,7 @@ # # Autor: Patrick Asmus # E-Mail: support@techniverse.net -# Datum: 2026-04-14 +# Datum: 2026-04-16 # Lizenz: MIT ############################################################################### @@ -25,6 +25,12 @@ 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" @@ -80,6 +86,7 @@ cleanup_expired_offenses() { now=$(date '+%s') local cleaned=0 + local batch_count=0 for offense_file in "${STATE_DIR}"/*.offenses; do [[ -f "$offense_file" ]] || continue @@ -101,6 +108,12 @@ cleanup_expired_offenses() { 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 From c2d6f872f5e0a9b9dd53f2a3e40b9abbbd942e37 Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Thu, 16 Apr 2026 22:22:13 +0200 Subject: [PATCH 10/10] Release: Version v0.8.1 --- adguard-shield.sh | 2 +- docs/benachrichtigungen.md | 4 ++-- install.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/adguard-shield.sh b/adguard-shield.sh index b5b9907..1cdde89 100644 --- a/adguard-shield.sh +++ b/adguard-shield.sh @@ -8,7 +8,7 @@ # Lizenz: MIT ############################################################################### -VERSION="v0.8.0" +VERSION="v0.8.1" set -euo pipefail diff --git a/docs/benachrichtigungen.md b/docs/benachrichtigungen.md index 580305c..7a6dcf3 100644 --- a/docs/benachrichtigungen.md +++ b/docs/benachrichtigungen.md @@ -116,12 +116,12 @@ Bei Sperren aus der **externen Blocklist** werden Benachrichtigungen separat üb ### Service gestartet **Überschrift:** ✅ AdGuard Shield -> 🟢 AdGuard Shield v0.8.0 wurde auf dns1 gestartet. +> 🟢 AdGuard Shield v0.8.1 wurde auf dns1 gestartet. ### Service gestoppt **Überschrift:** 🚨 🛡️ AdGuard Shield -> 🔴 AdGuard Shield v0.8.0 wurde auf dns1 gestoppt. +> 🔴 AdGuard Shield v0.8.1 wurde auf dns1 gestoppt. ### Watchdog — Service wiederhergestellt **Überschrift:** 🔄 AdGuard Shield Watchdog diff --git a/install.sh b/install.sh index 2c4b6e3..ac75c6c 100644 --- a/install.sh +++ b/install.sh @@ -6,7 +6,7 @@ # Lizenz: MIT ############################################################################### -VERSION="v0.8.0" +VERSION="v0.8.1" set -euo pipefail