Compare commits
57 Commits
v0.5.3
...
125329e4ff
| Author | SHA1 | Date | |
|---|---|---|---|
| 125329e4ff | |||
|
|
16b2c5950e | ||
|
|
b93689bbaf | ||
| e555db8092 | |||
| b04b8bf87d | |||
| 6b6a77a98c | |||
| ac21922178 | |||
| edd8cd4806 | |||
| 44936e9f20 | |||
| 440694925e | |||
| c97e327f0d | |||
| 12745c3fef | |||
| c2d6f872f5 | |||
| ccdc555246 | |||
| 633331748f | |||
| 2559ed89ea | |||
| df8b18ae08 | |||
| 6f9f7eba8e | |||
| a79586de94 | |||
| a132b2a0f1 | |||
| b42f458d5a | |||
| 70818698d1 | |||
| 83075f2782 | |||
| 0264e1e896 | |||
| 2a1d8ae975 | |||
| df15a587ee | |||
| 0da5d01641 | |||
| 3d60771a1b | |||
| 535be66b55 | |||
| 4d7e053ce7 | |||
| 2e78b9c14e | |||
| 23deae7d81 | |||
| 0af79e7a28 | |||
| 0602fbb596 | |||
| 606a28ed8e | |||
| a27c093d83 | |||
| 77a5ebb144 | |||
| cfd6fa9b70 | |||
| 01a99489ab | |||
| 2200e80f87 | |||
| 6bdeb5bc31 | |||
| 5451c01603 | |||
| 6daaf67f7c | |||
| 0970218f9b | |||
| db128f3076 | |||
| 6f14219445 | |||
| cb31aa48eb | |||
| 1e8b7557e7 | |||
| 4d1870cc85 | |||
| ebcd70ce8b | |||
| ba342dd571 | |||
| ac1af85810 | |||
| 54b6c877e5 | |||
| 8562202aa7 | |||
| 3361b571cf | |||
| 86eeb2b947 | |||
| cf915c5c80 |
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",
|
"files.eol": "\n",
|
||||||
"chat.tools.terminal.autoApprove": {
|
"chat.tools.terminal.autoApprove": {
|
||||||
"Rename-Item": true,
|
"Rename-Item": true,
|
||||||
"ForEach-Object": true
|
"ForEach-Object": true,
|
||||||
|
"&": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
232
README.md
232
README.md
@@ -1,166 +1,168 @@
|
|||||||
```
|
<p align="center">
|
||||||
▄▄▄ ▓█████▄ ▄████ █ ██ ▄▄▄ ██▀███ ▓█████▄ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄
|
<a href="https://techniverse.net">
|
||||||
▒████▄ ▒██▀ ██▌ ██▒ ▀█▒ ██ ▓██▒▒████▄ ▓██ ▒ ██▒▒██▀ ██▌ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌
|
<img src="https://assets.techniverse.net/f1/git/graphics/repo-techniverse-logo.png" alt="Techniverse Community" height="70" />
|
||||||
▒██ ▀█▄ ░██ █▌▒██░▄▄▄░▓██ ▒██░▒██ ▀█▄ ▓██ ░▄█ ▒░██ █▌ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌
|
</a>
|
||||||
░██▄▄▄▄██ ░▓█▄ ▌░▓█ ██▓▓▓█ ░██░░██▄▄▄▄██ ▒██▀▀█▄ ░▓█▄ ▌ ▒ ██▒░▓█ ░██ ░██░▒▓█ ▄ ▒██░ ░▓█▄ ▌
|
</p>
|
||||||
▓█ ▓██▒░▒████▓ ░▒▓███▀▒▒▒█████▓ ▓█ ▓██▒░██▓ ▒██▒░▒████▓ ▒██████▒▒░▓█▒░██▓░██░░▒████▒░██████▒░▒████▓
|
|
||||||
▒▒ ▓▒█░ ▒▒▓ ▒ ░▒ ▒ ░▒▓▒ ▒ ▒ ▒▒ ▓▒█░░ ▒▓ ░▒▓░ ▒▒▓ ▒ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░▓ ░░ ▒░ ░░ ▒░▓ ░ ▒▒▓ ▒
|
|
||||||
▒ ▒▒ ░ ░ ▒ ▒ ░ ░ ░░▒░ ░ ░ ▒ ▒▒ ░ ░▒ ░ ▒░ ░ ▒ ▒ ░ ░▒ ░ ░ ▒ ░▒░ ░ ▒ ░ ░ ░ ░░ ░ ▒ ░ ░ ▒ ▒
|
|
||||||
░ ▒ ░ ░ ░ ░ ░ ░ ░░░ ░ ░ ░ ▒ ░░ ░ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░ ░ ░ ░ ░
|
|
||||||
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
|
|
||||||
░ ░ ░
|
|
||||||
```
|
|
||||||
|
|
||||||
# AdGuard Shield
|
<h1 align="center">AdGuard Shield</h1>
|
||||||
|
|
||||||
Automatischer Schutz für deinen AdGuard Home DNS-Server gegen übermäßige Anfragen einzelner Clients. Überwacht die AdGuard Home API, erkennt Rate-Limit-Verstöße und sperrt missbrauchende Clients per iptables — für alle DNS-Protokolle (DNS, DoH, DoT, DoQ).
|
<h4 align="center">
|
||||||
|
Automatischer Schutz für AdGuard Home: erkennt auffällige DNS-Clients, sperrt sie per Firewall und hebt temporäre Sperren selbstständig wieder auf.
|
||||||
|
</h4>
|
||||||
|
|
||||||
## Was macht das Tool?
|
<h6 align="center">
|
||||||
|
<a href="https://www.cleveradmin.de">🏰 Website</a>
|
||||||
|
·
|
||||||
|
<a href="https://techniverse.net">📰 Community</a>
|
||||||
|
·
|
||||||
|
<a href="https://social.techniverse.net/@donnerwolke">🐘 Mastodon</a>
|
||||||
|
·
|
||||||
|
<a href="https://matrix.to/#/#support:techniverse.net">💬 Support</a>
|
||||||
|
</h6>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
Wenn ein Client eine bestimmte Domain zu oft anfragt (z.B. >30x pro Minute), wird er automatisch auf Firewall-Ebene für alle DNS-Ports gesperrt. Nach einer konfigurierbaren Zeitspanne wird die Sperre automatisch aufgehoben.
|
|
||||||
|
|
||||||
## Features
|
## ✨ Was ist AdGuard Shield?
|
||||||
|
|
||||||
- Automatische Erkennung und Sperre bei Rate-Limit-Verstößen
|
AdGuard Shield überwacht das Query Log deiner AdGuard-Home-Instanz und erkennt Clients, die eine Domain oder viele zufällige Subdomains in kurzer Zeit übermäßig oft anfragen. Auffällige Clients werden über eine eigene `iptables`/`ip6tables`-Chain auf DNS-relevanten Ports blockiert.
|
||||||
- **Subdomain-Flood-Erkennung** — erkennt Random-Subdomain-Attacken (z.B. `abc123.microsoft.com`, `xyz456.microsoft.com`, ...)
|
|
||||||
- **Progressive Sperren (Recidive)** — Wiederholungstäter werden stufenweise länger gesperrt (wie bei fail2ban)
|
|
||||||
- Unterstützt **alle DNS-Protokolle**: DNS (53), DoH (443), DoT (853), DoQ (784/853/8853)
|
|
||||||
- **IPv4 + IPv6**
|
|
||||||
- Eigene iptables Chain — greift nicht in bestehende Regeln ein
|
|
||||||
- Automatisches Entsperren nach konfigurierbarer Dauer
|
|
||||||
- **Externe Blocklisten** — IP-Adressen von externen Textdateien (URLs) laden und automatisch sperren
|
|
||||||
- **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
|
|
||||||
- Whitelist für vertrauenswürdige IPs
|
|
||||||
- Dry-Run Modus zum gefahrlosen Testen
|
|
||||||
- Benachrichtigungen (Discord, Slack, Gotify, Ntfy)
|
|
||||||
- systemd Service für dauerhaften Betrieb
|
|
||||||
|
|
||||||
## Voraussetzungen
|
Das schützt klassische DNS-Anfragen genauso wie DoH, DoT und DoQ, ohne deine bestehenden Firewall-Regeln unnötig anzufassen.
|
||||||
|
|
||||||
- Linux Server mit AdGuard Home (bare metal)
|
## 🚀 Highlights
|
||||||
- Root-Zugriff (`sudo`)
|
|
||||||
- AdGuard Home Web-API erreichbar (Standard: Port 3000)
|
|
||||||
- Pakete: `curl`, `jq`, `iptables`, `gawk`, `systemd` — werden bei der Installation **automatisch** installiert
|
|
||||||
|
|
||||||
## Schnellstart
|
- Automatische Sperren bei Rate-Limit-Verstößen
|
||||||
|
- Erkennung von Random-Subdomain-Floods, z.B. `abc123.example.com`
|
||||||
|
- DNS-Flood-Watchlist: sofortiger permanenter Ban + AbuseIPDB-Meldung für definierte Domains
|
||||||
|
- Progressive Sperren für Wiederholungstäter, ähnlich wie bei fail2ban
|
||||||
|
- Unterstützung für DNS, DoH, DoT, DoQ und DNSCrypt
|
||||||
|
- IPv4 und IPv6
|
||||||
|
- Eigene Firewall-Chain für sauberes Debugging und einfache Entfernung
|
||||||
|
- Externe Blocklisten und dynamische externe Whitelists
|
||||||
|
- GeoIP-Länderfilter mit Blocklist- oder Allowlist-Modus
|
||||||
|
- AbuseIPDB-Reporting für permanent gesperrte IPs
|
||||||
|
- Benachrichtigungen über Ntfy, Discord, Slack, Gotify oder Generic Webhook
|
||||||
|
- E-Mail-Reports als HTML oder Text
|
||||||
|
- Watchdog mit automatischem Health Check und Recovery
|
||||||
|
|
||||||
|
## ✅ Voraussetzungen
|
||||||
|
|
||||||
|
- Linux-Server mit AdGuard Home
|
||||||
|
- Root-Zugriff per `sudo`
|
||||||
|
- Erreichbare AdGuard Home Web-API, standardmäßig `http://127.0.0.1:3000`
|
||||||
|
- `curl`, `jq`, `iptables`, `gawk` und `systemd`
|
||||||
|
|
||||||
|
Die benötigten Pakete werden vom Installer automatisch installiert.
|
||||||
|
|
||||||
|
## ⚡ Schnellstart
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Repository klonen
|
|
||||||
git clone https://git.techniverse.net/scriptos/adguard-shield.git /tmp/adguard-shield
|
git clone https://git.techniverse.net/scriptos/adguard-shield.git /tmp/adguard-shield
|
||||||
cd /tmp/adguard-shield
|
cd /tmp/adguard-shield
|
||||||
|
|
||||||
# 2. Installer aufrufen (interaktives Menü)
|
# Interaktives Installationsmenü
|
||||||
sudo bash install.sh
|
sudo bash install.sh
|
||||||
|
|
||||||
# Oder direkt installieren:
|
# Vor dem produktiven Start testen: loggt nur, sperrt nichts
|
||||||
sudo bash install.sh install
|
|
||||||
|
|
||||||
# 3. Erst im Dry-Run testen (loggt nur, sperrt nichts)
|
|
||||||
sudo /opt/adguard-shield/adguard-shield.sh dry-run
|
sudo /opt/adguard-shield/adguard-shield.sh dry-run
|
||||||
|
|
||||||
# 4. Wenn alles passt — Service starten
|
# Service starten und prüfen
|
||||||
sudo systemctl start adguard-shield
|
sudo systemctl start adguard-shield
|
||||||
sudo systemctl status adguard-shield
|
sudo systemctl status adguard-shield
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Hinweis:** Bei der Installation werden alle benötigten Abhängigkeiten automatisch installiert und der Service wird für den Autostart beim Booten registriert.
|
> Beim Installieren wird der systemd-Service für den Autostart registriert. Der Watchdog-Timer wird ebenfalls eingerichtet und prüft den Service regelmäßig.
|
||||||
|
|
||||||
[](https://asciinema.techniverse.net/a/77)
|
[](https://asciinema.techniverse.net/a/77)
|
||||||
|
|
||||||
## Wichtigste Befehle
|
## 🔧 Wichtigste Befehle
|
||||||
|
|
||||||
|
### Installation & Updates
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Installer-Menü
|
sudo bash install.sh # Interaktives Menü
|
||||||
sudo bash install.sh # Interaktives Menü (Install/Update/Uninstall/Status)
|
sudo bash install.sh install # Direkt installieren
|
||||||
sudo bash install.sh --help # Hilfe anzeigen
|
sudo bash install.sh update # Update inkl. Konfigurations-Migration
|
||||||
sudo bash install.sh update # Update mit automatischer Konfigurations-Migration
|
|
||||||
sudo bash install.sh status # Installationsstatus prüfen
|
sudo bash install.sh status # Installationsstatus prüfen
|
||||||
|
sudo bash /opt/adguard-shield/uninstall.sh
|
||||||
# Deinstallation (kein install.sh benötigt)
|
|
||||||
sudo bash /opt/adguard-shield/uninstall.sh # Direkt aus dem Installationsverzeichnis
|
|
||||||
|
|
||||||
# Monitor
|
|
||||||
sudo /opt/adguard-shield/adguard-shield.sh status # Aktive Sperren anzeigen
|
|
||||||
sudo /opt/adguard-shield/adguard-shield.sh history # Ban-History anzeigen
|
|
||||||
sudo /opt/adguard-shield/adguard-shield.sh unban IP # Einzelne IP entsperren
|
|
||||||
sudo /opt/adguard-shield/adguard-shield.sh flush # Alle Sperren aufheben
|
|
||||||
sudo /opt/adguard-shield/adguard-shield.sh reset-offenses # Offense-Zähler zurücksetzen
|
|
||||||
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/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
|
|
||||||
sudo journalctl -u adguard-shield -f # Logs live verfolgen
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Projektstruktur
|
### Betrieb & Diagnose
|
||||||
|
|
||||||
```
|
```bash
|
||||||
├── adguard-shield.sh # Haupt-Monitor-Script
|
sudo systemctl status adguard-shield
|
||||||
├── adguard-shield.conf # Konfiguration
|
sudo systemctl restart adguard-shield
|
||||||
├── adguard-shield.service # systemd Unit
|
sudo journalctl -u adguard-shield -f
|
||||||
├── external-blocklist-worker.sh # Externer Blocklist-Worker
|
|
||||||
├── iptables-helper.sh # Manuelle iptables-Verwaltung
|
sudo /opt/adguard-shield/adguard-shield.sh status
|
||||||
├── unban-expired.sh # Cron-basiertes Entsperren
|
sudo /opt/adguard-shield/adguard-shield.sh history
|
||||||
├── report-generator.sh # E-Mail Report Generator
|
sudo /opt/adguard-shield/adguard-shield.sh test
|
||||||
├── install.sh # Installer / Updater
|
sudo /opt/adguard-shield/adguard-shield.sh unban 192.0.2.10
|
||||||
├── uninstall.sh # Uninstaller (wird ins Installationsverzeichnis kopiert)
|
sudo /opt/adguard-shield/adguard-shield.sh flush
|
||||||
├── templates/
|
|
||||||
│ ├── report.html # HTML-Report-Template
|
|
||||||
│ └── report.txt # TXT-Report-Template
|
|
||||||
├── README.md
|
|
||||||
└── docs/
|
|
||||||
├── architektur.md # Architektur & Funktionsweise
|
|
||||||
├── konfiguration.md # Alle Parameter erklärt + Konfig-Migration
|
|
||||||
├── befehle.md # Vollständige Befehlsreferenz inkl. Installer
|
|
||||||
├── benachrichtigungen.md # Webhook-Setup (Discord, Slack, Gotify, Ntfy)
|
|
||||||
├── report.md # E-Mail Report Setup & Konfiguration
|
|
||||||
└── tipps-und-troubleshooting.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dokumentation
|
### Optionale Module
|
||||||
|
|
||||||
| Dokument | Inhalt |
|
```bash
|
||||||
|----------|--------|
|
sudo /opt/adguard-shield/adguard-shield.sh blocklist-status
|
||||||
| [Architektur](docs/architektur.md) | Wie das Tool funktioniert, iptables-Strategie, Konfig-Migration |
|
sudo /opt/adguard-shield/adguard-shield.sh whitelist-status
|
||||||
| [Konfiguration](docs/konfiguration.md) | Alle Parameter, Ports, Whitelist-Pflege, automatische Migration |
|
sudo /opt/adguard-shield/adguard-shield.sh geoip-status
|
||||||
| [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
|
sudo /opt/adguard-shield/report-generator.sh status
|
||||||
|
sudo /opt/adguard-shield/report-generator.sh send
|
||||||
|
sudo /opt/adguard-shield/report-generator.sh install
|
||||||
|
```
|
||||||
|
|
||||||
[MIT](LICENSE)
|
Die vollständige Befehlsreferenz steht in [docs/befehle.md](docs/befehle.md).
|
||||||
|
|
||||||
---
|
## ⚙️ Konfiguration
|
||||||
|
|
||||||
## 👥 Techniverse Community
|
Die zentrale Konfiguration liegt nach der Installation hier:
|
||||||
|
|
||||||
Lust auf Austausch rund um Matrix, Selfhosting und andere smarte IT-Lösungen?
|
```text
|
||||||
In der **Techniverse Community** triffst du Gleichgesinnte, kannst Fragen stellen oder einfach nerdigen Talk genießen. 🚀
|
/opt/adguard-shield/adguard-shield.conf
|
||||||
|
```
|
||||||
|
|
||||||
👉 **[Jetzt der Gruppe auf Matrix beitreten](https://matrix.to/#/#community:techniverse.net)**
|
Wichtige Startpunkte:
|
||||||
~ Direkte Raumadresse: `#community:techniverse.net`
|
|
||||||
|
|
||||||
👉 **[Für lockere Gespräche abseits der Kernthemen komm in den Talkraum](https://matrix.to/#/#talk:techniverse.net)**
|
- `ADGUARD_URL`, `ADGUARD_USER`, `ADGUARD_PASS` für die AdGuard-Home-API
|
||||||
~ Direkte Raumadresse: `#talk:techniverse.net`
|
- `RATE_LIMIT_MAX_REQUESTS`, `RATE_LIMIT_WINDOW` und `CHECK_INTERVAL` für die Erkennung
|
||||||
|
- `BAN_DURATION` und `PROGRESSIVE_BAN_*` für temporäre und progressive Sperren
|
||||||
|
- `WHITELIST` für vertrauenswürdige Clients wie Router, Management-IPs oder lokale Resolver
|
||||||
|
- `DNS_FLOOD_WATCHLIST_*` für sofortigen Permanent-Ban bei bekannten Flood-Domains
|
||||||
|
- `NOTIFY_*`, `REPORT_*`, `GEOIP_*`, `EXTERNAL_BLOCKLIST_*` und `EXTERNAL_WHITELIST_*` für optionale Funktionen
|
||||||
|
|
||||||
Wir freuen uns, wenn du dabei bist!
|
Bei Updates migriert der Installer die bestehende Konfiguration automatisch: vorhandene Werte bleiben erhalten, neue Parameter werden ergänzt und die alte Datei wird als `adguard-shield.conf.old` gesichert.
|
||||||
|
|
||||||
---
|
Mehr Details findest du in [docs/konfiguration.md](docs/konfiguration.md).
|
||||||
|
|
||||||
📝 **Blog:** [www.cleveradmin.de](https://www.cleveradmin.de)
|
## 🧭 Dokumentation
|
||||||
🌐 **Webseite:** [www.patrick-asmus.de](https://www.patrick-asmus.de)
|
|
||||||
📧 **E-Mail:** [support@techniverse.net](mailto:support@techniverse.net)
|
|
||||||
|
|
||||||
|
| Thema | Link |
|
||||||
|
|---|---|
|
||||||
|
| Architektur & Funktionsweise | [docs/architektur.md](docs/architektur.md) |
|
||||||
|
| Befehle & Nutzung | [docs/befehle.md](docs/befehle.md) |
|
||||||
|
| Konfiguration | [docs/konfiguration.md](docs/konfiguration.md) |
|
||||||
|
| Benachrichtigungen | [docs/benachrichtigungen.md](docs/benachrichtigungen.md) |
|
||||||
|
| E-Mail Report | [docs/report.md](docs/report.md) |
|
||||||
|
| Updates | [docs/update.md](docs/update.md) |
|
||||||
|
| Tipps & Troubleshooting | [docs/tipps-und-troubleshooting.md](docs/tipps-und-troubleshooting.md) |
|
||||||
|
|
||||||
|
## 🧩 Wie es arbeitet
|
||||||
|
|
||||||
|
1. AdGuard Shield liest regelmäßig das AdGuard-Home-Query-Log über die API.
|
||||||
|
2. Anfragen werden pro Client, Domain und Protokoll ausgewertet.
|
||||||
|
3. Überschreitet ein Client die konfigurierten Limits, wird er gegen Whitelist und Sonderregeln geprüft.
|
||||||
|
4. Die Sperre landet in der eigenen Firewall-Chain `ADGUARD_SHIELD`.
|
||||||
|
5. Ban-History, Logs und optionale Benachrichtigungen dokumentieren das Ereignis.
|
||||||
|
6. Temporäre Sperren werden automatisch entfernt, permanente Sperren bleiben bis zur manuellen Freigabe aktiv.
|
||||||
|
|
||||||
|
<br><br>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://assets.techniverse.net/f1/git/graphics/gray0-catonline.svg" alt="">
|
<img src="https://assets.techniverse.net/f1/git/graphics/gray0-catonline.svg" alt="">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://assets.techniverse.net/f1/logos/small/license.png" alt="License" width="15" height="15"> <a href="./LICENSE">License</a> | <img src="https://assets.techniverse.net/f1/logos/small/matrix2.svg" alt="Matrix" width="15" height="15"> <a href="https://matrix.to/#/#community:techniverse.net">Matrix</a> | <img src="https://assets.techniverse.net/f1/logos/small/mastodon2.svg" alt="Mastodon" width="15" height="15"> <a href="https://social.techniverse.net/@donnerwolke">Mastodon</a>
|
<sub>
|
||||||
|
Patrick Asmus · Techniverse Network · <a href="./LICENSE">Lizenz</a>
|
||||||
|
</sub>
|
||||||
</p>
|
</p>
|
||||||
7
adguard-shield-watchdog.service
Normal file
7
adguard-shield-watchdog.service
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=AdGuard Shield - Watchdog Health Check
|
||||||
|
Documentation=https://git.techniverse.net/scriptos/adguard-shield
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/opt/adguard-shield/adguard-shield-watchdog.sh
|
||||||
166
adguard-shield-watchdog.sh
Normal file
166
adguard-shield-watchdog.sh
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
###############################################################################
|
||||||
|
# AdGuard Shield - Watchdog
|
||||||
|
# Prüft ob der Hauptservice läuft und startet ihn bei Bedarf neu.
|
||||||
|
# Wird über adguard-shield-watchdog.timer alle 5 Minuten ausgeführt.
|
||||||
|
#
|
||||||
|
# Autor: Patrick Asmus
|
||||||
|
# E-Mail: support@techniverse.net
|
||||||
|
# Lizenz: MIT
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
INSTALL_DIR="/opt/adguard-shield"
|
||||||
|
CONFIG_FILE="${INSTALL_DIR}/adguard-shield.conf"
|
||||||
|
SERVICE_NAME="adguard-shield.service"
|
||||||
|
LOG_FILE="/var/log/adguard-shield.log"
|
||||||
|
WATCHDOG_STATE_FILE="/var/lib/adguard-shield/watchdog.state"
|
||||||
|
|
||||||
|
# ─── Logging ──────────────────────────────────────────────────────────────────
|
||||||
|
log() {
|
||||||
|
local level="$1"
|
||||||
|
shift
|
||||||
|
local message="$*"
|
||||||
|
local timestamp
|
||||||
|
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
local log_entry="[$timestamp] [WATCHDOG] [$level] $message"
|
||||||
|
|
||||||
|
echo "$log_entry" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Benachrichtigung senden ──────────────────────────────────────────────────
|
||||||
|
send_watchdog_notification() {
|
||||||
|
local action="$1" # "recovery" oder "failure"
|
||||||
|
local detail="$2"
|
||||||
|
|
||||||
|
# Konfiguration laden für Benachrichtigungs-Einstellungen
|
||||||
|
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
|
if [[ "${NOTIFY_ENABLED:-false}" != "true" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local my_hostname
|
||||||
|
my_hostname=$(hostname)
|
||||||
|
local title message
|
||||||
|
|
||||||
|
if [[ "$action" == "recovery" ]]; then
|
||||||
|
title="🔄 AdGuard Shield Watchdog"
|
||||||
|
message="🔄 AdGuard Shield Watchdog auf ${my_hostname}
|
||||||
|
---
|
||||||
|
Der Service war ausgefallen und wurde automatisch neu gestartet.
|
||||||
|
${detail}"
|
||||||
|
elif [[ "$action" == "failure" ]]; then
|
||||||
|
title="🚨 AdGuard Shield Watchdog"
|
||||||
|
message="🚨 AdGuard Shield Watchdog auf ${my_hostname}
|
||||||
|
---
|
||||||
|
Der Service konnte NICHT automatisch neu gestartet werden!
|
||||||
|
Manuelles Eingreifen erforderlich.
|
||||||
|
${detail}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${NOTIFY_TYPE:-}" in
|
||||||
|
discord)
|
||||||
|
local json_payload
|
||||||
|
json_payload=$(jq -nc --arg msg "$message" '{content: $msg}')
|
||||||
|
curl -s -H "Content-Type: application/json" \
|
||||||
|
-d "$json_payload" \
|
||||||
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null || true
|
||||||
|
;;
|
||||||
|
slack)
|
||||||
|
local json_payload
|
||||||
|
json_payload=$(jq -nc --arg msg "$message" '{text: $msg}')
|
||||||
|
curl -s -H "Content-Type: application/json" \
|
||||||
|
-d "$json_payload" \
|
||||||
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null || true
|
||||||
|
;;
|
||||||
|
gotify)
|
||||||
|
curl -s -X POST "$NOTIFY_WEBHOOK_URL" \
|
||||||
|
-F "title=${title}" \
|
||||||
|
-F "message=${message}" \
|
||||||
|
-F "priority=5" &>/dev/null || true
|
||||||
|
;;
|
||||||
|
ntfy)
|
||||||
|
if [[ -n "${NTFY_TOPIC:-}" ]]; then
|
||||||
|
local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}"
|
||||||
|
local auth_args=()
|
||||||
|
if [[ -n "${NTFY_TOKEN:-}" ]]; then
|
||||||
|
auth_args=(-H "Authorization: Bearer ${NTFY_TOKEN}")
|
||||||
|
fi
|
||||||
|
curl -s \
|
||||||
|
-H "Title: ${title}" \
|
||||||
|
-H "Priority: ${NTFY_PRIORITY:-5}" \
|
||||||
|
-H "Tags: warning,watchdog" \
|
||||||
|
"${auth_args[@]}" \
|
||||||
|
-d "$message" \
|
||||||
|
"${ntfy_url}/${NTFY_TOPIC}" &>/dev/null || true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
generic)
|
||||||
|
local json_payload
|
||||||
|
json_payload=$(jq -nc --arg msg "$message" --arg act "watchdog_${action}" \
|
||||||
|
'{message: $msg, action: $act}')
|
||||||
|
curl -s -H "Content-Type: application/json" \
|
||||||
|
-d "$json_payload" \
|
||||||
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Hauptlogik ──────────────────────────────────────────────────────────────
|
||||||
|
main() {
|
||||||
|
# Verzeichnis für State-Datei sicherstellen
|
||||||
|
mkdir -p "$(dirname "$WATCHDOG_STATE_FILE")"
|
||||||
|
|
||||||
|
# Prüfen ob der Service aktiv ist
|
||||||
|
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||||
|
# Service läuft – falls vorher ausgefallen war, Status zurücksetzen
|
||||||
|
if [[ -f "$WATCHDOG_STATE_FILE" ]]; then
|
||||||
|
rm -f "$WATCHDOG_STATE_FILE"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Service läuft NICHT – Recovery versuchen
|
||||||
|
log "WARN" "Service $SERVICE_NAME ist nicht aktiv – starte Recovery..."
|
||||||
|
|
||||||
|
# Zähler für fehlgeschlagene Recovery-Versuche
|
||||||
|
local fail_count=0
|
||||||
|
if [[ -f "$WATCHDOG_STATE_FILE" ]]; then
|
||||||
|
fail_count=$(cat "$WATCHDOG_STATE_FILE" 2>/dev/null || echo "0")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# systemd reset-failed damit StartLimit zurückgesetzt wird
|
||||||
|
systemctl reset-failed "$SERVICE_NAME" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Service starten
|
||||||
|
if systemctl start "$SERVICE_NAME" 2>/dev/null; then
|
||||||
|
# Kurz warten und prüfen ob er auch wirklich läuft
|
||||||
|
sleep 3
|
||||||
|
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||||
|
log "INFO" "Service $SERVICE_NAME erfolgreich neu gestartet (Watchdog Recovery)"
|
||||||
|
send_watchdog_notification "recovery" "Versuch: $((fail_count + 1))"
|
||||||
|
rm -f "$WATCHDOG_STATE_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start fehlgeschlagen
|
||||||
|
fail_count=$((fail_count + 1))
|
||||||
|
echo "$fail_count" > "$WATCHDOG_STATE_FILE"
|
||||||
|
log "ERROR" "Service $SERVICE_NAME konnte nicht gestartet werden (Fehlversuch: $fail_count)"
|
||||||
|
|
||||||
|
# Bei jedem 3. Fehlversuch eine Benachrichtigung senden (Spam vermeiden)
|
||||||
|
if [[ $((fail_count % 3)) -eq 1 ]]; then
|
||||||
|
send_watchdog_notification "failure" "Fehlversuche: $fail_count
|
||||||
|
Letzter Fehler: $(systemctl status "$SERVICE_NAME" 2>&1 | tail -5)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
11
adguard-shield-watchdog.timer
Normal file
11
adguard-shield-watchdog.timer
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=AdGuard Shield - Watchdog Timer
|
||||||
|
Documentation=https://git.techniverse.net/scriptos/adguard-shield
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=2min
|
||||||
|
OnUnitActiveSec=5min
|
||||||
|
AccuracySec=30s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
@@ -1,201 +1,106 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
# AdGuard Shield - Konfigurationsdatei
|
# AdGuard Shield - Konfigurationsdatei
|
||||||
# Schutz vor übermäßigen DNS-Anfragen einzelner Clients
|
# Ausführliche Dokumentation: docs/konfiguration.md
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# --- AdGuard Home API Einstellungen ---
|
# --- AdGuard Home API ---
|
||||||
# URL der AdGuard Home Web-Oberfläche (ohne trailing slash)
|
|
||||||
ADGUARD_URL="https://dns1.domain.com"
|
ADGUARD_URL="https://dns1.domain.com"
|
||||||
|
|
||||||
# AdGuard Home Zugangsdaten (Web-UI Login)
|
|
||||||
ADGUARD_USER="admin"
|
ADGUARD_USER="admin"
|
||||||
ADGUARD_PASS='changeme'
|
ADGUARD_PASS='changeme'
|
||||||
|
|
||||||
# --- Rate-Limit Einstellungen ---
|
# --- Rate-Limit ---
|
||||||
# Maximale Anfragen pro Domain pro Client innerhalb des Zeitfensters
|
RATE_LIMIT_MAX_REQUESTS=30 # Max. Anfragen pro Domain/Client im Zeitfenster
|
||||||
RATE_LIMIT_MAX_REQUESTS=30
|
RATE_LIMIT_WINDOW=60 # Zeitfenster in Sekunden
|
||||||
|
CHECK_INTERVAL=10 # Prüfintervall in Sekunden
|
||||||
|
|
||||||
# Zeitfenster in Sekunden (60 = 1 Minute)
|
# --- Subdomain-Flood-Erkennung ---
|
||||||
RATE_LIMIT_WINDOW=60
|
|
||||||
|
|
||||||
# Wie oft das Script die Logs prüft (in Sekunden)
|
|
||||||
CHECK_INTERVAL=10
|
|
||||||
|
|
||||||
# --- Subdomain-Flood-Erkennung (Random Subdomain Attack) ---
|
|
||||||
# Erkennt Bots/Clients die massenhaft zufällige Subdomains einer Domain abfragen
|
|
||||||
# Beispiel: abc123.microsoft.com, xyz456.microsoft.com, ...
|
|
||||||
# Dabei wird pro Client gezählt, wie viele EINDEUTIGE Subdomains einer
|
|
||||||
# Basisdomain (z.B. microsoft.com) im Zeitfenster aufgerufen werden.
|
|
||||||
|
|
||||||
# Subdomain-Flood-Erkennung aktivieren
|
|
||||||
SUBDOMAIN_FLOOD_ENABLED=true
|
SUBDOMAIN_FLOOD_ENABLED=true
|
||||||
|
SUBDOMAIN_FLOOD_MAX_UNIQUE=50 # Max. eindeutige Subdomains pro Basisdomain/Client
|
||||||
|
SUBDOMAIN_FLOOD_WINDOW=60 # Zeitfenster in Sekunden
|
||||||
|
|
||||||
# Maximale Anzahl eindeutiger Subdomains pro Basisdomain pro Client im Zeitfenster
|
# --- DNS-Flood-Watchlist ---
|
||||||
# Beispiel: 50 = ein Client darf max. 50 verschiedene Subdomains von microsoft.com abfragen
|
DNS_FLOOD_WATCHLIST_ENABLED=false
|
||||||
SUBDOMAIN_FLOOD_MAX_UNIQUE=50
|
DNS_FLOOD_WATCHLIST="" # Kommagetrennt, z.B. "example.com,evil.org"
|
||||||
|
|
||||||
# Zeitfenster in Sekunden für die Subdomain-Flood-Erkennung (60 = 1 Minute)
|
|
||||||
SUBDOMAIN_FLOOD_WINDOW=60
|
|
||||||
|
|
||||||
# --- Sperr-Einstellungen ---
|
# --- Sperr-Einstellungen ---
|
||||||
# Wie lange ein Client gesperrt wird (in Sekunden, 3600 = 1 Stunde)
|
BAN_DURATION=3600 # Basis-Sperrdauer in Sekunden
|
||||||
BAN_DURATION=3600
|
|
||||||
|
|
||||||
# iptables Chain-Name für die Sperren
|
|
||||||
IPTABLES_CHAIN="ADGUARD_SHIELD"
|
IPTABLES_CHAIN="ADGUARD_SHIELD"
|
||||||
|
BLOCKED_PORTS="53 443 853" # DNS(53), DoH(443), DoT/DoQ(853)
|
||||||
# Welche Ports gesperrt werden sollen (IPv4 + IPv6)
|
|
||||||
# Port 53 = DNS (UDP + TCP)
|
|
||||||
# Port 443 = DNS-over-HTTPS (DoH)
|
|
||||||
# Port 853 = DNS-over-TLS (tls://...:853) / DNS-over-QUIC (quic://...:853)
|
|
||||||
# Hinweis: Das verwendete Protokoll (DNS/DoH/DoT/DoQ) wird automatisch
|
|
||||||
# aus der AdGuard Home API erkannt und in Logs/History angezeigt.
|
|
||||||
BLOCKED_PORTS="53 443 853"
|
|
||||||
|
|
||||||
# --- Whitelist ---
|
# --- Whitelist ---
|
||||||
# IP-Adressen die NIEMALS gesperrt werden (kommagetrennt)
|
# IPs die niemals gesperrt werden (kommagetrennt)
|
||||||
# Lokale Netze und wichtige Server hier eintragen
|
|
||||||
WHITELIST="127.0.0.1,::1"
|
WHITELIST="127.0.0.1,::1"
|
||||||
|
|
||||||
# --- Logging ---
|
# --- Logging ---
|
||||||
# Log-Datei Pfad
|
|
||||||
LOG_FILE="/var/log/adguard-shield.log"
|
LOG_FILE="/var/log/adguard-shield.log"
|
||||||
|
LOG_LEVEL="INFO" # DEBUG, INFO, WARN, ERROR
|
||||||
# Log-Level: DEBUG, INFO, WARN, ERROR
|
LOG_MAX_SIZE_MB=50 # Max. Größe in MB (danach Rotation)
|
||||||
LOG_LEVEL="INFO"
|
|
||||||
|
|
||||||
# Maximale Größe der Log-Datei in MB (danach wird rotiert)
|
|
||||||
LOG_MAX_SIZE_MB=50
|
|
||||||
|
|
||||||
# Ban-History Datei (protokolliert alle Sperren & Entsperrungen dauerhaft)
|
|
||||||
BAN_HISTORY_FILE="/var/log/adguard-shield-bans.log"
|
BAN_HISTORY_FILE="/var/log/adguard-shield-bans.log"
|
||||||
|
BAN_HISTORY_RETENTION_DAYS=0 # 0 = unbegrenzt
|
||||||
|
|
||||||
# Maximale Aufbewahrungsdauer der Ban-History in Tagen
|
# --- Benachrichtigungen ---
|
||||||
# 0 = unbegrenzt (niemals automatisch löschen)
|
|
||||||
# Beispiel: 90 = Einträge älter als 90 Tage werden beim nächsten Report entfernt
|
|
||||||
BAN_HISTORY_RETENTION_DAYS=0
|
|
||||||
|
|
||||||
# --- Benachrichtigungen (optional) ---
|
|
||||||
# Aktiviert Benachrichtigungen bei Sperren/Entsperrungen
|
|
||||||
NOTIFY_ENABLED=false
|
NOTIFY_ENABLED=false
|
||||||
|
NOTIFY_TYPE="ntfy" # ntfy, discord, slack, gotify, generic
|
||||||
|
NOTIFY_WEBHOOK_URL="" # Webhook-URL (nicht für ntfy)
|
||||||
|
|
||||||
# Benachrichtigungs-Typ: "ntfy", "discord", "slack", "gotify", "generic"
|
# Ntfy-Einstellungen (nur bei NOTIFY_TYPE="ntfy")
|
||||||
NOTIFY_TYPE="ntfy"
|
|
||||||
|
|
||||||
# Webhook-URL (nur für discord, slack, gotify, generic – bei ntfy nicht nötig)
|
|
||||||
# Discord: https://discord.com/api/webhooks/xxx/yyy
|
|
||||||
# Gotify: https://gotify.example.com/message?token=xxx
|
|
||||||
NOTIFY_WEBHOOK_URL=""
|
|
||||||
|
|
||||||
# --- Ntfy Einstellungen (nur bei NOTIFY_TYPE="ntfy") ---
|
|
||||||
# Server-URL der Ntfy-Instanz (ohne trailing slash)
|
|
||||||
NTFY_SERVER_URL="https://ntfy.sh"
|
NTFY_SERVER_URL="https://ntfy.sh"
|
||||||
|
|
||||||
# Topic-Name für die Benachrichtigungen
|
|
||||||
NTFY_TOPIC=""
|
NTFY_TOPIC=""
|
||||||
|
|
||||||
# Optionaler Access-Token (leer lassen wenn nicht benötigt)
|
|
||||||
NTFY_TOKEN=""
|
NTFY_TOKEN=""
|
||||||
|
NTFY_PRIORITY="4" # 1=min, 3=default, 5=max
|
||||||
|
|
||||||
# Priorität der Ntfy-Nachrichten (1=min, 3=default, 5=max)
|
# --- E-Mail Report ---
|
||||||
NTFY_PRIORITY="4"
|
|
||||||
|
|
||||||
# --- E-Mail Report (optional) ---
|
|
||||||
# Regelmäßiger Statistik-Report per E-Mail
|
|
||||||
# Voraussetzung: Ein funktionierender Mail-Transport (z.B. msmtp)
|
|
||||||
# Anleitung für msmtp: https://www.cleveradmin.de/blog/2024/12/linux-einfach-emails-versenden-mit-msmtp/
|
|
||||||
REPORT_ENABLED=false
|
REPORT_ENABLED=false
|
||||||
|
REPORT_INTERVAL="weekly" # daily, weekly, biweekly, monthly
|
||||||
# Report-Intervall: "daily", "weekly", "biweekly", "monthly"
|
|
||||||
# daily = täglich um die konfigurierte Uhrzeit
|
|
||||||
# weekly = wöchentlich am Montag
|
|
||||||
# biweekly = alle zwei Wochen am Montag
|
|
||||||
# monthly = monatlich am 1. des Monats
|
|
||||||
REPORT_INTERVAL="weekly"
|
|
||||||
|
|
||||||
# Uhrzeit für den Report-Versand (Format: HH:MM, 24h)
|
|
||||||
REPORT_TIME="08:00"
|
REPORT_TIME="08:00"
|
||||||
|
|
||||||
# E-Mail-Empfänger
|
|
||||||
REPORT_EMAIL_TO="admin@example.com"
|
REPORT_EMAIL_TO="admin@example.com"
|
||||||
|
|
||||||
# E-Mail-Absender
|
|
||||||
REPORT_EMAIL_FROM="adguard-shield@example.com"
|
REPORT_EMAIL_FROM="adguard-shield@example.com"
|
||||||
|
REPORT_FORMAT="html" # html, txt
|
||||||
# E-Mail-Format: "html" oder "txt"
|
|
||||||
REPORT_FORMAT="html"
|
|
||||||
|
|
||||||
# Mail-Befehl (z.B. "msmtp", "sendmail", "mail")
|
|
||||||
REPORT_MAIL_CMD="msmtp"
|
REPORT_MAIL_CMD="msmtp"
|
||||||
|
REPORT_BUSIEST_DAY_RANGE=30 # Tage für "Aktivster Tag" (0 = nur Berichtszeitraum)
|
||||||
|
|
||||||
# --- Externe Blocklist (optional) ---
|
# --- Externe Whitelist ---
|
||||||
# Aktiviert den externen Blocklist-Worker
|
# Externe Whitelist-Dateien mit Domains/IPs; Domains werden per DNS aufgelöst
|
||||||
|
EXTERNAL_WHITELIST_ENABLED=false
|
||||||
|
EXTERNAL_WHITELIST_URLS="" # URL(s) kommagetrennt
|
||||||
|
EXTERNAL_WHITELIST_INTERVAL=300 # Prüfintervall in Sekunden
|
||||||
|
EXTERNAL_WHITELIST_CACHE_DIR="/var/lib/adguard-shield/external-whitelist"
|
||||||
|
|
||||||
|
# --- Externe Blocklist ---
|
||||||
EXTERNAL_BLOCKLIST_ENABLED=false
|
EXTERNAL_BLOCKLIST_ENABLED=false
|
||||||
|
EXTERNAL_BLOCKLIST_URLS="" # URL(s) kommagetrennt
|
||||||
# URL(s) zu externen Textdateien mit IP-Adressen (eine IP pro Zeile)
|
EXTERNAL_BLOCKLIST_INTERVAL=300 # Prüfintervall in Sekunden
|
||||||
# Mehrere URLs kommagetrennt angeben
|
EXTERNAL_BLOCKLIST_BAN_DURATION=0 # 0 = permanent bis IP aus Liste entfernt
|
||||||
# Beispiel: "https://example.com/blocklist.txt,https://other.com/bad-ips.txt"
|
|
||||||
EXTERNAL_BLOCKLIST_URLS=""
|
|
||||||
|
|
||||||
# Wie oft die externe Blocklist geprüft wird (in Sekunden, 300 = 5 Minuten)
|
|
||||||
EXTERNAL_BLOCKLIST_INTERVAL=300
|
|
||||||
|
|
||||||
# Sperrdauer für externe Blocklist-IPs in Sekunden (0 = permanent bis IP aus Liste entfernt)
|
|
||||||
EXTERNAL_BLOCKLIST_BAN_DURATION=0
|
|
||||||
|
|
||||||
# Automatisch IPs entsperren die aus der externen Liste entfernt wurden?
|
|
||||||
EXTERNAL_BLOCKLIST_AUTO_UNBAN=true
|
EXTERNAL_BLOCKLIST_AUTO_UNBAN=true
|
||||||
|
EXTERNAL_BLOCKLIST_NOTIFY=false # Bei großen Listen auf false lassen
|
||||||
# Benachrichtigungen bei Blocklist-Sperren senden?
|
|
||||||
# Bei Listen mit vielen IPs empfiehlt sich false, da sonst beim Sync
|
|
||||||
# hunderte Benachrichtigungen auf einmal verschickt werden.
|
|
||||||
EXTERNAL_BLOCKLIST_NOTIFY=false
|
|
||||||
|
|
||||||
# Lokaler Cache-Pfad für die heruntergeladene Blocklist
|
|
||||||
EXTERNAL_BLOCKLIST_CACHE_DIR="/var/lib/adguard-shield/external-blocklist"
|
EXTERNAL_BLOCKLIST_CACHE_DIR="/var/lib/adguard-shield/external-blocklist"
|
||||||
|
|
||||||
# --- Progressive Sperren (Recidive) ---
|
# --- Progressive Sperren (Recidive) ---
|
||||||
# Wiederholungstäter werden stufenweise länger gesperrt (wie bei fail2ban)
|
# Wiederholungstäter werden stufenweise länger gesperrt
|
||||||
# Aktiviert das progressive Sperrsystem
|
|
||||||
PROGRESSIVE_BAN_ENABLED=true
|
PROGRESSIVE_BAN_ENABLED=true
|
||||||
|
PROGRESSIVE_BAN_MULTIPLIER=2 # Multiplikator pro Stufe (2 = Verdopplung)
|
||||||
|
PROGRESSIVE_BAN_MAX_LEVEL=5 # Ab dieser Stufe permanent sperren (0 = nie)
|
||||||
|
PROGRESSIVE_BAN_RESET_AFTER=86400 # Zähler-Reset nach X Sekunden ohne Vergehen
|
||||||
|
|
||||||
# Multiplikator pro Wiederholung (2 = Verdopplung der Sperrdauer)
|
# --- AbuseIPDB Reporting ---
|
||||||
# Stufe 1: BAN_DURATION × 1 (Standard-Sperrdauer)
|
# Meldet nur permanent gesperrte IPs an AbuseIPDB
|
||||||
# Stufe 2: BAN_DURATION × 2
|
|
||||||
# Stufe 3: BAN_DURATION × 4
|
|
||||||
# Stufe 4: BAN_DURATION × 8 ... usw.
|
|
||||||
PROGRESSIVE_BAN_MULTIPLIER=2
|
|
||||||
|
|
||||||
# Ab dieser Stufe wird die IP permanent gesperrt (0 = nie permanent sperren)
|
|
||||||
# Beispiel: 5 = nach dem 5. Vergehen wird die IP dauerhaft gesperrt
|
|
||||||
PROGRESSIVE_BAN_MAX_LEVEL=5
|
|
||||||
|
|
||||||
# Nach wie vielen Sekunden ohne erneutes Vergehen wird der Zähler zurückgesetzt
|
|
||||||
# (86400 = 24 Stunden, 604800 = 7 Tage)
|
|
||||||
PROGRESSIVE_BAN_RESET_AFTER=86400
|
|
||||||
|
|
||||||
# --- AbuseIPDB Reporting (optional) ---
|
|
||||||
# Meldet permanent gesperrte IPs automatisch an AbuseIPDB
|
|
||||||
# Nur bei PERMANENTEN Sperren wird ein Report gesendet.
|
|
||||||
ABUSEIPDB_ENABLED=false
|
ABUSEIPDB_ENABLED=false
|
||||||
|
|
||||||
# AbuseIPDB API-Key (https://www.abuseipdb.com/account/api)
|
|
||||||
ABUSEIPDB_API_KEY=""
|
ABUSEIPDB_API_KEY=""
|
||||||
|
ABUSEIPDB_CATEGORIES="4" # 4 = DDoS Attack (siehe abuseipdb.com/categories)
|
||||||
|
|
||||||
# Kategorien für den Report (kommagetrennt)
|
# --- GeoIP-basierte Länderfilter ---
|
||||||
# 4 = DDoS Attack
|
# Sperrt/erlaubt DNS-Anfragen nach Herkunftsland (lokale DB, keine Online-API)
|
||||||
# Siehe: https://www.abuseipdb.com/categories
|
GEOIP_ENABLED=false
|
||||||
ABUSEIPDB_CATEGORIES="4"
|
GEOIP_MODE="blocklist" # blocklist oder allowlist
|
||||||
|
GEOIP_COUNTRIES="" # ISO 3166-1 Alpha-2 Codes, z.B. "CN,RU,KP,IR"
|
||||||
|
GEOIP_CHECK_INTERVAL=0 # 0 = nutzt CHECK_INTERVAL
|
||||||
|
GEOIP_NOTIFY=true
|
||||||
|
GEOIP_SKIP_PRIVATE=true # Private IPs ausnehmen
|
||||||
|
GEOIP_LICENSE_KEY="" # MaxMind GeoLite2 Key (optional, für Auto-Download)
|
||||||
|
GEOIP_MMDB_PATH="" # Manueller DB-Pfad (optional, hat Vorrang)
|
||||||
|
|
||||||
# --- Erweiterte Einstellungen ---
|
# --- Erweiterte Einstellungen ---
|
||||||
# Pfad zur State-Datei (speichert aktive Sperren)
|
|
||||||
STATE_DIR="/var/lib/adguard-shield"
|
STATE_DIR="/var/lib/adguard-shield"
|
||||||
|
|
||||||
# Pfad zum PID-File
|
|
||||||
PID_FILE="/var/run/adguard-shield.pid"
|
PID_FILE="/var/run/adguard-shield.pid"
|
||||||
|
API_QUERY_LIMIT=500 # API-Einträge pro Abfrage (max 5000)
|
||||||
# Anzahl der API-Einträge die pro Abfrage geholt werden (max 5000)
|
DRY_RUN=false # true = nur loggen, nicht sperren
|
||||||
API_QUERY_LIMIT=500
|
|
||||||
|
|
||||||
# Dry-Run Modus: true = nur loggen, nicht sperren (zum Testen)
|
|
||||||
DRY_RUN=false
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Documentation=https://git.techniverse.net/scriptos/adguard-shield
|
|||||||
After=network.target AdGuardHome.service
|
After=network.target AdGuardHome.service
|
||||||
Wants=AdGuardHome.service
|
Wants=AdGuardHome.service
|
||||||
StartLimitBurst=5
|
StartLimitBurst=5
|
||||||
StartLimitIntervalSec=60
|
StartLimitIntervalSec=300
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
@@ -14,7 +14,7 @@ ExecReload=/bin/kill -HUP $MAINPID
|
|||||||
|
|
||||||
# Neustart-Verhalten
|
# Neustart-Verhalten
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=10
|
RestartSec=30
|
||||||
|
|
||||||
# Sicherheits-Hardening
|
# Sicherheits-Hardening
|
||||||
ProtectSystem=full
|
ProtectSystem=full
|
||||||
|
|||||||
@@ -5,11 +5,10 @@
|
|||||||
#
|
#
|
||||||
# Autor: Patrick Asmus
|
# Autor: Patrick Asmus
|
||||||
# E-Mail: support@techniverse.net
|
# E-Mail: support@techniverse.net
|
||||||
# Datum: 2026-03-04
|
|
||||||
# Lizenz: MIT
|
# Lizenz: MIT
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
VERSION="v0.5.2"
|
VERSION="v0.9.0"
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -231,6 +230,30 @@ format_protocol() {
|
|||||||
esac
|
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 ─────────────────────────────────────────────────────
|
# ─── AbuseIPDB Reporting ─────────────────────────────────────────────────────
|
||||||
# Meldet eine IP an AbuseIPDB (nur bei permanenten Sperren)
|
# Meldet eine IP an AbuseIPDB (nur bei permanenten Sperren)
|
||||||
report_to_abuseipdb() {
|
report_to_abuseipdb() {
|
||||||
@@ -306,6 +329,9 @@ cleanup() {
|
|||||||
sleep 1
|
sleep 1
|
||||||
fi
|
fi
|
||||||
stop_blocklist_worker
|
stop_blocklist_worker
|
||||||
|
stop_whitelist_worker
|
||||||
|
stop_geoip_worker
|
||||||
|
stop_offense_cleanup_worker
|
||||||
rm -f "$PID_FILE"
|
rm -f "$PID_FILE"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
@@ -333,6 +359,39 @@ is_whitelisted() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── DNS-Flood-Watchlist Prüfung ────────────────────────────────────────────
|
||||||
|
is_dns_flood_watchlist_match() {
|
||||||
|
local domain="$1"
|
||||||
|
|
||||||
|
if [[ "${DNS_FLOOD_WATCHLIST_ENABLED:-false}" != "true" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${DNS_FLOOD_WATCHLIST:-}" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local entry
|
||||||
|
IFS=',' read -ra watchlist_entries <<< "$DNS_FLOOD_WATCHLIST"
|
||||||
|
for entry in "${watchlist_entries[@]}"; do
|
||||||
|
entry=$(echo "$entry" | xargs)
|
||||||
|
[[ -z "$entry" ]] && continue
|
||||||
|
|
||||||
|
if [[ "$domain" == "$entry" || "$domain" == *".$entry" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,6 +451,13 @@ ban_client() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# DNS-Flood-Watchlist: Sofort permanent sperren
|
||||||
|
if [[ "$reason" == "dns-flood-watchlist" ]]; then
|
||||||
|
is_permanent=true
|
||||||
|
effective_duration=0
|
||||||
|
log "WARN" "DNS-Flood-Watchlist: Erzwinge permanente Sperre für $client_ip ($domain)"
|
||||||
|
fi
|
||||||
|
|
||||||
local ban_until
|
local ban_until
|
||||||
local ban_until_display
|
local ban_until_display
|
||||||
if [[ "$is_permanent" == "true" ]]; then
|
if [[ "$is_permanent" == "true" ]]; then
|
||||||
@@ -406,16 +472,16 @@ ban_client() {
|
|||||||
duration_display=$(format_duration "$effective_duration")
|
duration_display=$(format_duration "$effective_duration")
|
||||||
|
|
||||||
if [[ "$DRY_RUN" == "true" ]]; then
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then
|
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && "$reason" != "dns-flood-watchlist" ]]; then
|
||||||
log "WARN" "[DRY-RUN] WÜRDE sperren: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [Stufe $offense_level] [${reason}]"
|
log "WARN" "[DRY-RUN] WÜRDE sperren: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [Stufe $offense_level] [${reason}]"
|
||||||
else
|
else
|
||||||
log "WARN" "[DRY-RUN] WÜRDE sperren: $client_ip (${count}x $domain in ${window}s via $protocol) [${reason}]"
|
log "WARN" "[DRY-RUN] WÜRDE sperren: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [${reason}]"
|
||||||
fi
|
fi
|
||||||
log_ban_history "DRY" "$client_ip" "$domain" "$count" "dry-run (${reason})" "${duration_display}" "$protocol"
|
log_ban_history "DRY" "$client_ip" "$domain" "$count" "dry-run (${reason})" "${duration_display}" "$protocol"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then
|
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && "$reason" != "dns-flood-watchlist" ]]; then
|
||||||
log "WARN" "SPERRE Client: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [Stufe ${offense_level}/${PROGRESSIVE_BAN_MAX_LEVEL:-0}] [${reason}]"
|
log "WARN" "SPERRE Client: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [Stufe ${offense_level}/${PROGRESSIVE_BAN_MAX_LEVEL:-0}] [${reason}]"
|
||||||
else
|
else
|
||||||
log "WARN" "SPERRE Client: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [${reason}]"
|
log "WARN" "SPERRE Client: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [${reason}]"
|
||||||
@@ -447,12 +513,12 @@ EOF
|
|||||||
|
|
||||||
# Ban-History Eintrag
|
# Ban-History Eintrag
|
||||||
local history_duration="${duration_display}"
|
local history_duration="${duration_display}"
|
||||||
[[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]] && history_duration="${duration_display} (Stufe ${offense_level})"
|
[[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && "$reason" != "dns-flood-watchlist" ]] && history_duration="${duration_display} (Stufe ${offense_level})"
|
||||||
log_ban_history "BAN" "$client_ip" "$domain" "$count" "$reason" "$history_duration" "$protocol"
|
log_ban_history "BAN" "$client_ip" "$domain" "$count" "$reason" "$history_duration" "$protocol"
|
||||||
|
|
||||||
# Benachrichtigung senden
|
# Benachrichtigung senden
|
||||||
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
|
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
|
fi
|
||||||
|
|
||||||
# AbuseIPDB Report (nur bei permanenter Sperre)
|
# AbuseIPDB Report (nur bei permanenter Sperre)
|
||||||
@@ -490,7 +556,7 @@ unban_client() {
|
|||||||
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "$reason" "-" "$protocol"
|
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "$reason" "-" "$protocol"
|
||||||
|
|
||||||
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
|
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
|
||||||
send_notification "unban" "$client_ip" "" ""
|
send_notification "unban" "$client_ip" "$domain" ""
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,6 +598,7 @@ send_notification() {
|
|||||||
local reason="${7:-rate-limit}"
|
local reason="${7:-rate-limit}"
|
||||||
local window="${8:-$RATE_LIMIT_WINDOW}"
|
local window="${8:-$RATE_LIMIT_WINDOW}"
|
||||||
local protocol="${9:-DNS}"
|
local protocol="${9:-DNS}"
|
||||||
|
local is_permanent="${10:-false}"
|
||||||
|
|
||||||
# Ntfy benötigt keine Webhook-URL (nutzt NTFY_SERVER_URL + NTFY_TOPIC)
|
# Ntfy benötigt keine Webhook-URL (nutzt NTFY_SERVER_URL + NTFY_TOPIC)
|
||||||
if [[ "$NOTIFY_TYPE" != "ntfy" && -z "$NOTIFY_WEBHOOK_URL" ]]; then
|
if [[ "$NOTIFY_TYPE" != "ntfy" && -z "$NOTIFY_WEBHOOK_URL" ]]; then
|
||||||
@@ -540,47 +607,94 @@ send_notification() {
|
|||||||
|
|
||||||
local reason_label="Rate-Limit"
|
local reason_label="Rate-Limit"
|
||||||
[[ "$reason" == "subdomain-flood" ]] && reason_label="Subdomain-Flood"
|
[[ "$reason" == "subdomain-flood" ]] && reason_label="Subdomain-Flood"
|
||||||
|
[[ "$reason" == "dns-flood-watchlist" ]] && reason_label="DNS-Flood-Watchlist"
|
||||||
|
|
||||||
|
local title
|
||||||
local message
|
local message
|
||||||
|
local my_hostname
|
||||||
|
my_hostname=$(hostname)
|
||||||
|
|
||||||
if [[ "$action" == "ban" ]]; then
|
if [[ "$action" == "ban" ]]; then
|
||||||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && -n "$offense_level" ]]; then
|
title="🚨 🛡️ AdGuard Shield"
|
||||||
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}]."
|
local client_hostname
|
||||||
else
|
client_hostname=$(resolve_hostname "$client_ip")
|
||||||
local simple_dur
|
|
||||||
simple_dur=$(format_duration "${BAN_DURATION}")
|
# AbuseIPDB-Hinweis bei permanenter Sperre
|
||||||
message="🚫 AdGuard Shield: Client **$client_ip** gesperrt (${count}x $domain in ${window}s via **$protocol**, ${reason_label}). Sperre für ${simple_dur}."
|
local abuseipdb_hint=""
|
||||||
|
if [[ "$is_permanent" == "true" && "${ABUSEIPDB_ENABLED:-false}" == "true" ]]; then
|
||||||
|
abuseipdb_hint=$'\n⚠️ IP wurde an AbuseIPDB gemeldet'
|
||||||
fi
|
fi
|
||||||
elif [[ "$action" == "service_start" ]]; then
|
|
||||||
message="🟢 AdGuard Shield ${VERSION} wurde gestartet."
|
# Dauer-Anzeige mit Stufe (nicht bei Watchlist – dort ist es immer permanent)
|
||||||
elif [[ "$action" == "service_stop" ]]; then
|
local dur_line
|
||||||
message="🔴 AdGuard Shield ${VERSION} wurde gestoppt."
|
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && -n "$offense_level" && "$reason" != "dns-flood-watchlist" ]]; then
|
||||||
|
dur_line="**${duration_display}** [Stufe ${offense_level}/${PROGRESSIVE_BAN_MAX_LEVEL:-0}]"
|
||||||
else
|
else
|
||||||
message="✅ AdGuard Shield: Client **$client_ip** wurde entsperrt."
|
dur_line="**${duration_display}**"
|
||||||
|
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
|
||||||
|
title="✅ AdGuard Shield"
|
||||||
|
message="🟢 AdGuard Shield ${VERSION} wurde auf ${my_hostname} gestartet."
|
||||||
|
elif [[ "$action" == "service_stop" ]]; then
|
||||||
|
title="🚨 🛡️ AdGuard Shield"
|
||||||
|
message="🔴 AdGuard Shield ${VERSION} wurde auf ${my_hostname} gestoppt."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$NOTIFY_TYPE" in
|
case "$NOTIFY_TYPE" in
|
||||||
discord)
|
discord)
|
||||||
|
local json_payload
|
||||||
|
json_payload=$(jq -nc --arg msg "$message" '{content: $msg}')
|
||||||
curl -s -H "Content-Type: application/json" \
|
curl -s -H "Content-Type: application/json" \
|
||||||
-d "{\"content\": \"$message\"}" \
|
-d "$json_payload" \
|
||||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||||
;;
|
;;
|
||||||
slack)
|
slack)
|
||||||
|
local json_payload
|
||||||
|
json_payload=$(jq -nc --arg msg "$message" '{text: $msg}')
|
||||||
curl -s -H "Content-Type: application/json" \
|
curl -s -H "Content-Type: application/json" \
|
||||||
-d "{\"text\": \"$message\"}" \
|
-d "$json_payload" \
|
||||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||||
;;
|
;;
|
||||||
gotify)
|
gotify)
|
||||||
|
local clean_message
|
||||||
|
clean_message=$(echo "$message" | sed 's/\*\*//g')
|
||||||
curl -s -X POST "$NOTIFY_WEBHOOK_URL" \
|
curl -s -X POST "$NOTIFY_WEBHOOK_URL" \
|
||||||
-F "title=AdGuard Shield" \
|
-F "title=${title}" \
|
||||||
-F "message=$message" \
|
-F "message=${clean_message}" \
|
||||||
-F "priority=5" &>/dev/null &
|
-F "priority=5" &>/dev/null &
|
||||||
;;
|
;;
|
||||||
ntfy)
|
ntfy)
|
||||||
send_ntfy_notification "$action" "$message"
|
send_ntfy_notification "$action" "$title" "$message"
|
||||||
;;
|
;;
|
||||||
generic)
|
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" \
|
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 &
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -589,7 +703,8 @@ send_notification() {
|
|||||||
# ─── Ntfy Benachrichtigung ───────────────────────────────────────────────────
|
# ─── Ntfy Benachrichtigung ───────────────────────────────────────────────────
|
||||||
send_ntfy_notification() {
|
send_ntfy_notification() {
|
||||||
local action="$1"
|
local action="$1"
|
||||||
local message="$2"
|
local title="$2"
|
||||||
|
local message="$3"
|
||||||
|
|
||||||
if [[ -z "${NTFY_TOPIC:-}" ]]; then
|
if [[ -z "${NTFY_TOPIC:-}" ]]; then
|
||||||
log "WARN" "Ntfy: Kein Topic konfiguriert (NTFY_TOPIC ist leer)"
|
log "WARN" "Ntfy: Kein Topic konfiguriert (NTFY_TOPIC ist leer)"
|
||||||
@@ -598,7 +713,6 @@ send_ntfy_notification() {
|
|||||||
|
|
||||||
local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}"
|
local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}"
|
||||||
local priority="${NTFY_PRIORITY:-4}"
|
local priority="${NTFY_PRIORITY:-4}"
|
||||||
local title="AdGuard Shield"
|
|
||||||
local tags
|
local tags
|
||||||
|
|
||||||
if [[ "$action" == "ban" ]]; then
|
if [[ "$action" == "ban" ]]; then
|
||||||
@@ -615,11 +729,18 @@ send_ntfy_notification() {
|
|||||||
local clean_message
|
local clean_message
|
||||||
clean_message=$(echo "$message" | sed 's/\*\*//g')
|
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=(
|
local -a curl_args=(
|
||||||
-s
|
-s
|
||||||
-X POST
|
-X POST
|
||||||
"${ntfy_url}/${NTFY_TOPIC}"
|
"${ntfy_url}/${NTFY_TOPIC}"
|
||||||
-H "Title: ${title}"
|
-H "Title: ${ntfy_title}"
|
||||||
-H "Priority: ${priority}"
|
-H "Priority: ${priority}"
|
||||||
-H "Tags: ${tags}"
|
-H "Tags: ${tags}"
|
||||||
-d "${clean_message}"
|
-d "${clean_message}"
|
||||||
@@ -752,7 +873,13 @@ analyze_queries() {
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ban_client "$client" "$domain" "$count" "rate-limit" "$RATE_LIMIT_WINDOW" "$proto_display"
|
local ban_reason="rate-limit"
|
||||||
|
if is_dns_flood_watchlist_match "$domain"; then
|
||||||
|
ban_reason="dns-flood-watchlist"
|
||||||
|
log "WARN" "DNS-Flood-Watchlist Treffer: $client → $domain (${count}x in ${RATE_LIMIT_WINDOW}s) → permanenter Ban + AbuseIPDB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ban_client "$client" "$domain" "$count" "$ban_reason" "$RATE_LIMIT_WINDOW" "$proto_display"
|
||||||
fi
|
fi
|
||||||
done <<< "$violations"
|
done <<< "$violations"
|
||||||
}
|
}
|
||||||
@@ -874,7 +1001,13 @@ analyze_subdomain_flood() {
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ban_client "$client" "*.${base_domain}" "$unique_count" "subdomain-flood" "$window" "$proto_display"
|
local ban_reason="subdomain-flood"
|
||||||
|
if is_dns_flood_watchlist_match "$base_domain"; then
|
||||||
|
ban_reason="dns-flood-watchlist"
|
||||||
|
log "WARN" "DNS-Flood-Watchlist Treffer (Subdomain-Flood): $client → *.${base_domain} → permanenter Ban + AbuseIPDB"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ban_client "$client" "*.${base_domain}" "$unique_count" "$ban_reason" "$window" "$proto_display"
|
||||||
done <<< "$violations"
|
done <<< "$violations"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -902,6 +1035,25 @@ show_status() {
|
|||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# DNS-Flood-Watchlist Info
|
||||||
|
if [[ "${DNS_FLOOD_WATCHLIST_ENABLED:-false}" == "true" ]]; then
|
||||||
|
echo " 🎯 DNS-Flood-Watchlist: AKTIV"
|
||||||
|
echo " Domains: ${DNS_FLOOD_WATCHLIST:-<keine>}"
|
||||||
|
echo " Aktion: Sofort permanenter Ban + AbuseIPDB-Meldung"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GeoIP-Filter Info
|
||||||
|
if [[ "${GEOIP_ENABLED:-false}" == "true" ]]; then
|
||||||
|
local geoip_mode_label
|
||||||
|
[[ "${GEOIP_MODE:-blocklist}" == "blocklist" ]] && geoip_mode_label="Blocklist" || geoip_mode_label="Allowlist"
|
||||||
|
echo " 🌍 GeoIP-Filter: AKTIV"
|
||||||
|
echo " Modus: ${geoip_mode_label}"
|
||||||
|
echo " Länder: ${GEOIP_COUNTRIES:-<keine>}"
|
||||||
|
echo " Sperrdauer: PERMANENT (Auto-Unban bei Änderung der Länderliste)"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
# Aktive Sperren
|
# Aktive Sperren
|
||||||
local ban_count=0
|
local ban_count=0
|
||||||
if [[ -d "$STATE_DIR" ]]; then
|
if [[ -d "$STATE_DIR" ]]; then
|
||||||
@@ -923,6 +1075,7 @@ show_status() {
|
|||||||
|
|
||||||
local reason_tag=""
|
local reason_tag=""
|
||||||
[[ "$s_reason" == "subdomain-flood" ]] && reason_tag=" (Subdomain-Flood)"
|
[[ "$s_reason" == "subdomain-flood" ]] && reason_tag=" (Subdomain-Flood)"
|
||||||
|
[[ "$s_reason" == "dns-flood-watchlist" ]] && reason_tag=" (DNS-Flood-Watchlist)"
|
||||||
|
|
||||||
local count_info=""
|
local count_info=""
|
||||||
if [[ -n "$s_count" && "$s_count" != "-" ]]; then
|
if [[ -n "$s_count" && "$s_count" != "-" ]]; then
|
||||||
@@ -935,7 +1088,9 @@ show_status() {
|
|||||||
|
|
||||||
local proto_tag=" via ${s_proto}"
|
local proto_tag=" via ${s_proto}"
|
||||||
|
|
||||||
if [[ "$s_perm" == "true" ]]; then
|
if [[ "$s_perm" == "true" && "$s_reason" == "dns-flood-watchlist" ]]; then
|
||||||
|
echo " 🚫 Gesperrt: $s_ip → $s_domain [PERMANENT${count_info}${proto_tag}]${reason_tag}"
|
||||||
|
elif [[ "$s_perm" == "true" ]]; then
|
||||||
echo " 🚫 Gesperrt: $s_ip → $s_domain [PERMANENT, Stufe ${s_level:-?}${count_info}${proto_tag}]${reason_tag}"
|
echo " 🚫 Gesperrt: $s_ip → $s_domain [PERMANENT, Stufe ${s_level:-?}${count_info}${proto_tag}]${reason_tag}"
|
||||||
elif [[ -n "$s_level" && "$s_level" -gt 0 ]]; then
|
elif [[ -n "$s_level" && "$s_level" -gt 0 ]]; then
|
||||||
echo " 🚫 Gesperrt: $s_ip → $s_domain [Stufe ${s_level}, $(format_duration "${s_dur:-$BAN_DURATION}"), bis $s_until${count_info}${proto_tag}]${reason_tag}"
|
echo " 🚫 Gesperrt: $s_ip → $s_domain [Stufe ${s_level}, $(format_duration "${s_dur:-$BAN_DURATION}"), bis $s_until${count_info}${proto_tag}]${reason_tag}"
|
||||||
@@ -1094,6 +1249,105 @@ stop_blocklist_worker() {
|
|||||||
fi
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── GeoIP-Worker starten ────────────────────────────────────────────────────
|
||||||
|
start_geoip_worker() {
|
||||||
|
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
||||||
|
log "DEBUG" "GeoIP-Worker ist deaktiviert"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local worker_script="${SCRIPT_DIR}/geoip-worker.sh"
|
||||||
|
if [[ ! -f "$worker_script" ]]; then
|
||||||
|
log "WARN" "GeoIP-Worker Script nicht gefunden: $worker_script"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "INFO" "Starte GeoIP-Worker im Hintergrund..."
|
||||||
|
bash "$worker_script" start &
|
||||||
|
GEOIP_WORKER_PID=$!
|
||||||
|
log "INFO" "GeoIP-Worker gestartet (PID: $GEOIP_WORKER_PID)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── GeoIP-Worker stoppen ────────────────────────────────────────────────────
|
||||||
|
stop_geoip_worker() {
|
||||||
|
local worker_pid_file="/var/run/adguard-geoip-worker.pid"
|
||||||
|
if [[ -f "$worker_pid_file" ]]; then
|
||||||
|
local wpid
|
||||||
|
wpid=$(cat "$worker_pid_file")
|
||||||
|
if kill -0 "$wpid" 2>/dev/null; then
|
||||||
|
log "INFO" "Stoppe GeoIP-Worker (PID: $wpid)..."
|
||||||
|
kill "$wpid" 2>/dev/null || true
|
||||||
|
rm -f "$worker_pid_file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Offense-Cleanup-Worker starten ──────────────────────────────────────────
|
||||||
|
start_offense_cleanup_worker() {
|
||||||
|
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" != "true" ]]; then
|
||||||
|
log "DEBUG" "Offense-Cleanup-Worker ist deaktiviert (Progressive Sperren inaktiv)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local worker_script="${SCRIPT_DIR}/offense-cleanup-worker.sh"
|
||||||
|
if [[ ! -f "$worker_script" ]]; then
|
||||||
|
log "WARN" "Offense-Cleanup-Worker Script nicht gefunden: $worker_script"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "INFO" "Starte Offense-Cleanup-Worker im Hintergrund (nice 19, idle I/O)..."
|
||||||
|
nice -n 19 ionice -c 3 bash "$worker_script" start &
|
||||||
|
OFFENSE_CLEANUP_WORKER_PID=$!
|
||||||
|
log "INFO" "Offense-Cleanup-Worker gestartet (PID: $OFFENSE_CLEANUP_WORKER_PID)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Offense-Cleanup-Worker stoppen ──────────────────────────────────────────
|
||||||
|
stop_offense_cleanup_worker() {
|
||||||
|
local worker_pid_file="/var/run/adguard-offense-cleanup-worker.pid"
|
||||||
|
if [[ -f "$worker_pid_file" ]]; then
|
||||||
|
local wpid
|
||||||
|
wpid=$(cat "$worker_pid_file")
|
||||||
|
if kill -0 "$wpid" 2>/dev/null; then
|
||||||
|
log "INFO" "Stoppe Offense-Cleanup-Worker (PID: $wpid)..."
|
||||||
|
kill "$wpid" 2>/dev/null || true
|
||||||
|
rm -f "$worker_pid_file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ─── Hauptschleife ──────────────────────────────────────────────────────────
|
# ─── Hauptschleife ──────────────────────────────────────────────────────────
|
||||||
main_loop() {
|
main_loop() {
|
||||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||||
@@ -1104,6 +1358,7 @@ main_loop() {
|
|||||||
log "INFO" " Dry-Run: ${DRY_RUN}"
|
log "INFO" " Dry-Run: ${DRY_RUN}"
|
||||||
log "INFO" " Whitelist: ${WHITELIST}"
|
log "INFO" " Whitelist: ${WHITELIST}"
|
||||||
log "INFO" " Externe Blocklist: ${EXTERNAL_BLOCKLIST_ENABLED:-false}"
|
log "INFO" " Externe Blocklist: ${EXTERNAL_BLOCKLIST_ENABLED:-false}"
|
||||||
|
log "INFO" " Externe Whitelist: ${EXTERNAL_WHITELIST_ENABLED:-false}"
|
||||||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then
|
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}"))"
|
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
|
else
|
||||||
@@ -1114,11 +1369,24 @@ main_loop() {
|
|||||||
else
|
else
|
||||||
log "INFO" " Subdomain-Flood-Schutz: deaktiviert"
|
log "INFO" " Subdomain-Flood-Schutz: deaktiviert"
|
||||||
fi
|
fi
|
||||||
|
if [[ "${DNS_FLOOD_WATCHLIST_ENABLED:-false}" == "true" ]]; then
|
||||||
|
log "INFO" " DNS-Flood-Watchlist: AKTIV (Domains: ${DNS_FLOOD_WATCHLIST:-<keine>})"
|
||||||
|
else
|
||||||
|
log "INFO" " DNS-Flood-Watchlist: deaktiviert"
|
||||||
|
fi
|
||||||
if [[ "${ABUSEIPDB_ENABLED:-false}" == "true" ]]; then
|
if [[ "${ABUSEIPDB_ENABLED:-false}" == "true" ]]; then
|
||||||
log "INFO" " AbuseIPDB Reporting: AKTIV (Kategorien: ${ABUSEIPDB_CATEGORIES:-4})"
|
log "INFO" " AbuseIPDB Reporting: AKTIV (Kategorien: ${ABUSEIPDB_CATEGORIES:-4})"
|
||||||
else
|
else
|
||||||
log "INFO" " AbuseIPDB Reporting: deaktiviert"
|
log "INFO" " AbuseIPDB Reporting: deaktiviert"
|
||||||
fi
|
fi
|
||||||
|
if [[ "${GEOIP_ENABLED:-false}" == "true" ]]; then
|
||||||
|
log "INFO" " GeoIP-Filter: AKTIV (Modus: ${GEOIP_MODE:-blocklist}, Länder: ${GEOIP_COUNTRIES:-<keine>})"
|
||||||
|
else
|
||||||
|
log "INFO" " GeoIP-Filter: deaktiviert"
|
||||||
|
fi
|
||||||
|
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then
|
||||||
|
log "INFO" " Offense-Cleanup: AKTIV (Reset: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}"), Prüfintervall: 1h)"
|
||||||
|
fi
|
||||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||||
|
|
||||||
# Service-Start-Benachrichtigung senden
|
# Service-Start-Benachrichtigung senden
|
||||||
@@ -1129,6 +1397,15 @@ main_loop() {
|
|||||||
# Blocklist-Worker als Hintergrundprozess starten
|
# Blocklist-Worker als Hintergrundprozess starten
|
||||||
start_blocklist_worker
|
start_blocklist_worker
|
||||||
|
|
||||||
|
# Whitelist-Worker als Hintergrundprozess starten
|
||||||
|
start_whitelist_worker
|
||||||
|
|
||||||
|
# GeoIP-Worker als Hintergrundprozess starten
|
||||||
|
start_geoip_worker
|
||||||
|
|
||||||
|
# Offense-Cleanup-Worker als Hintergrundprozess starten
|
||||||
|
start_offense_cleanup_worker
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
# Abgelaufene Sperren prüfen
|
# Abgelaufene Sperren prüfen
|
||||||
check_expired_bans
|
check_expired_bans
|
||||||
@@ -1195,6 +1472,74 @@ case "${1:-start}" in
|
|||||||
echo "Blocklist-Worker nicht gefunden"
|
echo "Blocklist-Worker nicht gefunden"
|
||||||
fi
|
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
|
||||||
|
;;
|
||||||
|
geoip-status)
|
||||||
|
init_directories
|
||||||
|
_worker_script="${SCRIPT_DIR}/geoip-worker.sh"
|
||||||
|
if [[ -f "$_worker_script" ]]; then
|
||||||
|
bash "$_worker_script" status
|
||||||
|
else
|
||||||
|
echo "GeoIP-Worker nicht gefunden"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
geoip-sync)
|
||||||
|
init_directories
|
||||||
|
setup_iptables_chain
|
||||||
|
_worker_script="${SCRIPT_DIR}/geoip-worker.sh"
|
||||||
|
if [[ -f "$_worker_script" ]]; then
|
||||||
|
bash "$_worker_script" sync
|
||||||
|
else
|
||||||
|
echo "GeoIP-Worker nicht gefunden"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
geoip-flush)
|
||||||
|
init_directories
|
||||||
|
_worker_script="${SCRIPT_DIR}/geoip-worker.sh"
|
||||||
|
if [[ -f "$_worker_script" ]]; then
|
||||||
|
bash "$_worker_script" flush
|
||||||
|
else
|
||||||
|
echo "GeoIP-Worker nicht gefunden"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
geoip-lookup)
|
||||||
|
if [[ -z "${2:-}" ]]; then
|
||||||
|
echo "Nutzung: $0 geoip-lookup <IP-Adresse>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
init_directories
|
||||||
|
_worker_script="${SCRIPT_DIR}/geoip-worker.sh"
|
||||||
|
if [[ -f "$_worker_script" ]]; then
|
||||||
|
bash "$_worker_script" lookup "$2"
|
||||||
|
else
|
||||||
|
echo "GeoIP-Worker nicht gefunden"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
status)
|
status)
|
||||||
init_directories
|
init_directories
|
||||||
show_status
|
show_status
|
||||||
@@ -1258,7 +1603,7 @@ Service-Steuerung (empfohlen):
|
|||||||
sudo systemctl restart adguard-shield
|
sudo systemctl restart adguard-shield
|
||||||
sudo systemctl status 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|geoip-status|geoip-sync|geoip-flush|geoip-lookup}
|
||||||
|
|
||||||
Verwaltungsbefehle:
|
Verwaltungsbefehle:
|
||||||
status Zeigt aktive Sperren, Regeln und Wiederholungstäter
|
status Zeigt aktive Sperren, Regeln und Wiederholungstäter
|
||||||
@@ -1271,6 +1616,13 @@ Verwaltungsbefehle:
|
|||||||
blocklist-status Zeigt Status der externen Blocklisten
|
blocklist-status Zeigt Status der externen Blocklisten
|
||||||
blocklist-sync Einmalige Synchronisation der externen Blocklisten
|
blocklist-sync Einmalige Synchronisation der externen Blocklisten
|
||||||
blocklist-flush Entfernt alle Sperren 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
|
||||||
|
geoip-status Zeigt Status der GeoIP-Länderfilter
|
||||||
|
geoip-sync Einmalige GeoIP-Prüfung aller aktiven Clients
|
||||||
|
geoip-flush Alle GeoIP-Sperren aufheben
|
||||||
|
geoip-lookup IP GeoIP-Lookup für eine einzelne IP-Adresse
|
||||||
|
|
||||||
Interne Befehle (nicht direkt verwenden — nur über systemd):
|
Interne Befehle (nicht direkt verwenden — nur über systemd):
|
||||||
start Startet den Monitor im Vordergrund
|
start Startet den Monitor im Vordergrund
|
||||||
|
|||||||
15
docs/README.md
Normal file
15
docs/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Dokumentation
|
||||||
|
|
||||||
|
Hier findest du die vollständige Dokumentation zu AdGuard Shield.
|
||||||
|
|
||||||
|
## Inhaltsverzeichnis
|
||||||
|
|
||||||
|
| Dokument | Beschreibung |
|
||||||
|
|---|---|
|
||||||
|
| [Architektur & Funktionsweise](architektur.md) | Überblick über den Systemaufbau, Datenfluss und die internen Komponenten |
|
||||||
|
| [Befehle & Nutzung](befehle.md) | Alle verfügbaren Befehle des Installers, des Hauptskripts und des Watchdogs |
|
||||||
|
| [Konfiguration](konfiguration.md) | Beschreibung aller Konfigurationsparameter in `adguard-shield.conf` |
|
||||||
|
| [Webhook-Benachrichtigungen](benachrichtigungen.md) | Einrichtung von Push-Benachrichtigungen über Telegram, Discord, Gotify u.a. |
|
||||||
|
| [E-Mail Report](report.md) | Konfiguration des automatischen Statistik-Reports per E-Mail |
|
||||||
|
| [Update-Anleitung](update.md) | Schritt-für-Schritt-Anleitung zum Aktualisieren einer bestehenden Installation |
|
||||||
|
| [Tipps & Troubleshooting](tipps-und-troubleshooting.md) | Best Practices, häufige Probleme und deren Lösungen |
|
||||||
@@ -56,6 +56,17 @@
|
|||||||
|
|
||||||
> **Hinweis:** Die Subdomain-Flood-Erkennung hat ein eigenes Zeitfenster (`SUBDOMAIN_FLOOD_WINDOW`) und einen eigenen Schwellwert (`SUBDOMAIN_FLOOD_MAX_UNIQUE`), unabhängig von den Rate-Limit-Einstellungen.
|
> **Hinweis:** Die Subdomain-Flood-Erkennung hat ein eigenes Zeitfenster (`SUBDOMAIN_FLOOD_WINDOW`) und einen eigenen Schwellwert (`SUBDOMAIN_FLOOD_MAX_UNIQUE`), unabhängig von den Rate-Limit-Einstellungen.
|
||||||
|
|
||||||
|
### DNS-Flood-Watchlist-Sperre
|
||||||
|
|
||||||
|
1. Client `10.0.0.42` fragt `microsoft.com` 35x in 60 Sekunden an
|
||||||
|
2. Monitor erkennt: 35 > 30 (Limit überschritten)
|
||||||
|
3. Domain `microsoft.com` steht auf der DNS-Flood-Watchlist → **sofortige permanente Sperre**
|
||||||
|
4. Progressive-Ban-Stufe wird ignoriert — kein stufenweises Hochstufen
|
||||||
|
5. IP wird an AbuseIPDB gemeldet (falls aktiviert)
|
||||||
|
6. Permanente Sperre bleibt bis zur manuellen Freigabe aktiv
|
||||||
|
|
||||||
|
> **Hinweis:** Die Watchlist greift sowohl bei normalen Rate-Limit-Verstößen als auch bei Subdomain-Flood-Erkennungen. Subdomains werden automatisch erkannt: `foo.microsoft.com` matcht den Watchlist-Eintrag `microsoft.com`.
|
||||||
|
|
||||||
## iptables Strategie
|
## iptables Strategie
|
||||||
|
|
||||||
Das Tool erstellt eine eigene Chain `ADGUARD_SHIELD`:
|
Das Tool erstellt eine eigene Chain `ADGUARD_SHIELD`:
|
||||||
@@ -132,17 +143,26 @@ Das ermöglicht:
|
|||||||
├── adguard-shield.sh # Haupt-Monitor-Script
|
├── adguard-shield.sh # Haupt-Monitor-Script
|
||||||
├── adguard-shield.conf # Konfiguration (chmod 600)
|
├── adguard-shield.conf # Konfiguration (chmod 600)
|
||||||
├── adguard-shield.conf.old # Backup der Konfig nach Update
|
├── adguard-shield.conf.old # Backup der Konfig nach Update
|
||||||
|
├── adguard-shield-watchdog.sh # Watchdog Health-Check-Script
|
||||||
├── iptables-helper.sh # iptables Verwaltung
|
├── iptables-helper.sh # iptables Verwaltung
|
||||||
├── external-blocklist-worker.sh # Externer Blocklist-Worker
|
├── external-blocklist-worker.sh # Externer Blocklist-Worker
|
||||||
└── unban-expired.sh # Cron-basiertes Entsperren
|
├── external-whitelist-worker.sh # Externer Whitelist-Worker (DNS-Auflösung)
|
||||||
|
├── geoip-worker.sh # GeoIP-Länderfilter-Worker
|
||||||
|
├── offense-cleanup-worker.sh # Aufräumen abgelaufener Offense-Zähler (nice 19, idle I/O)
|
||||||
|
├── unban-expired.sh # Cron-basiertes Entsperren
|
||||||
|
└── geoip/ # Auto-Download MaxMind GeoLite2 DB (optional)
|
||||||
|
|
||||||
/etc/systemd/system/
|
/etc/systemd/system/
|
||||||
└── adguard-shield.service # systemd Service (Autostart aktiv)
|
├── adguard-shield.service # systemd Service (Autostart aktiv)
|
||||||
|
├── adguard-shield-watchdog.service # systemd Watchdog-Unit (oneshot)
|
||||||
|
└── adguard-shield-watchdog.timer # systemd Timer (alle 5 Min.)
|
||||||
|
|
||||||
/var/lib/adguard-shield/
|
/var/lib/adguard-shield/
|
||||||
├── *.ban # State-Dateien aktiver Sperren
|
├── *.ban # State-Dateien aktiver Sperren
|
||||||
├── *.offenses # Offense-Zähler (Progressive 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
|
||||||
|
└── geoip-cache/ # Cache für GeoIP-Lookups (24h)
|
||||||
|
|
||||||
/var/log/
|
/var/log/
|
||||||
├── adguard-shield.log # Laufzeit-Log
|
├── adguard-shield.log # Laufzeit-Log
|
||||||
@@ -155,8 +175,8 @@ Der Installer (`install.sh`) bietet ein interaktives Menü und folgende Funktion
|
|||||||
|
|
||||||
| Befehl | Beschreibung |
|
| Befehl | Beschreibung |
|
||||||
|--------|--------------|
|
|--------|--------------|
|
||||||
| `install` | Vollständige Neuinstallation (Abhängigkeiten, Dateien, Konfiguration, Service) |
|
| `install` | Vollständige Neuinstallation (Abhängigkeiten, Dateien, Konfiguration, Service, Watchdog) |
|
||||||
| `update` | Update mit automatischer Konfigurations-Migration und Service-Neustart |
|
| `update` | Update mit automatischer Konfigurations-Migration, Watchdog-Aktivierung und Service-Neustart |
|
||||||
| `uninstall` | Deinstallation mit optionalem Behalten der Konfiguration |
|
| `uninstall` | Deinstallation mit optionalem Behalten der Konfiguration |
|
||||||
| `status` | Installationsstatus, Version und Service-Status anzeigen |
|
| `status` | Installationsstatus, Version und Service-Status anzeigen |
|
||||||
| `--help` | Hilfe und Befehlsübersicht |
|
| `--help` | Hilfe und Befehlsübersicht |
|
||||||
@@ -201,8 +221,10 @@ ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN
|
|||||||
|-------|----------|
|
|-------|----------|
|
||||||
| `rate-limit` | Automatische Sperre wegen Limit-Überschreitung |
|
| `rate-limit` | Automatische Sperre wegen Limit-Überschreitung |
|
||||||
| `subdomain-flood` | Sperre wegen zu vieler eindeutiger Subdomains einer Basisdomain |
|
| `subdomain-flood` | Sperre wegen zu vieler eindeutiger Subdomains einer Basisdomain |
|
||||||
|
| `dns-flood-watchlist` | Sofortige permanente Sperre + AbuseIPDB-Meldung (Domain auf der Watchlist) |
|
||||||
| `dry-run` | Im Dry-Run erkannt (nicht wirklich gesperrt) |
|
| `dry-run` | Im Dry-Run erkannt (nicht wirklich gesperrt) |
|
||||||
| `dry-run (subdomain-flood)` | Subdomain-Flood im Dry-Run erkannt |
|
| `dry-run (subdomain-flood)` | Subdomain-Flood im Dry-Run erkannt |
|
||||||
|
| `dry-run (dns-flood-watchlist)` | Watchlist-Treffer im Dry-Run erkannt |
|
||||||
| `expired` | Automatisch entsperrt nach Ablauf der Sperrdauer |
|
| `expired` | Automatisch entsperrt nach Ablauf der Sperrdauer |
|
||||||
| `expired-cron` | Entsperrt durch den Cron-Job (`unban-expired.sh`) |
|
| `expired-cron` | Entsperrt durch den Cron-Job (`unban-expired.sh`) |
|
||||||
| `manual` | Manuell entsperrt per `unban`-Befehl |
|
| `manual` | Manuell entsperrt per `unban`-Befehl |
|
||||||
|
|||||||
127
docs/befehle.md
127
docs/befehle.md
@@ -42,8 +42,9 @@ Beim Update passiert automatisch:
|
|||||||
2. Die bestehende Konfiguration wird als `adguard-shield.conf.old` gesichert
|
2. Die bestehende Konfiguration wird als `adguard-shield.conf.old` gesichert
|
||||||
3. Neue Konfigurationsparameter werden automatisch zur bestehenden Konfig hinzugefügt
|
3. Neue Konfigurationsparameter werden automatisch zur bestehenden Konfig hinzugefügt
|
||||||
4. Bestehende Einstellungen bleiben **immer** erhalten
|
4. Bestehende Einstellungen bleiben **immer** erhalten
|
||||||
5. Der systemd Service wird per `daemon-reload` neu geladen
|
5. Der systemd Service und Watchdog-Timer werden per `daemon-reload` neu geladen
|
||||||
6. Der Service wird automatisch neu gestartet (falls er lief)
|
6. Der Watchdog-Timer wird automatisch aktiviert (falls noch nicht aktiv)
|
||||||
|
7. Der Service wird automatisch neu gestartet (falls er lief)
|
||||||
|
|
||||||
### API-Verbindungstest nach Installation
|
### API-Verbindungstest nach Installation
|
||||||
|
|
||||||
@@ -86,6 +87,31 @@ sudo systemctl disable adguard-shield
|
|||||||
|
|
||||||
> **Hinweis:** Der Service wird bei der Installation automatisch für den Autostart beim Booten aktiviert. Nach einem Update wird der Service automatisch neu gestartet — ein manueller Neustart ist nicht nötig.
|
> **Hinweis:** Der Service wird bei der Installation automatisch für den Autostart beim Booten aktiviert. Nach einem Update wird der Service automatisch neu gestartet — ein manueller Neustart ist nicht nötig.
|
||||||
|
|
||||||
|
## Watchdog (automatischer Health Check)
|
||||||
|
|
||||||
|
Der Watchdog prüft alle 5 Minuten ob der Hauptservice läuft und startet ihn bei Bedarf automatisch neu. Er wird als systemd Timer betrieben und bei der Installation automatisch aktiviert.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Watchdog-Status
|
||||||
|
sudo systemctl status adguard-shield-watchdog.timer
|
||||||
|
|
||||||
|
# Nächste geplante Ausführung anzeigen
|
||||||
|
sudo systemctl list-timers adguard-shield-watchdog.timer
|
||||||
|
|
||||||
|
# Watchdog aktivieren / deaktivieren
|
||||||
|
sudo systemctl enable adguard-shield-watchdog.timer
|
||||||
|
sudo systemctl disable adguard-shield-watchdog.timer
|
||||||
|
|
||||||
|
# Watchdog starten / stoppen
|
||||||
|
sudo systemctl start adguard-shield-watchdog.timer
|
||||||
|
sudo systemctl stop adguard-shield-watchdog.timer
|
||||||
|
|
||||||
|
# Watchdog-Logs anzeigen
|
||||||
|
sudo journalctl -u adguard-shield-watchdog.service --no-pager -n 20
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Hinweis:** Der Watchdog sendet automatisch Benachrichtigungen (falls `NOTIFY_ENABLED=true`), wenn er den Service wiederbeleben muss oder die Recovery fehlschlägt.
|
||||||
|
|
||||||
## Monitor — Verwaltungsbefehle
|
## Monitor — Verwaltungsbefehle
|
||||||
|
|
||||||
Die folgenden Befehle dienen der **Verwaltung und Diagnose** und können jederzeit ausgeführt werden, auch während der Service läuft:
|
Die folgenden Befehle dienen der **Verwaltung und Diagnose** und können jederzeit ausgeführt werden, auch während der Service läuft:
|
||||||
@@ -158,6 +184,40 @@ sudo /opt/adguard-shield/iptables-helper.sh save
|
|||||||
sudo /opt/adguard-shield/iptables-helper.sh restore
|
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
|
## Externer Blocklist-Worker
|
||||||
|
|
||||||
Der Worker kann auch standalone gesteuert werden:
|
Der Worker kann auch standalone gesteuert werden:
|
||||||
@@ -179,6 +239,69 @@ sudo /opt/adguard-shield/external-blocklist-worker.sh status
|
|||||||
sudo /opt/adguard-shield/external-blocklist-worker.sh flush
|
sudo /opt/adguard-shield/external-blocklist-worker.sh flush
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## GeoIP-Worker (Länderfilter)
|
||||||
|
|
||||||
|
Der GeoIP-Worker prüft Client-IPs auf ihr Herkunftsland und sperrt/erlaubt sie basierend auf der Konfiguration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# GeoIP-Status anzeigen (Modus, Länder, aktive Sperren, verfügbare Tools)
|
||||||
|
sudo /opt/adguard-shield/adguard-shield.sh geoip-status
|
||||||
|
|
||||||
|
# Einmalige GeoIP-Prüfung aller aktiven Clients
|
||||||
|
sudo /opt/adguard-shield/adguard-shield.sh geoip-sync
|
||||||
|
|
||||||
|
# Alle GeoIP-Sperren aufheben
|
||||||
|
sudo /opt/adguard-shield/adguard-shield.sh geoip-flush
|
||||||
|
|
||||||
|
# GeoIP-Lookup für eine einzelne IP
|
||||||
|
sudo /opt/adguard-shield/adguard-shield.sh geoip-lookup 8.8.8.8
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Worker kann auch standalone gesteuert werden:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Worker manuell starten (normalerweise automatisch per Hauptscript)
|
||||||
|
sudo /opt/adguard-shield/geoip-worker.sh start
|
||||||
|
|
||||||
|
# Worker stoppen
|
||||||
|
sudo /opt/adguard-shield/geoip-worker.sh stop
|
||||||
|
|
||||||
|
# Einmalige Synchronisation
|
||||||
|
sudo /opt/adguard-shield/geoip-worker.sh sync
|
||||||
|
|
||||||
|
# Status anzeigen
|
||||||
|
sudo /opt/adguard-shield/geoip-worker.sh status
|
||||||
|
|
||||||
|
# IP nachschlagen
|
||||||
|
sudo /opt/adguard-shield/geoip-worker.sh lookup 1.2.3.4
|
||||||
|
|
||||||
|
# Alle GeoIP-Sperren aufheben
|
||||||
|
sudo /opt/adguard-shield/geoip-worker.sh flush
|
||||||
|
|
||||||
|
# GeoIP-Lookup-Cache leeren
|
||||||
|
sudo /opt/adguard-shield/geoip-worker.sh flush-cache
|
||||||
|
```
|
||||||
|
|
||||||
|
## Offense-Cleanup-Worker
|
||||||
|
|
||||||
|
Der Offense-Cleanup-Worker räumt abgelaufene Offense-Zähler (progressive Sperren) automatisch auf. Er startet automatisch mit dem Hauptservice, wenn progressive Sperren aktiviert sind, und prüft stündlich ob Zähler aufgeräumt werden können. Der Worker läuft mit niedrigster CPU- und I/O-Priorität (`nice 19`, `ionice idle`), um den DNS-Dienst nicht zu beeinträchtigen.
|
||||||
|
|
||||||
|
Der Worker kann auch standalone gesteuert werden:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Worker manuell starten (normalerweise automatisch per Hauptscript)
|
||||||
|
sudo /opt/adguard-shield/offense-cleanup-worker.sh start
|
||||||
|
|
||||||
|
# Worker stoppen
|
||||||
|
sudo /opt/adguard-shield/offense-cleanup-worker.sh stop
|
||||||
|
|
||||||
|
# Einmaliger Cleanup-Durchlauf
|
||||||
|
sudo /opt/adguard-shield/offense-cleanup-worker.sh run-once
|
||||||
|
|
||||||
|
# Status anzeigen (aktive/abgelaufene Zähler)
|
||||||
|
sudo /opt/adguard-shield/offense-cleanup-worker.sh status
|
||||||
|
```
|
||||||
|
|
||||||
## E-Mail Report
|
## E-Mail Report
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ Sendet einen POST mit JSON-Body:
|
|||||||
|
|
||||||
```json
|
```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",
|
"action": "ban",
|
||||||
"client": "192.168.1.50",
|
"client": "192.168.1.50",
|
||||||
"domain": "microsoft.com"
|
"domain": "microsoft.com"
|
||||||
@@ -113,14 +113,109 @@ Bei Sperren aus der **externen Blocklist** werden Benachrichtigungen separat üb
|
|||||||
|
|
||||||
## Beispiel-Nachrichten
|
## Beispiel-Nachrichten
|
||||||
|
|
||||||
**Service gestartet:**
|
### Service gestartet
|
||||||
> 🟢 AdGuard Shield v0.4.0 wurde gestartet.
|
**Überschrift:** ✅ AdGuard Shield
|
||||||
|
|
||||||
**Service gestoppt:**
|
> 🟢 AdGuard Shield v0.9.0 wurde auf dns1 gestartet.
|
||||||
> 🔴 AdGuard Shield v0.4.0 wurde gestoppt.
|
|
||||||
|
|
||||||
**Sperre:**
|
### Service gestoppt
|
||||||
> 🚫 AdGuard Shield: Client **192.168.1.50** gesperrt (45x microsoft.com in 60s). Sperre für 3600s.
|
**Überschrift:** 🚨 🛡️ AdGuard Shield
|
||||||
|
|
||||||
**Entsperrung:**
|
> 🔴 AdGuard Shield v0.9.0 wurde auf dns1 gestoppt.
|
||||||
> ✅ AdGuard Shield: Client **192.168.1.50** wurde entsperrt.
|
|
||||||
|
### Watchdog — Service wiederhergestellt
|
||||||
|
**Überschrift:** 🔄 AdGuard Shield Watchdog
|
||||||
|
|
||||||
|
> 🔄 AdGuard Shield Watchdog auf dns1
|
||||||
|
> ---
|
||||||
|
> Der Service war ausgefallen und wurde automatisch neu gestartet.
|
||||||
|
> Versuch: 1
|
||||||
|
|
||||||
|
### Watchdog — Recovery fehlgeschlagen
|
||||||
|
**Überschrift:** 🚨 AdGuard Shield Watchdog
|
||||||
|
|
||||||
|
> 🚨 AdGuard Shield Watchdog auf dns1
|
||||||
|
> ---
|
||||||
|
> Der Service konnte NICHT automatisch neu gestartet werden!
|
||||||
|
> Manuelles Eingreifen erforderlich.
|
||||||
|
> Fehlversuche: 1
|
||||||
|
> Letzter Fehler: (systemd Statusausgabe)
|
||||||
|
|
||||||
|
### Sperre (Ban)
|
||||||
|
**Überschrift:** 🚨 🛡️ AdGuard Shield
|
||||||
|
|
||||||
|
> 🚫 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
|
||||||
|
|
||||||
|
Bei DNS-Flood-Watchlist-Treffer (sofort permanent, ohne Stufe):
|
||||||
|
|
||||||
|
> 🚫 AdGuard Shield Ban auf dns1
|
||||||
|
> ⚠️ IP wurde an AbuseIPDB gemeldet
|
||||||
|
> ---
|
||||||
|
> IP: 95.71.42.116
|
||||||
|
> Hostname: example-host.provider.net
|
||||||
|
> Grund: 45x microsoft.com in 60s via DNS, DNS-Flood-Watchlist
|
||||||
|
> Dauer: PERMANENT
|
||||||
|
>
|
||||||
|
> 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.
|
||||||
|
|||||||
@@ -70,6 +70,34 @@ xk9z3a.microsoft.com
|
|||||||
|
|
||||||
> **Tipp:** Der Schwellwert `SUBDOMAIN_FLOOD_MAX_UNIQUE` sollte hoch genug sein, um legitime Clients nicht zu stören (z.B. CDNs nutzen oft viele Subdomains). Ein Wert von 50–100 ist in den meisten Fällen sinnvoll.
|
> **Tipp:** Der Schwellwert `SUBDOMAIN_FLOOD_MAX_UNIQUE` sollte hoch genug sein, um legitime Clients nicht zu stören (z.B. CDNs nutzen oft viele Subdomains). Ein Wert von 50–100 ist in den meisten Fällen sinnvoll.
|
||||||
|
|
||||||
|
### DNS-Flood-Watchlist
|
||||||
|
|
||||||
|
Domains bei denen eine Rate-Limit-Überschreitung **sofort** zu einer **permanenten Sperre** und einer **AbuseIPDB-Meldung** führt — ohne progressive Eskalation. Ideal für bekannte Angriffsziele, die regelmäßig geflutet werden (z.B. `microsoft.com`, `google.com`).
|
||||||
|
|
||||||
|
| Parameter | Standard | Beschreibung |
|
||||||
|
|-----------|----------|--------------|
|
||||||
|
| `DNS_FLOOD_WATCHLIST_ENABLED` | `false` | DNS-Flood-Watchlist aktivieren |
|
||||||
|
| `DNS_FLOOD_WATCHLIST` | *(leer)* | Überwachte Domains, kommagetrennt (z.B. `"microsoft.com,google.com"`) |
|
||||||
|
|
||||||
|
#### Wie funktioniert die Watchlist?
|
||||||
|
|
||||||
|
1. Die reguläre Rate-Limit-Prüfung erkennt, dass ein Client mehr als `RATE_LIMIT_MAX_REQUESTS` Anfragen für eine Domain gestellt hat
|
||||||
|
2. Zusätzlich wird geprüft, ob die angefragte Domain in der Watchlist steht (inkl. Subdomains: `foo.microsoft.com` matcht `microsoft.com`)
|
||||||
|
3. Trifft beides zu → **sofortige permanente Sperre** + **AbuseIPDB-Meldung** (falls aktiviert)
|
||||||
|
|
||||||
|
Die Watchlist greift sowohl bei normalen Rate-Limit-Verstößen als auch bei Subdomain-Flood-Erkennungen.
|
||||||
|
|
||||||
|
#### Beispiel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DNS_FLOOD_WATCHLIST_ENABLED=true
|
||||||
|
DNS_FLOOD_WATCHLIST="microsoft.com,google.com,apple.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
→ Ein Client der `35x foo.microsoft.com` in 60s abfragt (bei `RATE_LIMIT_MAX_REQUESTS=30`) wird **sofort permanent** gesperrt und an AbuseIPDB gemeldet.
|
||||||
|
|
||||||
|
> **Hinweis:** Damit die AbuseIPDB-Meldung funktioniert, muss `ABUSEIPDB_ENABLED=true` und ein gültiger `ABUSEIPDB_API_KEY` konfiguriert sein. Ohne AbuseIPDB-Konfiguration wird nur permanent gesperrt.
|
||||||
|
|
||||||
### Sperr-Einstellungen
|
### Sperr-Einstellungen
|
||||||
|
|
||||||
| Parameter | Standard | Beschreibung |
|
| Parameter | Standard | Beschreibung |
|
||||||
@@ -100,7 +128,7 @@ Wiederholungstäter werden wie bei fail2ban stufenweise länger gesperrt. Wird e
|
|||||||
| 4. Mal | 4 | 8 Stunden | 3600 × 8 |
|
| 4. Mal | 4 | 8 Stunden | 3600 × 8 |
|
||||||
| 5. Mal | 5 | **PERMANENT** | Max-Stufe erreicht |
|
| 5. Mal | 5 | **PERMANENT** | Max-Stufe erreicht |
|
||||||
|
|
||||||
> **Hinweis:** Der Offense-Zähler wird automatisch zurückgesetzt, wenn eine IP für den konfigurierten Zeitraum (`PROGRESSIVE_BAN_RESET_AFTER`) kein erneutes Vergehen begeht. Permanente Sperren werden **nicht** automatisch aufgehoben – sie müssen manuell mit `unban` oder `flush` entfernt werden.
|
> **Hinweis:** Abgelaufene Offense-Zähler werden automatisch vom **Offense-Cleanup-Worker** aufgeräumt, der stündlich prüft, ob das letzte Vergehen einer IP länger als `PROGRESSIVE_BAN_RESET_AFTER` zurückliegt. Der Worker startet automatisch zusammen mit dem Hauptservice, wenn progressive Sperren aktiviert sind. Er läuft mit niedrigster CPU- und I/O-Priorität (`nice 19`, `ionice idle`), sodass andere Dienste nicht beeinträchtigt werden. Manuelles Zurücksetzen ist jederzeit mit `reset-offenses` möglich. Permanente Sperren werden **nicht** automatisch aufgehoben – sie müssen manuell mit `unban` oder `flush` entfernt werden.
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
|
|
||||||
@@ -139,6 +167,7 @@ Regelmäßige Statistik-Reports per E-Mail. Voraussetzung ist ein funktionierend
|
|||||||
| `REPORT_EMAIL_FROM` | `adguard-shield@hostname` | E-Mail-Absender |
|
| `REPORT_EMAIL_FROM` | `adguard-shield@hostname` | E-Mail-Absender |
|
||||||
| `REPORT_FORMAT` | `html` | Format: `html` oder `txt` |
|
| `REPORT_FORMAT` | `html` | Format: `html` oder `txt` |
|
||||||
| `REPORT_MAIL_CMD` | `msmtp` | Mail-Befehl (`msmtp`, `sendmail`, `mail`) |
|
| `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.
|
> Siehe [E-Mail Report Dokumentation](report.md) für Details zu Inhalten, Templates und Befehlen.
|
||||||
|
|
||||||
@@ -149,6 +178,60 @@ Regelmäßige Statistik-Reports per E-Mail. Voraussetzung ist ein funktionierend
|
|||||||
| `STATE_DIR` | `/var/lib/adguard-shield` | Verzeichnis für State-Dateien |
|
| `STATE_DIR` | `/var/lib/adguard-shield` | Verzeichnis für State-Dateien |
|
||||||
| `PID_FILE` | `/var/run/adguard-shield.pid` | PID-Datei |
|
| `PID_FILE` | `/var/run/adguard-shield.pid` | PID-Datei |
|
||||||
| `DRY_RUN` | `false` | Testmodus — nur loggen, nicht sperren |
|
| `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
|
### 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.
|
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.
|
||||||
@@ -196,10 +279,97 @@ sudo systemctl restart adguard-shield
|
|||||||
|
|
||||||
Der Report an AbuseIPDB enthält (auf Englisch):
|
Der Report an AbuseIPDB enthält (auf Englisch):
|
||||||
|
|
||||||
- **Bei Rate-Limit:** `DNS flooding on our DNS server: 100x microsoft.com in 60s. Banned by Adguard Shield 🔗 https://tnvs.de/as`
|
- **Bei Rate-Limit / DNS-Flood-Watchlist:** `DNS flooding on our DNS server: 100x microsoft.com in 60s. Banned by Adguard Shield 🔗 https://tnvs.de/as`
|
||||||
- **Bei Subdomain-Flood:** `DNS flooding on our DNS server: 85x *.microsoft.com in 60s (random subdomain attack). Banned by Adguard Shield 🔗 https://tnvs.de/as`
|
- **Bei Subdomain-Flood:** `DNS flooding on our DNS server: 85x *.microsoft.com in 60s (random subdomain attack). Banned by Adguard Shield 🔗 https://tnvs.de/as`
|
||||||
|
|
||||||
Die Kategorie `4` (DDoS Attack) wird standardmäßig verwendet. Weitere Kategorien können kommagetrennt angegeben werden (z.B. `"4,15"`).
|
Die Kategorie `4` (DDoS Attack) wird standardmäßig verwendet. Weitere Kategorien können kommagetrennt angegeben werden (z.B. `"4,15"`).
|
||||||
|
|
||||||
|
### GeoIP-basierte Länderfilter
|
||||||
|
|
||||||
|
Ermöglicht das Sperren oder Erlauben von DNS-Anfragen basierend auf dem Herkunftsland der Client-IP. Unterstützt zwei Modi:
|
||||||
|
|
||||||
|
- **Blocklist-Modus:** Nur die gelisteten Länder werden gesperrt (alle anderen erlaubt)
|
||||||
|
- **Allowlist-Modus:** Nur die gelisteten Länder werden erlaubt (alle anderen gesperrt)
|
||||||
|
|
||||||
|
| Parameter | Standard | Beschreibung |
|
||||||
|
|-----------|----------|--------------|
|
||||||
|
| `GEOIP_ENABLED` | `false` | GeoIP-Filter aktivieren |
|
||||||
|
| `GEOIP_MODE` | `blocklist` | Modus: `blocklist` oder `allowlist` |
|
||||||
|
| `GEOIP_COUNTRIES` | *(leer)* | ISO 3166-1 Alpha-2 Ländercodes (kommagetrennt), z.B. `CN,RU,KP,IR` |
|
||||||
|
| `GEOIP_CHECK_INTERVAL` | `0` | Prüfintervall in Sekunden (`0` = nutzt `CHECK_INTERVAL`) |
|
||||||
|
| `GEOIP_NOTIFY` | `true` | Benachrichtigungen bei GeoIP-Sperren senden |
|
||||||
|
| `GEOIP_SKIP_PRIVATE` | `true` | Private/lokale IPs von der GeoIP-Prüfung ausnehmen |
|
||||||
|
| `GEOIP_LICENSE_KEY` | *(leer)* | MaxMind License-Key für automatischen DB-Download (kostenlos) |
|
||||||
|
| `GEOIP_MMDB_PATH` | *(leer)* | Manueller Pfad zur MaxMind GeoLite2 Datenbank (überschreibt Auto-Download) |
|
||||||
|
|
||||||
|
#### Voraussetzungen
|
||||||
|
|
||||||
|
Es muss mindestens eines der folgenden GeoIP-Tools installiert sein:
|
||||||
|
|
||||||
|
1. **Automatischer MaxMind-Download** (empfohlen):
|
||||||
|
```bash
|
||||||
|
# Kostenlosen Account erstellen: https://www.maxmind.com/en/geolite2/signup
|
||||||
|
# License-Key generieren und in adguard-shield.conf eintragen:
|
||||||
|
GEOIP_LICENSE_KEY="dein_license_key_hier"
|
||||||
|
```
|
||||||
|
Die GeoLite2-Country-Datenbank wird automatisch heruntergeladen und alle 24 Stunden aktualisiert.
|
||||||
|
Es wird zusätzlich `mmdbinspect` oder `mmdblookup` benötigt:
|
||||||
|
```bash
|
||||||
|
sudo apt install mmdb-bin # für mmdblookup
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **geoiplookup** (einfachster Einstieg, weniger genau):
|
||||||
|
```bash
|
||||||
|
sudo apt install geoip-bin geoip-database
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Manueller MaxMind-Pfad** (eigene Datenbank):
|
||||||
|
```bash
|
||||||
|
# mmdbinspect oder mmdblookup installieren
|
||||||
|
# Datenbank manuell herunterladen: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
|
||||||
|
GEOIP_MMDB_PATH="/usr/share/GeoIP/GeoLite2-Country.mmdb"
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Priorität:** `GEOIP_MMDB_PATH` (manuell) → Auto-Download-DB → `geoiplookup` (Legacy-Fallback)
|
||||||
|
|
||||||
|
#### Beispiel: Bestimmte Länder sperren (Blocklist)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GEOIP_ENABLED=true
|
||||||
|
GEOIP_MODE="blocklist"
|
||||||
|
GEOIP_COUNTRIES="CN,RU,KP,IR"
|
||||||
|
GEOIP_LICENSE_KEY="dein_maxmind_license_key" # optional, für Auto-Download
|
||||||
|
```
|
||||||
|
|
||||||
|
→ Alle Anfragen aus China, Russland, Nordkorea und Iran werden permanent gesperrt.
|
||||||
|
|
||||||
|
#### Beispiel: Nur bestimmte Länder erlauben (Allowlist)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GEOIP_ENABLED=true
|
||||||
|
GEOIP_MODE="allowlist"
|
||||||
|
GEOIP_COUNTRIES="DE,AT,CH"
|
||||||
|
```
|
||||||
|
|
||||||
|
→ Nur Anfragen aus Deutschland, Österreich und der Schweiz werden erlaubt. Alle anderen Länder werden gesperrt.
|
||||||
|
|
||||||
|
> **Hinweis:** Private IP-Adressen (10.x.x.x, 192.168.x.x, etc.) und Whitelist-IPs werden niemals durch GeoIP gesperrt. GeoIP-Sperren sind **immer permanent**.
|
||||||
|
|
||||||
|
> **Auto-Unban:** Wird ein Land aus `GEOIP_COUNTRIES` entfernt oder der Modus (`GEOIP_MODE`) geändert, werden die nicht mehr zutreffenden Sperren beim nächsten Sync **automatisch aufgehoben**. Dasselbe gilt, wenn GeoIP komplett deaktiviert wird (`GEOIP_ENABLED=false`).
|
||||||
|
|
||||||
|
> **Tipp:** GeoIP-Lookups werden für 24 Stunden gecacht. Mit `geoip-flush-cache` kann der Cache manuell geleert werden.
|
||||||
|
|
||||||
|
> **Auto-Download:** Ist `GEOIP_LICENSE_KEY` gesetzt, wird die GeoLite2-Country-Datenbank automatisch nach `<INSTALL_DIR>/geoip/` heruntergeladen und alle 24 Stunden aktualisiert. Bei einem Update wird der Download im Hintergrund durchgeführt — der Worker läuft während des Downloads normal weiter. Ein manuell gesetzter `GEOIP_MMDB_PATH` hat immer Vorrang vor der automatisch heruntergeladenen Datenbank.
|
||||||
|
|
||||||
|
#### GeoIP-Befehle
|
||||||
|
|
||||||
|
| Befehl | Beschreibung |
|
||||||
|
|--------|--------------|
|
||||||
|
| `adguard-shield.sh geoip-status` | Zeigt GeoIP-Status, aktive Sperren und verfügbare Tools |
|
||||||
|
| `adguard-shield.sh geoip-sync` | Einmalige GeoIP-Prüfung aller aktiven Clients |
|
||||||
|
| `adguard-shield.sh geoip-flush` | Alle GeoIP-Sperren aufheben |
|
||||||
|
| `adguard-shield.sh geoip-lookup <IP>` | GeoIP-Lookup einer einzelnen IP-Adresse |
|
||||||
|
|
||||||
#### Externe Blocklist einrichten
|
#### Externe Blocklist einrichten
|
||||||
|
|
||||||
1. Erstelle eine Textdatei auf einem Webserver. Pro Zeile ein Eintrag — IPv4, IPv6, CIDR oder Hostname:
|
1. Erstelle eine Textdatei auf einem Webserver. Pro Zeile ein Eintrag — IPv4, IPv6, CIDR oder Hostname:
|
||||||
@@ -338,3 +508,7 @@ Beispiel:
|
|||||||
```
|
```
|
||||||
WHITELIST="127.0.0.1,::1,192.168.1.1,192.168.1.10,fd00::1"
|
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_EMAIL_FROM` | `adguard-shield@hostname` | E-Mail-Absender |
|
||||||
| `REPORT_FORMAT` | `html` | Format: `html` oder `txt` |
|
| `REPORT_FORMAT` | `html` | Format: `html` oder `txt` |
|
||||||
| `REPORT_MAIL_CMD` | `msmtp` | Mail-Befehl (`msmtp`, `sendmail`, `mail`) |
|
| `REPORT_MAIL_CMD` | `msmtp` | Mail-Befehl (`msmtp`, `sendmail`, `mail`) |
|
||||||
|
| `REPORT_BUSIEST_DAY_RANGE` | `30` | Zeitraum in Tagen für „Aktivster Tag“ (0 = Berichtszeitraum) |
|
||||||
|
|
||||||
### Versandintervalle
|
### Versandintervalle
|
||||||
|
|
||||||
@@ -56,7 +57,26 @@ sudo /opt/adguard-shield/report-generator.sh install
|
|||||||
|
|
||||||
Der Report enthält folgende Statistiken:
|
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
|
- Gesamtzahl der Sperren und Entsperrungen
|
||||||
- Anzahl eindeutiger gesperrter IPs
|
- Anzahl eindeutiger gesperrter IPs
|
||||||
- Permanente Sperren
|
- Permanente Sperren
|
||||||
@@ -67,7 +87,7 @@ Der Report enthält folgende Statistiken:
|
|||||||
- Rate-Limit Sperren
|
- Rate-Limit Sperren
|
||||||
- Subdomain-Flood Sperren
|
- Subdomain-Flood Sperren
|
||||||
- Externe Blocklist 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
|
### Top 10 Listen
|
||||||
- **Auffälligste IPs** — Die 10 IPs mit den meisten Sperren (mit Balkendiagramm im HTML-Format)
|
- **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 |
|
| `{{RATELIMIT_BANS}}` | Rate-Limit Sperren |
|
||||||
| `{{SUBDOMAIN_FLOOD_BANS}}` | Subdomain-Flood Sperren |
|
| `{{SUBDOMAIN_FLOOD_BANS}}` | Subdomain-Flood Sperren |
|
||||||
| `{{EXTERNAL_BLOCKLIST_BANS}}` | Externe Blocklist 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_TABLE}}` | Top 10 IPs (HTML-Tabelle) |
|
||||||
| `{{TOP10_IPS_TEXT}}` | Top 10 IPs (Text-Tabelle) |
|
| `{{TOP10_IPS_TEXT}}` | Top 10 IPs (Text-Tabelle) |
|
||||||
| `{{TOP10_DOMAINS_TABLE}}` | Top 10 Domains (HTML-Tabelle) |
|
| `{{TOP10_DOMAINS_TABLE}}` | Top 10 Domains (HTML-Tabelle) |
|
||||||
|
|||||||
@@ -193,6 +193,37 @@ sudo rm -f /var/run/adguard-shield.pid
|
|||||||
sudo systemctl start adguard-shield
|
sudo systemctl start adguard-shield
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Service ist ausgefallen und startet nicht mehr
|
||||||
|
|
||||||
|
Wenn systemd das Restart-Limit erreicht hat (z.B. `"Start request repeated too quickly"`), hilft der **Watchdog** — er prüft alle 5 Minuten ob der Service läuft und startet ihn automatisch neu.
|
||||||
|
|
||||||
|
**Watchdog-Status prüfen:**
|
||||||
|
```bash
|
||||||
|
# Timer-Status anzeigen
|
||||||
|
sudo systemctl status adguard-shield-watchdog.timer
|
||||||
|
|
||||||
|
# Letzte Watchdog-Ausführungen anzeigen
|
||||||
|
sudo systemctl list-timers adguard-shield-watchdog.timer
|
||||||
|
|
||||||
|
# Watchdog-Logs prüfen
|
||||||
|
sudo journalctl -u adguard-shield-watchdog.service --no-pager -n 20
|
||||||
|
```
|
||||||
|
|
||||||
|
**Manuelles Recovery (sofort):**
|
||||||
|
```bash
|
||||||
|
# systemd-Fehlerzähler zurücksetzen und Service starten
|
||||||
|
sudo systemctl reset-failed adguard-shield.service
|
||||||
|
sudo systemctl start adguard-shield.service
|
||||||
|
```
|
||||||
|
|
||||||
|
**Watchdog nachträglich aktivieren:**
|
||||||
|
```bash
|
||||||
|
sudo systemctl enable adguard-shield-watchdog.timer
|
||||||
|
sudo systemctl start adguard-shield-watchdog.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Hinweis:** Der Watchdog sendet automatisch eine Benachrichtigung (falls `NOTIFY_ENABLED=true`), wenn er den Service wiederbeleben muss oder die Recovery fehlschlägt.
|
||||||
|
|
||||||
## Update durchführen
|
## Update durchführen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -229,9 +260,13 @@ Oder manuell:
|
|||||||
```bash
|
```bash
|
||||||
sudo systemctl stop adguard-shield
|
sudo systemctl stop adguard-shield
|
||||||
sudo systemctl disable adguard-shield
|
sudo systemctl disable adguard-shield
|
||||||
|
sudo systemctl stop adguard-shield-watchdog.timer
|
||||||
|
sudo systemctl disable adguard-shield-watchdog.timer
|
||||||
sudo /opt/adguard-shield/iptables-helper.sh remove
|
sudo /opt/adguard-shield/iptables-helper.sh remove
|
||||||
sudo rm -rf /opt/adguard-shield
|
sudo rm -rf /opt/adguard-shield
|
||||||
sudo rm -f /etc/systemd/system/adguard-shield.service
|
sudo rm -f /etc/systemd/system/adguard-shield.service
|
||||||
|
sudo rm -f /etc/systemd/system/adguard-shield-watchdog.service
|
||||||
|
sudo rm -f /etc/systemd/system/adguard-shield-watchdog.timer
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,9 @@ Das Update-Script macht automatisch folgendes:
|
|||||||
2. **Scripts aktualisieren** — Alle `.sh`-Dateien werden nach `/opt/adguard-shield/` kopiert
|
2. **Scripts aktualisieren** — Alle `.sh`-Dateien werden nach `/opt/adguard-shield/` kopiert
|
||||||
3. **Konfigurations-Migration** — Neue Parameter werden automatisch zur bestehenden Konfiguration hinzugefügt, bestehende Einstellungen bleiben **unverändert**
|
3. **Konfigurations-Migration** — Neue Parameter werden automatisch zur bestehenden Konfiguration hinzugefügt, bestehende Einstellungen bleiben **unverändert**
|
||||||
4. **Backup erstellen** — Die alte Konfiguration wird als `adguard-shield.conf.old` gesichert
|
4. **Backup erstellen** — Die alte Konfiguration wird als `adguard-shield.conf.old` gesichert
|
||||||
5. **Service aktualisieren** — Die systemd Service-Datei wird aktualisiert und `daemon-reload` ausgeführt
|
5. **Service aktualisieren** — Die systemd Service-Datei und Watchdog-Dateien werden aktualisiert und `daemon-reload` ausgeführt
|
||||||
6. **Service neustarten** — Der Service wird automatisch neu gestartet (falls er vorher lief)
|
6. **Watchdog aktivieren** — Der Watchdog-Timer wird automatisch aktiviert (falls noch nicht aktiv)
|
||||||
|
7. **Service neustarten** — Der Service wird automatisch neu gestartet (falls er vorher lief)
|
||||||
|
|
||||||
### 3. Neue Parameter prüfen (optional)
|
### 3. Neue Parameter prüfen (optional)
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,13 @@ is_whitelisted() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
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
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +210,27 @@ unban_ip() {
|
|||||||
fi
|
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 ────────────────────────────────────────────────────────
|
# ─── Benachrichtigung ────────────────────────────────────────────────────────
|
||||||
send_notification() {
|
send_notification() {
|
||||||
local action="$1"
|
local action="$1"
|
||||||
@@ -213,45 +241,79 @@ send_notification() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
local title
|
||||||
local message
|
local message
|
||||||
|
local my_hostname
|
||||||
|
my_hostname=$(hostname)
|
||||||
|
local client_hostname
|
||||||
|
client_hostname=$(resolve_hostname "$ip")
|
||||||
|
|
||||||
if [[ "$action" == "ban" ]]; then
|
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
|
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
|
fi
|
||||||
|
|
||||||
case "${NOTIFY_TYPE:-generic}" in
|
case "${NOTIFY_TYPE:-generic}" in
|
||||||
discord)
|
discord)
|
||||||
|
local json_payload
|
||||||
|
json_payload=$(jq -nc --arg msg "$message" '{content: $msg}')
|
||||||
curl -s -H "Content-Type: application/json" \
|
curl -s -H "Content-Type: application/json" \
|
||||||
-d "{\"content\": \"$message\"}" \
|
-d "$json_payload" \
|
||||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||||
;;
|
;;
|
||||||
slack)
|
slack)
|
||||||
|
local json_payload
|
||||||
|
json_payload=$(jq -nc --arg msg "$message" '{text: $msg}')
|
||||||
curl -s -H "Content-Type: application/json" \
|
curl -s -H "Content-Type: application/json" \
|
||||||
-d "{\"text\": \"$message\"}" \
|
-d "$json_payload" \
|
||||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||||
;;
|
;;
|
||||||
gotify)
|
gotify)
|
||||||
curl -s -X POST "$NOTIFY_WEBHOOK_URL" \
|
curl -s -X POST "$NOTIFY_WEBHOOK_URL" \
|
||||||
-F "title=AdGuard Shield - Externe Blocklist" \
|
-F "title=${title}" \
|
||||||
-F "message=$message" \
|
-F "message=${message}" \
|
||||||
-F "priority=5" &>/dev/null &
|
-F "priority=5" &>/dev/null &
|
||||||
;;
|
;;
|
||||||
ntfy)
|
ntfy)
|
||||||
local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}"
|
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=(
|
local -a curl_args=(
|
||||||
-s -X POST "${ntfy_url}/${NTFY_TOPIC}"
|
-s -X POST "${ntfy_url}/${NTFY_TOPIC}"
|
||||||
-H "Title: AdGuard Shield - Externe Blocklist"
|
-H "Title: ${ntfy_title}"
|
||||||
-H "Priority: ${NTFY_PRIORITY:-3}"
|
-H "Priority: ${NTFY_PRIORITY:-3}"
|
||||||
-H "Tags: rotating_light,blocklist"
|
-H "Tags: ${tags}"
|
||||||
-d "$(echo "$message" | sed 's/\*\*//g')"
|
-d "${message}"
|
||||||
)
|
)
|
||||||
[[ -n "${NTFY_TOKEN:-}" ]] && curl_args+=(-H "Authorization: Bearer ${NTFY_TOKEN}")
|
[[ -n "${NTFY_TOKEN:-}" ]] && curl_args+=(-H "Authorization: Bearer ${NTFY_TOKEN}")
|
||||||
curl "${curl_args[@]}" &>/dev/null &
|
curl "${curl_args[@]}" &>/dev/null &
|
||||||
;;
|
;;
|
||||||
generic)
|
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" \
|
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 &
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -439,8 +501,8 @@ parse_blocklist_ips() {
|
|||||||
log "WARN" "Eintrag übersprungen (ungültige IPv6-Adresse oder IP:Port): $line"
|
log "WARN" "Eintrag übersprungen (ungültige IPv6-Adresse oder IP:Port): $line"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif [[ "$line" =~ ^[0-9] ]]; then
|
elif [[ "$line" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?$ ]]; then
|
||||||
# ── IPv4 ──────────────────────────────────────────────────────────
|
# ── IPv4 (nur Ziffern, Punkte und optionaler CIDR-Suffix) ────────
|
||||||
[[ "$line" == "0.0.0.0"* ]] && continue
|
[[ "$line" == "0.0.0.0"* ]] && continue
|
||||||
if _is_valid_ipv4 "$line"; then
|
if _is_valid_ipv4 "$line"; then
|
||||||
echo "$line"
|
echo "$line"
|
||||||
|
|||||||
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]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?$ ]]; then
|
||||||
|
# IPv4 (nur Ziffern, Punkte und optionaler CIDR-Suffix)
|
||||||
|
[[ "$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
|
||||||
932
geoip-worker.sh
Normal file
932
geoip-worker.sh
Normal file
@@ -0,0 +1,932 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
###############################################################################
|
||||||
|
# AdGuard Shield - GeoIP Worker
|
||||||
|
# Prüft Client-IPs auf Herkunftsland und sperrt/erlaubt basierend auf Konfig.
|
||||||
|
# Wird als Hintergrundprozess vom Hauptscript gestartet.
|
||||||
|
#
|
||||||
|
# Autor: Patrick Asmus
|
||||||
|
# E-Mail: support@techniverse.net
|
||||||
|
# Lizenz: MIT
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_FILE="${SCRIPT_DIR}/adguard-shield.conf"
|
||||||
|
|
||||||
|
# ─── Konfiguration laden ───────────────────────────────────────────────────────
|
||||||
|
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||||
|
echo "FEHLER: Konfigurationsdatei nicht gefunden: $CONFIG_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# shellcheck source=adguard-shield.conf
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
|
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
||||||
|
WORKER_PID_FILE="/var/run/adguard-geoip-worker.pid"
|
||||||
|
|
||||||
|
# ─── GeoIP Cache ──────────────────────────────────────────────────────────────
|
||||||
|
GEOIP_CACHE_DIR="${STATE_DIR}/geoip-cache"
|
||||||
|
|
||||||
|
# ─── MaxMind Auto-Download Verzeichnis ────────────────────────────────────────
|
||||||
|
GEOIP_DB_DIR="${SCRIPT_DIR}/geoip"
|
||||||
|
GEOIP_AUTO_DB="${GEOIP_DB_DIR}/GeoLite2-Country.mmdb"
|
||||||
|
GEOIP_DB_UPDATE_INTERVAL=86400 # 24 Stunden (fest)
|
||||||
|
|
||||||
|
# ─── Logging (eigene Funktion, nutzt gleiche Log-Datei) ───────────────────────
|
||||||
|
declare -A LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
|
||||||
|
|
||||||
|
log() {
|
||||||
|
local level="$1"
|
||||||
|
shift
|
||||||
|
local message="$*"
|
||||||
|
local configured_level="${LOG_LEVEL:-INFO}"
|
||||||
|
|
||||||
|
if [[ ${LOG_LEVELS[$level]:-1} -ge ${LOG_LEVELS[$configured_level]:-1} ]]; then
|
||||||
|
local timestamp
|
||||||
|
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
local log_entry="[$timestamp] [$level] [GEOIP-WORKER] $message"
|
||||||
|
echo "$log_entry" | tee -a "$LOG_FILE" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Ban-History ─────────────────────────────────────────────────────────────
|
||||||
|
log_ban_history() {
|
||||||
|
local action="$1"
|
||||||
|
local client_ip="$2"
|
||||||
|
local country="${3:-}"
|
||||||
|
local reason="${4:-geoip}"
|
||||||
|
local timestamp
|
||||||
|
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
|
||||||
|
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
||||||
|
echo "# AdGuard Shield - Ban History" > "$BAN_HISTORY_FILE"
|
||||||
|
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | PROTOKOLL | GRUND" >> "$BAN_HISTORY_FILE"
|
||||||
|
echo "#──────────────────────────────────────────────────────────────────────────────────────────────────" >> "$BAN_HISTORY_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local duration="permanent"
|
||||||
|
|
||||||
|
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
|
||||||
|
"$timestamp" "$action" "$client_ip" "Land: ${country:-?}" "-" "$duration" "-" "$reason" \
|
||||||
|
>> "$BAN_HISTORY_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
||||||
|
init_directories() {
|
||||||
|
mkdir -p "$GEOIP_CACHE_DIR"
|
||||||
|
mkdir -p "$GEOIP_DB_DIR"
|
||||||
|
mkdir -p "$STATE_DIR"
|
||||||
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Private IP-Adressen erkennen ────────────────────────────────────────────
|
||||||
|
is_private_ip() {
|
||||||
|
local ip="$1"
|
||||||
|
|
||||||
|
# IPv6 Loopback und Link-Local
|
||||||
|
if [[ "$ip" == "::1" || "$ip" == fe80:* || "$ip" == fc00:* || "$ip" == fd00:* ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# IPv4 private Bereiche
|
||||||
|
if [[ "$ip" =~ ^10\. || "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. || "$ip" =~ ^192\.168\. || "$ip" =~ ^127\. || "$ip" == "0.0.0.0" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# IPv4 CGNAT
|
||||||
|
if [[ "$ip" =~ ^100\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\. ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Whitelist Prüfung ───────────────────────────────────────────────────────
|
||||||
|
is_whitelisted() {
|
||||||
|
local ip="$1"
|
||||||
|
IFS=',' read -ra wl_entries <<< "$WHITELIST"
|
||||||
|
for entry in "${wl_entries[@]}"; do
|
||||||
|
entry=$(echo "$entry" | xargs) # trim
|
||||||
|
if [[ "$ip" == "$entry" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Externe Whitelist prüfen
|
||||||
|
local ext_wl_file="${EXTERNAL_WHITELIST_CACHE_DIR:-/var/lib/adguard-shield/external-whitelist}/resolved_ips.txt"
|
||||||
|
if [[ -f "$ext_wl_file" ]] && grep -qxF "$ip" "$ext_wl_file" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── MaxMind GeoLite2 Auto-Download & Update ─────────────────────────────────
|
||||||
|
# Lädt die GeoLite2-Country.mmdb herunter, wenn GEOIP_LICENSE_KEY gesetzt ist
|
||||||
|
# und kein eigener GEOIP_MMDB_PATH angegeben wurde.
|
||||||
|
# Aktualisiert automatisch alle 24 Stunden.
|
||||||
|
update_maxmind_db() {
|
||||||
|
local license_key="${GEOIP_LICENSE_KEY:-}"
|
||||||
|
|
||||||
|
# Kein License-Key → nichts zu tun
|
||||||
|
if [[ -z "$license_key" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# User hat eigenen Pfad gesetzt → kein Auto-Download
|
||||||
|
if [[ -n "${GEOIP_MMDB_PATH:-}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prüfen ob Update nötig (alle 24h)
|
||||||
|
if [[ -f "$GEOIP_AUTO_DB" ]]; then
|
||||||
|
local db_age
|
||||||
|
db_age=$(( $(date '+%s') - $(stat -c '%Y' "$GEOIP_AUTO_DB" 2>/dev/null || stat -f '%m' "$GEOIP_AUTO_DB" 2>/dev/null || echo "0") ))
|
||||||
|
if [[ "$db_age" -lt "$GEOIP_DB_UPDATE_INTERVAL" ]]; then
|
||||||
|
log "DEBUG" "MaxMind DB ist aktuell (Alter: $((db_age / 3600))h, nächstes Update in $(( (GEOIP_DB_UPDATE_INTERVAL - db_age) / 3600 ))h)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
log "INFO" "MaxMind DB ist älter als 24h – starte Update..."
|
||||||
|
else
|
||||||
|
log "INFO" "MaxMind DB nicht vorhanden – starte Erstdownload..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Download-URL zusammenbauen (MaxMind Permalink)
|
||||||
|
local download_url="https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${license_key}&suffix=tar.gz"
|
||||||
|
local tmp_file="${GEOIP_DB_DIR}/GeoLite2-Country.tar.gz"
|
||||||
|
local tmp_extract="${GEOIP_DB_DIR}/extract_tmp"
|
||||||
|
|
||||||
|
# Herunterladen
|
||||||
|
local http_code
|
||||||
|
http_code=$(curl -s -o "$tmp_file" -w "%{http_code}" \
|
||||||
|
--connect-timeout 10 \
|
||||||
|
--max-time 60 \
|
||||||
|
"$download_url" 2>/dev/null) || true
|
||||||
|
|
||||||
|
if [[ "$http_code" != "200" ]]; then
|
||||||
|
rm -f "$tmp_file"
|
||||||
|
case "$http_code" in
|
||||||
|
401) log "ERROR" "MaxMind Download fehlgeschlagen: Ungültiger License-Key (HTTP 401)" ;;
|
||||||
|
403) log "ERROR" "MaxMind Download fehlgeschlagen: Zugriff verweigert (HTTP 403) – License-Key prüfen" ;;
|
||||||
|
*) log "ERROR" "MaxMind Download fehlgeschlagen (HTTP ${http_code:-timeout})" ;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Entpacken
|
||||||
|
rm -rf "$tmp_extract"
|
||||||
|
mkdir -p "$tmp_extract"
|
||||||
|
|
||||||
|
if ! tar -xzf "$tmp_file" -C "$tmp_extract" 2>/dev/null; then
|
||||||
|
log "ERROR" "MaxMind DB: tar-Archiv konnte nicht entpackt werden"
|
||||||
|
rm -f "$tmp_file"
|
||||||
|
rm -rf "$tmp_extract"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# .mmdb Datei finden und verschieben
|
||||||
|
local mmdb_file
|
||||||
|
mmdb_file=$(find "$tmp_extract" -name 'GeoLite2-Country.mmdb' -type f 2>/dev/null | head -1)
|
||||||
|
|
||||||
|
if [[ -z "$mmdb_file" || ! -f "$mmdb_file" ]]; then
|
||||||
|
log "ERROR" "MaxMind DB: GeoLite2-Country.mmdb nicht im Archiv gefunden"
|
||||||
|
rm -f "$tmp_file"
|
||||||
|
rm -rf "$tmp_extract"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$mmdb_file" "$GEOIP_AUTO_DB"
|
||||||
|
rm -f "$tmp_file"
|
||||||
|
rm -rf "$tmp_extract"
|
||||||
|
|
||||||
|
log "INFO" "MaxMind GeoLite2-Country DB erfolgreich aktualisiert: $GEOIP_AUTO_DB"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Effektiven MMDB-Pfad ermitteln ──────────────────────────────────────────
|
||||||
|
# Priorität: GEOIP_MMDB_PATH (User) > Auto-Download > leer (Fallback auf geoiplookup)
|
||||||
|
resolve_mmdb_path() {
|
||||||
|
# User hat eigenen Pfad gesetzt
|
||||||
|
if [[ -n "${GEOIP_MMDB_PATH:-}" && -f "${GEOIP_MMDB_PATH:-}" ]]; then
|
||||||
|
echo "$GEOIP_MMDB_PATH"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Auto-Download DB vorhanden
|
||||||
|
if [[ -f "$GEOIP_AUTO_DB" ]]; then
|
||||||
|
echo "$GEOIP_AUTO_DB"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Kein MMDB verfügbar
|
||||||
|
echo ""
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── GeoIP Lookup ────────────────────────────────────────────────────────────
|
||||||
|
# Gibt den ISO 3166-1 Alpha-2 Ländercode zurück (z.B. "DE", "US", "CN")
|
||||||
|
# Nutzt Cache um wiederholte Lookups zu vermeiden
|
||||||
|
geoip_lookup() {
|
||||||
|
local ip="$1"
|
||||||
|
local cache_file="${GEOIP_CACHE_DIR}/${ip//[:\/]/_}.country"
|
||||||
|
|
||||||
|
# Cache prüfen (max 24 Stunden alt)
|
||||||
|
if [[ -f "$cache_file" ]]; then
|
||||||
|
local cache_age
|
||||||
|
cache_age=$(( $(date '+%s') - $(stat -c '%Y' "$cache_file" 2>/dev/null || stat -f '%m' "$cache_file" 2>/dev/null || echo "0") ))
|
||||||
|
if [[ "$cache_age" -lt 86400 ]]; then
|
||||||
|
cat "$cache_file"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local country_code=""
|
||||||
|
|
||||||
|
# Effektiven MMDB-Pfad ermitteln (User-Pfad oder Auto-Download)
|
||||||
|
local effective_mmdb
|
||||||
|
effective_mmdb=$(resolve_mmdb_path 2>/dev/null) || true
|
||||||
|
|
||||||
|
# Methode 1: MaxMind mmdbinspect (bevorzugt, genauer)
|
||||||
|
if [[ -n "$effective_mmdb" && -f "$effective_mmdb" ]] && command -v mmdbinspect &>/dev/null; then
|
||||||
|
country_code=$(mmdbinspect -db "$effective_mmdb" -ip "$ip" 2>/dev/null \
|
||||||
|
| jq -r '.[0].Records[0].Record.country.iso_code // empty' 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Methode 2: geoiplookup (GeoIP Legacy)
|
||||||
|
if [[ -z "$country_code" ]] && command -v geoiplookup &>/dev/null; then
|
||||||
|
if [[ "$ip" == *:* ]]; then
|
||||||
|
# IPv6
|
||||||
|
if command -v geoiplookup6 &>/dev/null; then
|
||||||
|
country_code=$(geoiplookup6 "$ip" 2>/dev/null \
|
||||||
|
| grep -oP '(?<=: )[A-Z]{2}(?=,)' | head -1 || true)
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# IPv4
|
||||||
|
country_code=$(geoiplookup "$ip" 2>/dev/null \
|
||||||
|
| grep -oP '(?<=: )[A-Z]{2}(?=,)' | head -1 || true)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Methode 3: mmdblookup (libmaxminddb)
|
||||||
|
if [[ -z "$country_code" && -n "$effective_mmdb" && -f "$effective_mmdb" ]] && command -v mmdblookup &>/dev/null; then
|
||||||
|
country_code=$(mmdblookup --file "$effective_mmdb" --ip "$ip" country iso_code 2>/dev/null \
|
||||||
|
| grep -oP '"[A-Z]{2}"' | tr -d '"' | head -1 || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$country_code" ]]; then
|
||||||
|
echo "$country_code" > "$cache_file"
|
||||||
|
echo "$country_code"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unbekannt – nicht cachen (könnte temporärer Fehler sein)
|
||||||
|
echo ""
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── GeoIP Prüfung: Soll eine IP gesperrt werden? ────────────────────────────
|
||||||
|
# Return 0 = sperren, Return 1 = erlauben
|
||||||
|
should_block_by_geoip() {
|
||||||
|
local country_code="$1"
|
||||||
|
local mode="${GEOIP_MODE:-blocklist}"
|
||||||
|
local countries="${GEOIP_COUNTRIES:-}"
|
||||||
|
|
||||||
|
[[ -z "$country_code" || -z "$countries" ]] && return 1
|
||||||
|
|
||||||
|
# Länder-Liste in Array umwandeln
|
||||||
|
IFS=',' read -ra country_list <<< "$countries"
|
||||||
|
|
||||||
|
local found=false
|
||||||
|
for c in "${country_list[@]}"; do
|
||||||
|
c=$(echo "$c" | xargs | tr '[:lower:]' '[:upper:]') # trim + uppercase
|
||||||
|
if [[ "$country_code" == "$c" ]]; then
|
||||||
|
found=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$mode" == "blocklist" ]]; then
|
||||||
|
# Blocklist-Modus: Sperren wenn Land in der Liste
|
||||||
|
[[ "$found" == "true" ]] && return 0 || return 1
|
||||||
|
elif [[ "$mode" == "allowlist" ]]; then
|
||||||
|
# Allowlist-Modus: Sperren wenn Land NICHT in der Liste
|
||||||
|
[[ "$found" == "true" ]] && return 1 || return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── IP via iptables sperren ─────────────────────────────────────────────────
|
||||||
|
ban_ip_geoip() {
|
||||||
|
local client_ip="$1"
|
||||||
|
local country_code="$2"
|
||||||
|
local mode="${GEOIP_MODE:-blocklist}"
|
||||||
|
|
||||||
|
# Prüfen ob bereits gesperrt
|
||||||
|
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
|
||||||
|
if [[ -f "$state_file" ]]; then
|
||||||
|
log "DEBUG" "GeoIP: $client_ip ist bereits gesperrt"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GeoIP-Sperren sind immer permanent
|
||||||
|
local ban_until=0
|
||||||
|
local ban_until_display="PERMANENT"
|
||||||
|
|
||||||
|
local reason_text
|
||||||
|
if [[ "$mode" == "blocklist" ]]; then
|
||||||
|
reason_text="geoip-blocklist (Land: $country_code)"
|
||||||
|
else
|
||||||
|
reason_text="geoip-allowlist (Land: $country_code)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "WARN" "GeoIP SPERRE: $client_ip (Land: $country_code, Modus: $mode) PERMANENT"
|
||||||
|
|
||||||
|
# iptables Regel setzen
|
||||||
|
if [[ "$client_ip" == *:* ]]; then
|
||||||
|
ip6tables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
|
else
|
||||||
|
iptables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# State-Datei erstellen
|
||||||
|
cat > "$state_file" << EOF
|
||||||
|
CLIENT_IP=$client_ip
|
||||||
|
DOMAIN=GeoIP:${country_code}
|
||||||
|
COUNT=-
|
||||||
|
BAN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
BAN_UNTIL_EPOCH=0
|
||||||
|
BAN_UNTIL=PERMANENT
|
||||||
|
BAN_DURATION=0
|
||||||
|
OFFENSE_LEVEL=0
|
||||||
|
IS_PERMANENT=true
|
||||||
|
REASON=geoip
|
||||||
|
PROTOCOL=-
|
||||||
|
GEOIP_COUNTRY=$country_code
|
||||||
|
GEOIP_MODE=$mode
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Ban-History
|
||||||
|
log_ban_history "BAN" "$client_ip" "$country_code" "$reason_text"
|
||||||
|
|
||||||
|
# Benachrichtigung senden
|
||||||
|
if [[ "${GEOIP_NOTIFY:-true}" == "true" && "${NOTIFY_ENABLED:-false}" == "true" ]]; then
|
||||||
|
send_geoip_notification "ban" "$client_ip" "$country_code" "PERMANENT" "$mode"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── GeoIP Benachrichtigung ──────────────────────────────────────────────────
|
||||||
|
send_geoip_notification() {
|
||||||
|
local action="$1"
|
||||||
|
local client_ip="$2"
|
||||||
|
local country_code="$3"
|
||||||
|
local duration="${4:-PERMANENT}"
|
||||||
|
local mode="${5:-blocklist}"
|
||||||
|
local my_hostname
|
||||||
|
my_hostname=$(hostname)
|
||||||
|
|
||||||
|
local title="🌍 🛡️ AdGuard Shield"
|
||||||
|
local mode_label
|
||||||
|
[[ "$mode" == "blocklist" ]] && mode_label="Blocklist" || mode_label="Allowlist"
|
||||||
|
|
||||||
|
local message="🌍 AdGuard Shield GeoIP-Sperre auf ${my_hostname}
|
||||||
|
---
|
||||||
|
IP: ${client_ip}
|
||||||
|
Land: ${country_code}
|
||||||
|
Modus: ${mode_label}
|
||||||
|
Dauer: ${duration}
|
||||||
|
|
||||||
|
Whois: https://www.whois.com/whois/${client_ip}
|
||||||
|
AbuseIPDB: https://www.abuseipdb.com/check/${client_ip}"
|
||||||
|
|
||||||
|
case "${NOTIFY_TYPE:-}" in
|
||||||
|
discord)
|
||||||
|
local json_payload
|
||||||
|
json_payload=$(jq -nc --arg msg "$message" '{content: $msg}')
|
||||||
|
curl -s -H "Content-Type: application/json" \
|
||||||
|
-d "$json_payload" \
|
||||||
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||||
|
;;
|
||||||
|
slack)
|
||||||
|
local json_payload
|
||||||
|
json_payload=$(jq -nc --arg msg "$message" '{text: $msg}')
|
||||||
|
curl -s -H "Content-Type: application/json" \
|
||||||
|
-d "$json_payload" \
|
||||||
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||||
|
;;
|
||||||
|
gotify)
|
||||||
|
local clean_message
|
||||||
|
clean_message=$(echo "$message" | sed 's/\*\*//g')
|
||||||
|
curl -s -X POST "$NOTIFY_WEBHOOK_URL" \
|
||||||
|
-F "title=${title}" \
|
||||||
|
-F "message=${clean_message}" \
|
||||||
|
-F "priority=5" &>/dev/null &
|
||||||
|
;;
|
||||||
|
ntfy)
|
||||||
|
local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}"
|
||||||
|
local -a curl_args=(
|
||||||
|
-s -X POST
|
||||||
|
"${ntfy_url}/${NTFY_TOPIC}"
|
||||||
|
-H "Title: 🛡️ AdGuard Shield GeoIP"
|
||||||
|
-H "Priority: ${NTFY_PRIORITY:-4}"
|
||||||
|
-H "Tags: globe_with_meridians,ban"
|
||||||
|
-d "$message"
|
||||||
|
)
|
||||||
|
[[ -n "${NTFY_TOKEN:-}" ]] && curl_args+=(-H "Authorization: Bearer ${NTFY_TOKEN}")
|
||||||
|
curl "${curl_args[@]}" &>/dev/null &
|
||||||
|
;;
|
||||||
|
generic)
|
||||||
|
local json_payload
|
||||||
|
json_payload=$(jq -nc --arg msg "$message" --arg cl "$client_ip" --arg cc "$country_code" \
|
||||||
|
'{message: $msg, action: "geoip-ban", client: $cl, country: $cc}')
|
||||||
|
curl -s -H "Content-Type: application/json" \
|
||||||
|
-d "$json_payload" \
|
||||||
|
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── iptables Chain Setup ────────────────────────────────────────────────────
|
||||||
|
setup_iptables_chain() {
|
||||||
|
if ! iptables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||||
|
iptables -N "$IPTABLES_CHAIN"
|
||||||
|
for port in $BLOCKED_PORTS; do
|
||||||
|
iptables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||||
|
iptables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if ! ip6tables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||||
|
ip6tables -N "$IPTABLES_CHAIN"
|
||||||
|
for port in $BLOCKED_PORTS; do
|
||||||
|
ip6tables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||||
|
ip6tables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── GeoIP-Tools Verfügbarkeit prüfen ────────────────────────────────────────
|
||||||
|
check_geoip_tools() {
|
||||||
|
# Effektiven MMDB-Pfad ermitteln
|
||||||
|
local effective_mmdb
|
||||||
|
effective_mmdb=$(resolve_mmdb_path 2>/dev/null) || true
|
||||||
|
|
||||||
|
# mmdbinspect + MMDB
|
||||||
|
if [[ -n "$effective_mmdb" && -f "$effective_mmdb" ]]; then
|
||||||
|
if command -v mmdbinspect &>/dev/null; then
|
||||||
|
echo "mmdbinspect"
|
||||||
|
return 0
|
||||||
|
elif command -v mmdblookup &>/dev/null; then
|
||||||
|
echo "mmdblookup"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# geoiplookup (Legacy GeoIP)
|
||||||
|
if command -v geoiplookup &>/dev/null; then
|
||||||
|
echo "geoiplookup"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "none"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Client-IPs aus AdGuard API extrahieren ──────────────────────────────────
|
||||||
|
get_active_clients() {
|
||||||
|
local response
|
||||||
|
response=$(curl -s -u "${ADGUARD_USER}:${ADGUARD_PASS}" \
|
||||||
|
--connect-timeout 5 \
|
||||||
|
--max-time 10 \
|
||||||
|
-k "${ADGUARD_URL}/control/querylog?limit=${API_QUERY_LIMIT:-500}&response_status=all" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -z "$response" || "$response" == "null" ]]; then
|
||||||
|
log "ERROR" "Keine Antwort von AdGuard Home API"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Eindeutige Client-IPs extrahieren
|
||||||
|
echo "$response" | jq -r '.data // [] | [.[].client // .[].client_info.ip] | unique | .[]' 2>/dev/null | sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Auto-Unban: GeoIP-Sperren aufheben bei Konfigurationsänderung ────────────
|
||||||
|
# Prüft alle bestehenden GeoIP-Sperren und hebt sie auf, wenn:
|
||||||
|
# - Das Land nicht mehr in GEOIP_COUNTRIES steht
|
||||||
|
# - Der Modus gewechselt wurde (blocklist ↔ allowlist)
|
||||||
|
# - GeoIP deaktiviert wurde
|
||||||
|
auto_unban_geoip() {
|
||||||
|
local unban_count=0
|
||||||
|
|
||||||
|
for f in "${STATE_DIR}"/*.ban; do
|
||||||
|
[[ -f "$f" ]] || continue
|
||||||
|
|
||||||
|
local reason
|
||||||
|
reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true)
|
||||||
|
[[ "$reason" != "geoip" ]] && continue
|
||||||
|
|
||||||
|
local client_ip country_code old_mode
|
||||||
|
client_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true)
|
||||||
|
country_code=$(grep '^GEOIP_COUNTRY=' "$f" | cut -d= -f2 || true)
|
||||||
|
old_mode=$(grep '^GEOIP_MODE=' "$f" | cut -d= -f2 || true)
|
||||||
|
|
||||||
|
local should_unban=false
|
||||||
|
|
||||||
|
# GeoIP deaktiviert → alle GeoIP-Sperren aufheben
|
||||||
|
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
||||||
|
should_unban=true
|
||||||
|
# Modus gewechselt → alle GeoIP-Sperren aufheben und neu prüfen
|
||||||
|
elif [[ -n "$old_mode" && "$old_mode" != "${GEOIP_MODE:-blocklist}" ]]; then
|
||||||
|
should_unban=true
|
||||||
|
# Prüfen ob das Land nach aktueller Konfiguration noch gesperrt sein soll
|
||||||
|
elif [[ -n "$country_code" ]] && ! should_block_by_geoip "$country_code"; then
|
||||||
|
should_unban=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$should_unban" == "true" ]]; then
|
||||||
|
log "INFO" "GeoIP Auto-Unban: $client_ip (Land: ${country_code:-?}, war: ${old_mode:-?})"
|
||||||
|
|
||||||
|
# iptables Regel entfernen
|
||||||
|
if [[ "$client_ip" == *:* ]]; then
|
||||||
|
ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
|
else
|
||||||
|
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$f"
|
||||||
|
log_ban_history "UNBAN" "$client_ip" "$country_code" "geoip-auto-unban"
|
||||||
|
unban_count=$((unban_count + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $unban_count -gt 0 ]]; then
|
||||||
|
log "INFO" "GeoIP Auto-Unban: $unban_count Sperren aufgehoben (Länderliste/Modus geändert)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Einmaliger GeoIP-Sync ──────────────────────────────────────────────────
|
||||||
|
sync_geoip() {
|
||||||
|
# Auto-Unban zuerst: bestehende Sperren prüfen, die nicht mehr zur Config passen
|
||||||
|
auto_unban_geoip
|
||||||
|
|
||||||
|
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
||||||
|
log "INFO" "GeoIP ist deaktiviert"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# MaxMind DB automatisch herunterladen/aktualisieren (falls License-Key gesetzt)
|
||||||
|
update_maxmind_db || true
|
||||||
|
|
||||||
|
local countries="${GEOIP_COUNTRIES:-}"
|
||||||
|
if [[ -z "$countries" ]]; then
|
||||||
|
log "WARN" "GeoIP: Keine Länder konfiguriert (GEOIP_COUNTRIES ist leer)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tool
|
||||||
|
tool=$(check_geoip_tools) || {
|
||||||
|
log "ERROR" "GeoIP: Kein GeoIP-Tool verfügbar. Installiere geoip-bin oder mmdbinspect."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
log "INFO" "GeoIP-Sync gestartet (Tool: $tool, Modus: ${GEOIP_MODE:-blocklist}, Länder: $countries)"
|
||||||
|
|
||||||
|
# Client-IPs aus der API holen
|
||||||
|
local clients
|
||||||
|
clients=$(get_active_clients) || {
|
||||||
|
log "ERROR" "GeoIP: Konnte aktive Clients nicht ermitteln"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local checked=0
|
||||||
|
local blocked=0
|
||||||
|
local skipped=0
|
||||||
|
|
||||||
|
while IFS= read -r client_ip; do
|
||||||
|
[[ -z "$client_ip" || "$client_ip" == "null" ]] && continue
|
||||||
|
|
||||||
|
# Private IPs überspringen
|
||||||
|
if [[ "${GEOIP_SKIP_PRIVATE:-true}" == "true" ]] && is_private_ip "$client_ip"; then
|
||||||
|
log "DEBUG" "GeoIP: Private IP übersprungen: $client_ip"
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Whitelist prüfen
|
||||||
|
if is_whitelisted "$client_ip"; then
|
||||||
|
log "DEBUG" "GeoIP: Whitelisted IP übersprungen: $client_ip"
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Bereits gesperrt?
|
||||||
|
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
|
||||||
|
if [[ -f "$state_file" ]]; then
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
checked=$((checked + 1))
|
||||||
|
|
||||||
|
# GeoIP Lookup
|
||||||
|
local country_code
|
||||||
|
country_code=$(geoip_lookup "$client_ip") || true
|
||||||
|
|
||||||
|
if [[ -z "$country_code" ]]; then
|
||||||
|
log "DEBUG" "GeoIP: Kein Ergebnis für $client_ip"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "DEBUG" "GeoIP: $client_ip → $country_code"
|
||||||
|
|
||||||
|
# Prüfen ob gesperrt werden soll
|
||||||
|
if should_block_by_geoip "$country_code"; then
|
||||||
|
ban_ip_geoip "$client_ip" "$country_code"
|
||||||
|
blocked=$((blocked + 1))
|
||||||
|
fi
|
||||||
|
done <<< "$clients"
|
||||||
|
|
||||||
|
log "INFO" "GeoIP-Sync abgeschlossen: $checked geprüft, $blocked gesperrt, $skipped übersprungen"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Worker-Hauptschleife ────────────────────────────────────────────────────
|
||||||
|
start_worker() {
|
||||||
|
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
||||||
|
log "DEBUG" "GeoIP-Worker ist deaktiviert"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# PID schreiben
|
||||||
|
echo $$ > "$WORKER_PID_FILE"
|
||||||
|
trap 'rm -f "$WORKER_PID_FILE"; exit 0' SIGTERM SIGINT SIGHUP
|
||||||
|
|
||||||
|
local interval="${GEOIP_CHECK_INTERVAL:-0}"
|
||||||
|
[[ "$interval" -le 0 ]] && interval="${CHECK_INTERVAL:-10}"
|
||||||
|
|
||||||
|
log "INFO" "GeoIP-Worker gestartet (PID: $$, Intervall: ${interval}s)"
|
||||||
|
|
||||||
|
# Beim Start: MaxMind DB herunterladen/aktualisieren (falls License-Key gesetzt)
|
||||||
|
update_maxmind_db || true
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
sync_geoip
|
||||||
|
sleep "$interval"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Status anzeigen ─────────────────────────────────────────────────────────
|
||||||
|
show_status() {
|
||||||
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
|
echo " AdGuard Shield - GeoIP Status"
|
||||||
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
||||||
|
echo " ℹ️ GeoIP ist deaktiviert"
|
||||||
|
echo ""
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " Modus: ${GEOIP_MODE:-blocklist}"
|
||||||
|
echo " Länder: ${GEOIP_COUNTRIES:-<keine>}"
|
||||||
|
echo " Sperrdauer: PERMANENT (Auto-Unban bei Änderung der Länderliste)"
|
||||||
|
echo " Private IPs überspringen: ${GEOIP_SKIP_PRIVATE:-true}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# MaxMind DB Info
|
||||||
|
local eff_mmdb
|
||||||
|
eff_mmdb=$(resolve_mmdb_path)
|
||||||
|
if [[ -n "${GEOIP_MMDB_PATH:-}" ]]; then
|
||||||
|
echo " MMDB-Pfad: ${GEOIP_MMDB_PATH} (manuell konfiguriert)"
|
||||||
|
elif [[ -n "${GEOIP_LICENSE_KEY:-}" ]]; then
|
||||||
|
echo " MMDB-Pfad: ${GEOIP_AUTO_DB} (Auto-Download)"
|
||||||
|
if [[ -f "${GEOIP_AUTO_DB}" ]]; then
|
||||||
|
local db_age db_age_h
|
||||||
|
db_age=$(( $(date +%s) - $(stat -c %Y "${GEOIP_AUTO_DB}" 2>/dev/null || echo 0) ))
|
||||||
|
db_age_h=$(( db_age / 3600 ))
|
||||||
|
echo " DB-Alter: ${db_age_h}h (Update alle 24h)"
|
||||||
|
else
|
||||||
|
echo " DB-Status: ⚠️ Noch nicht heruntergeladen"
|
||||||
|
fi
|
||||||
|
elif [[ -n "$eff_mmdb" ]]; then
|
||||||
|
echo " MMDB-Pfad: ${eff_mmdb}"
|
||||||
|
else
|
||||||
|
echo " MMDB-Pfad: <nicht konfiguriert> (Fallback auf geoiplookup)"
|
||||||
|
fi
|
||||||
|
echo " License-Key: $(if [[ -n "${GEOIP_LICENSE_KEY:-}" ]]; then echo "✅ konfiguriert"; else echo "❌ nicht gesetzt (kein Auto-Download)"; fi)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# GeoIP Tools prüfen
|
||||||
|
echo " GeoIP Tools:"
|
||||||
|
local tool
|
||||||
|
tool=$(check_geoip_tools 2>/dev/null) || tool="none"
|
||||||
|
case "$tool" in
|
||||||
|
mmdbinspect) echo " ✅ mmdbinspect mit MaxMind DB" ;;
|
||||||
|
mmdblookup) echo " ✅ mmdblookup mit MaxMind DB" ;;
|
||||||
|
geoiplookup) echo " ✅ geoiplookup (Legacy GeoIP)" ;;
|
||||||
|
none) echo " ❌ Kein GeoIP-Tool gefunden!" ;;
|
||||||
|
esac
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Worker-Status
|
||||||
|
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||||
|
local wpid
|
||||||
|
wpid=$(cat "$WORKER_PID_FILE")
|
||||||
|
if kill -0 "$wpid" 2>/dev/null; then
|
||||||
|
echo " Worker: Läuft (PID: $wpid)"
|
||||||
|
else
|
||||||
|
echo " Worker: Abgestürzt (PID: $wpid existiert nicht mehr)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Worker: Nicht gestartet"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# GeoIP-Sperren anzeigen
|
||||||
|
local geoip_bans=0
|
||||||
|
if [[ -d "$STATE_DIR" ]]; then
|
||||||
|
for f in "${STATE_DIR}"/*.ban; do
|
||||||
|
[[ -f "$f" ]] || continue
|
||||||
|
local reason
|
||||||
|
reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true)
|
||||||
|
if [[ "$reason" == "geoip" ]]; then
|
||||||
|
geoip_bans=$((geoip_bans + 1))
|
||||||
|
local s_ip s_country s_until
|
||||||
|
s_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true)
|
||||||
|
s_country=$(grep '^GEOIP_COUNTRY=' "$f" | cut -d= -f2 || true)
|
||||||
|
s_until=$(grep '^BAN_UNTIL=' "$f" | cut -d= -f2 || true)
|
||||||
|
echo " 🌍 $s_ip → Land: ${s_country:-?} (bis: ${s_until:-?})"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $geoip_bans -eq 0 ]]; then
|
||||||
|
echo " Keine aktiven GeoIP-Sperren"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo " Gesamt: $geoip_bans aktive GeoIP-Sperren"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cache-Statistik
|
||||||
|
if [[ -d "$GEOIP_CACHE_DIR" ]]; then
|
||||||
|
local cache_count
|
||||||
|
cache_count=$(find "$GEOIP_CACHE_DIR" -name '*.country' -type f 2>/dev/null | wc -l)
|
||||||
|
echo ""
|
||||||
|
echo " Cache: $cache_count IP-Lookups zwischengespeichert"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Einzelne IP nachschlagen ────────────────────────────────────────────────
|
||||||
|
lookup_ip() {
|
||||||
|
local ip="$1"
|
||||||
|
|
||||||
|
local eff_mmdb
|
||||||
|
eff_mmdb=$(resolve_mmdb_path)
|
||||||
|
|
||||||
|
local tool
|
||||||
|
tool=$(check_geoip_tools 2>/dev/null) || tool="none"
|
||||||
|
|
||||||
|
if [[ "$tool" == "none" ]]; then
|
||||||
|
echo "❌ Kein GeoIP-Tool verfügbar."
|
||||||
|
echo " Installiere geoip-bin: sudo apt install geoip-bin geoip-database"
|
||||||
|
echo " Oder mmdbinspect mit MaxMind GeoLite2 DB"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local country_code
|
||||||
|
country_code=$(geoip_lookup "$ip") || true
|
||||||
|
|
||||||
|
if [[ -z "$country_code" ]]; then
|
||||||
|
echo "IP: $ip → Land: unbekannt (kein GeoIP-Ergebnis)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "IP: $ip → Land: $country_code (Tool: $tool)"
|
||||||
|
[[ -n "$eff_mmdb" ]] && echo " MMDB: $eff_mmdb"
|
||||||
|
|
||||||
|
# Prüfen ob diese IP gesperrt werden würde
|
||||||
|
if [[ "${GEOIP_ENABLED:-false}" == "true" && -n "${GEOIP_COUNTRIES:-}" ]]; then
|
||||||
|
if should_block_by_geoip "$country_code"; then
|
||||||
|
echo "→ Würde GESPERRT werden (Modus: ${GEOIP_MODE:-blocklist}, Länder: ${GEOIP_COUNTRIES})"
|
||||||
|
else
|
||||||
|
echo "→ Würde ERLAUBT werden (Modus: ${GEOIP_MODE:-blocklist}, Länder: ${GEOIP_COUNTRIES})"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Cache leeren ────────────────────────────────────────────────────────────
|
||||||
|
flush_cache() {
|
||||||
|
if [[ -d "$GEOIP_CACHE_DIR" ]]; then
|
||||||
|
local count
|
||||||
|
count=$(find "$GEOIP_CACHE_DIR" -name '*.country' -type f 2>/dev/null | wc -l)
|
||||||
|
rm -f "${GEOIP_CACHE_DIR}"/*.country 2>/dev/null || true
|
||||||
|
echo "✅ GeoIP-Cache geleert ($count Einträge entfernt)"
|
||||||
|
log "INFO" "GeoIP-Cache geleert ($count Einträge)"
|
||||||
|
else
|
||||||
|
echo "ℹ️ GeoIP-Cache-Verzeichnis existiert nicht"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── GeoIP-Sperren aufheben ─────────────────────────────────────────────────
|
||||||
|
flush_geoip_bans() {
|
||||||
|
local count=0
|
||||||
|
if [[ -d "$STATE_DIR" ]]; then
|
||||||
|
for f in "${STATE_DIR}"/*.ban; do
|
||||||
|
[[ -f "$f" ]] || continue
|
||||||
|
local reason
|
||||||
|
reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true)
|
||||||
|
if [[ "$reason" == "geoip" ]]; then
|
||||||
|
local client_ip
|
||||||
|
client_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true)
|
||||||
|
|
||||||
|
# iptables Regel entfernen
|
||||||
|
if [[ "$client_ip" == *:* ]]; then
|
||||||
|
ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
|
else
|
||||||
|
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$f"
|
||||||
|
log_ban_history "UNBAN" "$client_ip" "" "geoip-flush"
|
||||||
|
count=$((count + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ $count GeoIP-Sperren aufgehoben"
|
||||||
|
log "INFO" "$count GeoIP-Sperren aufgehoben (flush)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Hauptprogramm ──────────────────────────────────────────────────────────
|
||||||
|
case "${1:-help}" in
|
||||||
|
start)
|
||||||
|
init_directories
|
||||||
|
setup_iptables_chain
|
||||||
|
start_worker
|
||||||
|
;;
|
||||||
|
sync)
|
||||||
|
init_directories
|
||||||
|
setup_iptables_chain
|
||||||
|
sync_geoip
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
init_directories
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
lookup)
|
||||||
|
if [[ -z "${2:-}" ]]; then
|
||||||
|
echo "Nutzung: $0 lookup <IP-Adresse>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
init_directories
|
||||||
|
lookup_ip "$2"
|
||||||
|
;;
|
||||||
|
flush)
|
||||||
|
init_directories
|
||||||
|
flush_geoip_bans
|
||||||
|
;;
|
||||||
|
flush-cache)
|
||||||
|
init_directories
|
||||||
|
flush_cache
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||||
|
local wpid
|
||||||
|
wpid=$(cat "$WORKER_PID_FILE")
|
||||||
|
if kill -0 "$wpid" 2>/dev/null; then
|
||||||
|
kill "$wpid" 2>/dev/null || true
|
||||||
|
rm -f "$WORKER_PID_FILE"
|
||||||
|
echo "GeoIP-Worker gestoppt"
|
||||||
|
else
|
||||||
|
rm -f "$WORKER_PID_FILE"
|
||||||
|
echo "GeoIP-Worker war nicht aktiv"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "GeoIP-Worker läuft nicht"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
cat << USAGE
|
||||||
|
AdGuard Shield - GeoIP Worker
|
||||||
|
|
||||||
|
Nutzung: $0 {start|stop|sync|status|lookup|flush|flush-cache}
|
||||||
|
|
||||||
|
Befehle:
|
||||||
|
start Startet den GeoIP-Worker (Hintergrundprozess)
|
||||||
|
stop Stoppt den GeoIP-Worker
|
||||||
|
sync Einmalige GeoIP-Prüfung aller aktiven Clients
|
||||||
|
status Zeigt GeoIP-Status und aktive Sperren
|
||||||
|
lookup <IP> GeoIP-Lookup für eine einzelne IP
|
||||||
|
flush Alle GeoIP-Sperren aufheben
|
||||||
|
flush-cache GeoIP-Lookup-Cache leeren
|
||||||
|
|
||||||
|
Konfiguration in: $CONFIG_FILE
|
||||||
|
GEOIP_ENABLED=true/false
|
||||||
|
GEOIP_MODE=blocklist/allowlist
|
||||||
|
GEOIP_COUNTRIES="CN,RU,..."
|
||||||
|
|
||||||
|
USAGE
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
78
install.sh
78
install.sh
@@ -6,7 +6,7 @@
|
|||||||
# Lizenz: MIT
|
# Lizenz: MIT
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
VERSION="v0.5.2"
|
VERSION="v0.9.0"
|
||||||
|
|
||||||
set -euo pipefail
|
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 test${NC} # API-Verbindung testen"
|
||||||
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh dry-run${NC} # Testmodus (nur loggen)"
|
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh dry-run${NC} # Testmodus (nur loggen)"
|
||||||
echo ""
|
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 "${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 status${NC} # Firewall-Regeln anzeigen"
|
||||||
echo -e " ${CYAN}sudo /opt/adguard-shield/iptables-helper.sh ban IP${NC} # IP manuell sperren"
|
echo -e " ${CYAN}sudo /opt/adguard-shield/iptables-helper.sh ban IP${NC} # IP manuell sperren"
|
||||||
@@ -120,11 +125,25 @@ print_help() {
|
|||||||
echo -e " ${CYAN}sudo /opt/adguard-shield/report-generator.sh install${NC} # Cron-Job einrichten"
|
echo -e " ${CYAN}sudo /opt/adguard-shield/report-generator.sh install${NC} # Cron-Job einrichten"
|
||||||
echo -e " ${CYAN}sudo /opt/adguard-shield/report-generator.sh remove${NC} # Cron-Job entfernen"
|
echo -e " ${CYAN}sudo /opt/adguard-shield/report-generator.sh remove${NC} # Cron-Job entfernen"
|
||||||
echo ""
|
echo ""
|
||||||
|
echo -e "${BOLD}Watchdog-Befehle:${NC}"
|
||||||
|
echo -e " ${CYAN}sudo systemctl status adguard-shield-watchdog.timer${NC} # Watchdog-Status"
|
||||||
|
echo -e " ${CYAN}sudo systemctl list-timers adguard-shield-watchdog.timer${NC} # Nächste Ausführung"
|
||||||
|
echo -e " ${CYAN}sudo systemctl enable adguard-shield-watchdog.timer${NC} # Watchdog aktivieren"
|
||||||
|
echo -e " ${CYAN}sudo systemctl disable adguard-shield-watchdog.timer${NC} # Watchdog deaktivieren"
|
||||||
|
echo -e " ${CYAN}sudo journalctl -u adguard-shield-watchdog.service${NC} # Watchdog-Logs"
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}GeoIP-Befehle:${NC}"
|
||||||
|
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-status${NC} # GeoIP-Status anzeigen"
|
||||||
|
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-sync${NC} # Einmalige GeoIP-Prüfung"
|
||||||
|
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-flush${NC} # Alle GeoIP-Sperren aufheben"
|
||||||
|
echo -e " ${CYAN}sudo /opt/adguard-shield/adguard-shield.sh geoip-lookup IP${NC} # GeoIP-Lookup einer IP"
|
||||||
|
echo ""
|
||||||
echo -e "${BOLD}Voraussetzungen:${NC}"
|
echo -e "${BOLD}Voraussetzungen:${NC}"
|
||||||
echo " - Linux Server (Debian/Ubuntu empfohlen)"
|
echo " - Linux Server (Debian/Ubuntu empfohlen)"
|
||||||
echo " - Root-Zugriff (sudo)"
|
echo " - Root-Zugriff (sudo)"
|
||||||
echo " - AdGuard Home installiert und erreichbar"
|
echo " - AdGuard Home installiert und erreichbar"
|
||||||
echo " - Pakete: curl, jq, iptables, gawk (werden bei Installation automatisch installiert)"
|
echo " - Pakete: curl, jq, iptables, gawk (werden bei Installation automatisch installiert)"
|
||||||
|
echo " - GeoIP (optional): geoip-bin + geoip-database oder MaxMind GeoLite2 DB"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BOLD}Dokumentation:${NC}"
|
echo -e "${BOLD}Dokumentation:${NC}"
|
||||||
echo " https://git.techniverse.net/scriptos/adguard-shield"
|
echo " https://git.techniverse.net/scriptos/adguard-shield"
|
||||||
@@ -241,8 +260,12 @@ install_files() {
|
|||||||
cp "$SCRIPT_DIR/iptables-helper.sh" "$INSTALL_DIR/"
|
cp "$SCRIPT_DIR/iptables-helper.sh" "$INSTALL_DIR/"
|
||||||
cp "$SCRIPT_DIR/unban-expired.sh" "$INSTALL_DIR/"
|
cp "$SCRIPT_DIR/unban-expired.sh" "$INSTALL_DIR/"
|
||||||
cp "$SCRIPT_DIR/external-blocklist-worker.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/report-generator.sh" "$INSTALL_DIR/"
|
||||||
|
cp "$SCRIPT_DIR/adguard-shield-watchdog.sh" "$INSTALL_DIR/"
|
||||||
cp "$SCRIPT_DIR/uninstall.sh" "$INSTALL_DIR/"
|
cp "$SCRIPT_DIR/uninstall.sh" "$INSTALL_DIR/"
|
||||||
|
cp "$SCRIPT_DIR/geoip-worker.sh" "$INSTALL_DIR/"
|
||||||
|
cp "$SCRIPT_DIR/offense-cleanup-worker.sh" "$INSTALL_DIR/"
|
||||||
|
|
||||||
# Templates kopieren
|
# Templates kopieren
|
||||||
mkdir -p "$INSTALL_DIR/templates"
|
mkdir -p "$INSTALL_DIR/templates"
|
||||||
@@ -254,8 +277,12 @@ install_files() {
|
|||||||
chmod +x "$INSTALL_DIR/iptables-helper.sh"
|
chmod +x "$INSTALL_DIR/iptables-helper.sh"
|
||||||
chmod +x "$INSTALL_DIR/unban-expired.sh"
|
chmod +x "$INSTALL_DIR/unban-expired.sh"
|
||||||
chmod +x "$INSTALL_DIR/external-blocklist-worker.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/report-generator.sh"
|
||||||
|
chmod +x "$INSTALL_DIR/adguard-shield-watchdog.sh"
|
||||||
chmod +x "$INSTALL_DIR/uninstall.sh"
|
chmod +x "$INSTALL_DIR/uninstall.sh"
|
||||||
|
chmod +x "$INSTALL_DIR/geoip-worker.sh"
|
||||||
|
chmod +x "$INSTALL_DIR/offense-cleanup-worker.sh"
|
||||||
|
|
||||||
echo -e " ✅ Dateien installiert"
|
echo -e " ✅ Dateien installiert"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -349,18 +376,22 @@ install_service() {
|
|||||||
echo -e "${YELLOW}Installiere systemd Service...${NC}"
|
echo -e "${YELLOW}Installiere systemd Service...${NC}"
|
||||||
|
|
||||||
cp "$SCRIPT_DIR/adguard-shield.service" "$SERVICE_FILE"
|
cp "$SCRIPT_DIR/adguard-shield.service" "$SERVICE_FILE"
|
||||||
|
cp "$SCRIPT_DIR/adguard-shield-watchdog.service" /etc/systemd/system/adguard-shield-watchdog.service
|
||||||
|
cp "$SCRIPT_DIR/adguard-shield-watchdog.timer" /etc/systemd/system/adguard-shield-watchdog.timer
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
echo -e " ✅ Service-Datei installiert"
|
echo -e " ✅ Service-Dateien installiert (inkl. Watchdog)"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Interaktiv: Autostart beim Booten?
|
# Interaktiv: Autostart beim Booten?
|
||||||
read -rep " Soll AdGuard Shield beim Booten automatisch starten? [J/n]: " autostart
|
read -rep " Soll AdGuard Shield beim Booten automatisch starten? [J/n]: " autostart
|
||||||
if [[ "${autostart,,}" != "n" ]]; then
|
if [[ "${autostart,,}" != "n" ]]; then
|
||||||
systemctl enable adguard-shield.service
|
systemctl enable adguard-shield.service
|
||||||
echo -e " ✅ Autostart aktiviert"
|
systemctl enable adguard-shield-watchdog.timer
|
||||||
|
echo -e " ✅ Autostart aktiviert (inkl. Watchdog-Timer)"
|
||||||
else
|
else
|
||||||
systemctl disable adguard-shield.service 2>/dev/null || true
|
systemctl disable adguard-shield.service 2>/dev/null || true
|
||||||
|
systemctl disable adguard-shield-watchdog.timer 2>/dev/null || true
|
||||||
echo -e " ℹ️ Autostart nicht aktiviert"
|
echo -e " ℹ️ Autostart nicht aktiviert"
|
||||||
echo -e " ${YELLOW}Später aktivieren mit: sudo systemctl enable adguard-shield${NC}"
|
echo -e " ${YELLOW}Später aktivieren mit: sudo systemctl enable adguard-shield${NC}"
|
||||||
fi
|
fi
|
||||||
@@ -493,6 +524,15 @@ print_summary() {
|
|||||||
echo " Konfiguration: $INSTALL_DIR/adguard-shield.conf"
|
echo " Konfiguration: $INSTALL_DIR/adguard-shield.conf"
|
||||||
echo " Service: adguard-shield.service ($svc_status)"
|
echo " Service: adguard-shield.service ($svc_status)"
|
||||||
echo " Autostart: $autostart_status"
|
echo " Autostart: $autostart_status"
|
||||||
|
|
||||||
|
# Watchdog-Status
|
||||||
|
local watchdog_status="deaktiviert"
|
||||||
|
if systemctl is-active adguard-shield-watchdog.timer &>/dev/null 2>&1; then
|
||||||
|
watchdog_status="aktiv ✅"
|
||||||
|
elif systemctl is-enabled adguard-shield-watchdog.timer &>/dev/null 2>&1; then
|
||||||
|
watchdog_status="aktiviert (Timer nicht gestartet)"
|
||||||
|
fi
|
||||||
|
echo " Watchdog: $watchdog_status"
|
||||||
echo " Log-Datei: /var/log/adguard-shield.log"
|
echo " Log-Datei: /var/log/adguard-shield.log"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Nützliche Befehle:"
|
echo " Nützliche Befehle:"
|
||||||
@@ -572,6 +612,15 @@ do_status() {
|
|||||||
echo -e " ❌ Konfiguration: fehlt!"
|
echo -e " ❌ Konfiguration: fehlt!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Watchdog-Status
|
||||||
|
if systemctl is-active adguard-shield-watchdog.timer &>/dev/null 2>&1; then
|
||||||
|
echo -e " ✅ Watchdog-Timer: aktiv"
|
||||||
|
elif systemctl is-enabled adguard-shield-watchdog.timer &>/dev/null 2>&1; then
|
||||||
|
echo -e " ⚠️ Watchdog-Timer: aktiviert aber nicht gestartet"
|
||||||
|
else
|
||||||
|
echo -e " ❌ Watchdog-Timer: nicht installiert/deaktiviert"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,7 +660,8 @@ do_install() {
|
|||||||
read -rep " Soll der AdGuard Shield Service jetzt gestartet werden? [J/n]: " start_now
|
read -rep " Soll der AdGuard Shield Service jetzt gestartet werden? [J/n]: " start_now
|
||||||
if [[ "${start_now,,}" != "n" ]]; then
|
if [[ "${start_now,,}" != "n" ]]; then
|
||||||
systemctl start adguard-shield
|
systemctl start adguard-shield
|
||||||
echo -e " ✅ Service gestartet"
|
systemctl start adguard-shield-watchdog.timer 2>/dev/null || true
|
||||||
|
echo -e " ✅ Service gestartet (inkl. Watchdog-Timer)"
|
||||||
else
|
else
|
||||||
echo -e " ℹ️ Service nicht gestartet"
|
echo -e " ℹ️ Service nicht gestartet"
|
||||||
echo -e " ${YELLOW}Später starten mit: sudo systemctl start adguard-shield${NC}"
|
echo -e " ${YELLOW}Später starten mit: sudo systemctl start adguard-shield${NC}"
|
||||||
@@ -644,18 +694,28 @@ do_update() {
|
|||||||
# Service-Datei aktualisieren
|
# Service-Datei aktualisieren
|
||||||
echo -e "${YELLOW}Aktualisiere systemd Service...${NC}"
|
echo -e "${YELLOW}Aktualisiere systemd Service...${NC}"
|
||||||
cp "$SCRIPT_DIR/adguard-shield.service" "$SERVICE_FILE"
|
cp "$SCRIPT_DIR/adguard-shield.service" "$SERVICE_FILE"
|
||||||
|
cp "$SCRIPT_DIR/adguard-shield-watchdog.service" /etc/systemd/system/adguard-shield-watchdog.service
|
||||||
|
cp "$SCRIPT_DIR/adguard-shield-watchdog.timer" /etc/systemd/system/adguard-shield-watchdog.timer
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
echo -e " ✅ Service-Datei aktualisiert"
|
echo -e " ✅ Service-Dateien aktualisiert (inkl. Watchdog)"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Interaktiv: Autostart beim Booten?
|
# Interaktiv: Autostart beim Booten?
|
||||||
if systemctl is-enabled adguard-shield &>/dev/null; then
|
if systemctl is-enabled adguard-shield &>/dev/null; then
|
||||||
echo -e " ℹ️ Autostart ist bereits aktiviert"
|
echo -e " ℹ️ Autostart ist bereits aktiviert"
|
||||||
|
# Watchdog-Timer auch aktivieren falls noch nicht aktiv
|
||||||
|
if ! systemctl is-enabled adguard-shield-watchdog.timer &>/dev/null 2>&1; then
|
||||||
|
systemctl enable adguard-shield-watchdog.timer
|
||||||
|
systemctl start adguard-shield-watchdog.timer
|
||||||
|
echo -e " ✅ Watchdog-Timer aktiviert"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
read -rep " Soll AdGuard Shield beim Booten automatisch starten? [J/n]: " autostart
|
read -rep " Soll AdGuard Shield beim Booten automatisch starten? [J/n]: " autostart
|
||||||
if [[ "${autostart,,}" != "n" ]]; then
|
if [[ "${autostart,,}" != "n" ]]; then
|
||||||
systemctl enable adguard-shield.service
|
systemctl enable adguard-shield.service
|
||||||
echo -e " ✅ Autostart aktiviert"
|
systemctl enable adguard-shield-watchdog.timer
|
||||||
|
systemctl start adguard-shield-watchdog.timer
|
||||||
|
echo -e " ✅ Autostart aktiviert (inkl. Watchdog-Timer)"
|
||||||
else
|
else
|
||||||
echo -e " ℹ️ Autostart bleibt deaktiviert"
|
echo -e " ℹ️ Autostart bleibt deaktiviert"
|
||||||
fi
|
fi
|
||||||
@@ -751,8 +811,14 @@ do_uninstall() {
|
|||||||
rm -f "$INSTALL_DIR/iptables-helper.sh"
|
rm -f "$INSTALL_DIR/iptables-helper.sh"
|
||||||
rm -f "$INSTALL_DIR/unban-expired.sh"
|
rm -f "$INSTALL_DIR/unban-expired.sh"
|
||||||
rm -f "$INSTALL_DIR/external-blocklist-worker.sh"
|
rm -f "$INSTALL_DIR/external-blocklist-worker.sh"
|
||||||
|
rm -f "$INSTALL_DIR/external-whitelist-worker.sh"
|
||||||
|
rm -f "$INSTALL_DIR/offense-cleanup-worker.sh"
|
||||||
|
rm -f "$INSTALL_DIR/geoip-worker.sh"
|
||||||
rm -f "$INSTALL_DIR/report-generator.sh"
|
rm -f "$INSTALL_DIR/report-generator.sh"
|
||||||
|
rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh"
|
||||||
|
rm -f "$INSTALL_DIR/uninstall.sh"
|
||||||
rm -rf "$INSTALL_DIR/templates"
|
rm -rf "$INSTALL_DIR/templates"
|
||||||
|
rm -rf "$INSTALL_DIR/geoip"
|
||||||
echo " ✅ Scripts entfernt (Konfiguration und Logs behalten)"
|
echo " ✅ Scripts entfernt (Konfiguration und Logs behalten)"
|
||||||
else
|
else
|
||||||
rm -rf "$INSTALL_DIR"
|
rm -rf "$INSTALL_DIR"
|
||||||
|
|||||||
268
offense-cleanup-worker.sh
Normal file
268
offense-cleanup-worker.sh
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
###############################################################################
|
||||||
|
# AdGuard Shield - Offense-Cleanup-Worker
|
||||||
|
# Räumt abgelaufene Offense-Zähler (progressive Sperren) automatisch auf.
|
||||||
|
# Entfernt .offenses-Dateien, deren letztes Vergehen länger als
|
||||||
|
# PROGRESSIVE_BAN_RESET_AFTER zurückliegt.
|
||||||
|
# Wird als Hintergrundprozess vom Hauptscript gestartet.
|
||||||
|
#
|
||||||
|
# Autor: Patrick Asmus
|
||||||
|
# E-Mail: support@techniverse.net
|
||||||
|
# Datum: 2026-04-16
|
||||||
|
# Lizenz: MIT
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_FILE="${SCRIPT_DIR}/adguard-shield.conf"
|
||||||
|
|
||||||
|
# ─── Konfiguration laden ───────────────────────────────────────────────────────
|
||||||
|
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||||
|
echo "FEHLER: Konfigurationsdatei nicht gefunden: $CONFIG_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# shellcheck source=adguard-shield.conf
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
|
# ─── Niedrigste Priorität setzen (CPU + I/O) ─────────────────────────────────
|
||||||
|
# Stellt sicher, dass der Worker auch bei manuellem Start nie andere Dienste
|
||||||
|
# verdrängt. nice 19 = niedrigste CPU-Priorität, ionice idle = nur bei freier I/O.
|
||||||
|
renice -n 19 $$ >/dev/null 2>&1 || true
|
||||||
|
ionice -c 3 -p $$ >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
||||||
|
WORKER_PID_FILE="/var/run/adguard-offense-cleanup-worker.pid"
|
||||||
|
|
||||||
|
# ─── Prüfintervall ───────────────────────────────────────────────────────────
|
||||||
|
# Prüft einmal pro Stunde – das ist völlig ausreichend für diese Aufgabe
|
||||||
|
OFFENSE_CLEANUP_INTERVAL=3600
|
||||||
|
|
||||||
|
# ─── Logging (eigene Funktion, nutzt gleiche Log-Datei) ───────────────────────
|
||||||
|
declare -A LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
|
||||||
|
|
||||||
|
log() {
|
||||||
|
local level="$1"
|
||||||
|
shift
|
||||||
|
local message="$*"
|
||||||
|
local configured_level="${LOG_LEVEL:-INFO}"
|
||||||
|
|
||||||
|
if [[ ${LOG_LEVELS[$level]:-1} -ge ${LOG_LEVELS[$configured_level]:-1} ]]; then
|
||||||
|
local timestamp
|
||||||
|
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
local log_entry="[$timestamp] [$level] [OFFENSE-CLEANUP] $message"
|
||||||
|
echo "$log_entry" | tee -a "$LOG_FILE" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Hilfsfunktionen ─────────────────────────────────────────────────────────
|
||||||
|
format_duration() {
|
||||||
|
local seconds="$1"
|
||||||
|
if [[ "$seconds" -eq 0 ]]; then
|
||||||
|
echo "PERMANENT"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ "$seconds" -ge 86400 ]]; then
|
||||||
|
echo "$((seconds / 86400))d $((seconds % 86400 / 3600))h"
|
||||||
|
elif [[ "$seconds" -ge 3600 ]]; then
|
||||||
|
echo "$((seconds / 3600))h $((seconds % 3600 / 60))m"
|
||||||
|
elif [[ "$seconds" -ge 60 ]]; then
|
||||||
|
echo "$((seconds / 60))m $((seconds % 60))s"
|
||||||
|
else
|
||||||
|
echo "${seconds}s"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
||||||
|
init_directories() {
|
||||||
|
mkdir -p "${STATE_DIR}"
|
||||||
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Abgelaufene Offense-Zähler aufräumen ────────────────────────────────────
|
||||||
|
cleanup_expired_offenses() {
|
||||||
|
local reset_after="${PROGRESSIVE_BAN_RESET_AFTER:-86400}"
|
||||||
|
local now
|
||||||
|
now=$(date '+%s')
|
||||||
|
local cleaned=0
|
||||||
|
|
||||||
|
local batch_count=0
|
||||||
|
for offense_file in "${STATE_DIR}"/*.offenses; do
|
||||||
|
[[ -f "$offense_file" ]] || continue
|
||||||
|
|
||||||
|
local last_offense_epoch client_ip offense_level
|
||||||
|
last_offense_epoch=$(grep '^LAST_OFFENSE_EPOCH=' "$offense_file" | cut -d= -f2 || true)
|
||||||
|
client_ip=$(grep '^CLIENT_IP=' "$offense_file" | cut -d= -f2 || true)
|
||||||
|
offense_level=$(grep '^OFFENSE_LEVEL=' "$offense_file" | cut -d= -f2 || true)
|
||||||
|
|
||||||
|
# Kein Zeitstempel vorhanden → überspringen
|
||||||
|
if [[ -z "$last_offense_epoch" ]]; then
|
||||||
|
log "DEBUG" "Offense-Datei ohne Zeitstempel übersprungen: $offense_file"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
local elapsed=$((now - last_offense_epoch))
|
||||||
|
|
||||||
|
if [[ $elapsed -gt $reset_after ]]; then
|
||||||
|
log "INFO" "Offense-Zähler abgelaufen: $client_ip (Stufe $offense_level, letztes Vergehen vor $(format_duration $elapsed)) → entfernt"
|
||||||
|
rm -f "$offense_file"
|
||||||
|
cleaned=$((cleaned + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Alle 10 Dateien kurz pausieren, um I/O-Bursts zu vermeiden
|
||||||
|
batch_count=$((batch_count + 1))
|
||||||
|
if (( batch_count % 10 == 0 )); then
|
||||||
|
sleep 0.1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $cleaned -gt 0 ]]; then
|
||||||
|
log "INFO" "Offense-Cleanup: $cleaned abgelaufene Zähler entfernt"
|
||||||
|
else
|
||||||
|
log "DEBUG" "Offense-Cleanup: keine abgelaufenen Zähler gefunden"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── PID-Management ──────────────────────────────────────────────────────────
|
||||||
|
write_pid() {
|
||||||
|
echo $$ > "$WORKER_PID_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
log "INFO" "Offense-Cleanup-Worker wird beendet..."
|
||||||
|
rm -f "$WORKER_PID_FILE"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
check_already_running() {
|
||||||
|
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||||
|
local old_pid
|
||||||
|
old_pid=$(cat "$WORKER_PID_FILE")
|
||||||
|
if kill -0 "$old_pid" 2>/dev/null; then
|
||||||
|
log "DEBUG" "Offense-Cleanup-Worker läuft bereits (PID: $old_pid)"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
rm -f "$WORKER_PID_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Status anzeigen ─────────────────────────────────────────────────────────
|
||||||
|
show_status() {
|
||||||
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
|
echo " Offense-Cleanup-Worker - Status"
|
||||||
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" != "true" ]]; then
|
||||||
|
echo " ⚠️ Progressive Sperren sind deaktiviert"
|
||||||
|
echo " Aktivieren: PROGRESSIVE_BAN_ENABLED=true in $CONFIG_FILE"
|
||||||
|
echo ""
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Worker-Prozess Status
|
||||||
|
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||||
|
local pid
|
||||||
|
pid=$(cat "$WORKER_PID_FILE")
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
echo " 🟢 Worker läuft (PID: $pid)"
|
||||||
|
else
|
||||||
|
echo " 🔴 Worker nicht aktiv (veraltete PID-Datei)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " 🔴 Worker nicht aktiv"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Reset-Zeitraum: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}")"
|
||||||
|
echo " Prüfintervall: $(format_duration "$OFFENSE_CLEANUP_INTERVAL")"
|
||||||
|
|
||||||
|
# Aktuelle Offense-Dateien zählen
|
||||||
|
local total=0
|
||||||
|
local expired=0
|
||||||
|
local now
|
||||||
|
now=$(date '+%s')
|
||||||
|
local reset_after="${PROGRESSIVE_BAN_RESET_AFTER:-86400}"
|
||||||
|
|
||||||
|
for offense_file in "${STATE_DIR}"/*.offenses; do
|
||||||
|
[[ -f "$offense_file" ]] || continue
|
||||||
|
total=$((total + 1))
|
||||||
|
local last_epoch
|
||||||
|
last_epoch=$(grep '^LAST_OFFENSE_EPOCH=' "$offense_file" | cut -d= -f2 || true)
|
||||||
|
if [[ -n "$last_epoch" && $((now - last_epoch)) -gt $reset_after ]]; then
|
||||||
|
expired=$((expired + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Offense-Zähler gesamt: $total"
|
||||||
|
echo " Davon abgelaufen: $expired"
|
||||||
|
echo ""
|
||||||
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Hauptschleife ──────────────────────────────────────────────────────────
|
||||||
|
main_loop() {
|
||||||
|
init_directories
|
||||||
|
|
||||||
|
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||||
|
log "INFO" "Offense-Cleanup-Worker gestartet"
|
||||||
|
log "INFO" " Reset-Zeitraum: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}")"
|
||||||
|
log "INFO" " Prüfintervall: $(format_duration "$OFFENSE_CLEANUP_INTERVAL")"
|
||||||
|
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
cleanup_expired_offenses
|
||||||
|
sleep "$OFFENSE_CLEANUP_INTERVAL"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Signal-Handler ──────────────────────────────────────────────────────────
|
||||||
|
trap cleanup SIGTERM SIGINT SIGHUP
|
||||||
|
|
||||||
|
# ─── Kommandozeilen-Argumente ────────────────────────────────────────────────
|
||||||
|
case "${1:-start}" in
|
||||||
|
start)
|
||||||
|
if ! check_already_running; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
write_pid
|
||||||
|
main_loop
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||||
|
kill "$(cat "$WORKER_PID_FILE")" 2>/dev/null || true
|
||||||
|
rm -f "$WORKER_PID_FILE"
|
||||||
|
echo "Offense-Cleanup-Worker gestoppt"
|
||||||
|
else
|
||||||
|
echo "Offense-Cleanup-Worker läuft nicht"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
run-once)
|
||||||
|
init_directories
|
||||||
|
log "INFO" "Einmaliger Offense-Cleanup..."
|
||||||
|
cleanup_expired_offenses
|
||||||
|
log "INFO" "Cleanup abgeschlossen"
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
init_directories
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
cat << USAGE
|
||||||
|
AdGuard Shield - Offense-Cleanup-Worker
|
||||||
|
|
||||||
|
Nutzung: $0 {start|stop|run-once|status}
|
||||||
|
|
||||||
|
Befehle:
|
||||||
|
start Startet den Worker (Dauerbetrieb)
|
||||||
|
stop Stoppt den Worker
|
||||||
|
run-once Einmaliger Cleanup-Durchlauf
|
||||||
|
status Zeigt Status und aktuelle Offense-Zähler
|
||||||
|
|
||||||
|
Konfiguration: $CONFIG_FILE
|
||||||
|
USAGE
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -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_EMAIL_FROM="${REPORT_EMAIL_FROM:-adguard-shield@$(hostname -f 2>/dev/null || hostname)}"
|
||||||
REPORT_FORMAT="${REPORT_FORMAT:-html}"
|
REPORT_FORMAT="${REPORT_FORMAT:-html}"
|
||||||
REPORT_MAIL_CMD="${REPORT_MAIL_CMD:-msmtp}"
|
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_FILE="${BAN_HISTORY_FILE:-/var/log/adguard-shield-bans.log}"
|
||||||
BAN_HISTORY_RETENTION_DAYS="${BAN_HISTORY_RETENTION_DAYS:-0}"
|
BAN_HISTORY_RETENTION_DAYS="${BAN_HISTORY_RETENTION_DAYS:-0}"
|
||||||
STATE_DIR="${STATE_DIR:-/var/lib/adguard-shield}"
|
STATE_DIR="${STATE_DIR:-/var/lib/adguard-shield}"
|
||||||
@@ -65,6 +66,49 @@ log() {
|
|||||||
echo "[$timestamp] [$level] [REPORT] $message" | tee -a "$LOG_FILE" >&2
|
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 ───────────────────────────────────────────────
|
# ─── Berichtszeitraum berechnen ───────────────────────────────────────────────
|
||||||
|
|
||||||
# Gibt Epoch-Wert für heute 00:00:00 (Mitternacht) zurück
|
# Gibt Epoch-Wert für heute 00:00:00 (Mitternacht) zurück
|
||||||
@@ -135,40 +179,58 @@ get_period_end_epoch() {
|
|||||||
echo $((today_midnight - 1))
|
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 ────────────────────────────────────────
|
# ─── 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() {
|
filter_history_by_period() {
|
||||||
local start_epoch="$1"
|
local start_epoch="$1"
|
||||||
local end_epoch="$2"
|
local end_epoch="$2"
|
||||||
|
|
||||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
[[ ! -f "$BAN_HISTORY_FILE" ]] && return
|
||||||
return
|
_load_history_cache
|
||||||
fi
|
[[ -z "$HISTORY_CACHE" ]] && return
|
||||||
|
|
||||||
while IFS= read -r line; do
|
# Aus dem Cache filtern und im Original-Format ausgeben (Abwärtskompatibilität)
|
||||||
# Kommentare und leere Zeilen überspringen
|
echo "$HISTORY_CACHE" | awk -F'|' -v s="$start_epoch" -v e="$end_epoch" '
|
||||||
[[ "$line" =~ ^#.*$ || -z "$line" ]] && continue
|
$1 >= s && $1 <= e {
|
||||||
|
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n",
|
||||||
# Zeitstempel extrahieren (erstes Feld, Format: YYYY-MM-DD HH:MM:SS)
|
$2, $3, $4, $5, $6, $7, $8, $9
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Ban-History bereinigen ────────────────────────────────────────────────────
|
# ─── 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() {
|
cleanup_ban_history() {
|
||||||
[[ ! -f "$BAN_HISTORY_FILE" ]] && return
|
[[ ! -f "$BAN_HISTORY_FILE" ]] && return
|
||||||
[[ "$BAN_HISTORY_RETENTION_DAYS" == "0" || -z "$BAN_HISTORY_RETENTION_DAYS" ]] && return
|
[[ "$BAN_HISTORY_RETENTION_DAYS" == "0" || -z "$BAN_HISTORY_RETENTION_DAYS" ]] && return
|
||||||
@@ -178,36 +240,69 @@ cleanup_ban_history() {
|
|||||||
[[ -z "$cutoff_epoch" ]] && return
|
[[ -z "$cutoff_epoch" ]] && return
|
||||||
|
|
||||||
local tmp_file="${BAN_HISTORY_FILE}.tmp"
|
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
|
awk -v cutoff="$cutoff_epoch" '
|
||||||
# Header-Zeilen immer beibehalten
|
/^#/ || /^[[:space:]]*$/ { print; next }
|
||||||
if [[ "$line" =~ ^#.*$ || -z "$line" ]]; then
|
{
|
||||||
echo "$line"
|
n = split($0, f, "|")
|
||||||
continue
|
if (n < 2) { print; next }
|
||||||
fi
|
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
|
lines_after=$(wc -l < "$tmp_file")
|
||||||
timestamp=$(echo "$line" | awk -F'|' '{print $1}' | xargs)
|
local removed=$(( lines_before - lines_after ))
|
||||||
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"
|
|
||||||
|
|
||||||
if [[ $removed -gt 0 ]]; then
|
if [[ $removed -gt 0 ]]; then
|
||||||
mv "$tmp_file" "$BAN_HISTORY_FILE"
|
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"
|
log "INFO" "Ban-History bereinigt: $removed Einträge älter als ${BAN_HISTORY_RETENTION_DAYS} Tage entfernt"
|
||||||
else
|
else
|
||||||
rm -f "$tmp_file"
|
rm -f "$tmp_file"
|
||||||
fi
|
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 ────────────────────────────────────────────────────
|
# ─── 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() {
|
calculate_stats() {
|
||||||
# Ban-History bereinigen (falls Retention konfiguriert)
|
# Ban-History bereinigen (falls Retention konfiguriert)
|
||||||
cleanup_ban_history
|
cleanup_ban_history
|
||||||
@@ -217,11 +312,10 @@ calculate_stats() {
|
|||||||
local end_epoch
|
local end_epoch
|
||||||
end_epoch=$(get_period_end_epoch)
|
end_epoch=$(get_period_end_epoch)
|
||||||
|
|
||||||
local filtered_data
|
_load_history_cache
|
||||||
filtered_data=$(filter_history_by_period "$start_epoch" "$end_epoch")
|
|
||||||
|
|
||||||
# Wenn keine Daten vorhanden, Standardwerte
|
# Wenn keine History-Datei vorhanden, Standardwerte setzen
|
||||||
if [[ -z "$filtered_data" ]]; then
|
if [[ -z "$HISTORY_CACHE" ]]; then
|
||||||
TOTAL_BANS=0
|
TOTAL_BANS=0
|
||||||
TOTAL_UNBANS=0
|
TOTAL_UNBANS=0
|
||||||
UNIQUE_IPS=0
|
UNIQUE_IPS=0
|
||||||
@@ -232,6 +326,7 @@ calculate_stats() {
|
|||||||
SUBDOMAIN_FLOOD_BANS=0
|
SUBDOMAIN_FLOOD_BANS=0
|
||||||
EXTERNAL_BLOCKLIST_BANS=0
|
EXTERNAL_BLOCKLIST_BANS=0
|
||||||
BUSIEST_DAY="–"
|
BUSIEST_DAY="–"
|
||||||
|
BUSIEST_DAY_LABEL="Aktivster Tag"
|
||||||
TOP10_IPS=""
|
TOP10_IPS=""
|
||||||
TOP10_DOMAINS=""
|
TOP10_DOMAINS=""
|
||||||
PROTOCOL_STATS=""
|
PROTOCOL_STATS=""
|
||||||
@@ -239,17 +334,105 @@ calculate_stats() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Gesamtzahl Sperren
|
# Einen einzigen awk-Pass über den Cache: alle Statistiken auf einmal
|
||||||
TOTAL_BANS=$(echo "$filtered_data" | grep -c '| BAN ' || echo "0")
|
# 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
|
local awk_result
|
||||||
TOTAL_UNBANS=$(echo "$filtered_data" | grep -c '| UNBAN ' || echo "0")
|
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)
|
# Einfache Kennzahlen aus dem awk-Ergebnis extrahieren
|
||||||
UNIQUE_IPS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $3}' | xargs -I{} echo {} | sort -u | wc -l | xargs)
|
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")
|
local busiest_raw
|
||||||
PERMANENT_BANS=$(echo "$filtered_data" | grep '| BAN ' | awk -F'|' '{print $6}' | grep -ic 'permanent' || echo "0")
|
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)
|
# Aktuell aktive Sperren (aus State-Dateien)
|
||||||
ACTIVE_BANS=0
|
ACTIVE_BANS=0
|
||||||
@@ -259,41 +442,21 @@ calculate_stats() {
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# AbuseIPDB Reports (suche in Log-Datei)
|
# AbuseIPDB Reports – zeitraum-gefiltert aus der Logdatei via awk+mktime
|
||||||
ABUSEIPDB_REPORTS=0
|
ABUSEIPDB_REPORTS=0
|
||||||
if [[ -f "$LOG_FILE" ]]; then
|
if [[ -f "$LOG_FILE" ]]; then
|
||||||
local abuseipdb_start_date
|
ABUSEIPDB_REPORTS=$(grep "AbuseIPDB:.*erfolgreich gemeldet" "$LOG_FILE" 2>/dev/null | \
|
||||||
abuseipdb_start_date=$(date -d "@$start_epoch" '+%Y-%m-%d' 2>/dev/null || date -r "$start_epoch" '+%Y-%m-%d')
|
awk -v s="$start_epoch" -v e="$end_epoch" '
|
||||||
ABUSEIPDB_REPORTS=$(grep -c "AbuseIPDB: IP .* erfolgreich gemeldet" "$LOG_FILE" 2>/dev/null | head -1 || echo "0")
|
{
|
||||||
|
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
|
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 ─────────────────────────────────────────────────
|
# ─── HTML-Tabellen generieren ─────────────────────────────────────────────────
|
||||||
@@ -420,6 +583,65 @@ generate_recent_bans_html() {
|
|||||||
echo "$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 ──────────────────────────────────────────────────
|
# ─── TXT-Tabellen generieren ──────────────────────────────────────────────────
|
||||||
generate_top10_ips_txt() {
|
generate_top10_ips_txt() {
|
||||||
if [[ -z "$TOP10_IPS" ]]; then
|
if [[ -z "$TOP10_IPS" ]]; then
|
||||||
@@ -495,6 +717,46 @@ generate_recent_bans_txt() {
|
|||||||
done <<< "$RECENT_BANS"
|
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 ────────────────────────────────────────────────────────
|
# ─── Report generieren ────────────────────────────────────────────────────────
|
||||||
generate_report() {
|
generate_report() {
|
||||||
local format="${1:-$REPORT_FORMAT}"
|
local format="${1:-$REPORT_FORMAT}"
|
||||||
@@ -504,6 +766,9 @@ generate_report() {
|
|||||||
# Statistiken berechnen
|
# Statistiken berechnen
|
||||||
calculate_stats
|
calculate_stats
|
||||||
|
|
||||||
|
# Update-Verfügbarkeit prüfen
|
||||||
|
check_for_update
|
||||||
|
|
||||||
local report_period
|
local report_period
|
||||||
report_period=$(get_report_period)
|
report_period=$(get_report_period)
|
||||||
local report_date
|
local report_date
|
||||||
@@ -530,6 +795,8 @@ generate_report() {
|
|||||||
protocol_table=$(generate_protocol_html)
|
protocol_table=$(generate_protocol_html)
|
||||||
local recent_bans_table
|
local recent_bans_table
|
||||||
recent_bans_table=$(generate_recent_bans_html)
|
recent_bans_table=$(generate_recent_bans_html)
|
||||||
|
local period_overview_table
|
||||||
|
period_overview_table=$(generate_period_overview_html)
|
||||||
|
|
||||||
# Platzhalter ersetzen
|
# Platzhalter ersetzen
|
||||||
report="${report//\{\{REPORT_PERIOD\}\}/$report_period}"
|
report="${report//\{\{REPORT_PERIOD\}\}/$report_period}"
|
||||||
@@ -546,10 +813,13 @@ generate_report() {
|
|||||||
report="${report//\{\{SUBDOMAIN_FLOOD_BANS\}\}/$SUBDOMAIN_FLOOD_BANS}"
|
report="${report//\{\{SUBDOMAIN_FLOOD_BANS\}\}/$SUBDOMAIN_FLOOD_BANS}"
|
||||||
report="${report//\{\{EXTERNAL_BLOCKLIST_BANS\}\}/$EXTERNAL_BLOCKLIST_BANS}"
|
report="${report//\{\{EXTERNAL_BLOCKLIST_BANS\}\}/$EXTERNAL_BLOCKLIST_BANS}"
|
||||||
report="${report//\{\{BUSIEST_DAY\}\}/$BUSIEST_DAY}"
|
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_IPS_TABLE\}\}/$top10_ips_table}"
|
||||||
report="${report//\{\{TOP10_DOMAINS_TABLE\}\}/$top10_domains_table}"
|
report="${report//\{\{TOP10_DOMAINS_TABLE\}\}/$top10_domains_table}"
|
||||||
report="${report//\{\{PROTOCOL_TABLE\}\}/$protocol_table}"
|
report="${report//\{\{PROTOCOL_TABLE\}\}/$protocol_table}"
|
||||||
report="${report//\{\{RECENT_BANS_TABLE\}\}/$recent_bans_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"
|
echo "$report"
|
||||||
|
|
||||||
@@ -572,6 +842,8 @@ generate_report() {
|
|||||||
protocol_txt=$(generate_protocol_txt)
|
protocol_txt=$(generate_protocol_txt)
|
||||||
local recent_bans_txt
|
local recent_bans_txt
|
||||||
recent_bans_txt=$(generate_recent_bans_txt)
|
recent_bans_txt=$(generate_recent_bans_txt)
|
||||||
|
local period_overview_txt
|
||||||
|
period_overview_txt=$(generate_period_overview_txt)
|
||||||
|
|
||||||
# Platzhalter ersetzen
|
# Platzhalter ersetzen
|
||||||
report="${report//\{\{REPORT_PERIOD\}\}/$report_period}"
|
report="${report//\{\{REPORT_PERIOD\}\}/$report_period}"
|
||||||
@@ -588,10 +860,13 @@ generate_report() {
|
|||||||
report="${report//\{\{SUBDOMAIN_FLOOD_BANS\}\}/$SUBDOMAIN_FLOOD_BANS}"
|
report="${report//\{\{SUBDOMAIN_FLOOD_BANS\}\}/$SUBDOMAIN_FLOOD_BANS}"
|
||||||
report="${report//\{\{EXTERNAL_BLOCKLIST_BANS\}\}/$EXTERNAL_BLOCKLIST_BANS}"
|
report="${report//\{\{EXTERNAL_BLOCKLIST_BANS\}\}/$EXTERNAL_BLOCKLIST_BANS}"
|
||||||
report="${report//\{\{BUSIEST_DAY\}\}/$BUSIEST_DAY}"
|
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_IPS_TEXT\}\}/$top10_ips_txt}"
|
||||||
report="${report//\{\{TOP10_DOMAINS_TEXT\}\}/$top10_domains_txt}"
|
report="${report//\{\{TOP10_DOMAINS_TEXT\}\}/$top10_domains_txt}"
|
||||||
report="${report//\{\{PROTOCOL_TEXT\}\}/$protocol_txt}"
|
report="${report//\{\{PROTOCOL_TEXT\}\}/$protocol_txt}"
|
||||||
report="${report//\{\{RECENT_BANS_TEXT\}\}/$recent_bans_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"
|
echo "$report"
|
||||||
else
|
else
|
||||||
@@ -737,6 +1012,7 @@ show_cron_status() {
|
|||||||
echo " Empfänger: ${REPORT_EMAIL_TO:-nicht konfiguriert}"
|
echo " Empfänger: ${REPORT_EMAIL_TO:-nicht konfiguriert}"
|
||||||
echo " Absender: ${REPORT_EMAIL_FROM}"
|
echo " Absender: ${REPORT_EMAIL_FROM}"
|
||||||
echo " Mail-Befehl: ${REPORT_MAIL_CMD}"
|
echo " Mail-Befehl: ${REPORT_MAIL_CMD}"
|
||||||
|
echo " Aktivster Tag: letzte ${REPORT_BUSIEST_DAY_RANGE:-30} Tage"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if command -v "$REPORT_MAIL_CMD" &>/dev/null; then
|
if command -v "$REPORT_MAIL_CMD" &>/dev/null; then
|
||||||
@@ -836,6 +1112,11 @@ send_test_email() {
|
|||||||
local content_type="text/plain"
|
local content_type="text/plain"
|
||||||
[[ "$REPORT_FORMAT" == "html" ]] && content_type="text/html"
|
[[ "$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
|
local test_body
|
||||||
if [[ "$REPORT_FORMAT" == "html" ]]; then
|
if [[ "$REPORT_FORMAT" == "html" ]]; then
|
||||||
test_body=$(cat <<TESTHTML
|
test_body=$(cat <<TESTHTML
|
||||||
@@ -861,13 +1142,17 @@ send_test_email() {
|
|||||||
</table>
|
</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>
|
<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>
|
||||||
<div style="background:#f8f9fc;padding:20px;text-align:center;font-size:12px;color:#6c757d;border-top:1px solid #e8ecf1;">
|
<div style="background:#f8f9fc;padding:20px;font-size:12px;color:#6c757d;border-top:1px solid #e8ecf1;text-align:center;">
|
||||||
<a href="https://www.patrick-asmus.de" style="color:#0f3460;text-decoration:none;font-weight:600;">Patrick-Asmus.DE</a>
|
<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>
|
<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>
|
<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>
|
<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 style="margin-top:8px;font-size:11px;color:#adb5bd;">AdGuard Shield v${VERSION} · ${hostname}</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</body></html>
|
</body></html>
|
||||||
@@ -895,12 +1180,14 @@ TESTHTML
|
|||||||
Ab jetzt kannst du den automatischen Versand aktivieren mit:
|
Ab jetzt kannst du den automatischen Versand aktivieren mit:
|
||||||
sudo $(basename "$0") install
|
sudo $(basename "$0") install
|
||||||
|
|
||||||
|
${test_update_notice_txt}
|
||||||
═══════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════
|
||||||
AdGuard Shield v${VERSION} · ${hostname}
|
AdGuard Shield ${VERSION} · ${hostname}
|
||||||
|
|
||||||
Web: https://www.patrick-asmus.de
|
Web: https://www.patrick-asmus.de
|
||||||
Blog: https://www.cleveradmin.de
|
Blog: https://www.cleveradmin.de
|
||||||
Repo: https://git.techniverse.net/scriptos/adguard-shield.git
|
Repo: https://git.techniverse.net/scriptos/adguard-shield.git
|
||||||
|
Docs: https://git.techniverse.net/scriptos/adguard-shield/src/branch/main/docs
|
||||||
═══════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════
|
||||||
TESTTXT
|
TESTTXT
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -210,17 +210,57 @@
|
|||||||
}
|
}
|
||||||
.footer .links {
|
.footer .links {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.footer .separator {
|
.footer .separator {
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
color: #ced4da;
|
color: #ced4da;
|
||||||
}
|
}
|
||||||
.version-tag {
|
.version-tag {
|
||||||
display: inline-block;
|
display: block;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #adb5bd;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -234,6 +274,11 @@
|
|||||||
|
|
||||||
<!-- Statistik-Übersicht -->
|
<!-- Statistik-Übersicht -->
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
<!-- Zeitraum-Schnellübersicht -->
|
||||||
|
<h2>📅 Zeitraum-Schnellübersicht</h2>
|
||||||
|
{{PERIOD_OVERVIEW_TABLE}}
|
||||||
|
|
||||||
|
<!-- Gesamt-Übersicht des Berichtszeitraums -->
|
||||||
<h2>📊 Übersicht</h2>
|
<h2>📊 Übersicht</h2>
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-card danger">
|
<div class="stat-card danger">
|
||||||
@@ -279,7 +324,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="stat-card success">
|
<div class="stat-card success">
|
||||||
<div class="stat-value">{{BUSIEST_DAY}}</div>
|
<div class="stat-value">{{BUSIEST_DAY}}</div>
|
||||||
<div class="stat-label">Aktivster Tag</div>
|
<div class="stat-label">{{BUSIEST_DAY_LABEL}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -302,16 +347,23 @@
|
|||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<div class="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>
|
Dieser Report wurde automatisch von <strong>AdGuard Shield</strong> generiert.<br>
|
||||||
Generiert am: {{REPORT_DATE}}
|
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>
|
<div class="version-tag">AdGuard Shield {{VERSION}} · {{HOSTNAME}}</div>
|
||||||
|
{{UPDATE_NOTICE}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -7,7 +7,13 @@
|
|||||||
Host: {{HOSTNAME}}
|
Host: {{HOSTNAME}}
|
||||||
|
|
||||||
───────────────────────────────────────────────────────────────
|
───────────────────────────────────────────────────────────────
|
||||||
📊 ÜBERSICHT
|
<EFBFBD> ZEITRAUM-SCHNELLÜBERSICHT
|
||||||
|
───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
{{PERIOD_OVERVIEW_TEXT}}
|
||||||
|
|
||||||
|
───────────────────────────────────────────────────────────────
|
||||||
|
📊 ÜBERSICHT (Berichtszeitraum)
|
||||||
───────────────────────────────────────────────────────────────
|
───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
Sperren gesamt: {{TOTAL_BANS}}
|
Sperren gesamt: {{TOTAL_BANS}}
|
||||||
@@ -24,7 +30,7 @@
|
|||||||
Rate-Limit Sperren: {{RATELIMIT_BANS}}
|
Rate-Limit Sperren: {{RATELIMIT_BANS}}
|
||||||
Subdomain-Flood Sperren: {{SUBDOMAIN_FLOOD_BANS}}
|
Subdomain-Flood Sperren: {{SUBDOMAIN_FLOOD_BANS}}
|
||||||
Externe Blocklist: {{EXTERNAL_BLOCKLIST_BANS}}
|
Externe Blocklist: {{EXTERNAL_BLOCKLIST_BANS}}
|
||||||
Aktivster Tag: {{BUSIEST_DAY}}
|
{{BUSIEST_DAY_LABEL}}: {{BUSIEST_DAY}}
|
||||||
|
|
||||||
───────────────────────────────────────────────────────────────
|
───────────────────────────────────────────────────────────────
|
||||||
🏴☠️ TOP 10 – AUFFÄLLIGSTE IPs
|
🏴☠️ TOP 10 – AUFFÄLLIGSTE IPs
|
||||||
@@ -50,6 +56,7 @@
|
|||||||
|
|
||||||
{{RECENT_BANS_TEXT}}
|
{{RECENT_BANS_TEXT}}
|
||||||
|
|
||||||
|
{{UPDATE_NOTICE_TXT}}
|
||||||
═══════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════
|
||||||
Dieser Report wurde automatisch von AdGuard Shield generiert.
|
Dieser Report wurde automatisch von AdGuard Shield generiert.
|
||||||
AdGuard Shield {{VERSION}}
|
AdGuard Shield {{VERSION}}
|
||||||
@@ -57,4 +64,5 @@
|
|||||||
Web: https://www.patrick-asmus.de
|
Web: https://www.patrick-asmus.de
|
||||||
Blog: https://www.cleveradmin.de
|
Blog: https://www.cleveradmin.de
|
||||||
Repo: https://git.techniverse.net/scriptos/adguard-shield.git
|
Repo: https://git.techniverse.net/scriptos/adguard-shield.git
|
||||||
|
Docs: https://git.techniverse.net/scriptos/adguard-shield/src/branch/main/docs
|
||||||
═══════════════════════════════════════════════════════════════
|
═══════════════════════════════════════════════════════════════
|
||||||
|
|||||||
23
uninstall.sh
23
uninstall.sh
@@ -13,6 +13,8 @@
|
|||||||
# INSTALL_DIR ergibt sich aus dem Verzeichnis, in dem dieses Script liegt
|
# INSTALL_DIR ergibt sich aus dem Verzeichnis, in dem dieses Script liegt
|
||||||
INSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
INSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
SERVICE_FILE="/etc/systemd/system/adguard-shield.service"
|
SERVICE_FILE="/etc/systemd/system/adguard-shield.service"
|
||||||
|
WATCHDOG_SERVICE_FILE="/etc/systemd/system/adguard-shield-watchdog.service"
|
||||||
|
WATCHDOG_TIMER_FILE="/etc/systemd/system/adguard-shield-watchdog.timer"
|
||||||
|
|
||||||
# Farben
|
# Farben
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@@ -79,6 +81,16 @@ do_uninstall() {
|
|||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Watchdog-Timer stoppen und deaktivieren
|
||||||
|
if systemctl is-active adguard-shield-watchdog.timer &>/dev/null 2>&1; then
|
||||||
|
systemctl stop adguard-shield-watchdog.timer
|
||||||
|
echo " ✅ Watchdog-Timer gestoppt"
|
||||||
|
fi
|
||||||
|
if systemctl is-enabled adguard-shield-watchdog.timer &>/dev/null 2>&1; then
|
||||||
|
systemctl disable adguard-shield-watchdog.timer
|
||||||
|
echo " ✅ Watchdog-Timer deaktiviert"
|
||||||
|
fi
|
||||||
|
|
||||||
# Service stoppen und deaktivieren
|
# Service stoppen und deaktivieren
|
||||||
if systemctl is-active adguard-shield &>/dev/null; then
|
if systemctl is-active adguard-shield &>/dev/null; then
|
||||||
systemctl stop adguard-shield
|
systemctl stop adguard-shield
|
||||||
@@ -90,9 +102,13 @@ do_uninstall() {
|
|||||||
fi
|
fi
|
||||||
if [[ -f "$SERVICE_FILE" ]]; then
|
if [[ -f "$SERVICE_FILE" ]]; then
|
||||||
rm -f "$SERVICE_FILE"
|
rm -f "$SERVICE_FILE"
|
||||||
systemctl daemon-reload
|
|
||||||
echo " ✅ Service-Datei entfernt"
|
echo " ✅ Service-Datei entfernt"
|
||||||
fi
|
fi
|
||||||
|
rm -f "$WATCHDOG_SERVICE_FILE" "$WATCHDOG_TIMER_FILE"
|
||||||
|
if [[ -f "$WATCHDOG_SERVICE_FILE" ]] || [[ -f "$WATCHDOG_TIMER_FILE" ]]; then
|
||||||
|
echo " ✅ Watchdog-Dateien entfernt"
|
||||||
|
fi
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
# iptables Chain aufräumen
|
# iptables Chain aufräumen
|
||||||
if [[ -f "$INSTALL_DIR/iptables-helper.sh" ]]; then
|
if [[ -f "$INSTALL_DIR/iptables-helper.sh" ]]; then
|
||||||
@@ -106,9 +122,14 @@ do_uninstall() {
|
|||||||
rm -f "$INSTALL_DIR/iptables-helper.sh"
|
rm -f "$INSTALL_DIR/iptables-helper.sh"
|
||||||
rm -f "$INSTALL_DIR/unban-expired.sh"
|
rm -f "$INSTALL_DIR/unban-expired.sh"
|
||||||
rm -f "$INSTALL_DIR/external-blocklist-worker.sh"
|
rm -f "$INSTALL_DIR/external-blocklist-worker.sh"
|
||||||
|
rm -f "$INSTALL_DIR/external-whitelist-worker.sh"
|
||||||
|
rm -f "$INSTALL_DIR/offense-cleanup-worker.sh"
|
||||||
rm -f "$INSTALL_DIR/report-generator.sh"
|
rm -f "$INSTALL_DIR/report-generator.sh"
|
||||||
|
rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh"
|
||||||
|
rm -f "$INSTALL_DIR/geoip-worker.sh"
|
||||||
rm -f "$INSTALL_DIR/uninstall.sh"
|
rm -f "$INSTALL_DIR/uninstall.sh"
|
||||||
rm -rf "$INSTALL_DIR/templates"
|
rm -rf "$INSTALL_DIR/templates"
|
||||||
|
rm -rf "$INSTALL_DIR/geoip"
|
||||||
echo " ✅ Scripts entfernt (Konfiguration und Logs behalten)"
|
echo " ✅ Scripts entfernt (Konfiguration und Logs behalten)"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${YELLOW} Konfiguration verbleibt in: $INSTALL_DIR/adguard-shield.conf${NC}"
|
echo -e "${YELLOW} Konfiguration verbleibt in: $INSTALL_DIR/adguard-shield.conf${NC}"
|
||||||
|
|||||||
Reference in New Issue
Block a user