Compare commits
4 Commits
7bbdb104c4
...
0dcfb5edba
| Author | SHA1 | Date | |
|---|---|---|---|
| 0dcfb5edba | |||
|
|
dc7467f5d5 | ||
|
|
9e3656bfbc | ||
|
|
de521f73ed |
12
README.md
12
README.md
@@ -94,15 +94,16 @@ docker run --rm -v "$PWD":/src -w /src -e GOOS=linux -e GOARCH=amd64 -e CGO_ENAB
|
|||||||
# Binary auf dem Server installieren
|
# Binary auf dem Server installieren
|
||||||
sudo ./adguard-shield install
|
sudo ./adguard-shield install
|
||||||
# Der Installer fragt am Ende, ob AdGuard Shield direkt gestartet werden soll.
|
# Der Installer fragt am Ende, ob AdGuard Shield direkt gestartet werden soll.
|
||||||
|
# Dabei wird der Befehl adguard-shield in /usr/local/bin registriert.
|
||||||
|
|
||||||
# Konfiguration anpassen (mindestens API-Zugangsdaten und Whitelist)
|
# Konfiguration anpassen (mindestens API-Zugangsdaten und Whitelist)
|
||||||
sudo nano /opt/adguard-shield/adguard-shield.conf
|
sudo nano /opt/adguard-shield/adguard-shield.conf
|
||||||
|
|
||||||
# API-Verbindung testen
|
# API-Verbindung testen
|
||||||
sudo /opt/adguard-shield/adguard-shield test
|
sudo adguard-shield test
|
||||||
|
|
||||||
# Dry-Run: loggt Erkennungen, sperrt aber nicht
|
# Dry-Run: loggt Erkennungen, sperrt aber nicht
|
||||||
sudo /opt/adguard-shield/adguard-shield dry-run
|
sudo adguard-shield dry-run
|
||||||
|
|
||||||
# Service starten und prüfen
|
# Service starten und prüfen
|
||||||
sudo systemctl start adguard-shield
|
sudo systemctl start adguard-shield
|
||||||
@@ -110,6 +111,7 @@ sudo systemctl status adguard-shield
|
|||||||
```
|
```
|
||||||
|
|
||||||
> Beim Installieren wird der systemd-Service für den Autostart registriert und am Ende nach dem direkten Start gefragt. Die Go-Version nutzt `Restart=on-failure`; einen separaten Watchdog-Timer wie in der alten Shell-Version gibt es nicht mehr.
|
> Beim Installieren wird der systemd-Service für den Autostart registriert und am Ende nach dem direkten Start gefragt. Die Go-Version nutzt `Restart=on-failure`; einen separaten Watchdog-Timer wie in der alten Shell-Version gibt es nicht mehr.
|
||||||
|
> Zusätzlich legt der Installer standardmäßig `/usr/local/bin/adguard-shield` als Symlink auf das installierte Binary an. Danach kannst du `sudo adguard-shield <befehl>` statt `sudo /opt/adguard-shield/adguard-shield <befehl>` verwenden.
|
||||||
|
|
||||||
> **Bestehende Shell-Installation?** Der Go-Installer bricht ab und meldet die gefundenen Script-Artefakte. Die alte Version muss zuerst deinstalliert werden (Konfiguration behalten). Details unter [docs/update.md](docs/update.md).
|
> **Bestehende Shell-Installation?** Der Go-Installer bricht ab und meldet die gefundenen Script-Artefakte. Die alte Version muss zuerst deinstalliert werden (Konfiguration behalten). Details unter [docs/update.md](docs/update.md).
|
||||||
|
|
||||||
@@ -120,7 +122,7 @@ sudo systemctl status adguard-shield
|
|||||||
AdGuard Shield wird über ein einzelnes Binary bedient. Die Grundform lautet:
|
AdGuard Shield wird über ein einzelnes Binary bedient. Die Grundform lautet:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield <befehl>
|
sudo adguard-shield <befehl>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Installation & Updates
|
### Installation & Updates
|
||||||
@@ -130,9 +132,11 @@ sudo /opt/adguard-shield/adguard-shield <befehl>
|
|||||||
| `install` | Binary, Konfiguration und systemd-Service installieren |
|
| `install` | Binary, Konfiguration und systemd-Service installieren |
|
||||||
| `install --skip-deps` | Installation ohne automatische Paketprüfung |
|
| `install --skip-deps` | Installation ohne automatische Paketprüfung |
|
||||||
| `install --no-enable` | Installation ohne systemd-Autostart |
|
| `install --no-enable` | Installation ohne systemd-Autostart |
|
||||||
|
| `install --no-register` | Installation ohne globalen CLI-Befehl in `/usr/local/bin` |
|
||||||
| `install --config-source <pfad>` | Bestehende Konfiguration als Vorlage übernehmen |
|
| `install --config-source <pfad>` | Bestehende Konfiguration als Vorlage übernehmen |
|
||||||
| `update` | Binary, Service und Konfiguration aktualisieren |
|
| `update` | Binary, Service und Konfiguration aktualisieren |
|
||||||
| `install-status` | Installationsstatus anzeigen (Binary, Service, Version) |
|
| `update --no-register` | Update ohne Änderung des globalen CLI-Befehls |
|
||||||
|
| `install-status` | Installationsstatus anzeigen (Binary, CLI-Befehl, Service, Version) |
|
||||||
| `uninstall` | Vollständige Deinstallation |
|
| `uninstall` | Vollständige Deinstallation |
|
||||||
| `uninstall --keep-config` | Deinstallation mit Erhalt der Konfiguration |
|
| `uninstall --keep-config` | Deinstallation mit Erhalt der Konfiguration |
|
||||||
|
|
||||||
|
|||||||
@@ -244,7 +244,11 @@ func run() error {
|
|||||||
case "report-test":
|
case "report-test":
|
||||||
return report.SendTest(ctx, cfg)
|
return report.SendTest(ctx, cfg)
|
||||||
case "report-install":
|
case "report-install":
|
||||||
return report.InstallCron("/opt/adguard-shield/adguard-shield", cfg.Path, cfg)
|
binary := "/opt/adguard-shield/adguard-shield"
|
||||||
|
if _, err := os.Stat(installer.CLICommandPath); err == nil {
|
||||||
|
binary = installer.CLICommandPath
|
||||||
|
}
|
||||||
|
return report.InstallCron(binary, cfg.Path, cfg)
|
||||||
case "report-remove":
|
case "report-remove":
|
||||||
return report.RemoveCron()
|
return report.RemoveCron()
|
||||||
case "firewall-create":
|
case "firewall-create":
|
||||||
@@ -463,8 +467,8 @@ func usage() {
|
|||||||
|
|
||||||
Nutzung:
|
Nutzung:
|
||||||
adguard-shield version
|
adguard-shield version
|
||||||
adguard-shield install [--config-source PATH] [--skip-deps]
|
adguard-shield install [--config-source PATH] [--skip-deps] [--no-register]
|
||||||
adguard-shield update [--config-source PATH] [--skip-deps]
|
adguard-shield update [--config-source PATH] [--skip-deps] [--no-register]
|
||||||
adguard-shield uninstall [--keep-config]
|
adguard-shield uninstall [--keep-config]
|
||||||
adguard-shield install-status
|
adguard-shield install-status
|
||||||
adguard-shield [-config PATH] run|start|stop|dry-run
|
adguard-shield [-config PATH] run|start|stop|dry-run
|
||||||
@@ -653,9 +657,11 @@ func parseInstallFlags(name string, args []string) (installer.Options, error) {
|
|||||||
fs.StringVar(&opts.ConfigSource, "config-source", "", "Konfiguration fuer Neuinstallation uebernehmen")
|
fs.StringVar(&opts.ConfigSource, "config-source", "", "Konfiguration fuer Neuinstallation uebernehmen")
|
||||||
fs.BoolVar(&opts.SkipDeps, "skip-deps", false, "Paketpruefung ueberspringen")
|
fs.BoolVar(&opts.SkipDeps, "skip-deps", false, "Paketpruefung ueberspringen")
|
||||||
noEnable := fs.Bool("no-enable", false, "systemd Autostart nicht aktivieren")
|
noEnable := fs.Bool("no-enable", false, "systemd Autostart nicht aktivieren")
|
||||||
|
noRegister := fs.Bool("no-register", false, "CLI-Befehl nicht in /usr/local/bin registrieren")
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := fs.Parse(args); err != nil {
|
||||||
return opts, err
|
return opts, err
|
||||||
}
|
}
|
||||||
opts.Enable = !*noEnable
|
opts.Enable = !*noEnable
|
||||||
|
opts.RegisterCLI = !*noRegister
|
||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ Die Go-Version bündelt alle Aufgaben in einem einzelnen Binary:
|
|||||||
/opt/adguard-shield/adguard-shield
|
/opt/adguard-shield/adguard-shield
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Bei Installation und Update wird zusätzlich ein Symlink in den üblichen PATH gelegt:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/usr/local/bin/adguard-shield -> /opt/adguard-shield/adguard-shield
|
||||||
|
```
|
||||||
|
|
||||||
Dieses Binary ist gleichzeitig:
|
Dieses Binary ist gleichzeitig:
|
||||||
|
|
||||||
- **Daemon** für den produktiven Betrieb (Querylog-Polling, Erkennung, Sperren)
|
- **Daemon** für den produktiven Betrieb (Querylog-Polling, Erkennung, Sperren)
|
||||||
@@ -33,10 +39,10 @@ Dieses Binary ist gleichzeitig:
|
|||||||
- **Report-Generator** für HTML- und Text-Reports
|
- **Report-Generator** für HTML- und Text-Reports
|
||||||
- **Hintergrundprozess** für externe Whitelist, externe Blocklist, GeoIP und Offense-Cleanup
|
- **Hintergrundprozess** für externe Whitelist, externe Blocklist, GeoIP und Offense-Cleanup
|
||||||
|
|
||||||
Die meisten Befehle beginnen daher mit:
|
Die meisten Befehle beginnen daher nach der Installation mit:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield <befehl>
|
sudo adguard-shield <befehl>
|
||||||
```
|
```
|
||||||
|
|
||||||
Für Installation oder Update nutzt du das neue Binary aus dem Repository, Release oder Build-Verzeichnis:
|
Für Installation oder Update nutzt du das neue Binary aus dem Repository, Release oder Build-Verzeichnis:
|
||||||
|
|||||||
200
docs/befehle.md
200
docs/befehle.md
@@ -6,18 +6,24 @@ AdGuard Shield wird in der Go-Version über ein einzelnes Binary bedient:
|
|||||||
/opt/adguard-shield/adguard-shield
|
/opt/adguard-shield/adguard-shield
|
||||||
```
|
```
|
||||||
|
|
||||||
Dieses Binary ist Daemon, CLI, Installer, Updater, Uninstaller und Report-Generator. Dadurch gibt es keine getrennten Shell-Skripte mehr.
|
Bei Installation und Update registriert der Installer zusätzlich den globalen Befehl:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/usr/local/bin/adguard-shield
|
||||||
|
```
|
||||||
|
|
||||||
|
Dieser Symlink zeigt auf das installierte Binary. Dadurch gibt es keine getrennten Shell-Skripte mehr, und du kannst AdGuard Shield nach der Installation ohne vollständigen Pfad aufrufen.
|
||||||
|
|
||||||
## Grundform
|
## Grundform
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield <befehl>
|
sudo adguard-shield <befehl>
|
||||||
```
|
```
|
||||||
|
|
||||||
Wenn du eine andere Konfigurationsdatei verwenden möchtest, muss `-config` direkt vor dem Befehl stehen:
|
Wenn du eine andere Konfigurationsdatei verwenden möchtest, muss `-config` direkt vor dem Befehl stehen:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield -config /pfad/zur/adguard-shield.conf status
|
sudo adguard-shield -config /pfad/zur/adguard-shield.conf status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Standardpfade
|
### Standardpfade
|
||||||
@@ -25,6 +31,7 @@ sudo /opt/adguard-shield/adguard-shield -config /pfad/zur/adguard-shield.conf st
|
|||||||
| Datei | Pfad |
|
| Datei | Pfad |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Binary | `/opt/adguard-shield/adguard-shield` |
|
| Binary | `/opt/adguard-shield/adguard-shield` |
|
||||||
|
| CLI-Befehl | `/usr/local/bin/adguard-shield` |
|
||||||
| Konfiguration | `/opt/adguard-shield/adguard-shield.conf` |
|
| Konfiguration | `/opt/adguard-shield/adguard-shield.conf` |
|
||||||
| SQLite-Datenbank | `/var/lib/adguard-shield/adguard-shield.db` |
|
| SQLite-Datenbank | `/var/lib/adguard-shield/adguard-shield.db` |
|
||||||
| Logdatei | `/var/log/adguard-shield.log` |
|
| Logdatei | `/var/log/adguard-shield.log` |
|
||||||
@@ -34,13 +41,13 @@ sudo /opt/adguard-shield/adguard-shield -config /pfad/zur/adguard-shield.conf st
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Version anzeigen
|
# Version anzeigen
|
||||||
/opt/adguard-shield/adguard-shield version
|
adguard-shield version
|
||||||
|
|
||||||
# Installation und Update
|
# Installation und Update
|
||||||
sudo ./adguard-shield install
|
sudo ./adguard-shield install
|
||||||
sudo ./adguard-shield update
|
sudo ./adguard-shield update
|
||||||
sudo ./adguard-shield install-status
|
sudo adguard-shield install-status
|
||||||
sudo /opt/adguard-shield/adguard-shield uninstall --keep-config
|
sudo adguard-shield uninstall --keep-config
|
||||||
|
|
||||||
# Service-Management über systemd
|
# Service-Management über systemd
|
||||||
sudo systemctl start adguard-shield
|
sudo systemctl start adguard-shield
|
||||||
@@ -49,16 +56,16 @@ sudo systemctl restart adguard-shield
|
|||||||
sudo systemctl status adguard-shield
|
sudo systemctl status adguard-shield
|
||||||
|
|
||||||
# Diagnose und Monitoring
|
# Diagnose und Monitoring
|
||||||
sudo /opt/adguard-shield/adguard-shield test
|
sudo adguard-shield test
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
sudo /opt/adguard-shield/adguard-shield live
|
sudo adguard-shield live
|
||||||
sudo /opt/adguard-shield/adguard-shield history 100
|
sudo adguard-shield history 100
|
||||||
sudo /opt/adguard-shield/adguard-shield logs --level warn --limit 100
|
sudo adguard-shield logs --level warn --limit 100
|
||||||
|
|
||||||
# Manuelle Eingriffe
|
# Manuelle Eingriffe
|
||||||
sudo /opt/adguard-shield/adguard-shield ban 192.168.1.100
|
sudo adguard-shield ban 192.168.1.100
|
||||||
sudo /opt/adguard-shield/adguard-shield unban 192.168.1.100
|
sudo adguard-shield unban 192.168.1.100
|
||||||
sudo /opt/adguard-shield/adguard-shield flush
|
sudo adguard-shield flush
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -71,6 +78,12 @@ Das installierte Binary landet standardmäßig unter:
|
|||||||
/opt/adguard-shield/adguard-shield
|
/opt/adguard-shield/adguard-shield
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Zusätzlich wird standardmäßig dieser CLI-Befehl angelegt:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/usr/local/bin/adguard-shield -> /opt/adguard-shield/adguard-shield
|
||||||
|
```
|
||||||
|
|
||||||
### Standardinstallation
|
### Standardinstallation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -87,6 +100,7 @@ Am Ende fragt der Installer, ob AdGuard Shield direkt gestartet oder neu gestart
|
|||||||
| `--config-source <pfad>` | Bestehende Konfigurationsdatei als Vorlage übernehmen |
|
| `--config-source <pfad>` | Bestehende Konfigurationsdatei als Vorlage übernehmen |
|
||||||
| `--skip-deps` | Automatische Paketprüfung und -installation überspringen |
|
| `--skip-deps` | Automatische Paketprüfung und -installation überspringen |
|
||||||
| `--no-enable` | systemd-Autostart nicht aktivieren |
|
| `--no-enable` | systemd-Autostart nicht aktivieren |
|
||||||
|
| `--no-register` | Globalen CLI-Befehl `/usr/local/bin/adguard-shield` nicht anlegen |
|
||||||
| `--install-dir <pfad>` | Abweichendes Installationsverzeichnis verwenden |
|
| `--install-dir <pfad>` | Abweichendes Installationsverzeichnis verwenden |
|
||||||
|
|
||||||
**Beispiele:**
|
**Beispiele:**
|
||||||
@@ -98,6 +112,9 @@ sudo ./adguard-shield install --config-source ./adguard-shield.conf
|
|||||||
# Ohne Paketprüfung installieren
|
# Ohne Paketprüfung installieren
|
||||||
sudo ./adguard-shield install --skip-deps
|
sudo ./adguard-shield install --skip-deps
|
||||||
|
|
||||||
|
# Ohne globalen CLI-Befehl installieren
|
||||||
|
sudo ./adguard-shield install --no-register
|
||||||
|
|
||||||
# In anderes Verzeichnis installieren
|
# In anderes Verzeichnis installieren
|
||||||
sudo ./adguard-shield install --install-dir /opt/adguard-shield-test
|
sudo ./adguard-shield install --install-dir /opt/adguard-shield-test
|
||||||
```
|
```
|
||||||
@@ -113,11 +130,12 @@ Der Installer führt diese Schritte automatisch durch:
|
|||||||
| 3 | Installation fehlender Abhängigkeiten über `apt-get` (sofern möglich) |
|
| 3 | Installation fehlender Abhängigkeiten über `apt-get` (sofern möglich) |
|
||||||
| 4 | Anlage von Installations- und State-Verzeichnissen |
|
| 4 | Anlage von Installations- und State-Verzeichnissen |
|
||||||
| 5 | Kopieren des Binarys nach `/opt/adguard-shield/` |
|
| 5 | Kopieren des Binarys nach `/opt/adguard-shield/` |
|
||||||
| 6 | Anlage oder Migration der Konfiguration |
|
| 6 | CLI-Befehl `/usr/local/bin/adguard-shield` registrieren (sofern nicht `--no-register`) |
|
||||||
| 7 | Schreiben der systemd-Unit |
|
| 7 | Report-Templates installieren |
|
||||||
| 8 | `systemctl daemon-reload` |
|
| 8 | Anlage oder Migration der Konfiguration |
|
||||||
| 9 | Optional: Autostart aktivieren |
|
| 9 | Schreiben der systemd-Unit |
|
||||||
| 10 | Nachfrage: Service direkt starten oder neu starten |
|
| 10 | `systemctl daemon-reload` und optional Autostart aktivieren |
|
||||||
|
| 11 | Nachfrage: Service direkt starten oder neu starten |
|
||||||
|
|
||||||
### Benötigte Systembefehle
|
### Benötigte Systembefehle
|
||||||
|
|
||||||
@@ -149,9 +167,16 @@ Am Ende fragt der Updater, ob AdGuard Shield direkt neu gestartet werden soll.
|
|||||||
sudo ./adguard-shield update --config-source ./adguard-shield.conf
|
sudo ./adguard-shield update --config-source ./adguard-shield.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Update ohne CLI-Registrierung
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./adguard-shield update --no-register
|
||||||
|
```
|
||||||
|
|
||||||
### Was beim Update passiert
|
### Was beim Update passiert
|
||||||
|
|
||||||
- Die Installation wird wie bei `install` aktualisiert
|
- Die Installation wird wie bei `install` aktualisiert
|
||||||
|
- Der CLI-Befehl `/usr/local/bin/adguard-shield` wird angelegt oder bestätigt, sofern `--no-register` nicht gesetzt ist
|
||||||
- Vorhandene Konfiguration bleibt erhalten
|
- Vorhandene Konfiguration bleibt erhalten
|
||||||
- Neue Konfigurationsparameter werden ergänzt
|
- Neue Konfigurationsparameter werden ergänzt
|
||||||
- Bei einer Migration wird `adguard-shield.conf.old` geschrieben
|
- Bei einer Migration wird `adguard-shield.conf.old` geschrieben
|
||||||
@@ -164,13 +189,14 @@ Weitere Details stehen in der [Update-Anleitung](update.md).
|
|||||||
## Installationsstatus
|
## Installationsstatus
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ./adguard-shield install-status
|
sudo adguard-shield install-status
|
||||||
```
|
```
|
||||||
|
|
||||||
Zeigt eine Übersicht mit:
|
Zeigt eine Übersicht mit:
|
||||||
|
|
||||||
- Installationspfad und Binary-Status
|
- Installationspfad und Binary-Status
|
||||||
- Installierte Version
|
- Installierte Version
|
||||||
|
- CLI-Befehl in `/usr/local/bin` vorhanden
|
||||||
- Konfiguration vorhanden
|
- Konfiguration vorhanden
|
||||||
- systemd-Service vorhanden und Status
|
- systemd-Service vorhanden und Status
|
||||||
- Autostart aktiv
|
- Autostart aktiv
|
||||||
@@ -188,10 +214,10 @@ sudo ./adguard-shield install-status --install-dir /opt/adguard-shield-test
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Vollständige Deinstallation
|
# Vollständige Deinstallation
|
||||||
sudo /opt/adguard-shield/adguard-shield uninstall
|
sudo adguard-shield uninstall
|
||||||
|
|
||||||
# Deinstallation mit Konfigurationserhalt
|
# Deinstallation mit Konfigurationserhalt
|
||||||
sudo /opt/adguard-shield/adguard-shield uninstall --keep-config
|
sudo adguard-shield uninstall --keep-config
|
||||||
```
|
```
|
||||||
|
|
||||||
**Was bei der Deinstallation passiert:**
|
**Was bei der Deinstallation passiert:**
|
||||||
@@ -275,19 +301,19 @@ Für Debugging oder Dry-Run kann der Daemon im Vordergrund gestartet werden:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Normaler Vordergrundlauf
|
# Normaler Vordergrundlauf
|
||||||
sudo /opt/adguard-shield/adguard-shield run
|
sudo adguard-shield run
|
||||||
|
|
||||||
# Alias für run
|
# Alias für run
|
||||||
sudo /opt/adguard-shield/adguard-shield start
|
sudo adguard-shield start
|
||||||
|
|
||||||
# Analysieren ohne echte Sperren
|
# Analysieren ohne echte Sperren
|
||||||
sudo /opt/adguard-shield/adguard-shield dry-run
|
sudo adguard-shield dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Daemon über PID-Datei stoppen
|
### Daemon über PID-Datei stoppen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield stop
|
sudo adguard-shield stop
|
||||||
```
|
```
|
||||||
|
|
||||||
Für den Alltag gilt: Nutze `systemctl`. Der direkte Vordergrundlauf endet, sobald die Shell beendet wird oder du `Strg+C` drückst.
|
Für den Alltag gilt: Nutze `systemctl`. Der direkte Vordergrundlauf endet, sobald die Shell beendet wird oder du `Strg+C` drückst.
|
||||||
@@ -297,7 +323,7 @@ Für den Alltag gilt: Nutze `systemctl`. Der direkte Vordergrundlauf endet, soba
|
|||||||
## API-Test
|
## API-Test
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield test
|
sudo adguard-shield test
|
||||||
```
|
```
|
||||||
|
|
||||||
Der `test`-Befehl prüft die Verbindung zur AdGuard-Home-API:
|
Der `test`-Befehl prüft die Verbindung zur AdGuard-Home-API:
|
||||||
@@ -322,7 +348,7 @@ Wenn der Test fehlschlägt, zuerst die Konfiguration und die AdGuard-Home-Webobe
|
|||||||
## Status
|
## Status
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
```
|
```
|
||||||
|
|
||||||
Zeigt eine Übersicht des aktuellen Zustands:
|
Zeigt eine Übersicht des aktuellen Zustands:
|
||||||
@@ -341,7 +367,7 @@ Bei sehr vielen aktiven Sperren werden nur die ersten 50 angezeigt. Für Details
|
|||||||
## Live-Ansicht
|
## Live-Ansicht
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield live
|
sudo adguard-shield live
|
||||||
```
|
```
|
||||||
|
|
||||||
Die `live`-Ansicht ist das beste Werkzeug, wenn du verstehen möchtest, was gerade passiert. Sie zeigt in Echtzeit:
|
Die `live`-Ansicht ist das beste Werkzeug, wenn du verstehen möchtest, was gerade passiert. Sie zeigt in Echtzeit:
|
||||||
@@ -371,7 +397,7 @@ Die `live`-Ansicht ist das beste Werkzeug, wenn du verstehen möchtest, was gera
|
|||||||
### Alias
|
### Alias
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield watch
|
sudo adguard-shield watch
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -380,10 +406,10 @@ sudo /opt/adguard-shield/adguard-shield watch
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Letzte 50 Einträge (Standard)
|
# Letzte 50 Einträge (Standard)
|
||||||
sudo /opt/adguard-shield/adguard-shield history
|
sudo adguard-shield history
|
||||||
|
|
||||||
# Letzte 200 Einträge
|
# Letzte 200 Einträge
|
||||||
sudo /opt/adguard-shield/adguard-shield history 200
|
sudo adguard-shield history 200
|
||||||
```
|
```
|
||||||
|
|
||||||
Die History kommt aus der SQLite-Tabelle `ban_history`.
|
Die History kommt aus der SQLite-Tabelle `ban_history`.
|
||||||
@@ -432,16 +458,16 @@ AdGuard Shield schreibt Daemon-Ereignisse in `LOG_FILE`, standardmäßig:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Letzte INFO/WARN/ERROR-Einträge
|
# Letzte INFO/WARN/ERROR-Einträge
|
||||||
sudo /opt/adguard-shield/adguard-shield logs
|
sudo adguard-shield logs
|
||||||
|
|
||||||
# Letzte 100 Warnungen und Fehler
|
# Letzte 100 Warnungen und Fehler
|
||||||
sudo /opt/adguard-shield/adguard-shield logs --level warn --limit 100
|
sudo adguard-shield logs --level warn --limit 100
|
||||||
|
|
||||||
# Kurzform (Level als Argument)
|
# Kurzform (Level als Argument)
|
||||||
sudo /opt/adguard-shield/adguard-shield logs debug
|
sudo adguard-shield logs debug
|
||||||
|
|
||||||
# Laufende Ansicht (wie tail -f)
|
# Laufende Ansicht (wie tail -f)
|
||||||
sudo /opt/adguard-shield/adguard-shield logs-follow --level info
|
sudo adguard-shield logs-follow --level info
|
||||||
```
|
```
|
||||||
|
|
||||||
### Erlaubte Log-Level
|
### Erlaubte Log-Level
|
||||||
@@ -469,7 +495,7 @@ sudo journalctl -u adguard-shield --no-pager -n 100
|
|||||||
### IP permanent sperren
|
### IP permanent sperren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield ban 192.168.1.100
|
sudo adguard-shield ban 192.168.1.100
|
||||||
```
|
```
|
||||||
|
|
||||||
Legt eine manuelle permanente Sperre an. Die IP wird sofort in die Firewall eingetragen.
|
Legt eine manuelle permanente Sperre an. Die IP wird sofort in die Firewall eingetragen.
|
||||||
@@ -477,7 +503,7 @@ Legt eine manuelle permanente Sperre an. Die IP wird sofort in die Firewall eing
|
|||||||
### IP entsperren
|
### IP entsperren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield unban 192.168.1.100
|
sudo adguard-shield unban 192.168.1.100
|
||||||
```
|
```
|
||||||
|
|
||||||
Entfernt die IP aus Firewall und Datenbank. Funktioniert für alle Sperrtypen (automatisch, manuell, GeoIP, Blocklist).
|
Entfernt die IP aus Firewall und Datenbank. Funktioniert für alle Sperrtypen (automatisch, manuell, GeoIP, Blocklist).
|
||||||
@@ -485,7 +511,7 @@ Entfernt die IP aus Firewall und Datenbank. Funktioniert für alle Sperrtypen (a
|
|||||||
### Alle Sperren aufheben
|
### Alle Sperren aufheben
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield flush
|
sudo adguard-shield flush
|
||||||
```
|
```
|
||||||
|
|
||||||
Hebt alle aktiven Sperren auf. Bei aktivierten Benachrichtigungen wird eine zusammenfassende Meldung gesendet, nicht eine Nachricht pro IP.
|
Hebt alle aktiven Sperren auf. Bei aktivierten Benachrichtigungen wird eine zusammenfassende Meldung gesendet, nicht eine Nachricht pro IP.
|
||||||
@@ -499,7 +525,7 @@ Hebt alle aktiven Sperren auf. Bei aktivierten Benachrichtigungen wird eine zusa
|
|||||||
### Offense-Status anzeigen
|
### Offense-Status anzeigen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield offense-status
|
sudo adguard-shield offense-status
|
||||||
```
|
```
|
||||||
|
|
||||||
Zeigt die Gesamtzahl der Offense-Zähler, davon abgelaufene, und die Konfiguration.
|
Zeigt die Gesamtzahl der Offense-Zähler, davon abgelaufene, und die Konfiguration.
|
||||||
@@ -507,19 +533,19 @@ Zeigt die Gesamtzahl der Offense-Zähler, davon abgelaufene, und die Konfigurati
|
|||||||
### Abgelaufene Zähler entfernen
|
### Abgelaufene Zähler entfernen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield offense-cleanup
|
sudo adguard-shield offense-cleanup
|
||||||
```
|
```
|
||||||
|
|
||||||
### Alle Offense-Zähler zurücksetzen
|
### Alle Offense-Zähler zurücksetzen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield reset-offenses
|
sudo adguard-shield reset-offenses
|
||||||
```
|
```
|
||||||
|
|
||||||
### Zähler für eine IP zurücksetzen
|
### Zähler für eine IP zurücksetzen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield reset-offenses 192.168.1.100
|
sudo adguard-shield reset-offenses 192.168.1.100
|
||||||
```
|
```
|
||||||
|
|
||||||
### Typischer Ablauf nach Fehlkonfiguration
|
### Typischer Ablauf nach Fehlkonfiguration
|
||||||
@@ -528,10 +554,10 @@ Wenn ein Client fälschlicherweise eskaliert wurde:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Sperre aufheben
|
# Sperre aufheben
|
||||||
sudo /opt/adguard-shield/adguard-shield unban 192.168.1.100
|
sudo adguard-shield unban 192.168.1.100
|
||||||
|
|
||||||
# Offense-Zähler zurücksetzen
|
# Offense-Zähler zurücksetzen
|
||||||
sudo /opt/adguard-shield/adguard-shield reset-offenses 192.168.1.100
|
sudo adguard-shield reset-offenses 192.168.1.100
|
||||||
|
|
||||||
# IP dauerhaft in Whitelist aufnehmen (in adguard-shield.conf)
|
# IP dauerhaft in Whitelist aufnehmen (in adguard-shield.conf)
|
||||||
# WHITELIST="127.0.0.1,::1,192.168.1.100"
|
# WHITELIST="127.0.0.1,::1,192.168.1.100"
|
||||||
@@ -545,13 +571,13 @@ sudo systemctl restart adguard-shield
|
|||||||
### Chain und ipsets anlegen
|
### Chain und ipsets anlegen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-create
|
sudo adguard-shield firewall-create
|
||||||
```
|
```
|
||||||
|
|
||||||
### Status anzeigen
|
### Status anzeigen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-status
|
sudo adguard-shield firewall-status
|
||||||
```
|
```
|
||||||
|
|
||||||
Zeigt die aktuelle Firewall-Struktur: Chain, ipsets und eingehängte Regeln.
|
Zeigt die aktuelle Firewall-Struktur: Chain, ipsets und eingehängte Regeln.
|
||||||
@@ -559,7 +585,7 @@ Zeigt die aktuelle Firewall-Struktur: Chain, ipsets und eingehängte Regeln.
|
|||||||
### ipsets leeren
|
### ipsets leeren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-flush
|
sudo adguard-shield firewall-flush
|
||||||
```
|
```
|
||||||
|
|
||||||
Entfernt alle IPs aus den ipsets. Die Firewall-Struktur (Chain, Regeln) bleibt bestehen.
|
Entfernt alle IPs aus den ipsets. Die Firewall-Struktur (Chain, Regeln) bleibt bestehen.
|
||||||
@@ -567,13 +593,13 @@ Entfernt alle IPs aus den ipsets. Die Firewall-Struktur (Chain, Regeln) bleibt b
|
|||||||
### Chain und ipsets vollständig entfernen
|
### Chain und ipsets vollständig entfernen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-remove
|
sudo adguard-shield firewall-remove
|
||||||
```
|
```
|
||||||
|
|
||||||
### Firewall-Regeln sichern
|
### Firewall-Regeln sichern
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-save
|
sudo adguard-shield firewall-save
|
||||||
```
|
```
|
||||||
|
|
||||||
Speichert die aktuellen Regeln nach:
|
Speichert die aktuellen Regeln nach:
|
||||||
@@ -586,7 +612,7 @@ Speichert die aktuellen Regeln nach:
|
|||||||
### Gesicherte Regeln wiederherstellen
|
### Gesicherte Regeln wiederherstellen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-restore
|
sudo adguard-shield firewall-restore
|
||||||
```
|
```
|
||||||
|
|
||||||
**Hinweis:** Normalerweise musst du diese Befehle nicht manuell ausführen. Der Daemon erstellt die Firewall beim Start und schreibt aktive Sperren aus SQLite wieder hinein. Welche Host-Chain genutzt wird, hängt von `FIREWALL_MODE` ab. Details stehen in [Docker-Installationen](docker.md).
|
**Hinweis:** Normalerweise musst du diese Befehle nicht manuell ausführen. Der Daemon erstellt die Firewall beim Start und schreibt aktive Sperren aus SQLite wieder hinein. Welche Host-Chain genutzt wird, hängt von `FIREWALL_MODE` ab. Details stehen in [Docker-Installationen](docker.md).
|
||||||
@@ -598,19 +624,19 @@ sudo /opt/adguard-shield/adguard-shield firewall-restore
|
|||||||
### Status anzeigen
|
### Status anzeigen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield whitelist-status
|
sudo adguard-shield whitelist-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sofort synchronisieren
|
### Sofort synchronisieren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield whitelist-sync
|
sudo adguard-shield whitelist-sync
|
||||||
```
|
```
|
||||||
|
|
||||||
### Aufgelöste externe Whitelist entfernen
|
### Aufgelöste externe Whitelist entfernen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield whitelist-flush
|
sudo adguard-shield whitelist-flush
|
||||||
```
|
```
|
||||||
|
|
||||||
### Hinweise
|
### Hinweise
|
||||||
@@ -629,19 +655,19 @@ sudo /opt/adguard-shield/adguard-shield whitelist-flush
|
|||||||
### Status anzeigen
|
### Status anzeigen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield blocklist-status
|
sudo adguard-shield blocklist-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sofort synchronisieren
|
### Sofort synchronisieren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield blocklist-sync
|
sudo adguard-shield blocklist-sync
|
||||||
```
|
```
|
||||||
|
|
||||||
### Alle Sperren aus externer Blocklist aufheben
|
### Alle Sperren aus externer Blocklist aufheben
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield blocklist-flush
|
sudo adguard-shield blocklist-flush
|
||||||
```
|
```
|
||||||
|
|
||||||
### Hinweise
|
### Hinweise
|
||||||
@@ -658,13 +684,13 @@ sudo /opt/adguard-shield/adguard-shield blocklist-flush
|
|||||||
### Status anzeigen
|
### Status anzeigen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-status
|
sudo adguard-shield geoip-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Einzelne IP nachschlagen
|
### Einzelne IP nachschlagen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-lookup 8.8.8.8
|
sudo adguard-shield geoip-lookup 8.8.8.8
|
||||||
```
|
```
|
||||||
|
|
||||||
**Ausgabe:**
|
**Ausgabe:**
|
||||||
@@ -676,7 +702,7 @@ IP: 8.8.8.8 -> Land: US
|
|||||||
### Aktuelle Clients prüfen
|
### Aktuelle Clients prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-sync
|
sudo adguard-shield geoip-sync
|
||||||
```
|
```
|
||||||
|
|
||||||
Liest das aktuelle Querylog und prüft alle darin enthaltenen Client-IPs einmalig gegen die GeoIP-Regeln.
|
Liest das aktuelle Querylog und prüft alle darin enthaltenen Client-IPs einmalig gegen die GeoIP-Regeln.
|
||||||
@@ -684,13 +710,13 @@ Liest das aktuelle Querylog und prüft alle darin enthaltenen Client-IPs einmali
|
|||||||
### Alle GeoIP-Sperren aufheben
|
### Alle GeoIP-Sperren aufheben
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-flush
|
sudo adguard-shield geoip-flush
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cache leeren
|
### Cache leeren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-flush-cache
|
sudo adguard-shield geoip-flush-cache
|
||||||
```
|
```
|
||||||
|
|
||||||
### Hinweise
|
### Hinweise
|
||||||
@@ -705,25 +731,25 @@ sudo /opt/adguard-shield/adguard-shield geoip-flush-cache
|
|||||||
### Konfiguration und Cron-Status anzeigen
|
### Konfiguration und Cron-Status anzeigen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-status
|
sudo adguard-shield report-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### HTML-Report in Datei schreiben
|
### HTML-Report in Datei schreiben
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-generate html /tmp/adguard-shield-report.html
|
sudo adguard-shield report-generate html /tmp/adguard-shield-report.html
|
||||||
```
|
```
|
||||||
|
|
||||||
### Text-Report auf stdout ausgeben
|
### Text-Report auf stdout ausgeben
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-generate txt
|
sudo adguard-shield report-generate txt
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testmail senden
|
### Testmail senden
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-test
|
sudo adguard-shield report-test
|
||||||
```
|
```
|
||||||
|
|
||||||
Sendet eine einfache Testmail. Erst wenn diese funktioniert, lohnt sich die Fehlersuche am eigentlichen Report.
|
Sendet eine einfache Testmail. Erst wenn diese funktioniert, lohnt sich die Fehlersuche am eigentlichen Report.
|
||||||
@@ -731,21 +757,21 @@ Sendet eine einfache Testmail. Erst wenn diese funktioniert, lohnt sich die Fehl
|
|||||||
### Aktuellen Report erzeugen und versenden
|
### Aktuellen Report erzeugen und versenden
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-send
|
sudo adguard-shield report-send
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cron-Job installieren
|
### Cron-Job installieren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-install
|
sudo adguard-shield report-install
|
||||||
```
|
```
|
||||||
|
|
||||||
Erstellt die Datei `/etc/cron.d/adguard-shield-report` mit dem konfigurierten Intervall und der Versandzeit.
|
Erstellt die Datei `/etc/cron.d/adguard-shield-report` mit dem konfigurierten Intervall und der Versandzeit. Wenn der globale CLI-Befehl vorhanden ist, verwendet der Cron-Job `/usr/local/bin/adguard-shield`; sonst fällt er auf das installierte Binary unter `/opt/adguard-shield/adguard-shield` zurück.
|
||||||
|
|
||||||
### Cron-Job entfernen
|
### Cron-Job entfernen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-remove
|
sudo adguard-shield report-remove
|
||||||
```
|
```
|
||||||
|
|
||||||
Details zum Report-System stehen in [E-Mail Report](report.md).
|
Details zum Report-System stehen in [E-Mail Report](report.md).
|
||||||
@@ -755,7 +781,7 @@ Details zum Report-System stehen in [E-Mail Report](report.md).
|
|||||||
## Dry-Run
|
## Dry-Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield dry-run
|
sudo adguard-shield dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
Der Dry-Run ist der sicherste Weg, neue Konfigurationen zu prüfen, bevor sie produktiv gehen.
|
Der Dry-Run ist der sicherste Weg, neue Konfigurationen zu prüfen, bevor sie produktiv gehen.
|
||||||
@@ -773,11 +799,11 @@ Der Dry-Run ist der sicherste Weg, neue Konfigurationen zu prüfen, bevor sie pr
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Dry-Run starten (Strg+C zum Beenden)
|
# Dry-Run starten (Strg+C zum Beenden)
|
||||||
sudo /opt/adguard-shield/adguard-shield dry-run
|
sudo adguard-shield dry-run
|
||||||
|
|
||||||
# Ergebnisse prüfen
|
# Ergebnisse prüfen
|
||||||
sudo /opt/adguard-shield/adguard-shield history 50
|
sudo adguard-shield history 50
|
||||||
sudo /opt/adguard-shield/adguard-shield logs --level warn --limit 80
|
sudo adguard-shield logs --level warn --limit 80
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -785,7 +811,7 @@ sudo /opt/adguard-shield/adguard-shield logs --level warn --limit 80
|
|||||||
## Version
|
## Version
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/opt/adguard-shield/adguard-shield version
|
adguard-shield version
|
||||||
```
|
```
|
||||||
|
|
||||||
Zeigt die installierte Version an. Aliase: `--version`, `-v`.
|
Zeigt die installierte Version an. Aliase: `--version`, `-v`.
|
||||||
@@ -798,15 +824,15 @@ Zeigt die installierte Version an. Aliase: `--version`, `-v`.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl restart adguard-shield
|
sudo systemctl restart adguard-shield
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
sudo /opt/adguard-shield/adguard-shield logs --level info --limit 80
|
sudo adguard-shield logs --level info --limit 80
|
||||||
```
|
```
|
||||||
|
|
||||||
### Falsch gesperrte IP freigeben
|
### Falsch gesperrte IP freigeben
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield unban 192.168.1.100
|
sudo adguard-shield unban 192.168.1.100
|
||||||
sudo /opt/adguard-shield/adguard-shield reset-offenses 192.168.1.100
|
sudo adguard-shield reset-offenses 192.168.1.100
|
||||||
```
|
```
|
||||||
|
|
||||||
Danach die IP dauerhaft in `WHITELIST` oder eine externe Whitelist aufnehmen.
|
Danach die IP dauerhaft in `WHITELIST` oder eine externe Whitelist aufnehmen.
|
||||||
@@ -814,16 +840,16 @@ Danach die IP dauerhaft in `WHITELIST` oder eine externe Whitelist aufnehmen.
|
|||||||
### Externe Listen neu laden
|
### Externe Listen neu laden
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield whitelist-sync
|
sudo adguard-shield whitelist-sync
|
||||||
sudo /opt/adguard-shield/adguard-shield blocklist-sync
|
sudo adguard-shield blocklist-sync
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Firewall neu aufbauen
|
### Firewall neu aufbauen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-remove
|
sudo adguard-shield firewall-remove
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-create
|
sudo adguard-shield firewall-create
|
||||||
sudo systemctl restart adguard-shield
|
sudo systemctl restart adguard-shield
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -834,8 +860,8 @@ Nach dem Neustart schreibt der Daemon aktive Sperren aus SQLite wieder in die Fi
|
|||||||
```bash
|
```bash
|
||||||
sudo systemctl status adguard-shield
|
sudo systemctl status adguard-shield
|
||||||
sudo journalctl -u adguard-shield --no-pager -n 100
|
sudo journalctl -u adguard-shield --no-pager -n 100
|
||||||
sudo /opt/adguard-shield/adguard-shield test
|
sudo adguard-shield test
|
||||||
sudo /opt/adguard-shield/adguard-shield logs --level debug --limit 100
|
sudo adguard-shield logs --level debug --limit 100
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -900,7 +926,7 @@ Die Beispielzahlen liegen bewusst nahe an den Standardlimits `RATE_LIMIT_MAX_REQ
|
|||||||
## Eingebaute Hilfe
|
## Eingebaute Hilfe
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/opt/adguard-shield/adguard-shield --help
|
adguard-shield --help
|
||||||
```
|
```
|
||||||
|
|
||||||
Bei unbekannten Befehlen gibt das Binary die Usage-Ausgabe aus.
|
Bei unbekannten Befehlen gibt das Binary die Usage-Ausgabe aus.
|
||||||
|
|||||||
@@ -217,9 +217,9 @@ GEOIP_NOTIFY=false
|
|||||||
Diese Befehle können viele IPs auf einmal freigeben:
|
Diese Befehle können viele IPs auf einmal freigeben:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield flush
|
sudo adguard-shield flush
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-flush
|
sudo adguard-shield geoip-flush
|
||||||
sudo /opt/adguard-shield/adguard-shield blocklist-flush
|
sudo adguard-shield blocklist-flush
|
||||||
```
|
```
|
||||||
|
|
||||||
AdGuard Shield sendet dafür **nicht** eine Nachricht pro IP, sondern eine zusammenfassende Meldung mit der Anzahl der freigegebenen Sperren.
|
AdGuard Shield sendet dafür **nicht** eine Nachricht pro IP, sondern eine zusammenfassende Meldung mit der Anzahl der freigegebenen Sperren.
|
||||||
@@ -326,7 +326,7 @@ Aktion: Manual-Flush
|
|||||||
Wenn keine Benachrichtigung ankommt:
|
Wenn keine Benachrichtigung ankommt:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield logs --level warn --limit 100
|
sudo adguard-shield logs --level warn --limit 100
|
||||||
sudo journalctl -u adguard-shield --no-pager -n 100
|
sudo journalctl -u adguard-shield --no-pager -n 100
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -143,8 +143,8 @@ BLOCKED_PORTS="53 443 853"
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl restart adguard-shield
|
sudo systemctl restart adguard-shield
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-status
|
sudo adguard-shield firewall-status
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
```
|
```
|
||||||
|
|
||||||
## Firewall neu aufbauen
|
## Firewall neu aufbauen
|
||||||
@@ -152,9 +152,9 @@ sudo /opt/adguard-shield/adguard-shield status
|
|||||||
Falls der Modus gewechselt wurde:
|
Falls der Modus gewechselt wurde:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-remove
|
sudo adguard-shield firewall-remove
|
||||||
sudo systemctl restart adguard-shield
|
sudo systemctl restart adguard-shield
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-status
|
sudo adguard-shield firewall-status
|
||||||
```
|
```
|
||||||
|
|
||||||
Der Daemon erstellt die Firewall-Struktur beim Start automatisch neu und überträgt aktive Sperren aus SQLite.
|
Der Daemon erstellt die Firewall-Struktur beim Start automatisch neu und überträgt aktive Sperren aus SQLite.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Nach Änderungen muss der Service neu gestartet werden:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl restart adguard-shield
|
sudo systemctl restart adguard-shield
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
```
|
```
|
||||||
|
|
||||||
## Automatische Migration
|
## Automatische Migration
|
||||||
@@ -42,10 +42,10 @@ Nach dem Bearbeiten der Konfiguration:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# API-Verbindung testen
|
# API-Verbindung testen
|
||||||
sudo /opt/adguard-shield/adguard-shield test
|
sudo adguard-shield test
|
||||||
|
|
||||||
# Dry-Run: zeigt, was gesperrt würde, ohne die Firewall zu verändern
|
# Dry-Run: zeigt, was gesperrt würde, ohne die Firewall zu verändern
|
||||||
sudo /opt/adguard-shield/adguard-shield dry-run
|
sudo adguard-shield dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -262,10 +262,10 @@ Progressive Sperren gelten für Monitor-Sperren wie `rate-limit` und `subdomain-
|
|||||||
### Verwaltungsbefehle
|
### Verwaltungsbefehle
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield offense-status # Zähler anzeigen
|
sudo adguard-shield offense-status # Zähler anzeigen
|
||||||
sudo /opt/adguard-shield/adguard-shield offense-cleanup # Abgelaufene entfernen
|
sudo adguard-shield offense-cleanup # Abgelaufene entfernen
|
||||||
sudo /opt/adguard-shield/adguard-shield reset-offenses # Alle zurücksetzen
|
sudo adguard-shield reset-offenses # Alle zurücksetzen
|
||||||
sudo /opt/adguard-shield/adguard-shield reset-offenses <IP> # Eine IP zurücksetzen
|
sudo adguard-shield reset-offenses <IP> # Eine IP zurücksetzen
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -289,9 +289,9 @@ sudo /opt/adguard-shield/adguard-shield reset-offenses <IP> # Eine IP zurück
|
|||||||
### CLI-Befehle
|
### CLI-Befehle
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield logs --level warn --limit 100
|
sudo adguard-shield logs --level warn --limit 100
|
||||||
sudo /opt/adguard-shield/adguard-shield logs-follow debug
|
sudo adguard-shield logs-follow debug
|
||||||
sudo /opt/adguard-shield/adguard-shield live
|
sudo adguard-shield live
|
||||||
```
|
```
|
||||||
|
|
||||||
**Hinweis:** Query-Inhalte werden nicht dauerhaft ins Log geschrieben. Für Query-nahe Diagnose ist die Live-Ansicht gedacht.
|
**Hinweis:** Query-Inhalte werden nicht dauerhaft ins Log geschrieben. Für Query-nahe Diagnose ist die Live-Ansicht gedacht.
|
||||||
@@ -380,7 +380,7 @@ Details zu allen Kanälen stehen in [Benachrichtigungen](benachrichtigungen.md).
|
|||||||
| `REPORT_EMAIL_FROM` | `adguard-shield@example.com` | Absenderadresse |
|
| `REPORT_EMAIL_FROM` | `adguard-shield@example.com` | Absenderadresse |
|
||||||
| `REPORT_FORMAT` | `html` | Report-Format |
|
| `REPORT_FORMAT` | `html` | Report-Format |
|
||||||
| `REPORT_MAIL_CMD` | `msmtp` | Mailprogramm für den Versand |
|
| `REPORT_MAIL_CMD` | `msmtp` | Mailprogramm für den Versand |
|
||||||
| `REPORT_BUSIEST_DAY_RANGE` | `30` | Zeitraum für "Aktivster Tag" (Kompatibilitätsparameter) |
|
| `REPORT_BUSIEST_DAY_RANGE` | `30` | Zeitraum für "Aktivster Tag"; `0` nutzt den Berichtszeitraum |
|
||||||
|
|
||||||
### Verfügbare Intervalle
|
### Verfügbare Intervalle
|
||||||
|
|
||||||
@@ -388,7 +388,7 @@ Details zu allen Kanälen stehen in [Benachrichtigungen](benachrichtigungen.md).
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `daily` | Täglich zur konfigurierten Uhrzeit |
|
| `daily` | Täglich zur konfigurierten Uhrzeit |
|
||||||
| `weekly` | Montags zur konfigurierten Uhrzeit |
|
| `weekly` | Montags zur konfigurierten Uhrzeit |
|
||||||
| `biweekly` | Am 1. und 15. des Monats |
|
| `biweekly` | Montags in ungeraden ISO-Kalenderwochen |
|
||||||
| `monthly` | Am 1. des Monats |
|
| `monthly` | Am 1. des Monats |
|
||||||
|
|
||||||
### Verfügbare Formate
|
### Verfügbare Formate
|
||||||
@@ -413,11 +413,13 @@ REPORT_MAIL_CMD="msmtp"
|
|||||||
### Cron-Job installieren
|
### Cron-Job installieren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-install
|
sudo adguard-shield report-install
|
||||||
```
|
```
|
||||||
|
|
||||||
Details stehen in [E-Mail Report](report.md).
|
Details stehen in [E-Mail Report](report.md).
|
||||||
|
|
||||||
|
Die Templates werden bei `install`/`update` nach `/opt/adguard-shield/templates` geschrieben. Eigene Templates können alternativ über `ADGUARD_SHIELD_TEMPLATE_DIR` bereitgestellt werden.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Externe Whitelist
|
## Externe Whitelist
|
||||||
@@ -640,11 +642,11 @@ Die Datenbank wird unter `/opt/adguard-shield/geoip/` gespeichert und nach 24 St
|
|||||||
### GeoIP-Befehle
|
### GeoIP-Befehle
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-status # Status anzeigen
|
sudo adguard-shield geoip-status # Status anzeigen
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-lookup 8.8.8.8 # IP nachschlagen
|
sudo adguard-shield geoip-lookup 8.8.8.8 # IP nachschlagen
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-sync # Clients prüfen
|
sudo adguard-shield geoip-sync # Clients prüfen
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-flush-cache # Cache leeren
|
sudo adguard-shield geoip-flush-cache # Cache leeren
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-flush # Alle GeoIP-Sperren aufheben
|
sudo adguard-shield geoip-flush # Alle GeoIP-Sperren aufheben
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -737,6 +739,6 @@ NTFY_TOPIC="adguard-shield-prod"
|
|||||||
### Vor produktiver Aktivierung
|
### Vor produktiver Aktivierung
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield test
|
sudo adguard-shield test
|
||||||
sudo /opt/adguard-shield/adguard-shield dry-run
|
sudo adguard-shield dry-run
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -17,11 +17,26 @@ Der Report basiert auf der SQLite-Datenbank:
|
|||||||
| Zeitraum | Start- und Enddatum des Berichtszeitraums |
|
| Zeitraum | Start- und Enddatum des Berichtszeitraums |
|
||||||
| Sperren | Anzahl der Sperren im Zeitraum |
|
| Sperren | Anzahl der Sperren im Zeitraum |
|
||||||
| Freigaben | Anzahl der Freigaben im Zeitraum |
|
| Freigaben | Anzahl der Freigaben im Zeitraum |
|
||||||
|
| Zeitraum-Schnellübersicht | Heute, gestern, letzte 7/14/30 Tage |
|
||||||
| Aktive Sperren | Derzeit aktive Sperren zum Zeitpunkt der Report-Erstellung |
|
| Aktive Sperren | Derzeit aktive Sperren zum Zeitpunkt der Report-Erstellung |
|
||||||
|
| Permanente Sperren | Anzahl permanenter Sperren im Zeitraum |
|
||||||
|
| AbuseIPDB | Anzahl erfolgreicher AbuseIPDB-Meldungen im Zeitraum |
|
||||||
|
| Angriffsarten | Rate-Limit, Subdomain-Flood und externe Blocklist |
|
||||||
| Top-Clients | Die am häufigsten gesperrten Client-IPs |
|
| Top-Clients | Die am häufigsten gesperrten Client-IPs |
|
||||||
| Sperrgründe | Aufschlüsselung nach Grund (Rate-Limit, Subdomain-Flood, GeoIP usw.) |
|
| Top-Domains | Die am häufigsten betroffenen Domains |
|
||||||
| Sperrquellen | Aufschlüsselung nach Quelle (Monitor, GeoIP, Blocklist, manuell) |
|
| Protokolle | Verteilung nach DNS, DoH, DoT, DoQ usw. |
|
||||||
| Letzte Ereignisse | Die letzten 20 Einträge aus der Ban-History |
|
| Letzte Sperren | Die letzten 10 Sperren im Berichtszeitraum |
|
||||||
|
|
||||||
|
### Templates
|
||||||
|
|
||||||
|
Die Standard-Templates liegen im Code unter:
|
||||||
|
|
||||||
|
```text
|
||||||
|
internal/report/templates/report.html
|
||||||
|
internal/report/templates/report.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Sie werden ins Binary eingebettet und bei `install` sowie `update` nach `/opt/adguard-shield/templates` geschrieben. Zur Laufzeit verwendet AdGuard Shield zuerst externe Templates aus `ADGUARD_SHIELD_TEMPLATE_DIR`, danach `templates` neben der Konfiguration bzw. neben dem Binary und fällt erst zuletzt auf die eingebetteten Standard-Templates zurück.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -36,7 +51,7 @@ Der Report basiert auf der SQLite-Datenbank:
|
|||||||
| `REPORT_EMAIL_FROM` | `adguard-shield@example.com` | Absenderadresse |
|
| `REPORT_EMAIL_FROM` | `adguard-shield@example.com` | Absenderadresse |
|
||||||
| `REPORT_FORMAT` | `html` | Report-Format (`html` oder `txt`) |
|
| `REPORT_FORMAT` | `html` | Report-Format (`html` oder `txt`) |
|
||||||
| `REPORT_MAIL_CMD` | `msmtp` | Mailprogramm für den Versand |
|
| `REPORT_MAIL_CMD` | `msmtp` | Mailprogramm für den Versand |
|
||||||
| `REPORT_BUSIEST_DAY_RANGE` | `30` | Kompatibilitätsparameter für den Zeitraum "Aktivster Tag" |
|
| `REPORT_BUSIEST_DAY_RANGE` | `30` | Zeitraum für "Aktivster Tag"; `0` nutzt den Berichtszeitraum |
|
||||||
|
|
||||||
### Versandintervalle
|
### Versandintervalle
|
||||||
|
|
||||||
@@ -44,7 +59,7 @@ Der Report basiert auf der SQLite-Datenbank:
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `daily` | Täglich zur Uhrzeit aus `REPORT_TIME` |
|
| `daily` | Täglich zur Uhrzeit aus `REPORT_TIME` |
|
||||||
| `weekly` | Montags zur Uhrzeit aus `REPORT_TIME` |
|
| `weekly` | Montags zur Uhrzeit aus `REPORT_TIME` |
|
||||||
| `biweekly` | Am 1. und 15. des Monats zur Uhrzeit aus `REPORT_TIME` |
|
| `biweekly` | Montags in ungeraden ISO-Kalenderwochen zur Uhrzeit aus `REPORT_TIME` |
|
||||||
| `monthly` | Am 1. des Monats zur Uhrzeit aus `REPORT_TIME` |
|
| `monthly` | Am 1. des Monats zur Uhrzeit aus `REPORT_TIME` |
|
||||||
|
|
||||||
### Formate
|
### Formate
|
||||||
@@ -73,13 +88,13 @@ REPORT_MAIL_CMD="msmtp"
|
|||||||
### Konfiguration und Cron-Status anzeigen
|
### Konfiguration und Cron-Status anzeigen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-status
|
sudo adguard-shield report-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### HTML-Report in Datei schreiben
|
### HTML-Report in Datei schreiben
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-generate html /tmp/adguard-shield-report.html
|
sudo adguard-shield report-generate html /tmp/adguard-shield-report.html
|
||||||
```
|
```
|
||||||
|
|
||||||
Die Datei kann im Browser geöffnet werden, um das Ergebnis zu prüfen.
|
Die Datei kann im Browser geöffnet werden, um das Ergebnis zu prüfen.
|
||||||
@@ -87,13 +102,13 @@ Die Datei kann im Browser geöffnet werden, um das Ergebnis zu prüfen.
|
|||||||
### Text-Report auf stdout ausgeben
|
### Text-Report auf stdout ausgeben
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-generate txt
|
sudo adguard-shield report-generate txt
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testmail senden
|
### Testmail senden
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-test
|
sudo adguard-shield report-test
|
||||||
```
|
```
|
||||||
|
|
||||||
Sendet eine einfache Testmail. Erst wenn diese ankommt, lohnt sich die Fehlersuche am eigentlichen Report.
|
Sendet eine einfache Testmail. Erst wenn diese ankommt, lohnt sich die Fehlersuche am eigentlichen Report.
|
||||||
@@ -101,19 +116,19 @@ Sendet eine einfache Testmail. Erst wenn diese ankommt, lohnt sich die Fehlersuc
|
|||||||
### Aktuellen Report erzeugen und versenden
|
### Aktuellen Report erzeugen und versenden
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-send
|
sudo adguard-shield report-send
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cron-Job installieren
|
### Cron-Job installieren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-install
|
sudo adguard-shield report-install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cron-Job entfernen
|
### Cron-Job entfernen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-remove
|
sudo adguard-shield report-remove
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -133,7 +148,7 @@ REPORT_MAIL_CMD="msmtp"
|
|||||||
sudo apt install msmtp msmtp-mta
|
sudo apt install msmtp msmtp-mta
|
||||||
|
|
||||||
# Testmail senden
|
# Testmail senden
|
||||||
sudo /opt/adguard-shield/adguard-shield report-test
|
sudo adguard-shield report-test
|
||||||
```
|
```
|
||||||
|
|
||||||
### Eigene Mailprogramm-Argumente
|
### Eigene Mailprogramm-Argumente
|
||||||
@@ -160,7 +175,7 @@ REPORT_MAIL_CMD="msmtp --account=default"
|
|||||||
### Cron-Job installieren
|
### Cron-Job installieren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-install
|
sudo adguard-shield report-install
|
||||||
```
|
```
|
||||||
|
|
||||||
Dadurch wird diese Datei geschrieben:
|
Dadurch wird diese Datei geschrieben:
|
||||||
@@ -169,25 +184,27 @@ Dadurch wird diese Datei geschrieben:
|
|||||||
/etc/cron.d/adguard-shield-report
|
/etc/cron.d/adguard-shield-report
|
||||||
```
|
```
|
||||||
|
|
||||||
Der Cron-Eintrag ruft das installierte Binary mit der installierten Konfiguration auf:
|
Der Cron-Eintrag ruft den globalen CLI-Befehl mit der installierten Konfiguration auf, sofern er registriert ist:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/opt/adguard-shield/adguard-shield -config /opt/adguard-shield/adguard-shield.conf report-send
|
/usr/local/bin/adguard-shield -config /opt/adguard-shield/adguard-shield.conf report-send
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Falls die Installation mit `--no-register` erfolgt ist, verwendet `report-install` stattdessen `/opt/adguard-shield/adguard-shield`.
|
||||||
|
|
||||||
### Zeitplan nach Intervall
|
### Zeitplan nach Intervall
|
||||||
|
|
||||||
| Intervall | Cron-Verhalten |
|
| Intervall | Cron-Verhalten |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `daily` | Täglich zur Uhrzeit aus `REPORT_TIME` |
|
| `daily` | Täglich zur Uhrzeit aus `REPORT_TIME` |
|
||||||
| `weekly` | Montags zur Uhrzeit aus `REPORT_TIME` |
|
| `weekly` | Montags zur Uhrzeit aus `REPORT_TIME` |
|
||||||
| `biweekly` | Am 1. und 15. des Monats |
|
| `biweekly` | Montags in ungeraden ISO-Kalenderwochen |
|
||||||
| `monthly` | Am 1. des Monats |
|
| `monthly` | Am 1. des Monats |
|
||||||
|
|
||||||
### Cron-Job entfernen
|
### Cron-Job entfernen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-remove
|
sudo adguard-shield report-remove
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -197,33 +214,33 @@ sudo /opt/adguard-shield/adguard-shield report-remove
|
|||||||
### Schritt 1: Status prüfen
|
### Schritt 1: Status prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-status
|
sudo adguard-shield report-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Schritt 2: Report lokal erzeugen
|
### Schritt 2: Report lokal erzeugen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# HTML-Report zum Ansehen im Browser
|
# HTML-Report zum Ansehen im Browser
|
||||||
sudo /opt/adguard-shield/adguard-shield report-generate html /tmp/adguard-shield-report.html
|
sudo adguard-shield report-generate html /tmp/adguard-shield-report.html
|
||||||
|
|
||||||
# Text-Report in der Konsole
|
# Text-Report in der Konsole
|
||||||
sudo /opt/adguard-shield/adguard-shield report-generate txt
|
sudo adguard-shield report-generate txt
|
||||||
```
|
```
|
||||||
|
|
||||||
### Schritt 3: Versand testen
|
### Schritt 3: Versand testen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Einfache Testmail
|
# Einfache Testmail
|
||||||
sudo /opt/adguard-shield/adguard-shield report-test
|
sudo adguard-shield report-test
|
||||||
|
|
||||||
# Vollständigen Report senden
|
# Vollständigen Report senden
|
||||||
sudo /opt/adguard-shield/adguard-shield report-send
|
sudo adguard-shield report-send
|
||||||
```
|
```
|
||||||
|
|
||||||
### Schritt 4: Logs prüfen
|
### Schritt 4: Logs prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield logs --level warn --limit 100
|
sudo adguard-shield logs --level warn --limit 100
|
||||||
sudo journalctl -u cron --no-pager -n 100
|
sudo journalctl -u cron --no-pager -n 100
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -262,7 +279,7 @@ Oder setze `REPORT_MAIL_CMD` auf dein vorhandenes Mailprogramm.
|
|||||||
Prüfe die Konfiguration und den Cron-Job:
|
Prüfe die Konfiguration und den Cron-Job:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-send
|
sudo adguard-shield report-send
|
||||||
sudo cat /etc/cron.d/adguard-shield-report
|
sudo cat /etc/cron.d/adguard-shield-report
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -281,6 +298,6 @@ sudo cat /etc/cron.d/adguard-shield-report
|
|||||||
Du kannst das Format unabhängig von der Konfiguration wählen:
|
Du kannst das Format unabhängig von der Konfiguration wählen:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-generate txt
|
sudo adguard-shield report-generate txt
|
||||||
sudo /opt/adguard-shield/adguard-shield report-generate html /tmp/report.html
|
sudo adguard-shield report-generate html /tmp/report.html
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -14,19 +14,19 @@ sudo systemctl status adguard-shield
|
|||||||
sudo journalctl -u adguard-shield --no-pager -n 100
|
sudo journalctl -u adguard-shield --no-pager -n 100
|
||||||
|
|
||||||
# 3. Funktioniert die API?
|
# 3. Funktioniert die API?
|
||||||
sudo /opt/adguard-shield/adguard-shield test
|
sudo adguard-shield test
|
||||||
|
|
||||||
# 4. Was ist der aktuelle Zustand?
|
# 4. Was ist der aktuelle Zustand?
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
|
|
||||||
# 5. Gibt es Warnungen oder Fehler?
|
# 5. Gibt es Warnungen oder Fehler?
|
||||||
sudo /opt/adguard-shield/adguard-shield logs --level warn --limit 100
|
sudo adguard-shield logs --level warn --limit 100
|
||||||
```
|
```
|
||||||
|
|
||||||
Wenn du aktuelle Queries und den Echtzeit-Zustand sehen willst:
|
Wenn du aktuelle Queries und den Echtzeit-Zustand sehen willst:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield live
|
sudo adguard-shield live
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -68,7 +68,7 @@ sudo systemctl daemon-reload
|
|||||||
### Test
|
### Test
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield test
|
sudo adguard-shield test
|
||||||
```
|
```
|
||||||
|
|
||||||
### Konfiguration prüfen
|
### Konfiguration prüfen
|
||||||
@@ -104,9 +104,9 @@ Passe URL und Zugangsdaten entsprechend an.
|
|||||||
### Prüfen
|
### Prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield live --once
|
sudo adguard-shield live --once
|
||||||
sudo /opt/adguard-shield/adguard-shield history 50
|
sudo adguard-shield history 50
|
||||||
sudo /opt/adguard-shield/adguard-shield logs --level debug --limit 100
|
sudo adguard-shield logs --level debug --limit 100
|
||||||
```
|
```
|
||||||
|
|
||||||
### Mögliche Ursachen und Lösungen
|
### Mögliche Ursachen und Lösungen
|
||||||
@@ -131,8 +131,8 @@ sudo /opt/adguard-shield/adguard-shield logs --level debug --limit 100
|
|||||||
### Übersicht verschaffen
|
### Übersicht verschaffen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
sudo /opt/adguard-shield/adguard-shield history 100
|
sudo adguard-shield history 100
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ursachen und Gegenmaßnahmen
|
### Ursachen und Gegenmaßnahmen
|
||||||
@@ -149,10 +149,10 @@ sudo /opt/adguard-shield/adguard-shield history 100
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Sperre aufheben
|
# Sperre aufheben
|
||||||
sudo /opt/adguard-shield/adguard-shield unban 192.168.1.100
|
sudo adguard-shield unban 192.168.1.100
|
||||||
|
|
||||||
# Offense-Zähler zurücksetzen (damit progressive Sperren nicht sofort eskalieren)
|
# Offense-Zähler zurücksetzen (damit progressive Sperren nicht sofort eskalieren)
|
||||||
sudo /opt/adguard-shield/adguard-shield reset-offenses 192.168.1.100
|
sudo adguard-shield reset-offenses 192.168.1.100
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dauerhaft ausnehmen
|
### Dauerhaft ausnehmen
|
||||||
@@ -174,7 +174,7 @@ sudo systemctl restart adguard-shield
|
|||||||
### Status über AdGuard Shield
|
### Status über AdGuard Shield
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-status
|
sudo adguard-shield firewall-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Direkte Prüfung mit Systembefehlen
|
### Direkte Prüfung mit Systembefehlen
|
||||||
@@ -198,8 +198,8 @@ sudo iptables -n -L DOCKER-USER --line-numbers -v | grep ADGUARD
|
|||||||
### Firewall neu aufbauen
|
### Firewall neu aufbauen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-remove
|
sudo adguard-shield firewall-remove
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-create
|
sudo adguard-shield firewall-create
|
||||||
sudo systemctl restart adguard-shield
|
sudo systemctl restart adguard-shield
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -212,8 +212,8 @@ Nach dem Neustart werden aktive Sperren aus SQLite wieder in die ipsets geschrie
|
|||||||
### Prüfen
|
### Prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
sudo /opt/adguard-shield/adguard-shield history 100
|
sudo adguard-shield history 100
|
||||||
```
|
```
|
||||||
|
|
||||||
Temporäre Sperren werden beim Start und während jedes Pollings auf Ablauf geprüft. Wenn eine Sperre als permanent angezeigt wird, wird sie nicht automatisch freigegeben.
|
Temporäre Sperren werden beim Start und während jedes Pollings auf Ablauf geprüft. Wenn eine Sperre als permanent angezeigt wird, wird sie nicht automatisch freigegeben.
|
||||||
@@ -231,7 +231,7 @@ Temporäre Sperren werden beim Start und während jedes Pollings auf Ablauf gepr
|
|||||||
### Manuell freigeben
|
### Manuell freigeben
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield unban 192.168.1.100
|
sudo adguard-shield unban 192.168.1.100
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -242,13 +242,13 @@ Dry-Run ist ideal, um neue Konfigurationen zu prüfen, bevor sie produktiv gehen
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Dry-Run starten (Strg+C zum Beenden)
|
# Dry-Run starten (Strg+C zum Beenden)
|
||||||
sudo /opt/adguard-shield/adguard-shield dry-run
|
sudo adguard-shield dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
Währenddessen die Ergebnisse prüfen:
|
Währenddessen die Ergebnisse prüfen:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield history 50
|
sudo adguard-shield history 50
|
||||||
```
|
```
|
||||||
|
|
||||||
Im Dry-Run werden mögliche Sperren als `DRY` protokolliert. Es entstehen keine aktiven Sperren und keine Firewall-Änderungen.
|
Im Dry-Run werden mögliche Sperren als `DRY` protokolliert. Es entstehen keine aktiven Sperren und keine Firewall-Änderungen.
|
||||||
@@ -260,13 +260,13 @@ Im Dry-Run werden mögliche Sperren als `DRY` protokolliert. Es entstehen keine
|
|||||||
### Status prüfen
|
### Status prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield whitelist-status
|
sudo adguard-shield whitelist-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manuell synchronisieren
|
### Manuell synchronisieren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield whitelist-sync
|
sudo adguard-shield whitelist-sync
|
||||||
```
|
```
|
||||||
|
|
||||||
### Typische Probleme
|
### Typische Probleme
|
||||||
@@ -295,19 +295,19 @@ trusted.example.com # Hostname (wird per DNS aufgelöst)
|
|||||||
### Status prüfen
|
### Status prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield blocklist-status
|
sudo adguard-shield blocklist-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manuell synchronisieren
|
### Manuell synchronisieren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield blocklist-sync
|
sudo adguard-shield blocklist-sync
|
||||||
```
|
```
|
||||||
|
|
||||||
### Alle Blocklist-Sperren freigeben
|
### Alle Blocklist-Sperren freigeben
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield blocklist-flush
|
sudo adguard-shield blocklist-flush
|
||||||
```
|
```
|
||||||
|
|
||||||
### Zu viele IPs gesperrt?
|
### Zu viele IPs gesperrt?
|
||||||
@@ -325,25 +325,25 @@ sudo /opt/adguard-shield/adguard-shield blocklist-flush
|
|||||||
### Status prüfen
|
### Status prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-status
|
sudo adguard-shield geoip-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Einzelne IP prüfen
|
### Einzelne IP prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-lookup 8.8.8.8
|
sudo adguard-shield geoip-lookup 8.8.8.8
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cache leeren
|
### Cache leeren
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-flush-cache
|
sudo adguard-shield geoip-flush-cache
|
||||||
```
|
```
|
||||||
|
|
||||||
### Alle GeoIP-Sperren freigeben
|
### Alle GeoIP-Sperren freigeben
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield geoip-flush
|
sudo adguard-shield geoip-flush
|
||||||
```
|
```
|
||||||
|
|
||||||
### Typische Probleme und Lösungen
|
### Typische Probleme und Lösungen
|
||||||
@@ -368,17 +368,17 @@ Die GeoIP-Ländercodes folgen dem Standard ISO 3166-1 Alpha-2. Eine vollständig
|
|||||||
### Status prüfen
|
### Status prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield report-status
|
sudo adguard-shield report-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Funktionstest
|
### Funktionstest
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Testmail senden
|
# Testmail senden
|
||||||
sudo /opt/adguard-shield/adguard-shield report-test
|
sudo adguard-shield report-test
|
||||||
|
|
||||||
# Text-Report in der Konsole ansehen
|
# Text-Report in der Konsole ansehen
|
||||||
sudo /opt/adguard-shield/adguard-shield report-generate txt
|
sudo adguard-shield report-generate txt
|
||||||
```
|
```
|
||||||
|
|
||||||
### Keine Mail kommt an?
|
### Keine Mail kommt an?
|
||||||
@@ -396,7 +396,7 @@ sudo /opt/adguard-shield/adguard-shield report-generate txt
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo cat /etc/cron.d/adguard-shield-report
|
sudo cat /etc/cron.d/adguard-shield-report
|
||||||
sudo /opt/adguard-shield/adguard-shield report-send
|
sudo adguard-shield report-send
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -406,7 +406,7 @@ sudo /opt/adguard-shield/adguard-shield report-send
|
|||||||
### Prüfen
|
### Prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield logs --level warn --limit 100
|
sudo adguard-shield logs --level warn --limit 100
|
||||||
```
|
```
|
||||||
|
|
||||||
### Checkliste
|
### Checkliste
|
||||||
@@ -497,13 +497,13 @@ Wenn der Zustand unklar ist und ein sauberer Neustart nötig ist:
|
|||||||
sudo systemctl stop adguard-shield
|
sudo systemctl stop adguard-shield
|
||||||
|
|
||||||
# Firewall-Struktur entfernen
|
# Firewall-Struktur entfernen
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-remove
|
sudo adguard-shield firewall-remove
|
||||||
|
|
||||||
# Service neu starten (baut Firewall aus SQLite wieder auf)
|
# Service neu starten (baut Firewall aus SQLite wieder auf)
|
||||||
sudo systemctl start adguard-shield
|
sudo systemctl start adguard-shield
|
||||||
|
|
||||||
# Status prüfen
|
# Status prüfen
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
```
|
```
|
||||||
|
|
||||||
Das entfernt die Firewall-Struktur und lässt den Daemon sie beim Start wieder aus dem SQLite-State aufbauen. Aktive Sperren bleiben in der Datenbank erhalten.
|
Das entfernt die Firewall-Struktur und lässt den Daemon sie beim Start wieder aus dem SQLite-State aufbauen. Aktive Sperren bleiben in der Datenbank erhalten.
|
||||||
@@ -515,13 +515,13 @@ Das entfernt die Firewall-Struktur und lässt den Daemon sie beim Start wieder a
|
|||||||
### Konfiguration behalten
|
### Konfiguration behalten
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield uninstall --keep-config
|
sudo adguard-shield uninstall --keep-config
|
||||||
```
|
```
|
||||||
|
|
||||||
### Alles entfernen
|
### Alles entfernen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield uninstall
|
sudo adguard-shield uninstall
|
||||||
```
|
```
|
||||||
|
|
||||||
Ohne `--keep-config` werden Installationsverzeichnis, State-Verzeichnis und Logdatei entfernt.
|
Ohne `--keep-config` werden Installationsverzeichnis, State-Verzeichnis und Logdatei entfernt.
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ sudo ./adguard-shield update
|
|||||||
|
|
||||||
Am Ende fragt der Updater, ob AdGuard Shield direkt neu gestartet werden soll.
|
Am Ende fragt der Updater, ob AdGuard Shield direkt neu gestartet werden soll.
|
||||||
|
|
||||||
|
Der Updater registriert dabei auch den globalen CLI-Befehl `/usr/local/bin/adguard-shield`. Nach dem Update kannst du die installierte Anwendung daher direkt mit `sudo adguard-shield <befehl>` verwenden.
|
||||||
|
|
||||||
### Nach dem Update prüfen
|
### Nach dem Update prüfen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield install-status
|
sudo adguard-shield install-status
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
sudo journalctl -u adguard-shield --no-pager -n 50
|
sudo journalctl -u adguard-shield --no-pager -n 50
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -66,11 +68,12 @@ Der Update-Befehl nutzt intern dieselbe Routine wie die Installation:
|
|||||||
| 3 | Systemabhängigkeiten prüfen (sofern nicht `--skip-deps`) |
|
| 3 | Systemabhängigkeiten prüfen (sofern nicht `--skip-deps`) |
|
||||||
| 4 | Installationsverzeichnis sicherstellen |
|
| 4 | Installationsverzeichnis sicherstellen |
|
||||||
| 5 | Neues Binary nach `/opt/adguard-shield/adguard-shield` kopieren |
|
| 5 | Neues Binary nach `/opt/adguard-shield/adguard-shield` kopieren |
|
||||||
| 6 | Konfiguration migrieren (vorhandene Werte behalten, neue ergänzen) |
|
| 6 | CLI-Befehl `/usr/local/bin/adguard-shield` registrieren/aktualisieren (sofern nicht `--no-register`) |
|
||||||
| 7 | systemd-Service neu schreiben |
|
| 7 | Report-Templates installieren |
|
||||||
| 8 | `systemctl daemon-reload` |
|
| 8 | Konfiguration migrieren (vorhandene Werte behalten, neue ergänzen) |
|
||||||
| 9 | Autostart aktivieren (sofern nicht `--no-enable`) |
|
| 9 | systemd-Service neu schreiben |
|
||||||
| 10 | Nachfrage: Service direkt neu starten |
|
| 10 | `systemctl daemon-reload` und Autostart aktivieren (sofern nicht `--no-enable`) |
|
||||||
|
| 11 | Nachfrage: Service direkt neu starten |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -113,8 +116,8 @@ Wenn du vorher manuell prüfen möchtest:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ./adguard-shield update
|
sudo ./adguard-shield update
|
||||||
sudo /opt/adguard-shield/adguard-shield test
|
sudo adguard-shield test
|
||||||
sudo /opt/adguard-shield/adguard-shield dry-run
|
sudo adguard-shield dry-run
|
||||||
sudo systemctl restart adguard-shield
|
sudo systemctl restart adguard-shield
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -126,6 +129,14 @@ sudo ./adguard-shield update --skip-deps
|
|||||||
|
|
||||||
Sinnvoll, wenn `iptables`, `ip6tables`, `ipset` und `systemctl` bereits vorhanden sind oder die Paketinstallation nicht über `apt-get` laufen soll.
|
Sinnvoll, wenn `iptables`, `ip6tables`, `ipset` und `systemctl` bereits vorhanden sind oder die Paketinstallation nicht über `apt-get` laufen soll.
|
||||||
|
|
||||||
|
### Update ohne CLI-Registrierung
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./adguard-shield update --no-register
|
||||||
|
```
|
||||||
|
|
||||||
|
Damit wird kein Symlink unter `/usr/local/bin/adguard-shield` angelegt oder geändert. Die Anwendung bleibt dann weiterhin über `/opt/adguard-shield/adguard-shield` erreichbar.
|
||||||
|
|
||||||
### Update mit expliziter Konfigurationsquelle
|
### Update mit expliziter Konfigurationsquelle
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -138,7 +149,7 @@ sudo ./adguard-shield update --config-source ./adguard-shield.conf
|
|||||||
sudo ./adguard-shield update --install-dir /opt/adguard-shield-test
|
sudo ./adguard-shield update --install-dir /opt/adguard-shield-test
|
||||||
```
|
```
|
||||||
|
|
||||||
**Hinweis:** Die systemd-Unit heißt weiterhin `adguard-shield.service`. Mehrere parallele produktive Installationen über dieselbe Unit sind nicht vorgesehen.
|
**Hinweis:** Die systemd-Unit heißt weiterhin `adguard-shield.service`, und der globale CLI-Befehl heißt weiterhin `/usr/local/bin/adguard-shield`. Mehrere parallele produktive Installationen über dieselbe Unit oder denselben CLI-Befehl sind nicht vorgesehen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -178,10 +189,10 @@ sudo cp /opt/adguard-shield/adguard-shield.conf /root/adguard-shield.conf.backup
|
|||||||
sudo ./adguard-shield install --config-source /root/adguard-shield.conf.backup
|
sudo ./adguard-shield install --config-source /root/adguard-shield.conf.backup
|
||||||
|
|
||||||
# 4. API-Verbindung prüfen
|
# 4. API-Verbindung prüfen
|
||||||
sudo /opt/adguard-shield/adguard-shield test
|
sudo adguard-shield test
|
||||||
|
|
||||||
# 5. Dry-Run: prüfen, was gesperrt würde
|
# 5. Dry-Run: prüfen, was gesperrt würde
|
||||||
sudo /opt/adguard-shield/adguard-shield dry-run
|
sudo adguard-shield dry-run
|
||||||
|
|
||||||
# 6. Produktiven Service starten
|
# 6. Produktiven Service starten
|
||||||
sudo systemctl start adguard-shield
|
sudo systemctl start adguard-shield
|
||||||
@@ -197,7 +208,7 @@ sudo systemctl status adguard-shield
|
|||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield install-status
|
sudo adguard-shield install-status
|
||||||
```
|
```
|
||||||
|
|
||||||
### Service
|
### Service
|
||||||
@@ -210,20 +221,20 @@ sudo journalctl -u adguard-shield --no-pager -n 100
|
|||||||
### API-Verbindung
|
### API-Verbindung
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield test
|
sudo adguard-shield test
|
||||||
```
|
```
|
||||||
|
|
||||||
### Laufzeitstatus
|
### Laufzeitstatus
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield status
|
sudo adguard-shield status
|
||||||
sudo /opt/adguard-shield/adguard-shield live --once
|
sudo adguard-shield live --once
|
||||||
```
|
```
|
||||||
|
|
||||||
### Firewall
|
### Firewall
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo /opt/adguard-shield/adguard-shield firewall-status
|
sudo adguard-shield firewall-status
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -131,6 +131,16 @@ func (d *Daemon) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
d.runJob(ctx, "external-whitelist", d.Config.ExternalWhitelistEnabled, time.Duration(d.Config.ExternalWhitelistInterval)*time.Second, d.SyncWhitelist)
|
d.runJob(ctx, "external-whitelist", d.Config.ExternalWhitelistEnabled, time.Duration(d.Config.ExternalWhitelistInterval)*time.Second, d.SyncWhitelist)
|
||||||
d.runJob(ctx, "external-blocklist", d.Config.ExternalBlocklistEnabled, time.Duration(d.Config.ExternalBlocklistInterval)*time.Second, d.SyncBlocklist)
|
d.runJob(ctx, "external-blocklist", d.Config.ExternalBlocklistEnabled, time.Duration(d.Config.ExternalBlocklistInterval)*time.Second, d.SyncBlocklist)
|
||||||
|
d.runJob(ctx, "ban-expiry", true, 60*time.Second, func(ctx context.Context) error {
|
||||||
|
expired, err := d.Store.ExpiredBans(time.Now().Unix())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, ip := range expired {
|
||||||
|
_ = d.Unban(ctx, ip, "expired")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
d.runJob(ctx, "offense-cleanup", d.Config.ProgressiveBanEnabled, time.Hour, func(ctx context.Context) error {
|
d.runJob(ctx, "offense-cleanup", d.Config.ProgressiveBanEnabled, time.Hour, func(ctx context.Context) error {
|
||||||
n, err := d.Store.CleanupOffenses(d.Config.ProgressiveBanResetAfter)
|
n, err := d.Store.CleanupOffenses(d.Config.ProgressiveBanResetAfter)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
|
|||||||
@@ -26,15 +26,26 @@ type Ban struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ReportStats struct {
|
type ReportStats struct {
|
||||||
Since int64
|
Since int64
|
||||||
Until int64
|
Until int64
|
||||||
TotalBans int
|
TotalBans int
|
||||||
TotalUnbans int
|
TotalUnbans int
|
||||||
ActiveBans int
|
UniqueIPs int
|
||||||
TopClients []ReportCount
|
PermanentBans int
|
||||||
Reasons []ReportCount
|
ActiveBans int
|
||||||
Sources []ReportCount
|
AbuseIPDBReports int
|
||||||
RecentEvents []string
|
RateLimitBans int
|
||||||
|
SubdomainFloodBans int
|
||||||
|
ExternalBlocklistBans int
|
||||||
|
BusiestDay string
|
||||||
|
BusiestDayCount int
|
||||||
|
TopClients []ReportCount
|
||||||
|
TopDomains []ReportCount
|
||||||
|
Protocols []ReportCount
|
||||||
|
Reasons []ReportCount
|
||||||
|
Sources []ReportCount
|
||||||
|
RecentBans []ReportEvent
|
||||||
|
RecentEvents []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReportCount struct {
|
type ReportCount struct {
|
||||||
@@ -42,6 +53,17 @@ type ReportCount struct {
|
|||||||
Count int
|
Count int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReportEvent struct {
|
||||||
|
Timestamp string
|
||||||
|
Action string
|
||||||
|
IP string
|
||||||
|
Domain string
|
||||||
|
Count string
|
||||||
|
Duration string
|
||||||
|
Protocol string
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
func Open(path string) (*Store, error) {
|
func Open(path string) (*Store, error) {
|
||||||
db, err := sql.Open("sqlite", path+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)")
|
db, err := sql.Open("sqlite", path+"?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -356,7 +378,7 @@ func (s *Store) ClearGeoIPCache() (int64, error) {
|
|||||||
return res.RowsAffected()
|
return res.RowsAffected()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) ReportStats(since, until int64, limit int) (ReportStats, error) {
|
func (s *Store) ReportStats(since, until, busiestSince int64, limit int) (ReportStats, error) {
|
||||||
st := ReportStats{Since: since, Until: until}
|
st := ReportStats{Since: since, Until: until}
|
||||||
if err := s.DB.QueryRow(`SELECT COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ?`, since, until).Scan(&st.TotalBans); err != nil {
|
if err := s.DB.QueryRow(`SELECT COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ?`, since, until).Scan(&st.TotalBans); err != nil {
|
||||||
return st, err
|
return st, err
|
||||||
@@ -364,14 +386,43 @@ func (s *Store) ReportStats(since, until int64, limit int) (ReportStats, error)
|
|||||||
if err := s.DB.QueryRow(`SELECT COUNT(*) FROM ban_history WHERE action='UNBAN' AND timestamp_epoch BETWEEN ? AND ?`, since, until).Scan(&st.TotalUnbans); err != nil {
|
if err := s.DB.QueryRow(`SELECT COUNT(*) FROM ban_history WHERE action='UNBAN' AND timestamp_epoch BETWEEN ? AND ?`, since, until).Scan(&st.TotalUnbans); err != nil {
|
||||||
return st, err
|
return st, err
|
||||||
}
|
}
|
||||||
|
if err := s.DB.QueryRow(`SELECT COUNT(DISTINCT client_ip) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ?`, since, until).Scan(&st.UniqueIPs); err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
if err := s.DB.QueryRow(`SELECT COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? AND LOWER(COALESCE(duration,'')) LIKE '%permanent%'`, since, until).Scan(&st.PermanentBans); err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
if err := s.DB.QueryRow(`SELECT COUNT(*) FROM active_bans`).Scan(&st.ActiveBans); err != nil {
|
if err := s.DB.QueryRow(`SELECT COUNT(*) FROM active_bans`).Scan(&st.ActiveBans); err != nil {
|
||||||
return st, err
|
return st, err
|
||||||
}
|
}
|
||||||
|
if err := s.DB.QueryRow(`SELECT COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? AND LOWER(COALESCE(reason,'')) LIKE '%rate%limit%'`, since, until).Scan(&st.RateLimitBans); err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
if err := s.DB.QueryRow(`SELECT COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? AND LOWER(COALESCE(reason,'')) LIKE '%subdomain%flood%'`, since, until).Scan(&st.SubdomainFloodBans); err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
if err := s.DB.QueryRow(`SELECT COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? AND LOWER(COALESCE(reason,'')) LIKE '%external%blocklist%'`, since, until).Scan(&st.ExternalBlocklistBans); err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
if busiestSince <= 0 {
|
||||||
|
busiestSince = since
|
||||||
|
}
|
||||||
|
if err := s.DB.QueryRow(`SELECT SUBSTR(timestamp_text, 1, 10), COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? GROUP BY SUBSTR(timestamp_text, 1, 10) ORDER BY COUNT(*) DESC, SUBSTR(timestamp_text, 1, 10) DESC LIMIT 1`, busiestSince, until).Scan(&st.BusiestDay, &st.BusiestDayCount); err != nil && err != sql.ErrNoRows {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
st.TopClients, err = s.reportCounts(`SELECT client_ip, COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? GROUP BY client_ip ORDER BY COUNT(*) DESC, client_ip LIMIT ?`, since, until, limit)
|
st.TopClients, err = s.reportCounts(`SELECT client_ip, COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? GROUP BY client_ip ORDER BY COUNT(*) DESC, client_ip LIMIT ?`, since, until, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return st, err
|
return st, err
|
||||||
}
|
}
|
||||||
|
st.TopDomains, err = s.reportCounts(`SELECT domain, COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? AND COALESCE(domain,'') NOT IN ('', '-') GROUP BY domain ORDER BY COUNT(*) DESC, domain LIMIT ?`, since, until, limit)
|
||||||
|
if err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
st.Protocols, err = s.reportCounts(`SELECT COALESCE(NULLIF(protocol,''), 'unbekannt'), COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? GROUP BY COALESCE(NULLIF(protocol,''), 'unbekannt') ORDER BY COUNT(*) DESC LIMIT ?`, since, until, limit)
|
||||||
|
if err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
st.Reasons, err = s.reportCounts(`SELECT COALESCE(NULLIF(reason,''), 'unknown'), COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? GROUP BY COALESCE(NULLIF(reason,''), 'unknown') ORDER BY COUNT(*) DESC LIMIT ?`, since, until, limit)
|
st.Reasons, err = s.reportCounts(`SELECT COALESCE(NULLIF(reason,''), 'unknown'), COUNT(*) FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? GROUP BY COALESCE(NULLIF(reason,''), 'unknown') ORDER BY COUNT(*) DESC LIMIT ?`, since, until, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return st, err
|
return st, err
|
||||||
@@ -380,6 +431,10 @@ func (s *Store) ReportStats(since, until int64, limit int) (ReportStats, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return st, err
|
return st, err
|
||||||
}
|
}
|
||||||
|
st.RecentBans, err = s.reportRecentBans(since, until, 10)
|
||||||
|
if err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
st.RecentEvents, err = s.RecentHistory(limit)
|
st.RecentEvents, err = s.RecentHistory(limit)
|
||||||
return st, err
|
return st, err
|
||||||
}
|
}
|
||||||
@@ -406,3 +461,21 @@ func (s *Store) reportCounts(query string, since, until int64, limit int) ([]Rep
|
|||||||
}
|
}
|
||||||
return out, rows.Err()
|
return out, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) reportRecentBans(since, until int64, limit int) ([]ReportEvent, error) {
|
||||||
|
rows, err := s.DB.Query(`SELECT timestamp_text, action, client_ip, COALESCE(domain,''), COALESCE(count,''), COALESCE(duration,''), COALESCE(protocol,''), COALESCE(reason,'')
|
||||||
|
FROM ban_history WHERE action='BAN' AND timestamp_epoch BETWEEN ? AND ? ORDER BY id DESC LIMIT ?`, since, until, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var out []ReportEvent
|
||||||
|
for rows.Next() {
|
||||||
|
var e ReportEvent
|
||||||
|
if err := rows.Scan(&e.Timestamp, &e.Action, &e.IP, &e.Domain, &e.Count, &e.Duration, &e.Protocol, &e.Reason); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, e)
|
||||||
|
}
|
||||||
|
return out, rows.Err()
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,12 +12,15 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"adguard-shield/internal/report"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultInstallDir = "/opt/adguard-shield"
|
DefaultInstallDir = "/opt/adguard-shield"
|
||||||
DefaultStateDir = "/var/lib/adguard-shield"
|
DefaultStateDir = "/var/lib/adguard-shield"
|
||||||
DefaultLogFile = "/var/log/adguard-shield.log"
|
DefaultLogFile = "/var/log/adguard-shield.log"
|
||||||
|
CLICommandPath = "/usr/local/bin/adguard-shield"
|
||||||
ServiceName = "adguard-shield.service"
|
ServiceName = "adguard-shield.service"
|
||||||
ServicePath = "/etc/systemd/system/adguard-shield.service"
|
ServicePath = "/etc/systemd/system/adguard-shield.service"
|
||||||
)
|
)
|
||||||
@@ -28,19 +31,22 @@ type Options struct {
|
|||||||
Enable bool
|
Enable bool
|
||||||
SkipDeps bool
|
SkipDeps bool
|
||||||
KeepConfig bool
|
KeepConfig bool
|
||||||
|
RegisterCLI bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
InstallDir string
|
InstallDir string
|
||||||
BinaryPath string
|
BinaryPath string
|
||||||
ConfigPath string
|
ConfigPath string
|
||||||
BinaryExists bool
|
CLICommandPath string
|
||||||
ConfigExists bool
|
BinaryExists bool
|
||||||
ServiceExists bool
|
ConfigExists bool
|
||||||
ServiceEnabled bool
|
CLICommandInstalled bool
|
||||||
ServiceActive bool
|
ServiceExists bool
|
||||||
Version string
|
ServiceEnabled bool
|
||||||
LegacyFindings []string
|
ServiceActive bool
|
||||||
|
Version string
|
||||||
|
LegacyFindings []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type LegacyError struct {
|
type LegacyError struct {
|
||||||
@@ -52,30 +58,30 @@ func (e *LegacyError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DefaultOptions() Options {
|
func DefaultOptions() Options {
|
||||||
return Options{InstallDir: DefaultInstallDir, Enable: true}
|
return Options{InstallDir: DefaultInstallDir, Enable: true, RegisterCLI: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Install(opts Options) error {
|
func Install(opts Options) error {
|
||||||
opts = normalize(opts)
|
opts = normalize(opts)
|
||||||
fmt.Println("AdGuard Shield Go-Installation")
|
fmt.Println("AdGuard Shield Go-Installation")
|
||||||
fmt.Printf("Installationspfad: %s\n", opts.InstallDir)
|
fmt.Printf("Installationspfad: %s\n", opts.InstallDir)
|
||||||
fmt.Println("1/8 Pruefe Betriebssystem und root-Rechte ...")
|
fmt.Println("1/10 Pruefe Betriebssystem und root-Rechte ...")
|
||||||
if err := requireLinuxRoot(); err != nil {
|
if err := requireLinuxRoot(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("2/8 Pruefe auf scriptbasierte Altinstallation ...")
|
fmt.Println("2/10 Pruefe auf scriptbasierte Altinstallation ...")
|
||||||
if findings := DetectLegacy(opts.InstallDir); len(findings) > 0 {
|
if findings := DetectLegacy(opts.InstallDir); len(findings) > 0 {
|
||||||
return &LegacyError{Findings: findings}
|
return &LegacyError{Findings: findings}
|
||||||
}
|
}
|
||||||
if !opts.SkipDeps {
|
if !opts.SkipDeps {
|
||||||
fmt.Println("3/8 Pruefe System-Abhaengigkeiten ...")
|
fmt.Println("3/10 Pruefe System-Abhaengigkeiten ...")
|
||||||
if err := ensureDependencies(); err != nil {
|
if err := ensureDependencies(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("3/8 System-Abhaengigkeiten uebersprungen (--skip-deps)")
|
fmt.Println("3/10 System-Abhaengigkeiten uebersprungen (--skip-deps)")
|
||||||
}
|
}
|
||||||
fmt.Println("4/8 Erstelle Verzeichnisse ...")
|
fmt.Println("4/10 Erstelle Verzeichnisse ...")
|
||||||
if err := os.MkdirAll(opts.InstallDir, 0755); err != nil {
|
if err := os.MkdirAll(opts.InstallDir, 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -85,19 +91,31 @@ func Install(opts Options) error {
|
|||||||
if err := os.MkdirAll(filepath.Join(opts.InstallDir, "geoip"), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Join(opts.InstallDir, "geoip"), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("5/8 Installiere Binary ...")
|
fmt.Println("5/10 Installiere Binary ...")
|
||||||
if err := copySelf(filepath.Join(opts.InstallDir, "adguard-shield")); err != nil {
|
if err := copySelf(filepath.Join(opts.InstallDir, "adguard-shield")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("6/8 Installiere oder migriere Konfiguration ...")
|
if opts.RegisterCLI {
|
||||||
|
fmt.Printf("6/10 Registriere CLI-Befehl (%s) ...\n", CLICommandPath)
|
||||||
|
if err := registerCLICommand(opts.InstallDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("6/10 CLI-Registrierung uebersprungen (--no-register)")
|
||||||
|
}
|
||||||
|
fmt.Println("7/10 Installiere Report-Templates ...")
|
||||||
|
if err := report.InstallTemplates(filepath.Join(opts.InstallDir, "templates")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("8/10 Installiere oder migriere Konfiguration ...")
|
||||||
if err := ensureConfig(opts); err != nil {
|
if err := ensureConfig(opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("7/8 Schreibe systemd-Service ...")
|
fmt.Println("9/10 Schreibe systemd-Service ...")
|
||||||
if err := writeService(opts.InstallDir); err != nil {
|
if err := writeService(opts.InstallDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("8/8 Aktualisiere systemd ...")
|
fmt.Println("10/10 Aktualisiere systemd ...")
|
||||||
_ = run("systemctl", "daemon-reload")
|
_ = run("systemctl", "daemon-reload")
|
||||||
if opts.Enable {
|
if opts.Enable {
|
||||||
fmt.Println("Aktiviere Autostart ...")
|
fmt.Println("Aktiviere Autostart ...")
|
||||||
@@ -138,6 +156,7 @@ func Uninstall(opts Options) error {
|
|||||||
}
|
}
|
||||||
_ = os.Remove(ServicePath)
|
_ = os.Remove(ServicePath)
|
||||||
_ = run("systemctl", "daemon-reload")
|
_ = run("systemctl", "daemon-reload")
|
||||||
|
_ = unregisterCLICommand(opts.InstallDir)
|
||||||
if opts.KeepConfig {
|
if opts.KeepConfig {
|
||||||
for _, p := range []string{
|
for _, p := range []string{
|
||||||
filepath.Join(opts.InstallDir, "adguard-shield"),
|
filepath.Join(opts.InstallDir, "adguard-shield"),
|
||||||
@@ -160,13 +179,15 @@ func GetStatus(installDir string) Status {
|
|||||||
bin := filepath.Join(installDir, "adguard-shield")
|
bin := filepath.Join(installDir, "adguard-shield")
|
||||||
conf := filepath.Join(installDir, "adguard-shield.conf")
|
conf := filepath.Join(installDir, "adguard-shield.conf")
|
||||||
st := Status{
|
st := Status{
|
||||||
InstallDir: installDir,
|
InstallDir: installDir,
|
||||||
BinaryPath: bin,
|
BinaryPath: bin,
|
||||||
ConfigPath: conf,
|
ConfigPath: conf,
|
||||||
BinaryExists: fileExists(bin),
|
CLICommandPath: CLICommandPath,
|
||||||
ConfigExists: fileExists(conf),
|
BinaryExists: fileExists(bin),
|
||||||
ServiceExists: fileExists(ServicePath),
|
ConfigExists: fileExists(conf),
|
||||||
LegacyFindings: DetectLegacy(installDir),
|
CLICommandInstalled: cliCommandInstalled(installDir),
|
||||||
|
ServiceExists: fileExists(ServicePath),
|
||||||
|
LegacyFindings: DetectLegacy(installDir),
|
||||||
}
|
}
|
||||||
if st.BinaryExists {
|
if st.BinaryExists {
|
||||||
if out, err := exec.Command(bin, "version").Output(); err == nil {
|
if out, err := exec.Command(bin, "version").Output(); err == nil {
|
||||||
@@ -247,6 +268,7 @@ func PrintStatus(st Status) string {
|
|||||||
if st.Version != "" {
|
if st.Version != "" {
|
||||||
b.WriteString(fmt.Sprintf("Version: %s\n", st.Version))
|
b.WriteString(fmt.Sprintf("Version: %s\n", st.Version))
|
||||||
}
|
}
|
||||||
|
b.WriteString(fmt.Sprintf("CLI-Befehl (%s): %s\n", st.CLICommandPath, yesNo(st.CLICommandInstalled)))
|
||||||
b.WriteString(fmt.Sprintf("Konfiguration: %s\n", yesNo(st.ConfigExists)))
|
b.WriteString(fmt.Sprintf("Konfiguration: %s\n", yesNo(st.ConfigExists)))
|
||||||
b.WriteString(fmt.Sprintf("systemd Service: %s\n", yesNo(st.ServiceExists)))
|
b.WriteString(fmt.Sprintf("systemd Service: %s\n", yesNo(st.ServiceExists)))
|
||||||
b.WriteString(fmt.Sprintf("Autostart: %s\n", yesNo(st.ServiceEnabled)))
|
b.WriteString(fmt.Sprintf("Autostart: %s\n", yesNo(st.ServiceEnabled)))
|
||||||
@@ -385,6 +407,62 @@ func sameFile(a, b string) bool {
|
|||||||
return errA == nil && errB == nil && os.SameFile(ai, bi)
|
return errA == nil && errB == nil && os.SameFile(ai, bi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerCLICommand(installDir string) error {
|
||||||
|
target := filepath.Join(installDir, "adguard-shield")
|
||||||
|
if err := os.MkdirAll(filepath.Dir(CLICommandPath), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existingTarget, err := os.Readlink(CLICommandPath); err == nil {
|
||||||
|
resolved := resolveLinkTarget(CLICommandPath, existingTarget)
|
||||||
|
if filepath.Clean(resolved) == filepath.Clean(target) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s existiert bereits und verweist auf %s", CLICommandPath, existingTarget)
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fileExists(CLICommandPath) {
|
||||||
|
return fmt.Errorf("%s existiert bereits und ist kein Symlink", CLICommandPath)
|
||||||
|
}
|
||||||
|
return os.Symlink(target, CLICommandPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unregisterCLICommand(installDir string) error {
|
||||||
|
existingTarget, err := os.Readlink(CLICommandPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
target := filepath.Join(installDir, "adguard-shield")
|
||||||
|
resolved := resolveLinkTarget(CLICommandPath, existingTarget)
|
||||||
|
if filepath.Clean(resolved) != filepath.Clean(target) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return os.Remove(CLICommandPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cliCommandInstalled(installDir string) bool {
|
||||||
|
existingTarget, err := os.Readlink(CLICommandPath)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
target := filepath.Join(installDir, "adguard-shield")
|
||||||
|
if !fileExists(target) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
resolved := resolveLinkTarget(CLICommandPath, existingTarget)
|
||||||
|
return filepath.Clean(resolved) == filepath.Clean(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveLinkTarget(linkPath, target string) string {
|
||||||
|
if filepath.IsAbs(target) {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
return filepath.Join(filepath.Dir(linkPath), target)
|
||||||
|
}
|
||||||
|
|
||||||
func ensureConfig(opts Options) error {
|
func ensureConfig(opts Options) error {
|
||||||
target := filepath.Join(opts.InstallDir, "adguard-shield.conf")
|
target := filepath.Join(opts.InstallDir, "adguard-shield.conf")
|
||||||
defaults := []byte(defaultConfig)
|
defaults := []byte(defaultConfig)
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package report
|
package report
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
|
"io/fs"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -12,12 +17,16 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"adguard-shield/internal/appinfo"
|
||||||
"adguard-shield/internal/config"
|
"adguard-shield/internal/config"
|
||||||
"adguard-shield/internal/db"
|
"adguard-shield/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed templates/report.html templates/report.txt
|
||||||
|
var embeddedTemplates embed.FS
|
||||||
|
|
||||||
type Store interface {
|
type Store interface {
|
||||||
ReportStats(since, until int64, limit int) (db.ReportStats, error)
|
ReportStats(since, until, busiestSince int64, limit int) (db.ReportStats, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cronPath = "/etc/cron.d/adguard-shield-report"
|
const cronPath = "/etc/cron.d/adguard-shield-report"
|
||||||
@@ -27,50 +36,90 @@ func Status(c *config.Config) string {
|
|||||||
if _, err := os.Stat(cronPath); err == nil {
|
if _, err := os.Stat(cronPath); err == nil {
|
||||||
cron = "installiert (" + cronPath + ")"
|
cron = "installiert (" + cronPath + ")"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(`E-Mail Report
|
mailer := "nicht gefunden"
|
||||||
Aktiv: %v
|
if parts := strings.Fields(c.ReportMailCmd); len(parts) > 0 {
|
||||||
Intervall: %s
|
if p, err := exec.LookPath(parts[0]); err == nil {
|
||||||
Zeit: %s
|
mailer = "gefunden (" + p + ")"
|
||||||
Empfaenger: %s
|
}
|
||||||
Absender: %s
|
}
|
||||||
Format: %s
|
return fmt.Sprintf(`AdGuard Shield - Report Status
|
||||||
Mail-Befehl: %s
|
|
||||||
Cron: %s
|
Report aktiviert: %v
|
||||||
`, c.ReportEnabled, c.ReportInterval, c.ReportTime, c.ReportEmailTo, c.ReportEmailFrom, c.ReportFormat, c.ReportMailCmd, cron)
|
Intervall: %s
|
||||||
|
Uhrzeit: %s
|
||||||
|
Format: %s
|
||||||
|
Empfaenger: %s
|
||||||
|
Absender: %s
|
||||||
|
Mail-Befehl: %s
|
||||||
|
Mail-Befehl Status: %s
|
||||||
|
Aktivster Tag: letzte %d Tage
|
||||||
|
Cron: %s
|
||||||
|
`, c.ReportEnabled, c.ReportInterval, c.ReportTime, c.ReportFormat, empty(c.ReportEmailTo, "nicht konfiguriert"), c.ReportEmailFrom, c.ReportMailCmd, mailer, c.ReportBusiestDayRange, cron)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstallTemplates(dir string) error {
|
||||||
|
if dir == "" {
|
||||||
|
return fmt.Errorf("Template-Zielverzeichnis ist leer")
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, name := range []string{"report.html", "report.txt"} {
|
||||||
|
data, err := embeddedTemplates.ReadFile("templates/" + name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, name), data, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Generate(c *config.Config, st Store, format string) (string, error) {
|
func Generate(c *config.Config, st Store, format string) (string, error) {
|
||||||
if format == "" {
|
format = normalizeFormat(format, c.ReportFormat)
|
||||||
format = c.ReportFormat
|
since, until, period := reportWindow(c.ReportInterval, time.Now())
|
||||||
}
|
busiestSince := busiestWindowStart(c, since)
|
||||||
since, until := window(c.ReportInterval)
|
stats, err := st.ReportStats(since, until, busiestSince, 20)
|
||||||
stats, err := st.ReportStats(since, until, 20)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if strings.EqualFold(format, "html") {
|
stats.AbuseIPDBReports = countAbuseReports(c.LogFile, since, until)
|
||||||
return renderHTML(c, stats), nil
|
|
||||||
|
tpl, err := loadTemplate(c, format)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
return renderText(c, stats), nil
|
values, err := templateValues(c, st, stats, period, format)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for key, value := range values {
|
||||||
|
tpl = strings.ReplaceAll(tpl, "{{"+key+"}}", value)
|
||||||
|
}
|
||||||
|
return tpl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Send(ctx context.Context, c *config.Config, st Store) error {
|
func Send(ctx context.Context, c *config.Config, st Store) error {
|
||||||
body, err := Generate(c, st, c.ReportFormat)
|
format := normalizeFormat(c.ReportFormat, "html")
|
||||||
|
body, err := Generate(c, st, format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return sendMail(ctx, c, "AdGuard Shield Report", body)
|
_, _, period := reportWindow(c.ReportInterval, time.Now())
|
||||||
|
return sendMail(ctx, c, "AdGuard Shield "+period+" - "+hostname(), body, format, "AdGuard Shield Report Generator")
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendTest(ctx context.Context, c *config.Config) error {
|
func SendTest(ctx context.Context, c *config.Config) error {
|
||||||
body := fmt.Sprintf("AdGuard Shield Test-Mail\n\nHostname: %s\nZeitpunkt: %s\nEmpfaenger: %s\nAbsender: %s\n", hostname(), time.Now().Format("2006-01-02 15:04:05"), c.ReportEmailTo, c.ReportEmailFrom)
|
format := normalizeFormat(c.ReportFormat, "html")
|
||||||
if strings.EqualFold(c.ReportFormat, "html") {
|
body := testBody(c, format)
|
||||||
body = "<!doctype html><html><body><h1>AdGuard Shield Test-Mail</h1><p>Hostname: " + html.EscapeString(hostname()) + "</p><p>Zeitpunkt: " + html.EscapeString(time.Now().Format("2006-01-02 15:04:05")) + "</p></body></html>"
|
return sendMail(ctx, c, "AdGuard Shield Test-Mail - "+hostname(), body, format, "AdGuard Shield Report Generator (Test)")
|
||||||
}
|
|
||||||
return sendMail(ctx, c, "AdGuard Shield Test-Mail", body)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func InstallCron(binary, configPath string, c *config.Config) error {
|
func InstallCron(binary, configPath string, c *config.Config) error {
|
||||||
|
if !c.ReportEnabled {
|
||||||
|
return fmt.Errorf("Report ist deaktiviert (REPORT_ENABLED=false)")
|
||||||
|
}
|
||||||
minute, hour, err := parseReportTime(c.ReportTime)
|
minute, hour, err := parseReportTime(c.ReportTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -82,7 +131,18 @@ func InstallCron(binary, configPath string, c *config.Config) error {
|
|||||||
if configPath == "" {
|
if configPath == "" {
|
||||||
configPath = "/opt/adguard-shield/adguard-shield.conf"
|
configPath = "/opt/adguard-shield/adguard-shield.conf"
|
||||||
}
|
}
|
||||||
line := fmt.Sprintf("SHELL=/bin/sh\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n%s root %s -config %s report-send\n", schedule, binary, configPath)
|
command := shellQuote(binary) + " -config " + shellQuote(configPath) + " report-send"
|
||||||
|
if strings.EqualFold(c.ReportInterval, "biweekly") {
|
||||||
|
command = "[ $(( $(date +\\%V) \\% 2 )) -eq 1 ] && " + command
|
||||||
|
}
|
||||||
|
line := fmt.Sprintf(`# AdGuard Shield - Automatischer Report
|
||||||
|
# Intervall: %s
|
||||||
|
# Uhrzeit: %s
|
||||||
|
SHELL=/bin/sh
|
||||||
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
|
||||||
|
%s root %s >> %s 2>&1
|
||||||
|
`, c.ReportInterval, c.ReportTime, schedule, command, shellQuote(c.LogFile))
|
||||||
return os.WriteFile(cronPath, []byte(line), 0644)
|
return os.WriteFile(cronPath, []byte(line), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,25 +153,32 @@ func RemoveCron() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMail(ctx context.Context, c *config.Config, subject, body string) error {
|
func sendMail(ctx context.Context, c *config.Config, subject, body, format, xMailer string) error {
|
||||||
if c.ReportEmailTo == "" {
|
if strings.TrimSpace(c.ReportEmailTo) == "" {
|
||||||
return fmt.Errorf("REPORT_EMAIL_TO ist leer")
|
return fmt.Errorf("REPORT_EMAIL_TO ist leer")
|
||||||
}
|
}
|
||||||
if c.ReportMailCmd == "" {
|
|
||||||
return fmt.Errorf("REPORT_MAIL_CMD ist leer")
|
|
||||||
}
|
|
||||||
contentType := "text/plain; charset=utf-8"
|
|
||||||
if strings.EqualFold(c.ReportFormat, "html") {
|
|
||||||
contentType = "text/html; charset=utf-8"
|
|
||||||
}
|
|
||||||
msg := "From: " + c.ReportEmailFrom + "\n" +
|
|
||||||
"To: " + c.ReportEmailTo + "\n" +
|
|
||||||
"Subject: " + subject + "\n" +
|
|
||||||
"Content-Type: " + contentType + "\n\n" + body
|
|
||||||
parts := strings.Fields(c.ReportMailCmd)
|
parts := strings.Fields(c.ReportMailCmd)
|
||||||
if len(parts) == 0 {
|
if len(parts) == 0 {
|
||||||
return fmt.Errorf("REPORT_MAIL_CMD ist leer")
|
return fmt.Errorf("REPORT_MAIL_CMD ist leer")
|
||||||
}
|
}
|
||||||
|
if _, err := exec.LookPath(parts[0]); err != nil {
|
||||||
|
return fmt.Errorf("Mail-Befehl nicht gefunden: %s", parts[0])
|
||||||
|
}
|
||||||
|
contentType := "text/plain; charset=UTF-8"
|
||||||
|
if strings.EqualFold(format, "html") {
|
||||||
|
contentType = "text/html; charset=UTF-8"
|
||||||
|
}
|
||||||
|
msg := strings.Join([]string{
|
||||||
|
"From: " + c.ReportEmailFrom,
|
||||||
|
"To: " + c.ReportEmailTo,
|
||||||
|
"Subject: " + mime.QEncoding.Encode("utf-8", subject),
|
||||||
|
"MIME-Version: 1.0",
|
||||||
|
"Content-Type: " + contentType,
|
||||||
|
"Content-Transfer-Encoding: 8bit",
|
||||||
|
"X-Mailer: " + xMailer,
|
||||||
|
"",
|
||||||
|
body,
|
||||||
|
}, "\n")
|
||||||
args := append(parts[1:], "-t")
|
args := append(parts[1:], "-t")
|
||||||
cmd := exec.CommandContext(ctx, parts[0], args...)
|
cmd := exec.CommandContext(ctx, parts[0], args...)
|
||||||
cmd.Stdin = strings.NewReader(msg)
|
cmd.Stdin = strings.NewReader(msg)
|
||||||
@@ -120,6 +187,273 @@ func sendMail(ctx context.Context, c *config.Config, subject, body string) error
|
|||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func templateValues(c *config.Config, st Store, stats db.ReportStats, period, format string) (map[string]string, error) {
|
||||||
|
updateHTML, updateText := checkForUpdate()
|
||||||
|
busiestDay := "-"
|
||||||
|
if stats.BusiestDay != "" {
|
||||||
|
if t, err := time.ParseInLocation("2006-01-02", stats.BusiestDay, time.Local); err == nil {
|
||||||
|
busiestDay = t.Format("02.01.2006") + " (" + strconv.Itoa(stats.BusiestDayCount) + ")"
|
||||||
|
} else {
|
||||||
|
busiestDay = stats.BusiestDay + " (" + strconv.Itoa(stats.BusiestDayCount) + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
busiestLabel := "Aktivster Tag"
|
||||||
|
if c.ReportBusiestDayRange > 0 {
|
||||||
|
busiestLabel = fmt.Sprintf("Aktivster Tag (%d Tage)", c.ReportBusiestDayRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
values := map[string]string{
|
||||||
|
"REPORT_PERIOD": period,
|
||||||
|
"REPORT_DATE": time.Now().Format("02.01.2006 15:04:05"),
|
||||||
|
"HOSTNAME": hostname(),
|
||||||
|
"VERSION": appinfo.Version,
|
||||||
|
"TOTAL_BANS": strconv.Itoa(stats.TotalBans),
|
||||||
|
"TOTAL_UNBANS": strconv.Itoa(stats.TotalUnbans),
|
||||||
|
"UNIQUE_IPS": strconv.Itoa(stats.UniqueIPs),
|
||||||
|
"PERMANENT_BANS": strconv.Itoa(stats.PermanentBans),
|
||||||
|
"ACTIVE_BANS": strconv.Itoa(stats.ActiveBans),
|
||||||
|
"ABUSEIPDB_REPORTS": strconv.Itoa(stats.AbuseIPDBReports),
|
||||||
|
"RATELIMIT_BANS": strconv.Itoa(stats.RateLimitBans),
|
||||||
|
"SUBDOMAIN_FLOOD_BANS": strconv.Itoa(stats.SubdomainFloodBans),
|
||||||
|
"EXTERNAL_BLOCKLIST_BANS": strconv.Itoa(stats.ExternalBlocklistBans),
|
||||||
|
"BUSIEST_DAY": busiestDay,
|
||||||
|
"BUSIEST_DAY_LABEL": busiestLabel,
|
||||||
|
"TOP10_IPS_TABLE": topCountsHTML(stats.TopClients, "IP-Adresse"),
|
||||||
|
"TOP10_DOMAINS_TABLE": topCountsHTML(stats.TopDomains, "Domain"),
|
||||||
|
"PROTOCOL_TABLE": protocolHTML(stats.Protocols),
|
||||||
|
"RECENT_BANS_TABLE": recentBansHTML(stats.RecentBans),
|
||||||
|
"TOP10_IPS_TEXT": topCountsText(stats.TopClients, "IP-Adresse"),
|
||||||
|
"TOP10_DOMAINS_TEXT": topCountsText(stats.TopDomains, "Domain"),
|
||||||
|
"PROTOCOL_TEXT": protocolText(stats.Protocols),
|
||||||
|
"RECENT_BANS_TEXT": recentBansText(stats.RecentBans),
|
||||||
|
"UPDATE_NOTICE": updateHTML,
|
||||||
|
"UPDATE_NOTICE_TXT": updateText,
|
||||||
|
"PERIOD_OVERVIEW_TABLE": "",
|
||||||
|
"PERIOD_OVERVIEW_TEXT": "",
|
||||||
|
}
|
||||||
|
if strings.EqualFold(format, "html") {
|
||||||
|
values["PERIOD_OVERVIEW_TABLE"] = periodOverviewHTML(st)
|
||||||
|
} else {
|
||||||
|
values["PERIOD_OVERVIEW_TEXT"] = periodOverviewText(st)
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func topCountsHTML(rows []db.ReportCount, nameHeader string) string {
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return `<div class="no-data">Keine Daten im Berichtszeitraum</div>`
|
||||||
|
}
|
||||||
|
maxCount := rows[0].Count
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("<table><tr><th>#</th><th>" + html.EscapeString(nameHeader) + "</th><th>Sperren</th></tr>")
|
||||||
|
for i, r := range rows {
|
||||||
|
width := 100
|
||||||
|
if maxCount > 0 {
|
||||||
|
width = r.Count * 100 / maxCount
|
||||||
|
}
|
||||||
|
class := ""
|
||||||
|
if i < 3 {
|
||||||
|
class = " top3"
|
||||||
|
}
|
||||||
|
cellClass := ""
|
||||||
|
if strings.Contains(strings.ToLower(nameHeader), "ip") {
|
||||||
|
cellClass = ` class="ip-cell"`
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, `<tr><td><span class="rank%s">%d</span></td><td%s>%s</td><td><div class="bar-container"><div class="bar" style="width:%d%%"></div><span class="bar-value">%d</span></div></td></tr>`, class, i+1, cellClass, html.EscapeString(r.Name), width, r.Count)
|
||||||
|
}
|
||||||
|
b.WriteString("</table>")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func protocolHTML(rows []db.ReportCount) string {
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return `<div class="no-data">Keine Daten im Berichtszeitraum</div>`
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("<table><tr><th>Protokoll</th><th>Anzahl Sperren</th></tr>")
|
||||||
|
for _, r := range rows {
|
||||||
|
class := protocolClass(r.Name)
|
||||||
|
fmt.Fprintf(&b, `<tr><td><span class="protocol-badge %s">%s</span></td><td>%d</td></tr>`, class, html.EscapeString(r.Name), r.Count)
|
||||||
|
}
|
||||||
|
b.WriteString("</table>")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func recentBansHTML(rows []db.ReportEvent) string {
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return `<div class="no-data">Keine Sperren im Berichtszeitraum</div>`
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("<table><tr><th>Zeitpunkt</th><th>IP</th><th>Domain</th><th>Grund</th></tr>")
|
||||||
|
for _, e := range rows {
|
||||||
|
reason := fallback(e.Reason, "rate-limit")
|
||||||
|
domain := fallbackDash(e.Domain)
|
||||||
|
fmt.Fprintf(&b, `<tr><td>%s</td><td class="ip-cell">%s</td><td>%s</td><td><span class="reason-badge %s">%s</span></td></tr>`, html.EscapeString(shortTime(e.Timestamp)), html.EscapeString(e.IP), html.EscapeString(domain), reasonClass(reason), html.EscapeString(reason))
|
||||||
|
}
|
||||||
|
b.WriteString("</table>")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func periodOverviewHTML(st Store) string {
|
||||||
|
rows := periodOverviewRows(st)
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("<table><tr><th>Zeitraum</th><th>Sperren</th><th>Entsperrt</th><th>Unique IPs</th><th>Dauerhaft gebannt</th></tr>")
|
||||||
|
for _, r := range rows {
|
||||||
|
class := ""
|
||||||
|
if r.Label == "Heute" {
|
||||||
|
class = ` class="period-today"`
|
||||||
|
} else if r.Label == "Gestern" {
|
||||||
|
class = ` class="period-gestern"`
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, `<tr%s><td><strong>%s</strong></td><td>%d</td><td>%d</td><td>%d</td><td>%d</td></tr>`, class, html.EscapeString(r.Label), r.TotalBans, r.TotalUnbans, r.UniqueIPs, r.PermanentBans)
|
||||||
|
}
|
||||||
|
b.WriteString("</table>")
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func topCountsText(rows []db.ReportCount, nameHeader string) string {
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return " Keine Daten im Berichtszeitraum"
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
fmt.Fprintf(&b, " %-4s %-42s %s\n", "#", nameHeader, "Sperren")
|
||||||
|
fmt.Fprintf(&b, " %-4s %-42s %s\n", "--", strings.Repeat("-", 42), "-------")
|
||||||
|
for i, r := range rows {
|
||||||
|
fmt.Fprintf(&b, " %-4s %-42s %d\n", strconv.Itoa(i+1)+".", r.Name, r.Count)
|
||||||
|
}
|
||||||
|
return strings.TrimRight(b.String(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func protocolText(rows []db.ReportCount) string {
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return " Keine Daten im Berichtszeitraum"
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
fmt.Fprintf(&b, " %-20s %s\n", "Protokoll", "Anzahl")
|
||||||
|
fmt.Fprintf(&b, " %-20s %s\n", strings.Repeat("-", 20), "------")
|
||||||
|
for _, r := range rows {
|
||||||
|
fmt.Fprintf(&b, " %-20s %d\n", r.Name, r.Count)
|
||||||
|
}
|
||||||
|
return strings.TrimRight(b.String(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func recentBansText(rows []db.ReportEvent) string {
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return " Keine Sperren im Berichtszeitraum"
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
fmt.Fprintf(&b, " %-17s %-42s %-30s %s\n", "Zeitpunkt", "IP", "Domain", "Grund")
|
||||||
|
fmt.Fprintf(&b, " %-17s %-42s %-30s %s\n", strings.Repeat("-", 17), strings.Repeat("-", 42), strings.Repeat("-", 30), "----------")
|
||||||
|
for _, e := range rows {
|
||||||
|
fmt.Fprintf(&b, " %-17s %-42s %-30s %s\n", shortTime(e.Timestamp), e.IP, fallbackDash(e.Domain), fallback(e.Reason, "rate-limit"))
|
||||||
|
}
|
||||||
|
return strings.TrimRight(b.String(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func periodOverviewText(st Store) string {
|
||||||
|
rows := periodOverviewRows(st)
|
||||||
|
var b strings.Builder
|
||||||
|
fmt.Fprintf(&b, " %-15s %-9s %-12s %-14s %-11s\n", "Zeitraum", "Sperren", "Entsperrt", "Unique IPs", "Dauerhaft")
|
||||||
|
fmt.Fprintf(&b, " %-15s %-9s %-12s %-14s %-11s\n", strings.Repeat("-", 15), strings.Repeat("-", 9), strings.Repeat("-", 12), strings.Repeat("-", 14), strings.Repeat("-", 11))
|
||||||
|
for _, r := range rows {
|
||||||
|
fmt.Fprintf(&b, " %-15s %-9d %-12d %-14d %-11d\n", r.Label, r.TotalBans, r.TotalUnbans, r.UniqueIPs, r.PermanentBans)
|
||||||
|
}
|
||||||
|
return strings.TrimRight(b.String(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
type overviewRow struct {
|
||||||
|
Label string
|
||||||
|
db.ReportStats
|
||||||
|
}
|
||||||
|
|
||||||
|
func periodOverviewRows(st Store) []overviewRow {
|
||||||
|
now := time.Now()
|
||||||
|
today := midnight(now)
|
||||||
|
defs := []struct {
|
||||||
|
label string
|
||||||
|
since time.Time
|
||||||
|
until time.Time
|
||||||
|
}{}
|
||||||
|
if now.Hour() >= 20 {
|
||||||
|
defs = append(defs, struct {
|
||||||
|
label string
|
||||||
|
since time.Time
|
||||||
|
until time.Time
|
||||||
|
}{"Heute", today, now})
|
||||||
|
}
|
||||||
|
defs = append(defs,
|
||||||
|
struct {
|
||||||
|
label string
|
||||||
|
since time.Time
|
||||||
|
until time.Time
|
||||||
|
}{"Gestern", today.AddDate(0, 0, -1), today.Add(-time.Second)},
|
||||||
|
struct {
|
||||||
|
label string
|
||||||
|
since time.Time
|
||||||
|
until time.Time
|
||||||
|
}{"Letzte 7 Tage", today.AddDate(0, 0, -7), now},
|
||||||
|
struct {
|
||||||
|
label string
|
||||||
|
since time.Time
|
||||||
|
until time.Time
|
||||||
|
}{"Letzte 14 Tage", today.AddDate(0, 0, -14), now},
|
||||||
|
struct {
|
||||||
|
label string
|
||||||
|
since time.Time
|
||||||
|
until time.Time
|
||||||
|
}{"Letzte 30 Tage", today.AddDate(0, 0, -30), now},
|
||||||
|
)
|
||||||
|
rows := make([]overviewRow, 0, len(defs))
|
||||||
|
for _, d := range defs {
|
||||||
|
stats, err := st.ReportStats(d.since.Unix(), d.until.Unix(), d.since.Unix(), 0)
|
||||||
|
if err != nil {
|
||||||
|
stats = db.ReportStats{}
|
||||||
|
}
|
||||||
|
rows = append(rows, overviewRow{Label: d.label, ReportStats: stats})
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTemplate(c *config.Config, format string) (string, error) {
|
||||||
|
name := "report." + format
|
||||||
|
for _, dir := range templateDirs(c) {
|
||||||
|
data, err := os.ReadFile(filepath.Join(dir, name))
|
||||||
|
if err == nil {
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data, err := fs.ReadFile(embeddedTemplates, "templates/"+name)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Report-Template nicht gefunden: %s", name)
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func templateDirs(c *config.Config) []string {
|
||||||
|
var dirs []string
|
||||||
|
if v := strings.TrimSpace(os.Getenv("ADGUARD_SHIELD_TEMPLATE_DIR")); v != "" {
|
||||||
|
dirs = append(dirs, v)
|
||||||
|
}
|
||||||
|
if c.Path != "" {
|
||||||
|
dirs = append(dirs, filepath.Join(filepath.Dir(c.Path), "templates"))
|
||||||
|
}
|
||||||
|
if wd, err := os.Getwd(); err == nil {
|
||||||
|
dirs = append(dirs, filepath.Join(wd, "templates"))
|
||||||
|
}
|
||||||
|
if exe, err := os.Executable(); err == nil {
|
||||||
|
dirs = append(dirs, filepath.Join(filepath.Dir(exe), "templates"))
|
||||||
|
}
|
||||||
|
seen := map[string]bool{}
|
||||||
|
out := dirs[:0]
|
||||||
|
for _, d := range dirs {
|
||||||
|
if d != "" && !seen[d] {
|
||||||
|
seen[d] = true
|
||||||
|
out = append(out, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func parseReportTime(value string) (string, string, error) {
|
func parseReportTime(value string) (string, string, error) {
|
||||||
parts := strings.Split(value, ":")
|
parts := strings.Split(value, ":")
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
@@ -140,8 +474,8 @@ func cronSchedule(interval, minute, hour string) string {
|
|||||||
switch strings.ToLower(interval) {
|
switch strings.ToLower(interval) {
|
||||||
case "daily":
|
case "daily":
|
||||||
return fmt.Sprintf("%s %s * * *", minute, hour)
|
return fmt.Sprintf("%s %s * * *", minute, hour)
|
||||||
case "biweekly":
|
case "biweekly", "weekly":
|
||||||
return fmt.Sprintf("%s %s 1,15 * *", minute, hour)
|
return fmt.Sprintf("%s %s * * 1", minute, hour)
|
||||||
case "monthly":
|
case "monthly":
|
||||||
return fmt.Sprintf("%s %s 1 * *", minute, hour)
|
return fmt.Sprintf("%s %s 1 * *", minute, hour)
|
||||||
default:
|
default:
|
||||||
@@ -149,88 +483,213 @@ func cronSchedule(interval, minute, hour string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func window(interval string) (int64, int64) {
|
func reportWindow(interval string, now time.Time) (int64, int64, string) {
|
||||||
now := time.Now()
|
today := midnight(now)
|
||||||
days := 7
|
days, label := 7, "Bericht"
|
||||||
switch strings.ToLower(interval) {
|
switch strings.ToLower(interval) {
|
||||||
case "daily":
|
case "daily":
|
||||||
days = 1
|
days, label = 1, "Tagesbericht"
|
||||||
|
case "weekly":
|
||||||
|
days, label = 7, "Wochenbericht"
|
||||||
case "biweekly":
|
case "biweekly":
|
||||||
days = 14
|
days, label = 14, "Zweiwochenbericht"
|
||||||
case "monthly":
|
case "monthly":
|
||||||
days = 30
|
days, label = 30, "Monatsbericht"
|
||||||
}
|
}
|
||||||
return now.AddDate(0, 0, -days).Unix(), now.Unix()
|
start := today.AddDate(0, 0, -days)
|
||||||
|
end := today.Add(-time.Second)
|
||||||
|
if strings.EqualFold(interval, "daily") {
|
||||||
|
return start.Unix(), end.Unix(), label + ": " + start.Format("02.01.2006")
|
||||||
|
}
|
||||||
|
return start.Unix(), end.Unix(), label + ": " + start.Format("02.01.2006") + " - " + end.Format("02.01.2006")
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderText(c *config.Config, st db.ReportStats) string {
|
func busiestWindowStart(c *config.Config, fallbackSince int64) int64 {
|
||||||
var b strings.Builder
|
if c.ReportBusiestDayRange <= 0 {
|
||||||
b.WriteString("AdGuard Shield Report\n")
|
return fallbackSince
|
||||||
b.WriteString("Zeitraum: " + formatTime(st.Since) + " bis " + formatTime(st.Until) + "\n\n")
|
}
|
||||||
b.WriteString("Bans: " + strconv.Itoa(st.TotalBans) + "\n")
|
return midnight(time.Now()).AddDate(0, 0, -c.ReportBusiestDayRange).Unix()
|
||||||
b.WriteString("Unbans: " + strconv.Itoa(st.TotalUnbans) + "\n")
|
}
|
||||||
b.WriteString("Aktive Sperren: " + strconv.Itoa(st.ActiveBans) + "\n\n")
|
|
||||||
writeCountsText(&b, "Top Clients", st.TopClients)
|
func midnight(t time.Time) time.Time {
|
||||||
writeCountsText(&b, "Gruende", st.Reasons)
|
y, m, d := t.Date()
|
||||||
writeCountsText(&b, "Aktive Quellen", st.Sources)
|
return time.Date(y, m, d, 0, 0, 0, 0, t.Location())
|
||||||
if len(st.RecentEvents) > 0 {
|
}
|
||||||
b.WriteString("Letzte Ereignisse\n")
|
|
||||||
for _, e := range st.RecentEvents {
|
func countAbuseReports(path string, since, until int64) int {
|
||||||
b.WriteString("- " + e + "\n")
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
count := 0
|
||||||
|
sc := bufio.NewScanner(f)
|
||||||
|
for sc.Scan() {
|
||||||
|
line := sc.Text()
|
||||||
|
if !strings.Contains(line, "AbuseIPDB:") || !strings.Contains(line, "erfolgreich gemeldet") || len(line) < 21 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ts := strings.TrimPrefix(line[:20], "[")
|
||||||
|
t, err := time.ParseInLocation("2006-01-02 15:04:05", ts, time.Local)
|
||||||
|
if err == nil && t.Unix() >= since && t.Unix() <= until {
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = c
|
return count
|
||||||
return b.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderHTML(c *config.Config, st db.ReportStats) string {
|
func checkForUpdate() (string, string) {
|
||||||
var b bytes.Buffer
|
if appinfo.Version == "" || appinfo.Version == "unknown" || strings.EqualFold(os.Getenv("ADGUARD_SHIELD_SKIP_UPDATE_CHECK"), "true") {
|
||||||
b.WriteString("<!doctype html><html><head><meta charset=\"utf-8\"><title>AdGuard Shield Report</title>")
|
return "", ""
|
||||||
b.WriteString("<style>body{font-family:Arial,sans-serif;color:#1f2937}table{border-collapse:collapse;margin:12px 0}td,th{border:1px solid #d1d5db;padding:6px 9px;text-align:left}th{background:#f3f4f6}</style>")
|
}
|
||||||
b.WriteString("</head><body>")
|
client := http.Client{Timeout: 5 * time.Second}
|
||||||
b.WriteString("<h1>AdGuard Shield Report</h1>")
|
resp, err := client.Get("https://git.techniverse.net/api/v1/repos/scriptos/adguard-shield/releases?limit=1&page=1")
|
||||||
b.WriteString("<p>Zeitraum: " + html.EscapeString(formatTime(st.Since)) + " bis " + html.EscapeString(formatTime(st.Until)) + "</p>")
|
if err != nil {
|
||||||
b.WriteString("<ul><li>Bans: " + strconv.Itoa(st.TotalBans) + "</li><li>Unbans: " + strconv.Itoa(st.TotalUnbans) + "</li><li>Aktive Sperren: " + strconv.Itoa(st.ActiveBans) + "</li></ul>")
|
return "", ""
|
||||||
writeCountsHTML(&b, "Top Clients", st.TopClients)
|
}
|
||||||
writeCountsHTML(&b, "Gruende", st.Reasons)
|
defer resp.Body.Close()
|
||||||
writeCountsHTML(&b, "Aktive Quellen", st.Sources)
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||||
if len(st.RecentEvents) > 0 {
|
return "", ""
|
||||||
b.WriteString("<h2>Letzte Ereignisse</h2><table><tr><th>Ereignis</th></tr>")
|
}
|
||||||
for _, e := range st.RecentEvents {
|
var releases []struct {
|
||||||
b.WriteString("<tr><td>" + html.EscapeString(e) + "</td></tr>")
|
TagName string `json:"tag_name"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil || len(releases) == 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
latest := releases[0].TagName
|
||||||
|
if !versionGreater(latest, appinfo.Version) {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
htmlNotice := `<div class="update-notice">Update verfuegbar: <strong>` + html.EscapeString(latest) + `</strong> · <a href="https://git.techniverse.net/scriptos/adguard-shield/releases">Jetzt aktualisieren</a></div>`
|
||||||
|
textNotice := " Neue Version verfuegbar: " + latest + "\n Update: https://git.techniverse.net/scriptos/adguard-shield/releases\n"
|
||||||
|
return htmlNotice, textNotice
|
||||||
|
}
|
||||||
|
|
||||||
|
func versionGreater(a, b string) bool {
|
||||||
|
ap := versionParts(a)
|
||||||
|
bp := versionParts(b)
|
||||||
|
max := len(ap)
|
||||||
|
if len(bp) > max {
|
||||||
|
max = len(bp)
|
||||||
|
}
|
||||||
|
for i := 0; i < max; i++ {
|
||||||
|
ai, bi := 0, 0
|
||||||
|
if i < len(ap) {
|
||||||
|
ai = ap[i]
|
||||||
|
}
|
||||||
|
if i < len(bp) {
|
||||||
|
bi = bp[i]
|
||||||
|
}
|
||||||
|
if ai != bi {
|
||||||
|
return ai > bi
|
||||||
}
|
}
|
||||||
b.WriteString("</table>")
|
|
||||||
}
|
}
|
||||||
b.WriteString("</body></html>")
|
return false
|
||||||
_ = c
|
|
||||||
return b.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCountsText(b *strings.Builder, title string, rows []db.ReportCount) {
|
func versionParts(v string) []int {
|
||||||
b.WriteString(title + "\n")
|
v = strings.TrimPrefix(strings.TrimSpace(v), "v")
|
||||||
if len(rows) == 0 {
|
fields := strings.FieldsFunc(v, func(r rune) bool { return r == '.' || r == '-' || r == '+' })
|
||||||
b.WriteString("- keine Daten\n\n")
|
out := make([]int, 0, len(fields))
|
||||||
return
|
for _, f := range fields {
|
||||||
|
n, err := strconv.Atoi(f)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out = append(out, n)
|
||||||
}
|
}
|
||||||
for _, r := range rows {
|
return out
|
||||||
b.WriteString("- " + r.Name + ": " + strconv.Itoa(r.Count) + "\n")
|
|
||||||
}
|
|
||||||
b.WriteByte('\n')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCountsHTML(b *bytes.Buffer, title string, rows []db.ReportCount) {
|
func testBody(c *config.Config, format string) string {
|
||||||
b.WriteString("<h2>" + html.EscapeString(title) + "</h2><table><tr><th>Name</th><th>Anzahl</th></tr>")
|
now := time.Now().Format("02.01.2006 15:04:05")
|
||||||
if len(rows) == 0 {
|
host := hostname()
|
||||||
b.WriteString("<tr><td colspan=\"2\">keine Daten</td></tr>")
|
if strings.EqualFold(format, "html") {
|
||||||
|
return `<!DOCTYPE html><html lang="de"><head><meta charset="UTF-8"></head><body style="font-family:sans-serif;background:#f0f2f5;padding:30px;"><div style="max-width:600px;margin:0 auto;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,0.08);"><div style="background:#0f3460;color:#fff;padding:30px;text-align:center;"><h1 style="margin:0;">AdGuard Shield Test-Mail</h1><p style="margin:6px 0 0;color:#ccd6f6;">E-Mail-Versand funktioniert</p></div><div style="padding:30px;"><table style="width:100%;border-collapse:collapse;"><tr><td>Hostname</td><td><strong>` + html.EscapeString(host) + `</strong></td></tr><tr><td>Zeitpunkt</td><td><strong>` + html.EscapeString(now) + `</strong></td></tr><tr><td>Empfaenger</td><td><strong>` + html.EscapeString(c.ReportEmailTo) + `</strong></td></tr><tr><td>Absender</td><td><strong>` + html.EscapeString(c.ReportEmailFrom) + `</strong></td></tr><tr><td>Mail-Befehl</td><td><strong>` + html.EscapeString(c.ReportMailCmd) + `</strong></td></tr><tr><td>Format</td><td><strong>` + html.EscapeString(format) + `</strong></td></tr></table></div></div></body></html>`
|
||||||
}
|
}
|
||||||
for _, r := range rows {
|
return fmt.Sprintf(`AdGuard Shield - Test-Mail
|
||||||
b.WriteString("<tr><td>" + html.EscapeString(r.Name) + "</td><td>" + strconv.Itoa(r.Count) + "</td></tr>")
|
|
||||||
}
|
E-Mail-Versand funktioniert.
|
||||||
b.WriteString("</table>")
|
|
||||||
|
Hostname: %s
|
||||||
|
Zeitpunkt: %s
|
||||||
|
Empfaenger: %s
|
||||||
|
Absender: %s
|
||||||
|
Mail-Befehl: %s
|
||||||
|
Format: %s
|
||||||
|
`, host, now, c.ReportEmailTo, c.ReportEmailFrom, c.ReportMailCmd, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatTime(epoch int64) string {
|
func normalizeFormat(value, fallback string) string {
|
||||||
return time.Unix(epoch, 0).Format("2006-01-02 15:04:05")
|
format := strings.ToLower(strings.TrimSpace(value))
|
||||||
|
if format == "" {
|
||||||
|
format = strings.ToLower(strings.TrimSpace(fallback))
|
||||||
|
}
|
||||||
|
if format != "txt" {
|
||||||
|
format = "html"
|
||||||
|
}
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
func shortTime(value string) string {
|
||||||
|
if len(value) >= 16 {
|
||||||
|
return value[5:16]
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func protocolClass(proto string) string {
|
||||||
|
s := strings.ToLower(proto)
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(s, "dns"):
|
||||||
|
return "dns"
|
||||||
|
case strings.HasPrefix(s, "doh"):
|
||||||
|
return "doh"
|
||||||
|
case strings.HasPrefix(s, "dot"):
|
||||||
|
return "dot"
|
||||||
|
case strings.HasPrefix(s, "doq"):
|
||||||
|
return "doq"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reasonClass(reason string) string {
|
||||||
|
s := strings.ToLower(reason)
|
||||||
|
switch {
|
||||||
|
case strings.Contains(s, "subdomain"):
|
||||||
|
return "subdomain-flood"
|
||||||
|
case strings.Contains(s, "external"):
|
||||||
|
return "external"
|
||||||
|
default:
|
||||||
|
return "rate-limit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fallback(value, def string) string {
|
||||||
|
if strings.TrimSpace(value) == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func fallbackDash(value string) string {
|
||||||
|
if strings.TrimSpace(value) == "" || strings.TrimSpace(value) == "-" {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func empty(s, fallback string) string {
|
||||||
|
if strings.TrimSpace(s) == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func shellQuote(s string) string {
|
||||||
|
return "'" + strings.ReplaceAll(s, "'", "'\\''") + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
func hostname() string {
|
func hostname() string {
|
||||||
|
|||||||
108
internal/report/report_test.go
Normal file
108
internal/report/report_test.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"adguard-shield/internal/config"
|
||||||
|
"adguard-shield/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateUsesTemplatesAndFullStats(t *testing.T) {
|
||||||
|
t.Setenv("ADGUARD_SHIELD_SKIP_UPDATE_CHECK", "true")
|
||||||
|
store, err := db.Open(filepath.Join(t.TempDir(), "report.db"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer store.Close()
|
||||||
|
|
||||||
|
yesterday := time.Now().AddDate(0, 0, -1)
|
||||||
|
events := []struct {
|
||||||
|
ip string
|
||||||
|
domain string
|
||||||
|
duration string
|
||||||
|
proto string
|
||||||
|
reason string
|
||||||
|
}{
|
||||||
|
{"1.2.3.4", "example.com", "3600s", "DNS", "rate-limit"},
|
||||||
|
{"1.2.3.4", "example.com", "permanent", "DoH", "subdomain-flood"},
|
||||||
|
{"5.6.7.8", "block.example", "permanent", "DoT", "external-blocklist"},
|
||||||
|
}
|
||||||
|
for i, e := range events {
|
||||||
|
ts := yesterday.Add(time.Duration(i) * time.Hour)
|
||||||
|
_, err := store.DB.Exec(`INSERT INTO ban_history (timestamp_epoch, timestamp_text, action, client_ip, domain, count, duration, protocol, reason) VALUES (?, ?, 'BAN', ?, ?, '42', ?, ?, ?)`,
|
||||||
|
ts.Unix(), ts.Format("2006-01-02 15:04:05"), e.ip, e.domain, e.duration, e.proto, e.reason)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ts := yesterday.Add(4 * time.Hour)
|
||||||
|
if _, err := store.DB.Exec(`INSERT INTO ban_history (timestamp_epoch, timestamp_text, action, client_ip, domain, count, duration, protocol, reason) VALUES (?, ?, 'UNBAN', '1.2.3.4', '-', '-', '-', '-', 'manual')`, ts.Unix(), ts.Format("2006-01-02 15:04:05")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := store.InsertBan(db.Ban{IP: "5.6.7.8", Domain: "block.example", Permanent: true, Reason: "external-blocklist", Protocol: "DoT", Source: "external-blocklist"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logPath := filepath.Join(t.TempDir(), "shield.log")
|
||||||
|
if err := writeTestLog(logPath, yesterday); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cfg := &config.Config{
|
||||||
|
Path: filepath.Join(t.TempDir(), "adguard-shield.conf"),
|
||||||
|
ReportInterval: "weekly",
|
||||||
|
ReportFormat: "html",
|
||||||
|
ReportBusiestDayRange: 30,
|
||||||
|
LogFile: logPath,
|
||||||
|
ReportEmailTo: "admin@example.test",
|
||||||
|
ReportEmailFrom: "shield@example.test",
|
||||||
|
ReportMailCmd: "msmtp",
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlReport, err := Generate(cfg, store, "html")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"AdGuard Shield", "example.com", "1.2.3.4", "DoH", "Subdomain-Flood Sperren", "AbuseIPDB Reports"} {
|
||||||
|
if !strings.Contains(htmlReport, want) {
|
||||||
|
t.Fatalf("HTML report missing %q\n%s", want, htmlReport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(htmlReport, "{{") {
|
||||||
|
t.Fatalf("HTML report still contains placeholders")
|
||||||
|
}
|
||||||
|
|
||||||
|
txtReport, err := Generate(cfg, store, "txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, want := range []string{"Sperren gesamt: 3", "Entsperrungen: 1", "Permanente Sperren: 2", "block.example"} {
|
||||||
|
if !strings.Contains(txtReport, want) {
|
||||||
|
t.Fatalf("TXT report missing %q\n%s", want, txtReport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionGreater(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
a, b string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"v1.0.1", "v1.0.0", true},
|
||||||
|
{"v1.0.0", "v1.0.0", false},
|
||||||
|
{"v1.2.0", "v1.10.0", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := versionGreater(tt.a, tt.b); got != tt.want {
|
||||||
|
t.Fatalf("versionGreater(%q, %q) = %v, want %v", tt.a, tt.b, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTestLog(path string, when time.Time) error {
|
||||||
|
line := "[" + when.Format("2006-01-02 15:04:05") + "] [INFO] AbuseIPDB: 5.6.7.8 erfolgreich gemeldet\n"
|
||||||
|
return os.WriteFile(path, []byte(line), 0644)
|
||||||
|
}
|
||||||
108
internal/report/templates/report.html
Normal file
108
internal/report/templates/report.html
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AdGuard Shield - Report</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background-color: #f0f2f5; margin: 0; padding: 0; color: #1a1a2e; }
|
||||||
|
.container { max-width: 700px; margin: 30px auto; background: #ffffff; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); overflow: hidden; }
|
||||||
|
.header { background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); color: #ffffff; padding: 30px 35px; text-align: center; }
|
||||||
|
.header h1 { margin: 0 0 6px 0; font-size: 26px; font-weight: 700; letter-spacing: 0.5px; }
|
||||||
|
.header .subtitle { font-size: 14px; color: #a8b2d1; margin: 0; }
|
||||||
|
.header .period { display: inline-block; margin-top: 14px; padding: 6px 18px; background: rgba(255,255,255,0.12); border-radius: 20px; font-size: 13px; color: #ccd6f6; }
|
||||||
|
.content { padding: 30px 35px; }
|
||||||
|
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 28px; }
|
||||||
|
.stat-card { background: #f8f9fc; border-radius: 10px; padding: 18px 20px; border-left: 4px solid #0f3460; }
|
||||||
|
.stat-card.danger { border-left-color: #e74c3c; }
|
||||||
|
.stat-card.warning { border-left-color: #f39c12; }
|
||||||
|
.stat-card.success { border-left-color: #27ae60; }
|
||||||
|
.stat-card.info { border-left-color: #3498db; }
|
||||||
|
.stat-card .stat-value { font-size: 28px; font-weight: 700; color: #1a1a2e; line-height: 1.2; }
|
||||||
|
.stat-card .stat-label { font-size: 12px; color: #6c757d; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 4px; }
|
||||||
|
h2 { font-size: 18px; color: #1a1a2e; margin: 28px 0 14px 0; padding-bottom: 8px; border-bottom: 2px solid #f0f2f5; }
|
||||||
|
h2:first-child { margin-top: 0; }
|
||||||
|
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; font-size: 14px; }
|
||||||
|
th { background: #f8f9fc; color: #1a1a2e; font-weight: 600; text-align: left; padding: 10px 14px; border-bottom: 2px solid #e8ecf1; font-size: 12px; text-transform: uppercase; letter-spacing: 0.3px; }
|
||||||
|
td { padding: 10px 14px; border-bottom: 1px solid #f0f2f5; color: #495057; }
|
||||||
|
tr:last-child td { border-bottom: none; }
|
||||||
|
tr:hover td { background: #fafbfd; }
|
||||||
|
.rank { display: inline-block; width: 24px; height: 24px; line-height: 24px; text-align: center; background: #e8ecf1; border-radius: 50%; font-size: 12px; font-weight: 600; color: #495057; }
|
||||||
|
.rank.top3 { background: #0f3460; color: #ffffff; }
|
||||||
|
.ip-cell { font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; font-size: 13px; color: #1a1a2e; }
|
||||||
|
.bar-container { display: flex; align-items: center; gap: 8px; }
|
||||||
|
.bar { height: 8px; background: linear-gradient(90deg, #0f3460, #3498db); border-radius: 4px; min-width: 4px; }
|
||||||
|
.bar-value { font-size: 13px; font-weight: 600; color: #1a1a2e; white-space: nowrap; }
|
||||||
|
.protocol-badge { display: inline-block; padding: 3px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; background: #e8ecf1; color: #495057; margin: 2px; }
|
||||||
|
.protocol-badge.dns { background: #dff0d8; color: #3c763d; }
|
||||||
|
.protocol-badge.doh { background: #d9edf7; color: #31708f; }
|
||||||
|
.protocol-badge.dot { background: #fcf8e3; color: #8a6d3b; }
|
||||||
|
.protocol-badge.doq { background: #f2dede; color: #a94442; }
|
||||||
|
.reason-badge { display: inline-block; padding: 3px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }
|
||||||
|
.reason-badge.rate-limit { background: #fcf8e3; color: #8a6d3b; }
|
||||||
|
.reason-badge.subdomain-flood { background: #f2dede; color: #a94442; }
|
||||||
|
.reason-badge.external { background: #d9edf7; color: #31708f; }
|
||||||
|
.no-data { text-align: center; padding: 30px; color: #adb5bd; font-style: italic; }
|
||||||
|
.footer { background: #f8f9fc; padding: 24px 35px; text-align: center; font-size: 12px; color: #6c757d; border-top: 1px solid #e8ecf1; }
|
||||||
|
.footer a { color: #0f3460; text-decoration: none; font-weight: 600; }
|
||||||
|
.footer .links { margin-top: 10px; display: flex; justify-content: space-between; align-items: center; gap: 12px; }
|
||||||
|
.footer .separator { margin: 0 8px; color: #ced4da; }
|
||||||
|
.version-tag { display: block; margin-top: 8px; font-size: 11px; color: #adb5bd; }
|
||||||
|
.update-notice { display: inline-block; margin-top: 10px; padding: 7px 14px; background: #fff8e1; border: 1px solid #ffc107; border-radius: 8px; color: #7a5700; font-size: 12px; font-weight: 600; }
|
||||||
|
.update-notice a { color: #7a5700; text-decoration: none; font-weight: 700; }
|
||||||
|
.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) { .container { margin: 0; border-radius: 0; } .content, .header, .footer { padding-left: 18px; padding-right: 18px; } .stats-grid { grid-template-columns: 1fr; } table { font-size: 12px; } th, td { padding: 8px 8px; } .footer .links { display: block; } }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>AdGuard Shield</h1>
|
||||||
|
<p class="subtitle">Sicherheits-Report</p>
|
||||||
|
<div class="period">{{REPORT_PERIOD}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<h2>Zeitraum-Schnelluebersicht</h2>
|
||||||
|
{{PERIOD_OVERVIEW_TABLE}}
|
||||||
|
<h2>Uebersicht</h2>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-card danger"><div class="stat-value">{{TOTAL_BANS}}</div><div class="stat-label">Sperren gesamt</div></div>
|
||||||
|
<div class="stat-card success"><div class="stat-value">{{TOTAL_UNBANS}}</div><div class="stat-label">Entsperrungen</div></div>
|
||||||
|
<div class="stat-card warning"><div class="stat-value">{{UNIQUE_IPS}}</div><div class="stat-label">Eindeutige IPs</div></div>
|
||||||
|
<div class="stat-card info"><div class="stat-value">{{PERMANENT_BANS}}</div><div class="stat-label">Permanente Sperren</div></div>
|
||||||
|
<div class="stat-card"><div class="stat-value">{{ACTIVE_BANS}}</div><div class="stat-label">Aktuell aktive Sperren</div></div>
|
||||||
|
<div class="stat-card info"><div class="stat-value">{{ABUSEIPDB_REPORTS}}</div><div class="stat-label">AbuseIPDB Reports</div></div>
|
||||||
|
</div>
|
||||||
|
<h2>Angriffsarten</h2>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-card warning"><div class="stat-value">{{RATELIMIT_BANS}}</div><div class="stat-label">Rate-Limit Sperren</div></div>
|
||||||
|
<div class="stat-card danger"><div class="stat-value">{{SUBDOMAIN_FLOOD_BANS}}</div><div class="stat-label">Subdomain-Flood Sperren</div></div>
|
||||||
|
<div class="stat-card"><div class="stat-value">{{EXTERNAL_BLOCKLIST_BANS}}</div><div class="stat-label">Externe Blocklist</div></div>
|
||||||
|
<div class="stat-card success"><div class="stat-value">{{BUSIEST_DAY}}</div><div class="stat-label">{{BUSIEST_DAY_LABEL}}</div></div>
|
||||||
|
</div>
|
||||||
|
<h2>Top 10 - Auffaelligste IPs</h2>
|
||||||
|
{{TOP10_IPS_TABLE}}
|
||||||
|
<h2>Top 10 - Meistbetroffene Domains</h2>
|
||||||
|
{{TOP10_DOMAINS_TABLE}}
|
||||||
|
<h2>Protokoll-Verteilung</h2>
|
||||||
|
{{PROTOCOL_TABLE}}
|
||||||
|
<h2>Letzte 10 Sperren</h2>
|
||||||
|
{{RECENT_BANS_TABLE}}
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<div class="links">
|
||||||
|
<span><a href="https://www.patrick-asmus.de">Patrick-Asmus.de</a><span class="separator">|</span><a href="https://www.cleveradmin.de">CleverAdmin.de</a></span>
|
||||||
|
<span><a href="https://git.techniverse.net/scriptos/adguard-shield.git">AdGuard Shield auf Gitea</a><span class="separator">|</span><a href="https://git.techniverse.net/scriptos/adguard-shield/src/branch/main/docs">docs</a></span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
Dieser Report wurde automatisch von <strong>AdGuard Shield</strong> generiert.<br>
|
||||||
|
Generiert am: {{REPORT_DATE}}
|
||||||
|
<div class="version-tag">AdGuard Shield {{VERSION}} · {{HOSTNAME}}</div>
|
||||||
|
{{UPDATE_NOTICE}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
68
internal/report/templates/report.txt
Normal file
68
internal/report/templates/report.txt
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
================================================================
|
||||||
|
AdGuard Shield - Sicherheits-Report
|
||||||
|
================================================================
|
||||||
|
|
||||||
|
Zeitraum: {{REPORT_PERIOD}}
|
||||||
|
Erstellt: {{REPORT_DATE}}
|
||||||
|
Host: {{HOSTNAME}}
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
ZEITRAUM-SCHNELLUEBERSICHT
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
{{PERIOD_OVERVIEW_TEXT}}
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
UEBERSICHT (Berichtszeitraum)
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
Sperren gesamt: {{TOTAL_BANS}}
|
||||||
|
Entsperrungen: {{TOTAL_UNBANS}}
|
||||||
|
Eindeutige IPs: {{UNIQUE_IPS}}
|
||||||
|
Permanente Sperren: {{PERMANENT_BANS}}
|
||||||
|
Aktuell aktive Sperren: {{ACTIVE_BANS}}
|
||||||
|
AbuseIPDB Reports: {{ABUSEIPDB_REPORTS}}
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
ANGRIFFSARTEN
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
Rate-Limit Sperren: {{RATELIMIT_BANS}}
|
||||||
|
Subdomain-Flood Sperren: {{SUBDOMAIN_FLOOD_BANS}}
|
||||||
|
Externe Blocklist: {{EXTERNAL_BLOCKLIST_BANS}}
|
||||||
|
{{BUSIEST_DAY_LABEL}}: {{BUSIEST_DAY}}
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
TOP 10 - AUFFAELLIGSTE IPs
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
{{TOP10_IPS_TEXT}}
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
TOP 10 - MEISTBETROFFENE DOMAINS
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
{{TOP10_DOMAINS_TEXT}}
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
PROTOKOLL-VERTEILUNG
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
{{PROTOCOL_TEXT}}
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
LETZTE 10 SPERREN
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
{{RECENT_BANS_TEXT}}
|
||||||
|
|
||||||
|
{{UPDATE_NOTICE_TXT}}
|
||||||
|
================================================================
|
||||||
|
Dieser Report wurde automatisch von AdGuard Shield generiert.
|
||||||
|
AdGuard Shield {{VERSION}}
|
||||||
|
|
||||||
|
Web: https://www.patrick-asmus.de
|
||||||
|
Blog: https://www.cleveradmin.de
|
||||||
|
Repo: https://git.techniverse.net/scriptos/adguard-shield.git
|
||||||
|
Docs: https://git.techniverse.net/scriptos/adguard-shield/src/branch/main/docs
|
||||||
|
================================================================
|
||||||
Reference in New Issue
Block a user