Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6bdeb5bc31 | |||
| 5451c01603 | |||
| 6daaf67f7c | |||
| 0970218f9b | |||
| db128f3076 | |||
| 6f14219445 | |||
| cb31aa48eb | |||
| 1e8b7557e7 | |||
| 4d1870cc85 | |||
| ebcd70ce8b | |||
| ba342dd571 | |||
| ac1af85810 | |||
| 54b6c877e5 | |||
| 8562202aa7 | |||
| 3361b571cf | |||
| 86eeb2b947 | |||
| cf915c5c80 | |||
| cf1e554a28 | |||
| 657fdbaf6b | |||
| a39dc88770 | |||
| 19f72d5be4 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.ki-workspace
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -2,6 +2,7 @@
|
||||
"files.eol": "\n",
|
||||
"chat.tools.terminal.autoApprove": {
|
||||
"Rename-Item": true,
|
||||
"ForEach-Object": true
|
||||
"ForEach-Object": true,
|
||||
"&": true
|
||||
}
|
||||
}
|
||||
18
README.md
18
README.md
@@ -29,6 +29,7 @@ Wenn ein Client eine bestimmte Domain zu oft anfragt (z.B. >30x pro Minute), wir
|
||||
- Eigene iptables Chain — greift nicht in bestehende Regeln ein
|
||||
- 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)
|
||||
- **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
|
||||
@@ -90,6 +91,8 @@ sudo /opt/adguard-shield/adguard-shield.sh reset-offenses # Offense-Zähler
|
||||
sudo /opt/adguard-shield/adguard-shield.sh test # API-Verbindung testen
|
||||
sudo /opt/adguard-shield/adguard-shield.sh blocklist-status # Externe Blocklisten Status
|
||||
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/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
|
||||
@@ -103,6 +106,7 @@ sudo journalctl -u adguard-shield -f # Logs live ver
|
||||
├── adguard-shield.conf # Konfiguration
|
||||
├── adguard-shield.service # systemd Unit
|
||||
├── 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
|
||||
@@ -112,7 +116,7 @@ sudo journalctl -u adguard-shield -f # Logs live ver
|
||||
│ ├── report.html # HTML-Report-Template
|
||||
│ └── report.txt # TXT-Report-Template
|
||||
├── README.md
|
||||
└── doc/
|
||||
└── docs/
|
||||
├── architektur.md # Architektur & Funktionsweise
|
||||
├── konfiguration.md # Alle Parameter erklärt + Konfig-Migration
|
||||
├── befehle.md # Vollständige Befehlsreferenz inkl. Installer
|
||||
@@ -125,12 +129,12 @@ sudo journalctl -u adguard-shield -f # Logs live ver
|
||||
|
||||
| Dokument | Inhalt |
|
||||
|----------|--------|
|
||||
| [Architektur](doc/architektur.md) | Wie das Tool funktioniert, iptables-Strategie, Konfig-Migration |
|
||||
| [Konfiguration](doc/konfiguration.md) | Alle Parameter, Ports, Whitelist-Pflege, automatische Migration |
|
||||
| [Befehle](doc/befehle.md) | Vollständige Befehlsreferenz für Installer, Monitor, iptables-Helper und systemd |
|
||||
| [Benachrichtigungen](doc/benachrichtigungen.md) | Setup für Discord, Slack, Gotify, Ntfy |
|
||||
| [E-Mail Report](doc/report.md) | Periodische Statistik-Reports per E-Mail (HTML/TXT) |
|
||||
| [Tipps & Troubleshooting](doc/tipps-und-troubleshooting.md) | Best Practices, häufige Probleme, Deinstallation |
|
||||
| [Architektur](docs/architektur.md) | Wie das Tool funktioniert, iptables-Strategie, Konfig-Migration |
|
||||
| [Konfiguration](docs/konfiguration.md) | Alle Parameter, Ports, Whitelist-Pflege, automatische Migration |
|
||||
| [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 |
|
||||
|
||||
## Lizenz
|
||||
|
||||
|
||||
@@ -128,6 +128,26 @@ REPORT_FORMAT="html"
|
||||
# Mail-Befehl (z.B. "msmtp", "sendmail", "mail")
|
||||
REPORT_MAIL_CMD="msmtp"
|
||||
|
||||
# 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).
|
||||
EXTERNAL_WHITELIST_ENABLED=false
|
||||
|
||||
# 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
|
||||
EXTERNAL_BLOCKLIST_ENABLED=false
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
#
|
||||
# Autor: Patrick Asmus
|
||||
# E-Mail: support@techniverse.net
|
||||
# Datum: 2026-03-04
|
||||
# Lizenz: MIT
|
||||
###############################################################################
|
||||
|
||||
VERSION="v0.5.2"
|
||||
VERSION="v0.7.0"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -231,6 +230,30 @@ format_protocol() {
|
||||
esac
|
||||
}
|
||||
|
||||
# ─── Hostname-Auflösung ──────────────────────────────────────────────────────
|
||||
# Versucht den Hostnamen einer IP per Reverse-DNS aufzulösen
|
||||
resolve_hostname() {
|
||||
local ip="$1"
|
||||
local hostname=""
|
||||
|
||||
# Versuche Reverse-DNS-Auflösung via dig
|
||||
if command -v dig &>/dev/null; then
|
||||
hostname=$(dig +short -x "$ip" 2>/dev/null | head -1 | sed 's/\.$//')
|
||||
fi
|
||||
|
||||
# Fallback via host
|
||||
if [[ -z "$hostname" ]] && command -v host &>/dev/null; then
|
||||
hostname=$(host "$ip" 2>/dev/null | awk '/domain name pointer/ {print $NF}' | sed 's/\.$//' | head -1)
|
||||
fi
|
||||
|
||||
# Fallback via getent
|
||||
if [[ -z "$hostname" ]] && command -v getent &>/dev/null; then
|
||||
hostname=$(getent hosts "$ip" 2>/dev/null | awk '{print $2}' | head -1)
|
||||
fi
|
||||
|
||||
echo "${hostname:-(unbekannt)}"
|
||||
}
|
||||
|
||||
# ─── AbuseIPDB Reporting ─────────────────────────────────────────────────────
|
||||
# Meldet eine IP an AbuseIPDB (nur bei permanenten Sperren)
|
||||
report_to_abuseipdb() {
|
||||
@@ -306,6 +329,7 @@ cleanup() {
|
||||
sleep 1
|
||||
fi
|
||||
stop_blocklist_worker
|
||||
stop_whitelist_worker
|
||||
rm -f "$PID_FILE"
|
||||
exit 0
|
||||
}
|
||||
@@ -333,6 +357,13 @@ is_whitelisted() {
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Externe Whitelist prüfen (aufgelöste IPs aus dem Whitelist-Worker)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -452,7 +483,7 @@ EOF
|
||||
|
||||
# Benachrichtigung senden
|
||||
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
|
||||
send_notification "ban" "$client_ip" "$domain" "$count" "$offense_level" "$duration_display" "$reason" "$window" "$protocol"
|
||||
send_notification "ban" "$client_ip" "$domain" "$count" "$offense_level" "$duration_display" "$reason" "$window" "$protocol" "$is_permanent"
|
||||
fi
|
||||
|
||||
# AbuseIPDB Report (nur bei permanenter Sperre)
|
||||
@@ -490,7 +521,7 @@ unban_client() {
|
||||
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "$reason" "-" "$protocol"
|
||||
|
||||
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
|
||||
send_notification "unban" "$client_ip" "" ""
|
||||
send_notification "unban" "$client_ip" "$domain" ""
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -532,6 +563,7 @@ send_notification() {
|
||||
local reason="${7:-rate-limit}"
|
||||
local window="${8:-$RATE_LIMIT_WINDOW}"
|
||||
local protocol="${9:-DNS}"
|
||||
local is_permanent="${10:-false}"
|
||||
|
||||
# Ntfy benötigt keine Webhook-URL (nutzt NTFY_SERVER_URL + NTFY_TOPIC)
|
||||
if [[ "$NOTIFY_TYPE" != "ntfy" && -z "$NOTIFY_WEBHOOK_URL" ]]; then
|
||||
@@ -541,46 +573,92 @@ send_notification() {
|
||||
local reason_label="Rate-Limit"
|
||||
[[ "$reason" == "subdomain-flood" ]] && reason_label="Subdomain-Flood"
|
||||
|
||||
local title
|
||||
local message
|
||||
local my_hostname
|
||||
my_hostname=$(hostname)
|
||||
|
||||
if [[ "$action" == "ban" ]]; then
|
||||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && -n "$offense_level" ]]; then
|
||||
message="🚫 AdGuard Shield: Client **$client_ip** gesperrt (${count}x $domain in ${window}s via **$protocol**, ${reason_label}). Sperre für **${duration_display}** [Stufe ${offense_level}/${PROGRESSIVE_BAN_MAX_LEVEL:-0}]."
|
||||
else
|
||||
local simple_dur
|
||||
simple_dur=$(format_duration "${BAN_DURATION}")
|
||||
message="🚫 AdGuard Shield: Client **$client_ip** gesperrt (${count}x $domain in ${window}s via **$protocol**, ${reason_label}). Sperre für ${simple_dur}."
|
||||
title="🚨 🛡️ AdGuard Shield"
|
||||
local client_hostname
|
||||
client_hostname=$(resolve_hostname "$client_ip")
|
||||
|
||||
# AbuseIPDB-Hinweis bei permanenter Sperre
|
||||
local abuseipdb_hint=""
|
||||
if [[ "$is_permanent" == "true" && "${ABUSEIPDB_ENABLED:-false}" == "true" ]]; then
|
||||
abuseipdb_hint=$'\n⚠️ IP wurde an AbuseIPDB gemeldet'
|
||||
fi
|
||||
|
||||
# Dauer-Anzeige mit Stufe
|
||||
local dur_line
|
||||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && -n "$offense_level" ]]; then
|
||||
dur_line="**${duration_display}** [Stufe ${offense_level}/${PROGRESSIVE_BAN_MAX_LEVEL:-0}]"
|
||||
else
|
||||
dur_line=$(format_duration "${BAN_DURATION}")
|
||||
fi
|
||||
|
||||
message="🚫 AdGuard Shield Ban auf ${my_hostname}${abuseipdb_hint}
|
||||
---
|
||||
IP: ${client_ip}
|
||||
Hostname: ${client_hostname}
|
||||
Grund: ${count}x ${domain} in ${window}s via ${protocol}, ${reason_label}
|
||||
Dauer: ${dur_line}
|
||||
|
||||
Whois: https://www.whois.com/whois/${client_ip}
|
||||
AbuseIPDB: https://www.abuseipdb.com/check/${client_ip}"
|
||||
|
||||
elif [[ "$action" == "unban" ]]; then
|
||||
title="✅ AdGuard Shield"
|
||||
local client_hostname
|
||||
client_hostname=$(resolve_hostname "$client_ip")
|
||||
|
||||
message="✅ AdGuard Shield Freigabe auf ${my_hostname}
|
||||
---
|
||||
IP: ${client_ip}
|
||||
Hostname: ${client_hostname}
|
||||
|
||||
AbuseIPDB: https://www.abuseipdb.com/check/${client_ip}"
|
||||
|
||||
elif [[ "$action" == "service_start" ]]; then
|
||||
message="🟢 AdGuard Shield v${VERSION} wurde gestartet."
|
||||
title="✅ AdGuard Shield"
|
||||
message="🟢 AdGuard Shield ${VERSION} wurde auf ${my_hostname} gestartet."
|
||||
elif [[ "$action" == "service_stop" ]]; then
|
||||
message="🔴 AdGuard Shield v${VERSION} wurde gestoppt."
|
||||
else
|
||||
message="✅ AdGuard Shield: Client **$client_ip** wurde entsperrt."
|
||||
title="🚨 🛡️ AdGuard Shield"
|
||||
message="🔴 AdGuard Shield ${VERSION} wurde auf ${my_hostname} gestoppt."
|
||||
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 "{\"content\": \"$message\"}" \
|
||||
-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 "{\"text\": \"$message\"}" \
|
||||
-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=AdGuard Shield" \
|
||||
-F "message=$message" \
|
||||
-F "title=${title}" \
|
||||
-F "message=${clean_message}" \
|
||||
-F "priority=5" &>/dev/null &
|
||||
;;
|
||||
ntfy)
|
||||
send_ntfy_notification "$action" "$message"
|
||||
send_ntfy_notification "$action" "$title" "$message"
|
||||
;;
|
||||
generic)
|
||||
local json_payload
|
||||
json_payload=$(jq -nc --arg msg "$message" --arg act "$action" --arg cl "${client_ip:-}" --arg dom "${domain:-}" \
|
||||
'{message: $msg, action: $act, client: $cl, domain: $dom}')
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "{\"message\": \"$message\", \"action\": \"$action\", \"client\": \"${client_ip:-}\", \"domain\": \"${domain:-}\"}" \
|
||||
-d "$json_payload" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
esac
|
||||
@@ -589,7 +667,8 @@ send_notification() {
|
||||
# ─── Ntfy Benachrichtigung ───────────────────────────────────────────────────
|
||||
send_ntfy_notification() {
|
||||
local action="$1"
|
||||
local message="$2"
|
||||
local title="$2"
|
||||
local message="$3"
|
||||
|
||||
if [[ -z "${NTFY_TOPIC:-}" ]]; then
|
||||
log "WARN" "Ntfy: Kein Topic konfiguriert (NTFY_TOPIC ist leer)"
|
||||
@@ -598,7 +677,6 @@ send_ntfy_notification() {
|
||||
|
||||
local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}"
|
||||
local priority="${NTFY_PRIORITY:-4}"
|
||||
local title="AdGuard Shield"
|
||||
local tags
|
||||
|
||||
if [[ "$action" == "ban" ]]; then
|
||||
@@ -615,11 +693,18 @@ send_ntfy_notification() {
|
||||
local clean_message
|
||||
clean_message=$(echo "$message" | sed 's/\*\*//g')
|
||||
|
||||
# Ntfy fügt Emojis über Tags hinzu → Titel ohne führende Emojis setzen
|
||||
local ntfy_title
|
||||
case "$action" in
|
||||
ban) ntfy_title="🛡️ AdGuard Shield" ;;
|
||||
*) ntfy_title="AdGuard Shield" ;;
|
||||
esac
|
||||
|
||||
local -a curl_args=(
|
||||
-s
|
||||
-X POST
|
||||
"${ntfy_url}/${NTFY_TOPIC}"
|
||||
-H "Title: ${title}"
|
||||
-H "Title: ${ntfy_title}"
|
||||
-H "Priority: ${priority}"
|
||||
-H "Tags: ${tags}"
|
||||
-d "${clean_message}"
|
||||
@@ -1094,16 +1179,50 @@ stop_blocklist_worker() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Externer Whitelist-Worker starten ───────────────────────────────────────
|
||||
start_whitelist_worker() {
|
||||
if [[ "${EXTERNAL_WHITELIST_ENABLED:-false}" != "true" ]]; then
|
||||
log "DEBUG" "Externer Whitelist-Worker ist deaktiviert"
|
||||
return
|
||||
fi
|
||||
|
||||
local worker_script="${SCRIPT_DIR}/external-whitelist-worker.sh"
|
||||
if [[ ! -f "$worker_script" ]]; then
|
||||
log "WARN" "Whitelist-Worker Script nicht gefunden: $worker_script"
|
||||
return
|
||||
fi
|
||||
|
||||
log "INFO" "Starte externen Whitelist-Worker im Hintergrund..."
|
||||
bash "$worker_script" start &
|
||||
WHITELIST_WORKER_PID=$!
|
||||
log "INFO" "Whitelist-Worker gestartet (PID: $WHITELIST_WORKER_PID)"
|
||||
}
|
||||
|
||||
# ─── Externer Whitelist-Worker stoppen ───────────────────────────────────────
|
||||
stop_whitelist_worker() {
|
||||
local worker_pid_file="/var/run/adguard-whitelist-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 Whitelist-Worker (PID: $wpid)..."
|
||||
kill "$wpid" 2>/dev/null || true
|
||||
rm -f "$worker_pid_file"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Hauptschleife ──────────────────────────────────────────────────────────
|
||||
main_loop() {
|
||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||
log "INFO" "AdGuard Shield v${VERSION} gestartet"
|
||||
log "INFO" "AdGuard Shield ${VERSION} gestartet"
|
||||
log "INFO" " Limit: ${RATE_LIMIT_MAX_REQUESTS} Anfragen pro ${RATE_LIMIT_WINDOW}s"
|
||||
log "INFO" " Sperrdauer: $(format_duration "${BAN_DURATION}")"
|
||||
log "INFO" " Prüfintervall: ${CHECK_INTERVAL}s"
|
||||
log "INFO" " Dry-Run: ${DRY_RUN}"
|
||||
log "INFO" " Whitelist: ${WHITELIST}"
|
||||
log "INFO" " Externe Blocklist: ${EXTERNAL_BLOCKLIST_ENABLED:-false}"
|
||||
log "INFO" " Externe Whitelist: ${EXTERNAL_WHITELIST_ENABLED:-false}"
|
||||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then
|
||||
log "INFO" " Progressive Sperren: AKTIV (×${PROGRESSIVE_BAN_MULTIPLIER:-2}, Max-Stufe: ${PROGRESSIVE_BAN_MAX_LEVEL:-0}, Reset: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}"))"
|
||||
else
|
||||
@@ -1129,6 +1248,9 @@ main_loop() {
|
||||
# Blocklist-Worker als Hintergrundprozess starten
|
||||
start_blocklist_worker
|
||||
|
||||
# Whitelist-Worker als Hintergrundprozess starten
|
||||
start_whitelist_worker
|
||||
|
||||
while true; do
|
||||
# Abgelaufene Sperren prüfen
|
||||
check_expired_bans
|
||||
@@ -1150,7 +1272,7 @@ trap cleanup SIGTERM SIGINT SIGHUP
|
||||
# ─── Kommandozeilen-Argumente ────────────────────────────────────────────────
|
||||
case "${1:-start}" in
|
||||
start)
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] AdGuard Shield v${VERSION} wird gestartet..."
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] AdGuard Shield ${VERSION} wird gestartet..."
|
||||
check_dependencies
|
||||
check_already_running
|
||||
init_directories
|
||||
@@ -1195,6 +1317,33 @@ case "${1:-start}" in
|
||||
echo "Blocklist-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
whitelist-status)
|
||||
init_directories
|
||||
_worker_script="${SCRIPT_DIR}/external-whitelist-worker.sh"
|
||||
if [[ -f "$_worker_script" ]]; then
|
||||
bash "$_worker_script" status
|
||||
else
|
||||
echo "Whitelist-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
whitelist-sync)
|
||||
init_directories
|
||||
_worker_script="${SCRIPT_DIR}/external-whitelist-worker.sh"
|
||||
if [[ -f "$_worker_script" ]]; then
|
||||
bash "$_worker_script" sync
|
||||
else
|
||||
echo "Whitelist-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
whitelist-flush)
|
||||
init_directories
|
||||
_worker_script="${SCRIPT_DIR}/external-whitelist-worker.sh"
|
||||
if [[ -f "$_worker_script" ]]; then
|
||||
bash "$_worker_script" flush
|
||||
else
|
||||
echo "Whitelist-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
status)
|
||||
init_directories
|
||||
show_status
|
||||
@@ -1250,7 +1399,7 @@ case "${1:-start}" in
|
||||
;;
|
||||
*)
|
||||
cat << USAGE
|
||||
AdGuard Shield v${VERSION}
|
||||
AdGuard Shield ${VERSION}
|
||||
|
||||
Service-Steuerung (empfohlen):
|
||||
sudo systemctl start adguard-shield
|
||||
@@ -1258,7 +1407,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}
|
||||
Nutzung: $0 {status|history|flush|unban|reset-offenses|test|dry-run|blocklist-status|blocklist-sync|blocklist-flush|whitelist-status|whitelist-sync|whitelist-flush}
|
||||
|
||||
Verwaltungsbefehle:
|
||||
status Zeigt aktive Sperren, Regeln und Wiederholungstäter
|
||||
@@ -1271,6 +1420,9 @@ Verwaltungsbefehle:
|
||||
blocklist-status Zeigt Status der externen Blocklisten
|
||||
blocklist-sync Einmalige Synchronisation der externen Blocklisten
|
||||
blocklist-flush Entfernt alle Sperren der externen Blocklisten
|
||||
whitelist-status Zeigt Status der externen Whitelisten
|
||||
whitelist-sync Einmalige Synchronisation der externen Whitelisten
|
||||
whitelist-flush Entfernt alle aufgelösten Whitelist-IPs
|
||||
|
||||
Interne Befehle (nicht direkt verwenden — nur über systemd):
|
||||
start Startet den Monitor im Vordergrund
|
||||
|
||||
@@ -134,6 +134,7 @@ Das ermöglicht:
|
||||
├── adguard-shield.conf.old # Backup der Konfig nach Update
|
||||
├── 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/
|
||||
@@ -142,7 +143,8 @@ Das ermöglicht:
|
||||
/var/lib/adguard-shield/
|
||||
├── *.ban # State-Dateien aktiver Sperren
|
||||
├── *.offenses # Offense-Zähler (Progressive Sperren)
|
||||
└── external-blocklist/ # Cache für externe Blocklisten
|
||||
├── external-blocklist/ # Cache für externe Blocklisten
|
||||
└── external-whitelist/ # Cache für externe Whitelisten + aufgelöste IPs
|
||||
|
||||
/var/log/
|
||||
├── adguard-shield.log # Laufzeit-Log
|
||||
@@ -26,7 +26,7 @@ sudo bash install.sh --help
|
||||
|
||||
## Uninstaller (eigenständig)
|
||||
|
||||
Ab Version 0.6 wird bei der Installation ein eigenständiger Uninstaller nach `/opt/adguard-shield/uninstall.sh` kopiert. Die Deinstallation kann damit **ohne die originalen Installationsdateien** durchgeführt werden:
|
||||
Ab Version 0.5.2 wird bei der Installation ein eigenständiger Uninstaller nach `/opt/adguard-shield/uninstall.sh` kopiert. Die Deinstallation kann damit **ohne die originalen Installationsdateien** durchgeführt werden:
|
||||
|
||||
```bash
|
||||
# Direkt aus dem Installationsverzeichnis — kein install.sh benötigt
|
||||
@@ -158,6 +158,40 @@ sudo /opt/adguard-shield/iptables-helper.sh save
|
||||
sudo /opt/adguard-shield/iptables-helper.sh restore
|
||||
```
|
||||
|
||||
## Externer Whitelist-Worker
|
||||
|
||||
Der Whitelist-Worker löst Domains aus externen Listen regelmäßig per DNS auf und stellt die IPs als dynamische Whitelist bereit:
|
||||
|
||||
```bash
|
||||
# Status anzeigen (aufgelöste IPs, konfigurierte Listen)
|
||||
sudo /opt/adguard-shield/adguard-shield.sh whitelist-status
|
||||
|
||||
# Einmalige Synchronisation (z.B. nach Konfigurationsänderung)
|
||||
sudo /opt/adguard-shield/adguard-shield.sh whitelist-sync
|
||||
|
||||
# Alle aufgelösten Whitelist-IPs entfernen
|
||||
sudo /opt/adguard-shield/adguard-shield.sh whitelist-flush
|
||||
```
|
||||
|
||||
Der Worker kann auch standalone gesteuert werden:
|
||||
|
||||
```bash
|
||||
# Worker manuell starten (normalerweise automatisch per Hauptscript)
|
||||
sudo /opt/adguard-shield/external-whitelist-worker.sh start
|
||||
|
||||
# Worker stoppen
|
||||
sudo /opt/adguard-shield/external-whitelist-worker.sh stop
|
||||
|
||||
# Einmalige Synchronisation
|
||||
sudo /opt/adguard-shield/external-whitelist-worker.sh sync
|
||||
|
||||
# Status anzeigen
|
||||
sudo /opt/adguard-shield/external-whitelist-worker.sh status
|
||||
|
||||
# Aufgelöste IPs entfernen
|
||||
sudo /opt/adguard-shield/external-whitelist-worker.sh flush
|
||||
```
|
||||
|
||||
## Externer Blocklist-Worker
|
||||
|
||||
Der Worker kann auch standalone gesteuert werden:
|
||||
@@ -94,7 +94,7 @@ Sendet einen POST mit JSON-Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "🚫 AdGuard Shield: Client 192.168.1.50 gesperrt ...",
|
||||
"message": "🚫 AdGuard Shield Ban auf dns1\n---\nIP: 192.168.1.50\nHostname: client.local\nGrund: 45x microsoft.com in 60s via DNS, Rate-Limit\nDauer: 1h 0m\n\nWhois: https://www.whois.com/whois/192.168.1.50\nAbuseIPDB: https://www.abuseipdb.com/check/192.168.1.50",
|
||||
"action": "ban",
|
||||
"client": "192.168.1.50",
|
||||
"domain": "microsoft.com"
|
||||
@@ -113,14 +113,78 @@ Bei Sperren aus der **externen Blocklist** werden Benachrichtigungen separat üb
|
||||
|
||||
## Beispiel-Nachrichten
|
||||
|
||||
**Service gestartet:**
|
||||
> 🟢 AdGuard Shield v0.4.0 wurde gestartet.
|
||||
### Service gestartet
|
||||
**Überschrift:** ✅ AdGuard Shield
|
||||
|
||||
**Service gestoppt:**
|
||||
> 🔴 AdGuard Shield v0.4.0 wurde gestoppt.
|
||||
> 🟢 AdGuard Shield v0.7.0 wurde auf dns1 gestartet.
|
||||
|
||||
**Sperre:**
|
||||
> 🚫 AdGuard Shield: Client **192.168.1.50** gesperrt (45x microsoft.com in 60s). Sperre für 3600s.
|
||||
### Service gestoppt
|
||||
**Überschrift:** 🚨 🛡️ AdGuard Shield
|
||||
|
||||
**Entsperrung:**
|
||||
> ✅ AdGuard Shield: Client **192.168.1.50** wurde entsperrt.
|
||||
> 🔴 AdGuard Shield v0.7.0 wurde auf dns1 gestoppt.
|
||||
|
||||
### Sperre (Ban)
|
||||
**Überschrift:** 🚨 🛡️ AdGuard Shield
|
||||
|
||||
> 🚫 AdGuard Shield Ban auf dns1
|
||||
> ---
|
||||
> IP: 95.71.42.116
|
||||
> Hostname: example-host.provider.net
|
||||
> Grund: 153x radioportal.techniverse.net in 60s via DNS, Rate-Limit
|
||||
> Dauer: 1h 0m [Stufe 1/5]
|
||||
>
|
||||
> Whois: https://www.whois.com/whois/95.71.42.116
|
||||
> AbuseIPDB: https://www.abuseipdb.com/check/95.71.42.116
|
||||
|
||||
Bei permanenter Sperre mit aktiviertem AbuseIPDB-Reporting erscheint zusätzlich:
|
||||
|
||||
> 🚫 AdGuard Shield Ban auf dns1
|
||||
> ⚠️ IP wurde an AbuseIPDB gemeldet
|
||||
> ---
|
||||
> IP: 95.71.42.116
|
||||
> Hostname: example-host.provider.net
|
||||
> Grund: 153x radioportal.techniverse.net in 60s via DNS, Rate-Limit
|
||||
> Dauer: PERMANENT [Stufe 5/5]
|
||||
>
|
||||
> Whois: https://www.whois.com/whois/95.71.42.116
|
||||
> AbuseIPDB: https://www.abuseipdb.com/check/95.71.42.116
|
||||
|
||||
### Entsperrung (Unban)
|
||||
**Überschrift:** ✅ AdGuard Shield
|
||||
|
||||
> ✅ AdGuard Shield Freigabe auf dns1
|
||||
> ---
|
||||
> IP: 95.71.42.116
|
||||
> Hostname: example-host.provider.net
|
||||
>
|
||||
> AbuseIPDB: https://www.abuseipdb.com/check/95.71.42.116
|
||||
|
||||
### Externe Blocklist – Sperre
|
||||
**Überschrift:** 🚨 🛡️ AdGuard Shield
|
||||
|
||||
> 🚫 AdGuard Shield Ban auf dns1 (Externe Blocklist)
|
||||
> ---
|
||||
> IP: 203.0.113.50
|
||||
> Hostname: bad-actor.example.com
|
||||
>
|
||||
> Whois: https://www.whois.com/whois/203.0.113.50
|
||||
> AbuseIPDB: https://www.abuseipdb.com/check/203.0.113.50
|
||||
|
||||
### Externe Blocklist – Entsperrung
|
||||
**Überschrift:** ✅ AdGuard Shield
|
||||
|
||||
> ✅ AdGuard Shield Freigabe auf dns1 (Externe Blocklist)
|
||||
> ---
|
||||
> IP: 203.0.113.50
|
||||
> Hostname: bad-actor.example.com
|
||||
>
|
||||
> AbuseIPDB: https://www.abuseipdb.com/check/203.0.113.50
|
||||
|
||||
### Hinweise
|
||||
|
||||
- Der **Hostname** der IP wird automatisch per Reverse-DNS aufgelöst (`dig`, `host` oder `getent`). Ist kein PTR-Record vorhanden, wird `(unbekannt)` angezeigt.
|
||||
- Der **Servername** (`dns1` in den Beispielen) wird dynamisch über `$(hostname)` ermittelt und zeigt, auf welchem Server das Ereignis stattfand.
|
||||
- Die **Überschrift** unterscheidet sich je nach Aktion:
|
||||
- 🚨 🛡️ bei Sperren und Service-Stopp
|
||||
- ✅ bei Freigaben und Service-Start
|
||||
- Bei **permanenten Sperren** mit aktiviertem AbuseIPDB-Reporting wird ein Hinweis eingeblendet, dass die IP an AbuseIPDB gemeldet wurde.
|
||||
@@ -139,6 +139,7 @@ Regelmäßige Statistik-Reports per E-Mail. Voraussetzung ist ein funktionierend
|
||||
| `REPORT_EMAIL_FROM` | `adguard-shield@hostname` | E-Mail-Absender |
|
||||
| `REPORT_FORMAT` | `html` | Format: `html` oder `txt` |
|
||||
| `REPORT_MAIL_CMD` | `msmtp` | Mail-Befehl (`msmtp`, `sendmail`, `mail`) |
|
||||
| `REPORT_BUSIEST_DAY_RANGE` | `30` | Zeitraum in Tagen für „Aktivster Tag“. `30` = letzte 30 Tage. `0` = nur Berichtszeitraum (altes Verhalten) |
|
||||
|
||||
> Siehe [E-Mail Report Dokumentation](report.md) für Details zu Inhalten, Templates und Befehlen.
|
||||
|
||||
@@ -149,6 +150,60 @@ Regelmäßige Statistik-Reports per E-Mail. Voraussetzung ist ein funktionierend
|
||||
| `STATE_DIR` | `/var/lib/adguard-shield` | Verzeichnis für State-Dateien |
|
||||
| `PID_FILE` | `/var/run/adguard-shield.pid` | PID-Datei |
|
||||
| `DRY_RUN` | `false` | Testmodus — nur loggen, nicht sperren |
|
||||
|
||||
### Externe Whitelist
|
||||
|
||||
Ermöglicht das Einbinden externer Whitelist-Dateien mit Domains und IP-Adressen. Der Worker löst Domains regelmäßig per DNS auf — ideal für DynDNS-Einträge mit wechselnden IP-Adressen. Aufgelöste IPs werden automatisch zur Whitelist hinzugefügt und bei jeder Prüfung aktualisiert.
|
||||
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|--------------|
|
||||
| `EXTERNAL_WHITELIST_ENABLED` | `false` | Aktiviert den externen Whitelist-Worker |
|
||||
| `EXTERNAL_WHITELIST_URLS` | *(leer)* | URL(s) zu Whitelist-Textdateien (kommagetrennt). Unterstützt IPv4, IPv6, CIDR und Hostnamen |
|
||||
| `EXTERNAL_WHITELIST_INTERVAL` | `300` | Prüfintervall in Sekunden (300 = 5 Min.). Bei DynDNS-Einträgen ggf. kürzer wählen |
|
||||
| `EXTERNAL_WHITELIST_CACHE_DIR` | `/var/lib/adguard-shield/external-whitelist` | Lokaler Cache für heruntergeladene Listen und aufgelöste IPs |
|
||||
|
||||
#### Externe Whitelist einrichten
|
||||
|
||||
1. Erstelle eine Textdatei auf einem Webserver. Pro Zeile ein Eintrag — Domain, IPv4, IPv6 oder CIDR:
|
||||
|
||||
```text
|
||||
# Domains (werden regelmäßig per DNS aufgelöst — ideal für DynDNS)
|
||||
mein-router.dyndns.org
|
||||
homeserver.example.com
|
||||
|
||||
# Feste IPs
|
||||
192.168.1.100
|
||||
10.0.0.0/24
|
||||
2001:db8::1
|
||||
|
||||
# Kommentare und Inline-Kommentare werden unterstützt
|
||||
192.168.1.200 # Backup-Server
|
||||
```
|
||||
|
||||
2. Aktiviere die Whitelist in der Konfiguration:
|
||||
|
||||
```bash
|
||||
EXTERNAL_WHITELIST_ENABLED=true
|
||||
EXTERNAL_WHITELIST_URLS="https://example.com/whitelist.txt"
|
||||
EXTERNAL_WHITELIST_INTERVAL=300
|
||||
```
|
||||
|
||||
3. Mehrere Listen können kommagetrennt angegeben werden:
|
||||
|
||||
```bash
|
||||
EXTERNAL_WHITELIST_URLS="https://example.com/trusted.txt,https://other.com/whitelist.txt"
|
||||
```
|
||||
|
||||
4. Service neustarten:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart adguard-shield
|
||||
```
|
||||
|
||||
> **Hinweis:** Da Domains bei jedem Prüfintervall neu aufgelöst werden, eignet sich diese Funktion besonders für DynDNS-Einträge. Ändert sich die IP eines DynDNS-Hostnamens, wird die neue IP beim nächsten Sync automatisch erkannt und in die Whitelist aufgenommen.
|
||||
|
||||
> **Wichtig:** Wird eine bereits gesperrte IP durch eine Whitelist-Aktualisierung gewhitelistet, wird sie **automatisch entsperrt**.
|
||||
|
||||
### Externe Blocklist
|
||||
|
||||
Ermöglicht das Einbinden externer Blocklisten, die IPv4-Adressen, IPv6-Adressen und Hostnamen enthalten können. Der Worker läuft als Hintergrundprozess, prüft periodisch auf Änderungen und löst Hostnamen automatisch über den lokalen DNS-Resolver auf.
|
||||
@@ -338,3 +393,7 @@ Beispiel:
|
||||
```
|
||||
WHITELIST="127.0.0.1,::1,192.168.1.1,192.168.1.10,fd00::1"
|
||||
```
|
||||
|
||||
### Externe Whitelist für dynamische IPs
|
||||
|
||||
Für Clients mit wechselnden IP-Adressen (z.B. DynDNS) kann eine **externe Whitelist** genutzt werden. Der Whitelist-Worker löst Domains regelmäßig per DNS auf und fügt die aktuellen IPs automatisch zur Whitelist hinzu. Siehe [Externe Whitelist](#externe-whitelist) für die Konfiguration.
|
||||
@@ -42,6 +42,7 @@ sudo /opt/adguard-shield/report-generator.sh install
|
||||
| `REPORT_EMAIL_FROM` | `adguard-shield@hostname` | E-Mail-Absender |
|
||||
| `REPORT_FORMAT` | `html` | Format: `html` oder `txt` |
|
||||
| `REPORT_MAIL_CMD` | `msmtp` | Mail-Befehl (`msmtp`, `sendmail`, `mail`) |
|
||||
| `REPORT_BUSIEST_DAY_RANGE` | `30` | Zeitraum in Tagen für „Aktivster Tag“ (0 = Berichtszeitraum) |
|
||||
|
||||
### Versandintervalle
|
||||
|
||||
@@ -56,7 +57,26 @@ sudo /opt/adguard-shield/report-generator.sh install
|
||||
|
||||
Der Report enthält folgende Statistiken:
|
||||
|
||||
### Übersicht
|
||||
### Zeitraum-Schnellübersicht *(immer ganz oben)*
|
||||
|
||||
Eine Vergleichstabelle mit Live-Zahlen für vier feste Zeitfenster – unabhängig vom konfigurierten `REPORT_INTERVAL`:
|
||||
|
||||
| Zeitraum | Sperren | Entsperrungen | Eindeutige IPs | Permanent gebannt |
|
||||
|----------|---------|---------------|----------------|-------------------|
|
||||
| Heute *(nur nach 20:00 Uhr)* | … | … | … | … |
|
||||
| Gestern | … | … | … | … |
|
||||
| Letzte 7 Tage | … | … | … | … |
|
||||
| Letzte 14 Tage | … | … | … | … |
|
||||
| Letzte 30 Tage | … | … | … | … |
|
||||
|
||||
Im HTML-Format wird **Gestern** grün hervorgehoben, **Heute** blau (erscheint nur ab 20:00 Uhr).
|
||||
- **Gestern** umfasst exakt 00:00:00 – 23:59:59 des gestrigen Tages.
|
||||
- **Heute** umfasst den laufenden Tag von 00:00:00 bis zum Zeitpunkt der Reportgenerierung und wird nur eingeblendet, wenn der Report nach 20:00 Uhr erstellt wird.
|
||||
Die übrigen Zeiträume laufen vom Starttag 00:00 Uhr bis zum Zeitpunkt der Reportgenerierung.
|
||||
|
||||
> **Hinweis:** Die AbuseIPDB-Meldungen werden in der Schnellübersicht nicht mehr separat ausgewiesen, da sie immer mit einer Permanentsperre korrelieren – der Wert „Permanent gebannt" ist daher ausreichend. Die Gesamtanzahl der AbuseIPDB-Reports im Berichtszeitraum ist weiterhin in der allgemeinen Übersicht sichtbar.
|
||||
|
||||
### Übersicht (Berichtszeitraum)
|
||||
- Gesamtzahl der Sperren und Entsperrungen
|
||||
- Anzahl eindeutiger gesperrter IPs
|
||||
- Permanente Sperren
|
||||
@@ -67,7 +87,7 @@ Der Report enthält folgende Statistiken:
|
||||
- Rate-Limit Sperren
|
||||
- Subdomain-Flood Sperren
|
||||
- Externe Blocklist Sperren
|
||||
- Aktivster Tag im Berichtszeitraum
|
||||
- Aktivster Tag – wird über einen konfigurierbaren Zeitraum ermittelt (Standard: letzte 30 Tage, `REPORT_BUSIEST_DAY_RANGE`). Zeigt zusätzlich die Anzahl der Sperren an diesem Tag. Bei `REPORT_BUSIEST_DAY_RANGE=0` wird nur der Berichtszeitraum betrachtet.
|
||||
|
||||
### Top 10 Listen
|
||||
- **Auffälligste IPs** — Die 10 IPs mit den meisten Sperren (mit Balkendiagramm im HTML-Format)
|
||||
@@ -156,7 +176,8 @@ Die Templates verwenden Platzhalter (z.B. `{{TOTAL_BANS}}`, `{{TOP10_IPS_TABLE}}
|
||||
| `{{RATELIMIT_BANS}}` | Rate-Limit Sperren |
|
||||
| `{{SUBDOMAIN_FLOOD_BANS}}` | Subdomain-Flood Sperren |
|
||||
| `{{EXTERNAL_BLOCKLIST_BANS}}` | Externe Blocklist Sperren |
|
||||
| `{{BUSIEST_DAY}}` | Aktivster Tag |
|
||||
| `{{BUSIEST_DAY}}` | Aktivster Tag (Datum + Anzahl Sperren) |
|
||||
| `{{BUSIEST_DAY_LABEL}}` | Dynamisches Label für den aktivsten Tag (z.B. „Aktivster Tag (30 Tage)“) |
|
||||
| `{{TOP10_IPS_TABLE}}` | Top 10 IPs (HTML-Tabelle) |
|
||||
| `{{TOP10_IPS_TEXT}}` | Top 10 IPs (Text-Tabelle) |
|
||||
| `{{TOP10_DOMAINS_TABLE}}` | Top 10 Domains (HTML-Tabelle) |
|
||||
@@ -82,6 +82,13 @@ is_whitelisted() {
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Externe Whitelist prüfen (aufgelöste IPs aus dem Whitelist-Worker)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -203,6 +210,27 @@ unban_ip() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Hostname-Auflösung ──────────────────────────────────────────────────────
|
||||
# Versucht den Hostnamen einer IP per Reverse-DNS aufzulösen
|
||||
resolve_hostname() {
|
||||
local ip="$1"
|
||||
local hostname=""
|
||||
|
||||
if command -v dig &>/dev/null; then
|
||||
hostname=$(dig +short -x "$ip" 2>/dev/null | head -1 | sed 's/\.$//')
|
||||
fi
|
||||
|
||||
if [[ -z "$hostname" ]] && command -v host &>/dev/null; then
|
||||
hostname=$(host "$ip" 2>/dev/null | awk '/domain name pointer/ {print $NF}' | sed 's/\.$//' | head -1)
|
||||
fi
|
||||
|
||||
if [[ -z "$hostname" ]] && command -v getent &>/dev/null; then
|
||||
hostname=$(getent hosts "$ip" 2>/dev/null | awk '{print $2}' | head -1)
|
||||
fi
|
||||
|
||||
echo "${hostname:-(unbekannt)}"
|
||||
}
|
||||
|
||||
# ─── Benachrichtigung ────────────────────────────────────────────────────────
|
||||
send_notification() {
|
||||
local action="$1"
|
||||
@@ -213,45 +241,79 @@ send_notification() {
|
||||
return
|
||||
fi
|
||||
|
||||
local title
|
||||
local message
|
||||
local my_hostname
|
||||
my_hostname=$(hostname)
|
||||
local client_hostname
|
||||
client_hostname=$(resolve_hostname "$ip")
|
||||
|
||||
if [[ "$action" == "ban" ]]; then
|
||||
message="🚫 Externe Blocklist: IP **$ip** gesperrt."
|
||||
title="🚨 🛡️ AdGuard Shield"
|
||||
message="🚫 AdGuard Shield Ban auf ${my_hostname} (Externe Blocklist)
|
||||
---
|
||||
IP: ${ip}
|
||||
Hostname: ${client_hostname}
|
||||
|
||||
Whois: https://www.whois.com/whois/${ip}
|
||||
AbuseIPDB: https://www.abuseipdb.com/check/${ip}"
|
||||
else
|
||||
message="✅ Externe Blocklist: IP **$ip** entsperrt (aus Liste entfernt)."
|
||||
title="✅ AdGuard Shield"
|
||||
message="✅ AdGuard Shield Freigabe auf ${my_hostname} (Externe Blocklist)
|
||||
---
|
||||
IP: ${ip}
|
||||
Hostname: ${client_hostname}
|
||||
|
||||
AbuseIPDB: https://www.abuseipdb.com/check/${ip}"
|
||||
fi
|
||||
|
||||
case "${NOTIFY_TYPE:-generic}" in
|
||||
discord)
|
||||
local json_payload
|
||||
json_payload=$(jq -nc --arg msg "$message" '{content: $msg}')
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "{\"content\": \"$message\"}" \
|
||||
-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 "{\"text\": \"$message\"}" \
|
||||
-d "$json_payload" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
gotify)
|
||||
curl -s -X POST "$NOTIFY_WEBHOOK_URL" \
|
||||
-F "title=AdGuard Shield - Externe Blocklist" \
|
||||
-F "message=$message" \
|
||||
-F "title=${title}" \
|
||||
-F "message=${message}" \
|
||||
-F "priority=5" &>/dev/null &
|
||||
;;
|
||||
ntfy)
|
||||
local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}"
|
||||
local tags="rotating_light,blocklist"
|
||||
[[ "$action" != "ban" ]] && tags="white_check_mark,blocklist"
|
||||
# Ntfy fügt Emojis über Tags hinzu → Titel ohne führende Emojis setzen
|
||||
local ntfy_title
|
||||
case "$action" in
|
||||
ban) ntfy_title="🛡️ AdGuard Shield" ;;
|
||||
*) ntfy_title="AdGuard Shield" ;;
|
||||
esac
|
||||
local -a curl_args=(
|
||||
-s -X POST "${ntfy_url}/${NTFY_TOPIC}"
|
||||
-H "Title: AdGuard Shield - Externe Blocklist"
|
||||
-H "Title: ${ntfy_title}"
|
||||
-H "Priority: ${NTFY_PRIORITY:-3}"
|
||||
-H "Tags: rotating_light,blocklist"
|
||||
-d "$(echo "$message" | sed 's/\*\*//g')"
|
||||
-H "Tags: ${tags}"
|
||||
-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 act "$action" --arg cl "$ip" \
|
||||
'{message: $msg, action: $act, client: $cl, source: "external-blocklist"}')
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "{\"message\": \"$message\", \"action\": \"$action\", \"client\": \"$ip\", \"source\": \"external-blocklist\"}" \
|
||||
-d "$json_payload" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
esac
|
||||
|
||||
532
external-whitelist-worker.sh
Normal file
532
external-whitelist-worker.sh
Normal file
@@ -0,0 +1,532 @@
|
||||
#!/bin/bash
|
||||
###############################################################################
|
||||
# AdGuard Shield - Externer Whitelist-Worker
|
||||
# Lädt externe Whitelist-Dateien herunter, löst Domains zu IPs auf und
|
||||
# stellt diese dem Hauptscript als dynamische Whitelist zur Verfügung.
|
||||
# Ideal für DynDNS-Domains mit wechselnden IP-Adressen.
|
||||
# Wird als Hintergrundprozess vom Hauptscript gestartet.
|
||||
#
|
||||
# Autor: Patrick Asmus
|
||||
# E-Mail: support@techniverse.net
|
||||
# Datum: 2026-04-04
|
||||
# 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"
|
||||
|
||||
# ─── Standardwerte ────────────────────────────────────────────────────────────
|
||||
EXTERNAL_WHITELIST_CACHE_DIR="${EXTERNAL_WHITELIST_CACHE_DIR:-/var/lib/adguard-shield/external-whitelist}"
|
||||
EXTERNAL_WHITELIST_RESOLVED_FILE="${EXTERNAL_WHITELIST_CACHE_DIR}/resolved_ips.txt"
|
||||
|
||||
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
||||
WORKER_PID_FILE="/var/run/adguard-whitelist-worker.pid"
|
||||
|
||||
# ─── 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] [WHITELIST-WORKER] $message"
|
||||
echo "$log_entry" | tee -a "$LOG_FILE" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
||||
init_directories() {
|
||||
mkdir -p "$EXTERNAL_WHITELIST_CACHE_DIR"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
}
|
||||
|
||||
# ─── Eintrag-Validierung ─────────────────────────────────────────────────────
|
||||
|
||||
# Prüft IPv4-Adresse mit optionalem CIDR
|
||||
_is_valid_ipv4() {
|
||||
local ip="$1" addr="$1" prefix=""
|
||||
if [[ "$ip" == */* ]]; then
|
||||
addr="${ip%/*}"
|
||||
prefix="${ip#*/}"
|
||||
{ [[ "$prefix" =~ ^[0-9]+$ ]] && [[ "$prefix" -le 32 ]]; } || return 1
|
||||
fi
|
||||
local IFS='.'
|
||||
read -ra _octets <<< "$addr"
|
||||
[[ ${#_octets[@]} -eq 4 ]] || return 1
|
||||
local o
|
||||
for o in "${_octets[@]}"; do
|
||||
[[ "$o" =~ ^[0-9]+$ ]] || return 1
|
||||
[[ "$o" -le 255 ]] || return 1
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# Prüft IPv6-Adresse mit optionalem CIDR
|
||||
_is_valid_ipv6() {
|
||||
local ip="$1" addr="$1"
|
||||
if [[ "$ip" == */* ]]; then
|
||||
addr="${ip%/*}"
|
||||
local prefix="${ip#*/}"
|
||||
{ [[ "$prefix" =~ ^[0-9]+$ ]] && [[ "$prefix" -le 128 ]]; } || return 1
|
||||
fi
|
||||
[[ "$addr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9] ]] && return 1
|
||||
[[ "$addr" == *:* ]] || return 1
|
||||
[[ "$addr" =~ ^[0-9a-fA-F:\.]+$ ]] || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# Prüft ob ein Hostname syntaktisch plausibel ist
|
||||
_is_valid_hostname() {
|
||||
local host="$1"
|
||||
host="${host%.}" # trailing dot entfernen
|
||||
[[ -z "$host" ]] && return 1
|
||||
[[ ${#host} -gt 253 ]] && return 1
|
||||
[[ "$host" =~ ^[a-zA-Z0-9._-]+$ ]] || return 1
|
||||
[[ "$host" =~ ^[.\-] ]] && return 1
|
||||
[[ "$host" == *.* ]] || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# ─── Externe Whitelist herunterladen ─────────────────────────────────────────
|
||||
download_whitelist() {
|
||||
local url="$1"
|
||||
local index="$2"
|
||||
local cache_file="${EXTERNAL_WHITELIST_CACHE_DIR}/whitelist_${index}.txt"
|
||||
local etag_file="${EXTERNAL_WHITELIST_CACHE_DIR}/whitelist_${index}.etag"
|
||||
local tmp_file="${EXTERNAL_WHITELIST_CACHE_DIR}/whitelist_${index}.tmp"
|
||||
|
||||
log "DEBUG" "Prüfe externe Whitelist: $url"
|
||||
|
||||
local -a curl_args=(
|
||||
-s
|
||||
-L
|
||||
--connect-timeout 10
|
||||
--max-time 30
|
||||
-o "$tmp_file"
|
||||
-w "%{http_code}"
|
||||
)
|
||||
|
||||
if [[ -f "$etag_file" ]]; then
|
||||
local stored_etag
|
||||
stored_etag=$(cat "$etag_file")
|
||||
curl_args+=(-H "If-None-Match: ${stored_etag}")
|
||||
fi
|
||||
|
||||
local http_code
|
||||
http_code=$(curl "${curl_args[@]}" -D "${tmp_file}.headers" "$url" 2>/dev/null) || {
|
||||
log "WARN" "Fehler beim Download der Whitelist: $url"
|
||||
rm -f "$tmp_file" "${tmp_file}.headers"
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ "$http_code" == "304" ]]; then
|
||||
log "DEBUG" "Whitelist nicht geändert (HTTP 304): $url"
|
||||
rm -f "$tmp_file" "${tmp_file}.headers"
|
||||
# Auch bei 304 müssen wir DNS neu auflösen (dynamische IPs!)
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$http_code" != "200" ]]; then
|
||||
log "WARN" "Whitelist Download fehlgeschlagen (HTTP $http_code): $url"
|
||||
rm -f "$tmp_file" "${tmp_file}.headers"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -f "${tmp_file}.headers" ]]; then
|
||||
local new_etag
|
||||
new_etag=$(grep -i '^etag:' "${tmp_file}.headers" | head -1 | sed 's/^[^:]*: *//;s/\r$//')
|
||||
if [[ -n "$new_etag" ]]; then
|
||||
echo "$new_etag" > "$etag_file"
|
||||
fi
|
||||
fi
|
||||
rm -f "${tmp_file}.headers"
|
||||
|
||||
if [[ -f "$cache_file" ]]; then
|
||||
if diff -q "$tmp_file" "$cache_file" &>/dev/null; then
|
||||
log "DEBUG" "Whitelist Inhalt unverändert: $url"
|
||||
rm -f "$tmp_file"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
mv "$tmp_file" "$cache_file"
|
||||
log "INFO" "Whitelist aktualisiert: $url"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ─── Einträge aus Whitelist-Datei parsen und IPs auflösen ───────────────────
|
||||
# Gibt pro Zeile eine IP-Adresse aus (aufgelöste Domains + direkte IPs)
|
||||
parse_whitelist_entries() {
|
||||
local cache_file="$1"
|
||||
|
||||
[[ -f "$cache_file" ]] || return
|
||||
|
||||
while IFS= read -r line; do
|
||||
line="${line%$'\r'}"
|
||||
line="${line#$'\xef\xbb\xbf'}"
|
||||
|
||||
[[ -z "$line" ]] && continue
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
|
||||
line=$(echo "$line" | xargs)
|
||||
line=$(echo "$line" | sed 's/[[:space:]]*[#;].*$//' | xargs)
|
||||
[[ -z "$line" ]] && continue
|
||||
|
||||
# URLs ablehnen
|
||||
if [[ "$line" =~ ^[a-zA-Z][a-zA-Z0-9+.-]*:// ]]; then
|
||||
log "WARN" "Whitelist-Eintrag übersprungen (URL nicht erlaubt): $line"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Hosts-Datei-Format erkennen
|
||||
if [[ "$line" =~ ^[^[:space:]]+[[:space:]]+[^[:space:]] ]]; then
|
||||
local _first="${line%% *}"
|
||||
local _rest="${line#* }"
|
||||
local _second="${_rest%% *}"
|
||||
if [[ "$_first" == "0.0.0.0" || "$_first" =~ ^127\. ||
|
||||
"$_first" == "::1" || "$_first" == "::0" ||
|
||||
"$_first" == "::" ]]; then
|
||||
log "DEBUG" "Whitelist Hosts-Format erkannt, extrahiere: $_second"
|
||||
line="$_second"
|
||||
else
|
||||
log "WARN" "Whitelist-Eintrag übersprungen (unbekanntes Format): $line"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Klassifizieren und validieren
|
||||
if [[ "$line" == *:* ]]; then
|
||||
# IPv6
|
||||
if _is_valid_ipv6 "$line"; then
|
||||
echo "$line"
|
||||
else
|
||||
log "WARN" "Whitelist-Eintrag übersprungen (ungültige IPv6): $line"
|
||||
fi
|
||||
|
||||
elif [[ "$line" =~ ^[0-9] ]]; then
|
||||
# IPv4
|
||||
[[ "$line" == "0.0.0.0"* ]] && continue
|
||||
if _is_valid_ipv4 "$line"; then
|
||||
echo "$line"
|
||||
else
|
||||
log "WARN" "Whitelist-Eintrag übersprungen (ungültige IPv4): $line"
|
||||
fi
|
||||
|
||||
else
|
||||
# Hostname → DNS-Auflösung (wird bei jedem Durchlauf neu aufgelöst!)
|
||||
if ! _is_valid_hostname "$line"; then
|
||||
log "WARN" "Whitelist-Eintrag übersprungen (kein gültiger Hostname): $line"
|
||||
continue
|
||||
fi
|
||||
local resolved
|
||||
resolved=$(getent ahosts "$line" 2>/dev/null | awk '{print $1}' | sort -u) || resolved=""
|
||||
if [[ -z "$resolved" ]]; then
|
||||
log "WARN" "Whitelist-Hostname konnte nicht aufgelöst werden: $line"
|
||||
continue
|
||||
fi
|
||||
local resolved_count=0
|
||||
while IFS= read -r resolved_ip; do
|
||||
[[ -z "$resolved_ip" ]] && continue
|
||||
[[ "$resolved_ip" == "0.0.0.0" ]] && continue
|
||||
[[ "$resolved_ip" == "::" ]] && continue
|
||||
[[ "$resolved_ip" == "::0" ]] && continue
|
||||
echo "$resolved_ip"
|
||||
resolved_count=$((resolved_count + 1))
|
||||
done <<< "$resolved"
|
||||
if [[ $resolved_count -gt 0 ]]; then
|
||||
log "DEBUG" "Whitelist-Hostname aufgelöst: $line → $resolved_count IP(s)"
|
||||
else
|
||||
log "WARN" "Whitelist-Hostname lieferte nur ungültige Adressen: $line"
|
||||
fi
|
||||
fi
|
||||
done < "$cache_file"
|
||||
}
|
||||
|
||||
# ─── Whitelisten synchronisieren ─────────────────────────────────────────────
|
||||
sync_whitelists() {
|
||||
# Alle URLs herunterladen
|
||||
IFS=',' read -ra urls <<< "$EXTERNAL_WHITELIST_URLS"
|
||||
local index=0
|
||||
|
||||
for url in "${urls[@]}"; do
|
||||
url=$(echo "$url" | xargs)
|
||||
[[ -z "$url" ]] && continue
|
||||
|
||||
download_whitelist "$url" "$index" || true
|
||||
index=$((index + 1))
|
||||
done
|
||||
|
||||
# Alle Einträge aus Cache-Dateien parsen und IPs auflösen
|
||||
local all_ips_file="${EXTERNAL_WHITELIST_CACHE_DIR}/.all_ips.tmp"
|
||||
> "$all_ips_file"
|
||||
|
||||
for cache_file in "${EXTERNAL_WHITELIST_CACHE_DIR}"/whitelist_*.txt; do
|
||||
[[ -f "$cache_file" ]] || continue
|
||||
parse_whitelist_entries "$cache_file" >> "$all_ips_file"
|
||||
done
|
||||
|
||||
# Duplikate entfernen und in die resolved-Datei schreiben
|
||||
local unique_count
|
||||
sort -u "$all_ips_file" > "${EXTERNAL_WHITELIST_RESOLVED_FILE}.tmp"
|
||||
mv "${EXTERNAL_WHITELIST_RESOLVED_FILE}.tmp" "$EXTERNAL_WHITELIST_RESOLVED_FILE"
|
||||
unique_count=$(wc -l < "$EXTERNAL_WHITELIST_RESOLVED_FILE" | xargs)
|
||||
|
||||
rm -f "$all_ips_file"
|
||||
|
||||
log "DEBUG" "Externe Whitelist: $unique_count eindeutige IPs aufgelöst"
|
||||
|
||||
# Prüfe ob gesperrte IPs jetzt auf der Whitelist stehen und entsperrt werden müssen
|
||||
check_banned_whitelist_ips
|
||||
}
|
||||
|
||||
# ─── Gesperrte IPs prüfen die jetzt gewhitelistet sind ──────────────────────
|
||||
# Wenn eine IP nach einer Whitelist-Aktualisierung nun auf der externen
|
||||
# Whitelist steht, wird sie automatisch entsperrt.
|
||||
check_banned_whitelist_ips() {
|
||||
local state_dir="${STATE_DIR:-/var/lib/adguard-shield}"
|
||||
[[ -d "$state_dir" ]] || return
|
||||
[[ -f "$EXTERNAL_WHITELIST_RESOLVED_FILE" ]] || return
|
||||
|
||||
for state_file in "${state_dir}"/*.ban "${state_dir}"/ext_*.ban; do
|
||||
[[ -f "$state_file" ]] || continue
|
||||
local client_ip
|
||||
client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2)
|
||||
[[ -z "$client_ip" ]] && continue
|
||||
|
||||
if grep -qxF "$client_ip" "$EXTERNAL_WHITELIST_RESOLVED_FILE" 2>/dev/null; then
|
||||
log "INFO" "Gesperrte IP $client_ip ist jetzt auf externer Whitelist – entsperre automatisch"
|
||||
|
||||
# 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 "$state_file"
|
||||
|
||||
# Ban-History Eintrag
|
||||
if [[ -f "${BAN_HISTORY_FILE:-/var/log/adguard-shield-bans.log}" ]]; then
|
||||
local timestamp
|
||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
|
||||
"$timestamp" "UNBAN" "$client_ip" "-" "-" "-" "-" "external-whitelist" \
|
||||
>> "${BAN_HISTORY_FILE:-/var/log/adguard-shield-bans.log}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# ─── PID-Management ──────────────────────────────────────────────────────────
|
||||
write_pid() {
|
||||
echo $$ > "$WORKER_PID_FILE"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
log "INFO" "Externer Whitelist-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" "Whitelist-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 " Externer Whitelist-Worker - Status"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
if [[ "$EXTERNAL_WHITELIST_ENABLED" != "true" ]]; then
|
||||
echo " ⚠️ Externer Whitelist-Worker ist deaktiviert"
|
||||
echo " Aktivieren: EXTERNAL_WHITELIST_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 ""
|
||||
|
||||
# Konfigurierte URLs
|
||||
echo " Konfigurierte Whitelisten:"
|
||||
IFS=',' read -ra urls <<< "$EXTERNAL_WHITELIST_URLS"
|
||||
local index=0
|
||||
for url in "${urls[@]}"; do
|
||||
url=$(echo "$url" | xargs)
|
||||
[[ -z "$url" ]] && continue
|
||||
local cache_file="${EXTERNAL_WHITELIST_CACHE_DIR}/whitelist_${index}.txt"
|
||||
if [[ -f "$cache_file" ]]; then
|
||||
local entry_count
|
||||
entry_count=$(grep -cv '^\s*#\|^\s*$' "$cache_file" 2>/dev/null || echo "0")
|
||||
local last_modified
|
||||
last_modified=$(date -r "$cache_file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unbekannt")
|
||||
echo " [$index] $url"
|
||||
echo " Einträge: $entry_count | Zuletzt aktualisiert: $last_modified"
|
||||
else
|
||||
echo " [$index] $url (noch nicht heruntergeladen)"
|
||||
fi
|
||||
index=$((index + 1))
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# Aufgelöste IPs
|
||||
if [[ -f "$EXTERNAL_WHITELIST_RESOLVED_FILE" ]]; then
|
||||
local resolved_count
|
||||
resolved_count=$(wc -l < "$EXTERNAL_WHITELIST_RESOLVED_FILE" | xargs)
|
||||
local last_resolved
|
||||
last_resolved=$(date -r "$EXTERNAL_WHITELIST_RESOLVED_FILE" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unbekannt")
|
||||
echo " Aufgelöste IPs: $resolved_count"
|
||||
echo " Letzte Auflösung: $last_resolved"
|
||||
|
||||
if [[ "$resolved_count" -gt 0 && "$resolved_count" -le 20 ]]; then
|
||||
echo ""
|
||||
echo " Aktuelle IPs:"
|
||||
while IFS= read -r ip; do
|
||||
echo " ✅ $ip"
|
||||
done < "$EXTERNAL_WHITELIST_RESOLVED_FILE"
|
||||
elif [[ "$resolved_count" -gt 20 ]]; then
|
||||
echo ""
|
||||
echo " Erste 20 IPs:"
|
||||
head -20 "$EXTERNAL_WHITELIST_RESOLVED_FILE" | while IFS= read -r ip; do
|
||||
echo " ✅ $ip"
|
||||
done
|
||||
echo " ... ($((resolved_count - 20)) weitere)"
|
||||
fi
|
||||
else
|
||||
echo " Aufgelöste IPs: 0 (noch keine Synchronisation durchgeführt)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " Prüfintervall: ${EXTERNAL_WHITELIST_INTERVAL}s"
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
}
|
||||
|
||||
# ─── Einmalig synchronisieren ────────────────────────────────────────────────
|
||||
run_once() {
|
||||
init_directories
|
||||
|
||||
if [[ -z "${EXTERNAL_WHITELIST_URLS:-}" ]]; then
|
||||
log "ERROR" "Keine externen Whitelist-URLs konfiguriert (EXTERNAL_WHITELIST_URLS)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "INFO" "Einmalige Whitelist-Synchronisation..."
|
||||
sync_whitelists
|
||||
log "INFO" "Whitelist-Synchronisation abgeschlossen"
|
||||
}
|
||||
|
||||
# ─── Hauptschleife ──────────────────────────────────────────────────────────
|
||||
main_loop() {
|
||||
init_directories
|
||||
|
||||
if [[ -z "${EXTERNAL_WHITELIST_URLS:-}" ]]; then
|
||||
log "ERROR" "Keine externen Whitelist-URLs konfiguriert (EXTERNAL_WHITELIST_URLS)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||
log "INFO" "Externer Whitelist-Worker gestartet"
|
||||
log "INFO" " URLs: ${EXTERNAL_WHITELIST_URLS}"
|
||||
log "INFO" " Prüfintervall: ${EXTERNAL_WHITELIST_INTERVAL}s"
|
||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||
|
||||
while true; do
|
||||
sync_whitelists
|
||||
sleep "$EXTERNAL_WHITELIST_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 "Whitelist-Worker gestoppt"
|
||||
else
|
||||
echo "Whitelist-Worker läuft nicht"
|
||||
fi
|
||||
;;
|
||||
sync)
|
||||
run_once
|
||||
;;
|
||||
status)
|
||||
init_directories
|
||||
show_status
|
||||
;;
|
||||
flush)
|
||||
init_directories
|
||||
echo "Entferne aufgelöste externe Whitelist-IPs..."
|
||||
rm -f "$EXTERNAL_WHITELIST_RESOLVED_FILE"
|
||||
echo "Externe Whitelist-IPs entfernt"
|
||||
;;
|
||||
*)
|
||||
cat << USAGE
|
||||
AdGuard Shield - Externer Whitelist-Worker
|
||||
|
||||
Nutzung: $0 {start|stop|sync|status|flush}
|
||||
|
||||
Befehle:
|
||||
start Startet den Worker (Dauerbetrieb)
|
||||
stop Stoppt den Worker
|
||||
sync Einmalige Synchronisation (DNS-Auflösung)
|
||||
status Zeigt Status und aufgelöste IPs
|
||||
flush Entfernt alle aufgelösten Whitelist-IPs
|
||||
|
||||
Konfiguration: $CONFIG_FILE
|
||||
|
||||
USAGE
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
10
install.sh
10
install.sh
@@ -6,7 +6,7 @@
|
||||
# Lizenz: MIT
|
||||
###############################################################################
|
||||
|
||||
VERSION="v0.5.2"
|
||||
VERSION="v0.7.0"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -103,6 +103,11 @@ print_help() {
|
||||
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh test${NC} # API-Verbindung testen"
|
||||
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh dry-run${NC} # Testmodus (nur loggen)"
|
||||
echo ""
|
||||
echo -e "${BOLD}Externe Whitelist-Befehle:${NC}"
|
||||
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh whitelist-status${NC} # Status der externen Whitelisten"
|
||||
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh whitelist-sync${NC} # Einmalige Synchronisation"
|
||||
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh whitelist-flush${NC} # Aufgelöste IPs entfernen"
|
||||
echo ""
|
||||
echo -e "${BOLD}iptables-Befehle:${NC}"
|
||||
echo -e " ${CYAN}sudo /opt/adguard-shield/iptables-helper.sh status${NC} # Firewall-Regeln anzeigen"
|
||||
echo -e " ${CYAN}sudo /opt/adguard-shield/iptables-helper.sh ban IP${NC} # IP manuell sperren"
|
||||
@@ -241,6 +246,7 @@ install_files() {
|
||||
cp "$SCRIPT_DIR/iptables-helper.sh" "$INSTALL_DIR/"
|
||||
cp "$SCRIPT_DIR/unban-expired.sh" "$INSTALL_DIR/"
|
||||
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/uninstall.sh" "$INSTALL_DIR/"
|
||||
|
||||
@@ -254,6 +260,7 @@ install_files() {
|
||||
chmod +x "$INSTALL_DIR/iptables-helper.sh"
|
||||
chmod +x "$INSTALL_DIR/unban-expired.sh"
|
||||
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/uninstall.sh"
|
||||
|
||||
@@ -751,6 +758,7 @@ do_uninstall() {
|
||||
rm -f "$INSTALL_DIR/iptables-helper.sh"
|
||||
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/report-generator.sh"
|
||||
rm -rf "$INSTALL_DIR/templates"
|
||||
echo " ✅ Scripts entfernt (Konfiguration und Logs behalten)"
|
||||
|
||||
@@ -41,6 +41,7 @@ REPORT_EMAIL_TO="${REPORT_EMAIL_TO:-}"
|
||||
REPORT_EMAIL_FROM="${REPORT_EMAIL_FROM:-adguard-shield@$(hostname -f 2>/dev/null || hostname)}"
|
||||
REPORT_FORMAT="${REPORT_FORMAT:-html}"
|
||||
REPORT_MAIL_CMD="${REPORT_MAIL_CMD:-msmtp}"
|
||||
REPORT_BUSIEST_DAY_RANGE="${REPORT_BUSIEST_DAY_RANGE:-30}"
|
||||
BAN_HISTORY_FILE="${BAN_HISTORY_FILE:-/var/log/adguard-shield-bans.log}"
|
||||
BAN_HISTORY_RETENTION_DAYS="${BAN_HISTORY_RETENTION_DAYS:-0}"
|
||||
STATE_DIR="${STATE_DIR:-/var/lib/adguard-shield}"
|
||||
@@ -65,6 +66,49 @@ log() {
|
||||
echo "[$timestamp] [$level] [REPORT] $message" | tee -a "$LOG_FILE" >&2
|
||||
}
|
||||
|
||||
# ─── Versionsnummern vergleichen ──────────────────────────────────────────────
|
||||
# Gibt 0 zurück, wenn $1 > $2 (semver-Vergleich, v-Präfix wird ignoriert)
|
||||
version_gt() {
|
||||
local v1="${1#v}"
|
||||
local v2="${2#v}"
|
||||
[[ "$v1" == "$v2" ]] && return 1
|
||||
local IFS='.' i a b
|
||||
read -ra ver1 <<< "$v1"
|
||||
read -ra ver2 <<< "$v2"
|
||||
local max_len=$(( ${#ver1[@]} > ${#ver2[@]} ? ${#ver1[@]} : ${#ver2[@]} ))
|
||||
for ((i=0; i<max_len; i++)); do
|
||||
a="${ver1[i]:-0}"
|
||||
b="${ver2[i]:-0}"
|
||||
[[ "$((10#${a}))" -gt "$((10#${b}))" ]] && return 0
|
||||
[[ "$((10#${a}))" -lt "$((10#${b}))" ]] && return 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ─── Versionsprüfung gegen Gitea-Releases ─────────────────────────────────────
|
||||
check_for_update() {
|
||||
UPDATE_NOTICE_HTML=""
|
||||
UPDATE_NOTICE_TXT=""
|
||||
|
||||
[[ "$VERSION" == "unknown" ]] && return
|
||||
|
||||
local latest_tag
|
||||
latest_tag=$(curl -sf --max-time 5 \
|
||||
"https://git.techniverse.net/api/v1/repos/scriptos/adguard-shield/releases?limit=1&page=1" \
|
||||
2>/dev/null | grep -o '"tag_name":"[^"]*"' | head -1 | cut -d'"' -f4 || true)
|
||||
|
||||
[[ -z "$latest_tag" ]] && return
|
||||
|
||||
if version_gt "$latest_tag" "$VERSION"; then
|
||||
UPDATE_NOTICE_HTML='<div class="update-notice">🆕 Update verfügbar: <strong>'"${latest_tag}"'</strong> · <a href="https://git.techniverse.net/scriptos/adguard-shield/releases">Jetzt aktualisieren →</a></div>'
|
||||
UPDATE_NOTICE_TXT=" ⚠ Neue Version verfügbar: ${latest_tag}
|
||||
Update: https://git.techniverse.net/scriptos/adguard-shield/releases
|
||||
"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
# ─── Berichtszeitraum berechnen ───────────────────────────────────────────────
|
||||
|
||||
# Gibt Epoch-Wert für heute 00:00:00 (Mitternacht) zurück
|
||||
@@ -135,40 +179,58 @@ get_period_end_epoch() {
|
||||
echo $((today_midnight - 1))
|
||||
}
|
||||
|
||||
# ─── History-Cache (einmaliges Einlesen der Ban-History) ─────────────────────
|
||||
# Die Datei wird genau einmal mit awk geparst; alle Funktionen lesen danach
|
||||
# nur noch aus diesem In-Memory-Cache – keine date-Subprozesse pro Zeile mehr.
|
||||
#
|
||||
# Cache-Format pro Zeile (Pipe-separiert, alle Felder getrimmt):
|
||||
# EPOCH|TIMESTAMP|ACTION|IP|DOMAIN|COUNT|DURATION|PROTOCOL|REASON
|
||||
HISTORY_CACHE=""
|
||||
HISTORY_CACHE_LOADED=false
|
||||
|
||||
_load_history_cache() {
|
||||
[[ "$HISTORY_CACHE_LOADED" == "true" ]] && return
|
||||
HISTORY_CACHE_LOADED=true
|
||||
[[ ! -f "$BAN_HISTORY_FILE" ]] && return
|
||||
HISTORY_CACHE=$(awk '
|
||||
/^#/ || /^[[:space:]]*$/ { next }
|
||||
{
|
||||
n = split($0, f, "|")
|
||||
if (n < 2) next
|
||||
ts = f[1]; gsub(/^[[:space:]]+|[[:space:]]+$/, "", ts)
|
||||
if (length(ts) < 19) next
|
||||
ep = mktime(substr(ts,1,4) " " substr(ts,6,2) " " substr(ts,9,2) " " \
|
||||
substr(ts,12,2) " " substr(ts,15,2) " " substr(ts,18,2))
|
||||
if (ep < 0) next
|
||||
for (i = 1; i <= n; i++) gsub(/^[[:space:]]+|[[:space:]]+$/, "", f[i])
|
||||
print ep "|" f[1] "|" f[2] "|" f[3] "|" f[4] "|" f[5] "|" f[6] "|" f[7] "|" f[8]
|
||||
}
|
||||
' "$BAN_HISTORY_FILE")
|
||||
}
|
||||
|
||||
# ─── Ban-History filtern nach Zeitraum ────────────────────────────────────────
|
||||
# Gibt nur Zeilen zurück, deren Zeitstempel im Berichtszeitraum liegen
|
||||
# Gibt nur Zeilen zurück, deren Zeitstempel im Berichtszeitraum liegen.
|
||||
# Liest intern aus dem Cache – keine erneuten date-Subprozesse.
|
||||
filter_history_by_period() {
|
||||
local start_epoch="$1"
|
||||
local end_epoch="$2"
|
||||
|
||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
||||
return
|
||||
fi
|
||||
[[ ! -f "$BAN_HISTORY_FILE" ]] && return
|
||||
_load_history_cache
|
||||
[[ -z "$HISTORY_CACHE" ]] && return
|
||||
|
||||
while IFS= read -r line; do
|
||||
# Kommentare und leere Zeilen überspringen
|
||||
[[ "$line" =~ ^#.*$ || -z "$line" ]] && continue
|
||||
|
||||
# Zeitstempel extrahieren (erstes Feld, Format: YYYY-MM-DD HH:MM:SS)
|
||||
local timestamp
|
||||
timestamp=$(echo "$line" | awk -F'|' '{print $1}' | xargs)
|
||||
|
||||
if [[ -z "$timestamp" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Timestamp in Epoch umwandeln
|
||||
local line_epoch
|
||||
line_epoch=$(date -d "$timestamp" '+%s' 2>/dev/null || date -j -f '%Y-%m-%d %H:%M:%S' "$timestamp" '+%s' 2>/dev/null || echo "0")
|
||||
|
||||
if [[ "$line_epoch" -ge "$start_epoch" && "$line_epoch" -le "$end_epoch" ]]; then
|
||||
echo "$line"
|
||||
fi
|
||||
done < "$BAN_HISTORY_FILE"
|
||||
# Aus dem Cache filtern und im Original-Format ausgeben (Abwärtskompatibilität)
|
||||
echo "$HISTORY_CACHE" | awk -F'|' -v s="$start_epoch" -v e="$end_epoch" '
|
||||
$1 >= s && $1 <= e {
|
||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n",
|
||||
$2, $3, $4, $5, $6, $7, $8, $9
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
# ─── Ban-History bereinigen ────────────────────────────────────────────────────
|
||||
# Entfernt Einträge älter als BAN_HISTORY_RETENTION_DAYS (0 = deaktiviert)
|
||||
# Entfernt Einträge älter als BAN_HISTORY_RETENTION_DAYS (0 = deaktiviert).
|
||||
# Nutzt einen einzelnen awk-Durchlauf mit mktime() – kein date-Subprocess pro Zeile.
|
||||
cleanup_ban_history() {
|
||||
[[ ! -f "$BAN_HISTORY_FILE" ]] && return
|
||||
[[ "$BAN_HISTORY_RETENTION_DAYS" == "0" || -z "$BAN_HISTORY_RETENTION_DAYS" ]] && return
|
||||
@@ -178,36 +240,69 @@ cleanup_ban_history() {
|
||||
[[ -z "$cutoff_epoch" ]] && return
|
||||
|
||||
local tmp_file="${BAN_HISTORY_FILE}.tmp"
|
||||
local removed=0
|
||||
local lines_before lines_after
|
||||
lines_before=$(wc -l < "$BAN_HISTORY_FILE")
|
||||
|
||||
while IFS= read -r line; do
|
||||
# Header-Zeilen immer beibehalten
|
||||
if [[ "$line" =~ ^#.*$ || -z "$line" ]]; then
|
||||
echo "$line"
|
||||
continue
|
||||
fi
|
||||
awk -v cutoff="$cutoff_epoch" '
|
||||
/^#/ || /^[[:space:]]*$/ { print; next }
|
||||
{
|
||||
n = split($0, f, "|")
|
||||
if (n < 2) { print; next }
|
||||
ts = f[1]; gsub(/^[[:space:]]+|[[:space:]]+$/, "", ts)
|
||||
if (length(ts) < 19) { print; next }
|
||||
ep = mktime(substr(ts,1,4) " " substr(ts,6,2) " " substr(ts,9,2) " " \
|
||||
substr(ts,12,2) " " substr(ts,15,2) " " substr(ts,18,2))
|
||||
if (ep >= cutoff) print
|
||||
}
|
||||
' "$BAN_HISTORY_FILE" > "$tmp_file"
|
||||
|
||||
local timestamp
|
||||
timestamp=$(echo "$line" | awk -F'|' '{print $1}' | xargs)
|
||||
local line_epoch
|
||||
line_epoch=$(date -d "$timestamp" '+%s' 2>/dev/null || echo "0")
|
||||
|
||||
if [[ "$line_epoch" -ge "$cutoff_epoch" ]]; then
|
||||
echo "$line"
|
||||
else
|
||||
((removed++)) || true
|
||||
fi
|
||||
done < "$BAN_HISTORY_FILE" > "$tmp_file"
|
||||
lines_after=$(wc -l < "$tmp_file")
|
||||
local removed=$(( lines_before - lines_after ))
|
||||
|
||||
if [[ $removed -gt 0 ]]; then
|
||||
mv "$tmp_file" "$BAN_HISTORY_FILE"
|
||||
# Cache invalidieren, damit Folgeaufrufe die bereinigte Datei neu lesen
|
||||
HISTORY_CACHE=""
|
||||
HISTORY_CACHE_LOADED=false
|
||||
log "INFO" "Ban-History bereinigt: $removed Einträge älter als ${BAN_HISTORY_RETENTION_DAYS} Tage entfernt"
|
||||
else
|
||||
rm -f "$tmp_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Statistiken für beliebigen Zeitraum berechnen ──────────────────────────
|
||||
# Gibt "bans|unbans|unique_ips|permanent" für einen Epochen-Bereich zurück.
|
||||
# Liest direkt aus dem Cache in einem einzigen awk-Durchlauf.
|
||||
get_stats_for_epoch_range() {
|
||||
local start_epoch="$1"
|
||||
local end_epoch="$2"
|
||||
|
||||
_load_history_cache
|
||||
if [[ -z "$HISTORY_CACHE" ]]; then
|
||||
echo "0|0|0|0"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "$HISTORY_CACHE" | awk -F'|' -v s="$start_epoch" -v e="$end_epoch" '
|
||||
$1 >= s && $1 <= e {
|
||||
if ($3 == "BAN") {
|
||||
bans++
|
||||
ip_seen[$4] = 1
|
||||
if (tolower($7) ~ /permanent/) perm++
|
||||
} else if ($3 == "UNBAN") {
|
||||
unbans++
|
||||
}
|
||||
}
|
||||
END {
|
||||
for (ip in ip_seen) unique++
|
||||
print (bans+0) "|" (unbans+0) "|" (unique+0) "|" (perm+0)
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
# ─── Statistiken berechnen ────────────────────────────────────────────────────
|
||||
# Liest die Ban-History genau einmal aus dem Cache und berechnet alle
|
||||
# Kennzahlen in einem einzigen awk-Durchlauf – keine Subprozesse pro Zeile.
|
||||
calculate_stats() {
|
||||
# Ban-History bereinigen (falls Retention konfiguriert)
|
||||
cleanup_ban_history
|
||||
@@ -217,11 +312,10 @@ calculate_stats() {
|
||||
local end_epoch
|
||||
end_epoch=$(get_period_end_epoch)
|
||||
|
||||
local filtered_data
|
||||
filtered_data=$(filter_history_by_period "$start_epoch" "$end_epoch")
|
||||
_load_history_cache
|
||||
|
||||
# Wenn keine Daten vorhanden, Standardwerte
|
||||
if [[ -z "$filtered_data" ]]; then
|
||||
# Wenn keine History-Datei vorhanden, Standardwerte setzen
|
||||
if [[ -z "$HISTORY_CACHE" ]]; then
|
||||
TOTAL_BANS=0
|
||||
TOTAL_UNBANS=0
|
||||
UNIQUE_IPS=0
|
||||
@@ -232,6 +326,7 @@ calculate_stats() {
|
||||
SUBDOMAIN_FLOOD_BANS=0
|
||||
EXTERNAL_BLOCKLIST_BANS=0
|
||||
BUSIEST_DAY="–"
|
||||
BUSIEST_DAY_LABEL="Aktivster Tag"
|
||||
TOP10_IPS=""
|
||||
TOP10_DOMAINS=""
|
||||
PROTOCOL_STATS=""
|
||||
@@ -239,17 +334,105 @@ calculate_stats() {
|
||||
return
|
||||
fi
|
||||
|
||||
# Gesamtzahl Sperren
|
||||
TOTAL_BANS=$(echo "$filtered_data" | grep -c '| BAN ' || echo "0")
|
||||
# Einen einzigen awk-Pass über den Cache: alle Statistiken auf einmal
|
||||
# Busiest-Day-Bereich berechnen (konfigurierbar, Standard: 30 Tage)
|
||||
local busiest_start_epoch
|
||||
if [[ "$REPORT_BUSIEST_DAY_RANGE" == "0" || -z "$REPORT_BUSIEST_DAY_RANGE" ]]; then
|
||||
busiest_start_epoch="$start_epoch"
|
||||
else
|
||||
local today_midnight
|
||||
today_midnight=$(get_today_midnight)
|
||||
busiest_start_epoch=$((today_midnight - REPORT_BUSIEST_DAY_RANGE * 86400))
|
||||
fi
|
||||
|
||||
# Gesamtzahl Entsperrungen
|
||||
TOTAL_UNBANS=$(echo "$filtered_data" | grep -c '| UNBAN ' || echo "0")
|
||||
local awk_result
|
||||
awk_result=$(echo "$HISTORY_CACHE" | awk -F'|' -v s="$start_epoch" -v e="$end_epoch" -v bs="$busiest_start_epoch" '
|
||||
$1 >= s && $1 <= e {
|
||||
action = $3
|
||||
if (action == "BAN") {
|
||||
bans++
|
||||
ip_count[$4]++
|
||||
ip_seen[$4] = 1
|
||||
dom = $5
|
||||
if (dom != "" && dom != "-") dom_count[dom]++
|
||||
proto = $8
|
||||
if (proto == "" || proto == "-") proto = "unbekannt"
|
||||
proto_count[proto]++
|
||||
if (tolower($7) ~ /permanent/) perm++
|
||||
rsn = tolower($9)
|
||||
if (rsn ~ /rate.limit/) rl++
|
||||
if (rsn ~ /subdomain.flood/) sf++
|
||||
if (rsn ~ /external.blocklist/) eb++
|
||||
# Zirkulärer Puffer für die letzten 10 Sperren
|
||||
recent[bans % 10] = $2 "|" $3 "|" $4 "|" $5 "|" $6 "|" $7 "|" $8 "|" $9
|
||||
} else if (action == "UNBAN") {
|
||||
unbans++
|
||||
}
|
||||
}
|
||||
# Aktivster Tag: separater Zeitraum (konfigurierbar, z.B. letzte 30 Tage)
|
||||
$1 >= bs && $1 <= e && $3 == "BAN" {
|
||||
bday = substr($2, 1, 10)
|
||||
bday_count[bday]++
|
||||
}
|
||||
END {
|
||||
for (ip in ip_seen) unique++
|
||||
busiest = ""; max_d = 0
|
||||
for (d in bday_count) {
|
||||
if (bday_count[d] > max_d) { max_d = bday_count[d]; busiest = d; busiest_cnt = bday_count[d] }
|
||||
}
|
||||
print "BANS=" (bans+0)
|
||||
print "UNBANS=" (unbans+0)
|
||||
print "UNIQUE=" (unique+0)
|
||||
print "PERM=" (perm+0)
|
||||
print "RL=" (rl+0)
|
||||
print "SF=" (sf+0)
|
||||
print "EB=" (eb+0)
|
||||
print "BUSIEST=" busiest
|
||||
print "BUSIEST_CNT=" (busiest_cnt+0)
|
||||
for (ip in ip_count) print "IP\t" ip_count[ip] "\t" ip
|
||||
for (d in dom_count) print "DOMAIN\t" dom_count[d] "\t" d
|
||||
for (p in proto_count) print "PROTO\t" proto_count[p] "\t" p
|
||||
n = (bans < 10) ? bans : 10
|
||||
for (i = 0; i < n; i++) {
|
||||
idx = (bans - i) % 10
|
||||
print "RECENT\t" recent[idx]
|
||||
}
|
||||
}
|
||||
')
|
||||
|
||||
# Eindeutige IPs (nur BAN-Einträge)
|
||||
UNIQUE_IPS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $3}' | xargs -I{} echo {} | sort -u | wc -l | xargs)
|
||||
# Einfache Kennzahlen aus dem awk-Ergebnis extrahieren
|
||||
TOTAL_BANS=$( echo "$awk_result" | awk -F= '$1=="BANS" {print $2; exit}')
|
||||
TOTAL_UNBANS=$( echo "$awk_result" | awk -F= '$1=="UNBANS" {print $2; exit}')
|
||||
UNIQUE_IPS=$( echo "$awk_result" | awk -F= '$1=="UNIQUE" {print $2; exit}')
|
||||
PERMANENT_BANS=$(echo "$awk_result" | awk -F= '$1=="PERM" {print $2; exit}')
|
||||
RATELIMIT_BANS=$( echo "$awk_result" | awk -F= '$1=="RL" {print $2; exit}')
|
||||
SUBDOMAIN_FLOOD_BANS=$( echo "$awk_result" | awk -F= '$1=="SF" {print $2; exit}')
|
||||
EXTERNAL_BLOCKLIST_BANS=$(echo "$awk_result" | awk -F= '$1=="EB" {print $2; exit}')
|
||||
|
||||
# Permanente Sperren (Dauer enthält "PERMANENT" oder "permanent")
|
||||
PERMANENT_BANS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $6}' | grep -ic 'permanent' || echo "0")
|
||||
local busiest_raw
|
||||
busiest_raw=$(echo "$awk_result" | awk -F= '$1=="BUSIEST" {print $2; exit}')
|
||||
local busiest_cnt
|
||||
busiest_cnt=$(echo "$awk_result" | awk -F= '$1=="BUSIEST_CNT" {print $2; exit}')
|
||||
if [[ -n "$busiest_raw" ]]; then
|
||||
local busiest_formatted
|
||||
busiest_formatted=$(date -d "$busiest_raw" '+%d.%m.%Y' 2>/dev/null || echo "$busiest_raw")
|
||||
BUSIEST_DAY="${busiest_formatted} (${busiest_cnt})"
|
||||
else
|
||||
BUSIEST_DAY="–"
|
||||
fi
|
||||
|
||||
# Dynamisches Label für den aktivsten Tag
|
||||
if [[ "$REPORT_BUSIEST_DAY_RANGE" == "0" || -z "$REPORT_BUSIEST_DAY_RANGE" ]]; then
|
||||
BUSIEST_DAY_LABEL="Aktivster Tag"
|
||||
else
|
||||
BUSIEST_DAY_LABEL="Aktivster Tag (${REPORT_BUSIEST_DAY_RANGE} Tage)"
|
||||
fi
|
||||
|
||||
# Top-Listen: Tab-getrennte Felder sortieren und in das erwartete Format bringen
|
||||
TOP10_IPS=$( echo "$awk_result" | awk -F'\t' '$1=="IP" {print $2 " " $3}' | sort -rn | head -10)
|
||||
TOP10_DOMAINS=$(echo "$awk_result" | awk -F'\t' '$1=="DOMAIN" {print $2 " " $3}' | sort -rn | head -10)
|
||||
PROTOCOL_STATS=$(echo "$awk_result" | awk -F'\t' '$1=="PROTO" {print $2 " " $3}' | sort -rn)
|
||||
RECENT_BANS=$( echo "$awk_result" | awk -F'\t' '$1=="RECENT" {print $2}')
|
||||
|
||||
# Aktuell aktive Sperren (aus State-Dateien)
|
||||
ACTIVE_BANS=0
|
||||
@@ -259,41 +442,21 @@ calculate_stats() {
|
||||
done
|
||||
fi
|
||||
|
||||
# AbuseIPDB Reports (suche in Log-Datei)
|
||||
# AbuseIPDB Reports – zeitraum-gefiltert aus der Logdatei via awk+mktime
|
||||
ABUSEIPDB_REPORTS=0
|
||||
if [[ -f "$LOG_FILE" ]]; then
|
||||
local abuseipdb_start_date
|
||||
abuseipdb_start_date=$(date -d "@$start_epoch" '+%Y-%m-%d' 2>/dev/null || date -r "$start_epoch" '+%Y-%m-%d')
|
||||
ABUSEIPDB_REPORTS=$(grep -c "AbuseIPDB: IP .* erfolgreich gemeldet" "$LOG_FILE" 2>/dev/null | head -1 || echo "0")
|
||||
ABUSEIPDB_REPORTS=$(grep "AbuseIPDB:.*erfolgreich gemeldet" "$LOG_FILE" 2>/dev/null | \
|
||||
awk -v s="$start_epoch" -v e="$end_epoch" '
|
||||
{
|
||||
ts = substr($0, 2, 19)
|
||||
if (ts !~ /^[0-9]{4}/) next
|
||||
ep = mktime(substr(ts,1,4) " " substr(ts,6,2) " " substr(ts,9,2) " " \
|
||||
substr(ts,12,2) " " substr(ts,15,2) " " substr(ts,18,2))
|
||||
if (ep >= s && ep <= e) count++
|
||||
}
|
||||
END { print count+0 }
|
||||
' || echo "0")
|
||||
fi
|
||||
|
||||
# Angriffsarten
|
||||
RATELIMIT_BANS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $8}' | grep -ic 'rate-limit' || echo "0")
|
||||
SUBDOMAIN_FLOOD_BANS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $8}' | grep -ic 'subdomain-flood' || echo "0")
|
||||
EXTERNAL_BLOCKLIST_BANS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $8}' | grep -ic 'external-blocklist' || echo "0")
|
||||
|
||||
# Aktivster Tag
|
||||
BUSIEST_DAY=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $1}' | xargs -I{} echo {} | awk '{print $1}' | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' || echo "–")
|
||||
if [[ -z "$BUSIEST_DAY" ]]; then
|
||||
BUSIEST_DAY="–"
|
||||
else
|
||||
# Datum in DE-Format umwandeln
|
||||
local busiest_formatted
|
||||
busiest_formatted=$(date -d "$BUSIEST_DAY" '+%d.%m.%Y' 2>/dev/null || echo "$BUSIEST_DAY")
|
||||
BUSIEST_DAY="$busiest_formatted"
|
||||
fi
|
||||
|
||||
# Top 10 IPs (nach Häufigkeit der Sperren)
|
||||
TOP10_IPS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $3}' | xargs -I{} echo {} | sort | uniq -c | sort -rn | head -10)
|
||||
|
||||
# Top 10 Domains (nach Häufigkeit)
|
||||
TOP10_DOMAINS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $4}' | xargs -I{} echo {} | sed 's/^-$//' | grep -v '^$' | sort | uniq -c | sort -rn | head -10)
|
||||
|
||||
# Protokoll-Verteilung
|
||||
PROTOCOL_STATS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $7}' | xargs -I{} echo {} | sed 's/^-$/unbekannt/' | grep -v '^$' | sort | uniq -c | sort -rn)
|
||||
|
||||
# Letzte 10 Sperren
|
||||
RECENT_BANS=$(echo "$filtered_data" | grep '| BAN ' | tail -10)
|
||||
}
|
||||
|
||||
# ─── HTML-Tabellen generieren ─────────────────────────────────────────────────
|
||||
@@ -420,6 +583,65 @@ generate_recent_bans_html() {
|
||||
echo "$html"
|
||||
}
|
||||
|
||||
# ─── Zeitraum-Schnellübersicht (HTML) ─────────────────────────────────────────
|
||||
generate_period_overview_html() {
|
||||
local today_midnight
|
||||
today_midnight=$(get_today_midnight)
|
||||
local now
|
||||
now=$(date '+%s')
|
||||
local yesterday_start=$((today_midnight - 86400))
|
||||
local yesterday_end=$((today_midnight - 1))
|
||||
|
||||
# Zeiträume: "Label:start_epoch:end_epoch" (Doppelpunkt als Trennzeichen)
|
||||
local periods=()
|
||||
|
||||
# Heute nur nach 20:00 Uhr einblenden
|
||||
local current_hour
|
||||
current_hour=$(date '+%H' | sed 's/^0*//')
|
||||
if [[ "${current_hour:-0}" -ge 20 ]]; then
|
||||
periods+=("Heute:${today_midnight}:${now}")
|
||||
fi
|
||||
|
||||
periods+=(
|
||||
"Gestern:${yesterday_start}:${yesterday_end}"
|
||||
"Letzte 7 Tage:$((today_midnight - 7 * 86400)):${now}"
|
||||
"Letzte 14 Tage:$((today_midnight - 14 * 86400)):${now}"
|
||||
"Letzte 30 Tage:$((today_midnight - 30 * 86400)):${now}"
|
||||
)
|
||||
|
||||
local html='<table>'
|
||||
html+='<tr>'
|
||||
html+='<th>Zeitraum</th>'
|
||||
html+='<th>Sperren</th>'
|
||||
html+='<th>Entsperrt</th>'
|
||||
html+='<th>Unique IPs</th>'
|
||||
html+='<th>Dauerhaft gebannt</th>'
|
||||
html+='</tr>'
|
||||
|
||||
for period_def in "${periods[@]}"; do
|
||||
IFS=':' read -r label start_e end_e <<< "$period_def"
|
||||
local row_class=""
|
||||
case "$label" in
|
||||
Heute) row_class=' class="period-today"' ;;
|
||||
Gestern) row_class=' class="period-gestern"' ;;
|
||||
esac
|
||||
local stats
|
||||
stats=$(get_stats_for_epoch_range "$start_e" "$end_e")
|
||||
IFS='|' read -r bans unbans unique perm <<< "$stats"
|
||||
|
||||
html+="<tr${row_class}>"
|
||||
html+="<td><strong>${label}</strong></td>"
|
||||
html+="<td>${bans}</td>"
|
||||
html+="<td>${unbans}</td>"
|
||||
html+="<td>${unique}</td>"
|
||||
html+="<td>${perm}</td>"
|
||||
html+="</tr>"
|
||||
done
|
||||
|
||||
html+='</table>'
|
||||
echo "$html"
|
||||
}
|
||||
|
||||
# ─── TXT-Tabellen generieren ──────────────────────────────────────────────────
|
||||
generate_top10_ips_txt() {
|
||||
if [[ -z "$TOP10_IPS" ]]; then
|
||||
@@ -495,6 +717,46 @@ generate_recent_bans_txt() {
|
||||
done <<< "$RECENT_BANS"
|
||||
}
|
||||
|
||||
# ─── Zeitraum-Schnellübersicht (TXT) ──────────────────────────────────────────
|
||||
generate_period_overview_txt() {
|
||||
local today_midnight
|
||||
today_midnight=$(get_today_midnight)
|
||||
local now
|
||||
now=$(date '+%s')
|
||||
local yesterday_start=$((today_midnight - 86400))
|
||||
local yesterday_end=$((today_midnight - 1))
|
||||
|
||||
local periods=()
|
||||
|
||||
# Heute nur nach 20:00 Uhr einblenden
|
||||
local current_hour
|
||||
current_hour=$(date '+%H' | sed 's/^0*//')
|
||||
if [[ "${current_hour:-0}" -ge 20 ]]; then
|
||||
periods+=("Heute:${today_midnight}:${now}")
|
||||
fi
|
||||
|
||||
periods+=(
|
||||
"Gestern:${yesterday_start}:${yesterday_end}"
|
||||
"Letzte 7 Tage:$((today_midnight - 7 * 86400)):${now}"
|
||||
"Letzte 14 Tage:$((today_midnight - 14 * 86400)):${now}"
|
||||
"Letzte 30 Tage:$((today_midnight - 30 * 86400)):${now}"
|
||||
)
|
||||
|
||||
printf " %-15s %-9s %-12s %-14s %-11s\n" \
|
||||
"Zeitraum" "Sperren" "Entsperrt" "Unique IPs" "Dauerhaft"
|
||||
printf " %-15s %-9s %-12s %-14s %-11s\n" \
|
||||
"───────────────" "─────────" "────────────" "──────────────" "───────────"
|
||||
|
||||
for period_def in "${periods[@]}"; do
|
||||
IFS=':' read -r label start_e end_e <<< "$period_def"
|
||||
local stats
|
||||
stats=$(get_stats_for_epoch_range "$start_e" "$end_e")
|
||||
IFS='|' read -r bans unbans unique perm <<< "$stats"
|
||||
printf " %-15s %-9s %-12s %-14s %-11s\n" \
|
||||
"$label" "$bans" "$unbans" "$unique" "$perm"
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Report generieren ────────────────────────────────────────────────────────
|
||||
generate_report() {
|
||||
local format="${1:-$REPORT_FORMAT}"
|
||||
@@ -504,6 +766,9 @@ generate_report() {
|
||||
# Statistiken berechnen
|
||||
calculate_stats
|
||||
|
||||
# Update-Verfügbarkeit prüfen
|
||||
check_for_update
|
||||
|
||||
local report_period
|
||||
report_period=$(get_report_period)
|
||||
local report_date
|
||||
@@ -530,6 +795,8 @@ generate_report() {
|
||||
protocol_table=$(generate_protocol_html)
|
||||
local recent_bans_table
|
||||
recent_bans_table=$(generate_recent_bans_html)
|
||||
local period_overview_table
|
||||
period_overview_table=$(generate_period_overview_html)
|
||||
|
||||
# Platzhalter ersetzen
|
||||
report="${report//\{\{REPORT_PERIOD\}\}/$report_period}"
|
||||
@@ -546,10 +813,13 @@ generate_report() {
|
||||
report="${report//\{\{SUBDOMAIN_FLOOD_BANS\}\}/$SUBDOMAIN_FLOOD_BANS}"
|
||||
report="${report//\{\{EXTERNAL_BLOCKLIST_BANS\}\}/$EXTERNAL_BLOCKLIST_BANS}"
|
||||
report="${report//\{\{BUSIEST_DAY\}\}/$BUSIEST_DAY}"
|
||||
report="${report//\{\{BUSIEST_DAY_LABEL\}\}/$BUSIEST_DAY_LABEL}"
|
||||
report="${report//\{\{TOP10_IPS_TABLE\}\}/$top10_ips_table}"
|
||||
report="${report//\{\{TOP10_DOMAINS_TABLE\}\}/$top10_domains_table}"
|
||||
report="${report//\{\{PROTOCOL_TABLE\}\}/$protocol_table}"
|
||||
report="${report//\{\{RECENT_BANS_TABLE\}\}/$recent_bans_table}"
|
||||
report="${report//\{\{PERIOD_OVERVIEW_TABLE\}\}/$period_overview_table}"
|
||||
report="${report//\{\{UPDATE_NOTICE\}\}/$UPDATE_NOTICE_HTML}"
|
||||
|
||||
echo "$report"
|
||||
|
||||
@@ -572,6 +842,8 @@ generate_report() {
|
||||
protocol_txt=$(generate_protocol_txt)
|
||||
local recent_bans_txt
|
||||
recent_bans_txt=$(generate_recent_bans_txt)
|
||||
local period_overview_txt
|
||||
period_overview_txt=$(generate_period_overview_txt)
|
||||
|
||||
# Platzhalter ersetzen
|
||||
report="${report//\{\{REPORT_PERIOD\}\}/$report_period}"
|
||||
@@ -588,10 +860,13 @@ generate_report() {
|
||||
report="${report//\{\{SUBDOMAIN_FLOOD_BANS\}\}/$SUBDOMAIN_FLOOD_BANS}"
|
||||
report="${report//\{\{EXTERNAL_BLOCKLIST_BANS\}\}/$EXTERNAL_BLOCKLIST_BANS}"
|
||||
report="${report//\{\{BUSIEST_DAY\}\}/$BUSIEST_DAY}"
|
||||
report="${report//\{\{BUSIEST_DAY_LABEL\}\}/$BUSIEST_DAY_LABEL}"
|
||||
report="${report//\{\{TOP10_IPS_TEXT\}\}/$top10_ips_txt}"
|
||||
report="${report//\{\{TOP10_DOMAINS_TEXT\}\}/$top10_domains_txt}"
|
||||
report="${report//\{\{PROTOCOL_TEXT\}\}/$protocol_txt}"
|
||||
report="${report//\{\{RECENT_BANS_TEXT\}\}/$recent_bans_txt}"
|
||||
report="${report//\{\{PERIOD_OVERVIEW_TEXT\}\}/$period_overview_txt}"
|
||||
report="${report//\{\{UPDATE_NOTICE_TXT\}\}/$UPDATE_NOTICE_TXT}"
|
||||
|
||||
echo "$report"
|
||||
else
|
||||
@@ -737,6 +1012,7 @@ show_cron_status() {
|
||||
echo " Empfänger: ${REPORT_EMAIL_TO:-nicht konfiguriert}"
|
||||
echo " Absender: ${REPORT_EMAIL_FROM}"
|
||||
echo " Mail-Befehl: ${REPORT_MAIL_CMD}"
|
||||
echo " Aktivster Tag: letzte ${REPORT_BUSIEST_DAY_RANGE:-30} Tage"
|
||||
echo ""
|
||||
|
||||
if command -v "$REPORT_MAIL_CMD" &>/dev/null; then
|
||||
@@ -836,6 +1112,11 @@ send_test_email() {
|
||||
local content_type="text/plain"
|
||||
[[ "$REPORT_FORMAT" == "html" ]] && content_type="text/html"
|
||||
|
||||
local test_update_notice_html
|
||||
test_update_notice_html='<div style="display:inline-block;margin-top:10px;padding:7px 14px;background:#fff8e1;border:1px solid #ffc107;border-radius:8px;color:#7a5700;font-size:12px;font-weight:600;">🆕 Update verfügbar (Testanzeige): <strong>'"${VERSION}"'</strong> · <a href="https://git.techniverse.net/scriptos/adguard-shield/releases" style="color:#7a5700;font-weight:700;text-decoration:none;">Jetzt aktualisieren →</a></div>'
|
||||
local test_update_notice_txt
|
||||
test_update_notice_txt=" ⚠ Neue Version verfügbar (Testanzeige): ${VERSION}\n Update: https://git.techniverse.net/scriptos/adguard-shield/releases\n"
|
||||
|
||||
local test_body
|
||||
if [[ "$REPORT_FORMAT" == "html" ]]; then
|
||||
test_body=$(cat <<TESTHTML
|
||||
@@ -861,13 +1142,17 @@ send_test_email() {
|
||||
</table>
|
||||
<p style="color:#6c757d;font-size:13px;">Ab jetzt kannst du den automatischen Versand aktivieren mit:<br><code>sudo $(basename "$0") install</code></p>
|
||||
</div>
|
||||
<div style="background:#f8f9fc;padding:20px;text-align:center;font-size:12px;color:#6c757d;border-top:1px solid #e8ecf1;">
|
||||
<a href="https://www.patrick-asmus.de" style="color:#0f3460;text-decoration:none;font-weight:600;">Patrick-Asmus.DE</a>
|
||||
<div style="background:#f8f9fc;padding:20px;font-size:12px;color:#6c757d;border-top:1px solid #e8ecf1;text-align:center;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<span><a href="https://www.patrick-asmus.de" style="color:#0f3460;text-decoration:none;font-weight:600;">Patrick-Asmus.de</a>
|
||||
<span style="margin:0 8px;color:#ced4da;">|</span>
|
||||
<a href="https://www.cleveradmin.de" style="color:#0f3460;text-decoration:none;font-weight:600;">CleverAdmin.DE</a>
|
||||
<a href="https://www.cleveradmin.de" style="color:#0f3460;text-decoration:none;font-weight:600;">CleverAdmin.de</a></span>
|
||||
<span><a href="https://git.techniverse.net/scriptos/adguard-shield.git" style="color:#0f3460;text-decoration:none;font-weight:600;">AdGuard Shield auf Gitea</a>
|
||||
<span style="margin:0 8px;color:#ced4da;">|</span>
|
||||
<a href="https://git.techniverse.net/scriptos/adguard-shield.git" style="color:#0f3460;text-decoration:none;font-weight:600;">AdGuard Shield auf Gitea</a>
|
||||
<div style="margin-top:8px;font-size:11px;color:#adb5bd;">AdGuard Shield v${VERSION} · ${hostname}</div>
|
||||
<a href="https://git.techniverse.net/scriptos/adguard-shield/src/branch/main/docs" style="color:#0f3460;text-decoration:none;font-weight:600;">docs</a></span>
|
||||
</div>
|
||||
<div style="margin-top:8px;font-size:11px;color:#adb5bd;text-align:center;">AdGuard Shield ${VERSION} · ${hostname}</div>
|
||||
${test_update_notice_html}
|
||||
</div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -895,12 +1180,14 @@ TESTHTML
|
||||
Ab jetzt kannst du den automatischen Versand aktivieren mit:
|
||||
sudo $(basename "$0") install
|
||||
|
||||
${test_update_notice_txt}
|
||||
═══════════════════════════════════════════════════════════════
|
||||
AdGuard Shield v${VERSION} · ${hostname}
|
||||
AdGuard Shield ${VERSION} · ${hostname}
|
||||
|
||||
Web: https://www.patrick-asmus.de
|
||||
Blog: https://www.cleveradmin.de
|
||||
Repo: https://git.techniverse.net/scriptos/adguard-shield.git
|
||||
Docs: https://git.techniverse.net/scriptos/adguard-shield/src/branch/main/docs
|
||||
═══════════════════════════════════════════════════════════════
|
||||
TESTTXT
|
||||
)
|
||||
|
||||
@@ -210,17 +210,57 @@
|
||||
}
|
||||
.footer .links {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.footer .separator {
|
||||
margin: 0 8px;
|
||||
color: #ced4da;
|
||||
}
|
||||
.version-tag {
|
||||
display: inline-block;
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-size: 11px;
|
||||
color: #adb5bd;
|
||||
}
|
||||
.update-notice {
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
padding: 7px 14px;
|
||||
background: #fff8e1;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 8px;
|
||||
color: #7a5700;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.update-notice a {
|
||||
color: #7a5700;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
}
|
||||
.update-notice a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.period-today td {
|
||||
background: #eef4ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.period-today td:first-child {
|
||||
color: #0f3460;
|
||||
}
|
||||
.period-gestern td {
|
||||
background: #f0faf3;
|
||||
font-weight: 600;
|
||||
}
|
||||
.period-gestern td:first-child {
|
||||
color: #27ae60;
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
table { font-size: 12px; }
|
||||
th, td { padding: 8px 8px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -234,6 +274,11 @@
|
||||
|
||||
<!-- Statistik-Übersicht -->
|
||||
<div class="content">
|
||||
<!-- Zeitraum-Schnellübersicht -->
|
||||
<h2>📅 Zeitraum-Schnellübersicht</h2>
|
||||
{{PERIOD_OVERVIEW_TABLE}}
|
||||
|
||||
<!-- Gesamt-Übersicht des Berichtszeitraums -->
|
||||
<h2>📊 Übersicht</h2>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card danger">
|
||||
@@ -279,7 +324,7 @@
|
||||
</div>
|
||||
<div class="stat-card success">
|
||||
<div class="stat-value">{{BUSIEST_DAY}}</div>
|
||||
<div class="stat-label">Aktivster Tag</div>
|
||||
<div class="stat-label">{{BUSIEST_DAY_LABEL}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -302,16 +347,23 @@
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="footer">
|
||||
<div class="links">
|
||||
<span>
|
||||
<a href="https://www.patrick-asmus.de">Patrick-Asmus.de</a>
|
||||
<span class="separator">|</span>
|
||||
<a href="https://www.cleveradmin.de">CleverAdmin.de</a>
|
||||
</span>
|
||||
<span>
|
||||
<a href="https://git.techniverse.net/scriptos/adguard-shield.git">AdGuard Shield auf Gitea</a>
|
||||
<span class="separator">|</span>
|
||||
<a href="https://git.techniverse.net/scriptos/adguard-shield/src/branch/main/docs">docs</a>
|
||||
</span>
|
||||
</div>
|
||||
<br>
|
||||
Dieser Report wurde automatisch von <strong>AdGuard Shield</strong> generiert.<br>
|
||||
Generiert am: {{REPORT_DATE}}
|
||||
<div class="links">
|
||||
<a href="https://www.patrick-asmus.de">Patrick-Asmus.DE</a>
|
||||
<span class="separator">|</span>
|
||||
<a href="https://www.cleveradmin.de">CleverAdmin.DE</a>
|
||||
<span class="separator">|</span>
|
||||
<a href="https://git.techniverse.net/scriptos/adguard-shield.git">AdGuard Shield auf Gitea</a>
|
||||
</div>
|
||||
<div class="version-tag">AdGuard Shield {{VERSION}} · {{HOSTNAME}}</div>
|
||||
{{UPDATE_NOTICE}}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -7,7 +7,13 @@
|
||||
Host: {{HOSTNAME}}
|
||||
|
||||
───────────────────────────────────────────────────────────────
|
||||
📊 ÜBERSICHT
|
||||
<EFBFBD> ZEITRAUM-SCHNELLÜBERSICHT
|
||||
───────────────────────────────────────────────────────────────
|
||||
|
||||
{{PERIOD_OVERVIEW_TEXT}}
|
||||
|
||||
───────────────────────────────────────────────────────────────
|
||||
📊 ÜBERSICHT (Berichtszeitraum)
|
||||
───────────────────────────────────────────────────────────────
|
||||
|
||||
Sperren gesamt: {{TOTAL_BANS}}
|
||||
@@ -24,7 +30,7 @@
|
||||
Rate-Limit Sperren: {{RATELIMIT_BANS}}
|
||||
Subdomain-Flood Sperren: {{SUBDOMAIN_FLOOD_BANS}}
|
||||
Externe Blocklist: {{EXTERNAL_BLOCKLIST_BANS}}
|
||||
Aktivster Tag: {{BUSIEST_DAY}}
|
||||
{{BUSIEST_DAY_LABEL}}: {{BUSIEST_DAY}}
|
||||
|
||||
───────────────────────────────────────────────────────────────
|
||||
🏴☠️ TOP 10 – AUFFÄLLIGSTE IPs
|
||||
@@ -50,6 +56,7 @@
|
||||
|
||||
{{RECENT_BANS_TEXT}}
|
||||
|
||||
{{UPDATE_NOTICE_TXT}}
|
||||
═══════════════════════════════════════════════════════════════
|
||||
Dieser Report wurde automatisch von AdGuard Shield generiert.
|
||||
AdGuard Shield {{VERSION}}
|
||||
@@ -57,4 +64,5 @@
|
||||
Web: https://www.patrick-asmus.de
|
||||
Blog: https://www.cleveradmin.de
|
||||
Repo: https://git.techniverse.net/scriptos/adguard-shield.git
|
||||
Docs: https://git.techniverse.net/scriptos/adguard-shield/src/branch/main/docs
|
||||
═══════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -106,6 +106,7 @@ do_uninstall() {
|
||||
rm -f "$INSTALL_DIR/iptables-helper.sh"
|
||||
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/report-generator.sh"
|
||||
rm -f "$INSTALL_DIR/uninstall.sh"
|
||||
rm -rf "$INSTALL_DIR/templates"
|
||||
|
||||
Reference in New Issue
Block a user