feat: Migration auf sqlite3
This commit is contained in:
@@ -49,7 +49,7 @@ Das schützt klassische DNS-Anfragen genauso wie DoH, DoT und DoQ, ohne deine be
|
|||||||
- Linux-Server mit AdGuard Home
|
- Linux-Server mit AdGuard Home
|
||||||
- Root-Zugriff per `sudo`
|
- Root-Zugriff per `sudo`
|
||||||
- Erreichbare AdGuard Home Web-API, standardmäßig `http://127.0.0.1:3000`
|
- Erreichbare AdGuard Home Web-API, standardmäßig `http://127.0.0.1:3000`
|
||||||
- `curl`, `jq`, `iptables`, `gawk` und `systemd`
|
- `curl`, `jq`, `iptables`, `gawk`, `sqlite3` und `systemd`
|
||||||
|
|
||||||
Die benötigten Pakete werden vom Installer automatisch installiert.
|
Die benötigten Pakete werden vom Installer automatisch installiert.
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ sudo systemctl status adguard-shield
|
|||||||
```bash
|
```bash
|
||||||
sudo bash install.sh # Interaktives Menü
|
sudo bash install.sh # Interaktives Menü
|
||||||
sudo bash install.sh install # Direkt installieren
|
sudo bash install.sh install # Direkt installieren
|
||||||
sudo bash install.sh update # Update inkl. Konfigurations-Migration
|
sudo bash install.sh update # Update inkl. Konfig- & Datenbank-Migration
|
||||||
sudo bash install.sh status # Installationsstatus prüfen
|
sudo bash install.sh status # Installationsstatus prüfen
|
||||||
sudo bash /opt/adguard-shield/uninstall.sh
|
sudo bash /opt/adguard-shield/uninstall.sh
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ GEOIP_LICENSE_KEY="" # MaxMind GeoLite2 Key (optional, für Auto-
|
|||||||
GEOIP_MMDB_PATH="" # Manueller DB-Pfad (optional, hat Vorrang)
|
GEOIP_MMDB_PATH="" # Manueller DB-Pfad (optional, hat Vorrang)
|
||||||
|
|
||||||
# --- Erweiterte Einstellungen ---
|
# --- Erweiterte Einstellungen ---
|
||||||
STATE_DIR="/var/lib/adguard-shield"
|
STATE_DIR="/var/lib/adguard-shield" # SQLite-DB: ${STATE_DIR}/adguard-shield.db
|
||||||
PID_FILE="/var/run/adguard-shield.pid"
|
PID_FILE="/var/run/adguard-shield.pid"
|
||||||
API_QUERY_LIMIT=500 # API-Einträge pro Abfrage (max 5000)
|
API_QUERY_LIMIT=500 # API-Einträge pro Abfrage (max 5000)
|
||||||
DRY_RUN=false # true = nur loggen, nicht sperren
|
DRY_RUN=false # true = nur loggen, nicht sperren
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
# Lizenz: MIT
|
# Lizenz: MIT
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
VERSION="v0.9.0"
|
VERSION="v1.0.0"
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -26,10 +26,14 @@ fi
|
|||||||
# shellcheck source=adguard-shield.conf
|
# shellcheck source=adguard-shield.conf
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
|
# ─── Datenbank-Bibliothek laden ───────────────────────────────────────────────
|
||||||
|
# shellcheck source=db.sh
|
||||||
|
source "${SCRIPT_DIR}/db.sh"
|
||||||
|
|
||||||
# ─── Abhängigkeiten prüfen ────────────────────────────────────────────────────
|
# ─── Abhängigkeiten prüfen ────────────────────────────────────────────────────
|
||||||
check_dependencies() {
|
check_dependencies() {
|
||||||
local missing=()
|
local missing=()
|
||||||
for cmd in curl jq iptables ip6tables date; do
|
for cmd in curl jq iptables ip6tables date sqlite3; do
|
||||||
if ! command -v "$cmd" &>/dev/null; then
|
if ! command -v "$cmd" &>/dev/null; then
|
||||||
missing+=("$cmd")
|
missing+=("$cmd")
|
||||||
fi
|
fi
|
||||||
@@ -79,15 +83,6 @@ log_ban_history() {
|
|||||||
local reason="${5:-}"
|
local reason="${5:-}"
|
||||||
local duration="${6:-}"
|
local duration="${6:-}"
|
||||||
local protocol="${7:-}"
|
local protocol="${7:-}"
|
||||||
local timestamp
|
|
||||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
||||||
|
|
||||||
# Header schreiben falls Datei neu ist
|
|
||||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
|
||||||
echo "# AdGuard Shield - Ban History" > "$BAN_HISTORY_FILE"
|
|
||||||
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | PROTOKOLL | GRUND" >> "$BAN_HISTORY_FILE"
|
|
||||||
echo "#──────────────────────────────────────────────────────────────────────────────────────────────────" >> "$BAN_HISTORY_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "$duration" && "$action" == "BAN" ]]; then
|
if [[ -z "$duration" && "$action" == "BAN" ]]; then
|
||||||
duration="${BAN_DURATION}s"
|
duration="${BAN_DURATION}s"
|
||||||
@@ -95,65 +90,20 @@ log_ban_history() {
|
|||||||
[[ -z "$duration" ]] && duration="-"
|
[[ -z "$duration" ]] && duration="-"
|
||||||
[[ -z "$protocol" ]] && protocol="-"
|
[[ -z "$protocol" ]] && protocol="-"
|
||||||
|
|
||||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
|
db_history_add "$action" "$client_ip" "${domain:--}" "${count:--}" "${reason:-rate-limit}" "$duration" "$protocol"
|
||||||
"$timestamp" "$action" "$client_ip" "${domain:--}" "${count:--}" "$duration" "$protocol" "${reason:-rate-limit}" \
|
|
||||||
>> "$BAN_HISTORY_FILE"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Progressive Ban (Recidive) ─────────────────────────────────────────────
|
# ─── Progressive Ban (Recidive) ─────────────────────────────────────────────
|
||||||
# Liest die aktuelle Offense-Stufe einer IP aus der Offense-Datei
|
|
||||||
get_offense_level() {
|
get_offense_level() {
|
||||||
local client_ip="$1"
|
local client_ip="$1"
|
||||||
local offense_file="${STATE_DIR}/${client_ip//[:\/]/_}.offenses"
|
local level
|
||||||
|
level=$(db_offense_get_level "$client_ip" "${PROGRESSIVE_BAN_RESET_AFTER:-86400}")
|
||||||
if [[ ! -f "$offense_file" ]]; then
|
echo "$level"
|
||||||
echo "0"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
local level last_offense now reset_after
|
|
||||||
level=$(grep '^OFFENSE_LEVEL=' "$offense_file" | cut -d= -f2 || true)
|
|
||||||
last_offense=$(grep '^LAST_OFFENSE_EPOCH=' "$offense_file" | cut -d= -f2 || true)
|
|
||||||
now=$(date '+%s')
|
|
||||||
reset_after="${PROGRESSIVE_BAN_RESET_AFTER:-86400}"
|
|
||||||
|
|
||||||
# Prüfen ob der Zähler abgelaufen ist (Reset nach Zeitraum ohne Vergehen)
|
|
||||||
if [[ -n "$last_offense" && $((now - last_offense)) -gt "$reset_after" ]]; then
|
|
||||||
log "INFO" "Progressive Ban: Offense-Zähler für $client_ip zurückgesetzt (>${reset_after}s ohne Vergehen)"
|
|
||||||
rm -f "$offense_file"
|
|
||||||
echo "0"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "${level:-0}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Erhöht die Offense-Stufe einer IP und gibt die neue Stufe zurück
|
|
||||||
increment_offense_level() {
|
increment_offense_level() {
|
||||||
local client_ip="$1"
|
local client_ip="$1"
|
||||||
local offense_file="${STATE_DIR}/${client_ip//[:\/]/_}.offenses"
|
db_offense_increment "$client_ip"
|
||||||
local current_level
|
|
||||||
current_level=$(get_offense_level "$client_ip")
|
|
||||||
local new_level=$((current_level + 1))
|
|
||||||
local now
|
|
||||||
now=$(date '+%s')
|
|
||||||
local now_readable
|
|
||||||
now_readable=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
|
|
||||||
# Erstes Vergehen merken (bevor Datei überschrieben wird)
|
|
||||||
local first_offense
|
|
||||||
first_offense=$(grep '^FIRST_OFFENSE=' "$offense_file" 2>/dev/null | cut -d= -f2 || true)
|
|
||||||
[[ -z "$first_offense" ]] && first_offense="$now_readable"
|
|
||||||
|
|
||||||
cat > "$offense_file" << EOF
|
|
||||||
CLIENT_IP=$client_ip
|
|
||||||
OFFENSE_LEVEL=$new_level
|
|
||||||
LAST_OFFENSE_EPOCH=$now
|
|
||||||
LAST_OFFENSE=$now_readable
|
|
||||||
FIRST_OFFENSE=$first_offense
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "$new_level"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Berechnet die Sperrdauer basierend auf der Offense-Stufe
|
# Berechnet die Sperrdauer basierend auf der Offense-Stufe
|
||||||
@@ -207,11 +157,9 @@ format_duration() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Setzt den Offense-Zähler einer IP zurück
|
|
||||||
reset_offense_level() {
|
reset_offense_level() {
|
||||||
local client_ip="$1"
|
local client_ip="$1"
|
||||||
local offense_file="${STATE_DIR}/${client_ip//[:\/]/_}.offenses"
|
db_offense_delete "$client_ip"
|
||||||
rm -f "$offense_file"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Protokoll-Erkennung ─────────────────────────────────────────────────────
|
# ─── Protokoll-Erkennung ─────────────────────────────────────────────────────
|
||||||
@@ -307,12 +255,23 @@ report_to_abuseipdb() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
# ─── Verzeichnisse und Datenbank erstellen ───────────────────────────────────
|
||||||
init_directories() {
|
init_directories() {
|
||||||
mkdir -p "$STATE_DIR"
|
mkdir -p "$STATE_DIR"
|
||||||
mkdir -p "$(dirname "$LOG_FILE")"
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
mkdir -p "$(dirname "$PID_FILE")"
|
mkdir -p "$(dirname "$PID_FILE")"
|
||||||
mkdir -p "$(dirname "$BAN_HISTORY_FILE")"
|
|
||||||
|
db_init
|
||||||
|
|
||||||
|
# Migration von Flat-Files (einmalig beim ersten Start nach Update)
|
||||||
|
if [[ ! -f "$_DB_MIGRATION_MARKER" ]]; then
|
||||||
|
local migrated
|
||||||
|
migrated=$(db_migrate_from_files)
|
||||||
|
if [[ "${migrated:-0}" -gt 0 ]]; then
|
||||||
|
log "INFO" "SQLite-Migration abgeschlossen: $migrated Eintraege migriert"
|
||||||
|
log "INFO" "Backup der alten Dateien: ${STATE_DIR}/.backup_pre_sqlite/"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── PID-Management ──────────────────────────────────────────────────────────
|
# ─── PID-Management ──────────────────────────────────────────────────────────
|
||||||
@@ -354,15 +313,14 @@ is_whitelisted() {
|
|||||||
local ip="$1"
|
local ip="$1"
|
||||||
IFS=',' read -ra wl_entries <<< "$WHITELIST"
|
IFS=',' read -ra wl_entries <<< "$WHITELIST"
|
||||||
for entry in "${wl_entries[@]}"; do
|
for entry in "${wl_entries[@]}"; do
|
||||||
entry=$(echo "$entry" | xargs) # trim
|
entry=$(echo "$entry" | xargs)
|
||||||
if [[ "$ip" == "$entry" ]]; then
|
if [[ "$ip" == "$entry" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Externe Whitelist prüfen (aufgelöste IPs aus dem Whitelist-Worker)
|
# Externe Whitelist prüfen (SQLite)
|
||||||
local ext_wl_file="${EXTERNAL_WHITELIST_CACHE_DIR:-/var/lib/adguard-shield/external-whitelist}/resolved_ips.txt"
|
if db_whitelist_contains "$ip"; then
|
||||||
if [[ -f "$ext_wl_file" ]] && grep -qxF "$ip" "$ext_wl_file" 2>/dev/null; then
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -431,8 +389,7 @@ ban_client() {
|
|||||||
local protocol="${6:-DNS}"
|
local protocol="${6:-DNS}"
|
||||||
|
|
||||||
# Prüfen ob bereits gesperrt
|
# Prüfen ob bereits gesperrt
|
||||||
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
|
if db_ban_exists "$client_ip"; then
|
||||||
if [[ -f "$state_file" ]]; then
|
|
||||||
log "DEBUG" "Client $client_ip ist bereits gesperrt"
|
log "DEBUG" "Client $client_ip ist bereits gesperrt"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -496,20 +453,10 @@ ban_client() {
|
|||||||
iptables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
iptables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# State speichern
|
# State in Datenbank speichern
|
||||||
cat > "$state_file" << EOF
|
local perm_int=0
|
||||||
CLIENT_IP=$client_ip
|
[[ "$is_permanent" == "true" ]] && perm_int=1
|
||||||
DOMAIN=$domain
|
db_ban_insert "$client_ip" "$domain" "$count" "$(date '+%Y-%m-%d %H:%M:%S')" "$ban_until" "$effective_duration" "$offense_level" "$perm_int" "$reason" "$protocol" "monitor"
|
||||||
COUNT=$count
|
|
||||||
BAN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
BAN_UNTIL_EPOCH=$ban_until
|
|
||||||
BAN_UNTIL=$ban_until_display
|
|
||||||
BAN_DURATION=${effective_duration}
|
|
||||||
OFFENSE_LEVEL=$offense_level
|
|
||||||
IS_PERMANENT=$is_permanent
|
|
||||||
REASON=$reason
|
|
||||||
PROTOCOL=$protocol
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Ban-History Eintrag
|
# Ban-History Eintrag
|
||||||
local history_duration="${duration_display}"
|
local history_duration="${duration_display}"
|
||||||
@@ -531,16 +478,17 @@ EOF
|
|||||||
unban_client() {
|
unban_client() {
|
||||||
local client_ip="$1"
|
local client_ip="$1"
|
||||||
local reason="${2:-expired}"
|
local reason="${2:-expired}"
|
||||||
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
|
|
||||||
|
|
||||||
# Domain und Protokoll aus State lesen bevor wir löschen
|
# Domain und Protokoll aus DB lesen bevor wir loeschen
|
||||||
|
local ban_data
|
||||||
|
ban_data=$(db_ban_get "$client_ip")
|
||||||
local domain="-"
|
local domain="-"
|
||||||
local protocol="-"
|
local protocol="-"
|
||||||
if [[ -f "$state_file" ]]; then
|
if [[ -n "$ban_data" ]]; then
|
||||||
domain=$(grep '^DOMAIN=' "$state_file" | cut -d= -f2 || true)
|
IFS='|' read -r _ b_domain _ _ _ _ _ _ _ b_protocol _ _ _ <<< "$ban_data"
|
||||||
protocol=$(grep '^PROTOCOL=' "$state_file" | cut -d= -f2 || true)
|
domain="${b_domain:--}"
|
||||||
|
protocol="${b_protocol:--}"
|
||||||
fi
|
fi
|
||||||
[[ -z "$protocol" ]] && protocol="-"
|
|
||||||
|
|
||||||
log "INFO" "ENTSPERRE Client: $client_ip ($reason)"
|
log "INFO" "ENTSPERRE Client: $client_ip ($reason)"
|
||||||
|
|
||||||
@@ -550,9 +498,8 @@ unban_client() {
|
|||||||
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$state_file"
|
db_ban_delete "$client_ip"
|
||||||
|
|
||||||
# Ban-History Eintrag
|
|
||||||
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "$reason" "-" "$protocol"
|
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "$reason" "-" "$protocol"
|
||||||
|
|
||||||
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
|
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
|
||||||
@@ -562,29 +509,14 @@ unban_client() {
|
|||||||
|
|
||||||
# ─── Abgelaufene Sperren aufheben ───────────────────────────────────────────
|
# ─── Abgelaufene Sperren aufheben ───────────────────────────────────────────
|
||||||
check_expired_bans() {
|
check_expired_bans() {
|
||||||
local now
|
local expired_ips
|
||||||
now=$(date '+%s')
|
expired_ips=$(db_ban_get_expired)
|
||||||
|
[[ -z "$expired_ips" ]] && return
|
||||||
|
|
||||||
for state_file in "${STATE_DIR}"/*.ban; do
|
while IFS= read -r client_ip; do
|
||||||
[[ -f "$state_file" ]] || continue
|
[[ -z "$client_ip" ]] && continue
|
||||||
|
|
||||||
local ban_until_epoch
|
|
||||||
ban_until_epoch=$(grep '^BAN_UNTIL_EPOCH=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
local client_ip
|
|
||||||
client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
local is_permanent
|
|
||||||
is_permanent=$(grep '^IS_PERMANENT=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
|
|
||||||
# Permanente Sperren nicht automatisch aufheben
|
|
||||||
if [[ "$is_permanent" == "true" || "$ban_until_epoch" == "0" ]]; then
|
|
||||||
log "DEBUG" "Client $client_ip ist permanent gesperrt – überspringe"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$ban_until_epoch" && "$now" -ge "$ban_until_epoch" ]]; then
|
|
||||||
unban_client "$client_ip" "expired"
|
unban_client "$client_ip" "expired"
|
||||||
fi
|
done <<< "$expired_ips"
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Benachrichtigungen ─────────────────────────────────────────────────────
|
# ─── Benachrichtigungen ─────────────────────────────────────────────────────
|
||||||
@@ -995,8 +927,7 @@ analyze_subdomain_flood() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Prüfen ob bereits gesperrt
|
# Prüfen ob bereits gesperrt
|
||||||
local state_file="${STATE_DIR}/${client//[:\/]/_}.ban"
|
if db_ban_exists "$client"; then
|
||||||
if [[ -f "$state_file" ]]; then
|
|
||||||
log "DEBUG" "Client $client ist bereits gesperrt (Subdomain-Flood übersprungen)"
|
log "DEBUG" "Client $client ist bereits gesperrt (Subdomain-Flood übersprungen)"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -1054,22 +985,15 @@ show_status() {
|
|||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Aktive Sperren
|
# Aktive Sperren aus Datenbank
|
||||||
local ban_count=0
|
local ban_count=0
|
||||||
if [[ -d "$STATE_DIR" ]]; then
|
local all_bans
|
||||||
for state_file in "${STATE_DIR}"/*.ban; do
|
all_bans=$(db_ban_get_all)
|
||||||
[[ -f "$state_file" ]] || continue
|
|
||||||
|
if [[ -n "$all_bans" ]]; then
|
||||||
|
while IFS='|' read -r s_ip s_domain s_count s_ban_time s_ban_until_epoch s_dur s_level s_perm_int s_reason s_proto s_source s_geoip_country s_geoip_mode; do
|
||||||
|
[[ -z "$s_ip" ]] && continue
|
||||||
ban_count=$((ban_count + 1))
|
ban_count=$((ban_count + 1))
|
||||||
local s_ip s_domain s_level s_perm s_dur s_until s_reason s_count s_proto
|
|
||||||
s_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
s_domain=$(grep '^DOMAIN=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
s_level=$(grep '^OFFENSE_LEVEL=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
s_perm=$(grep '^IS_PERMANENT=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
s_dur=$(grep '^BAN_DURATION=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
s_until=$(grep '^BAN_UNTIL=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
s_reason=$(grep '^REASON=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
s_count=$(grep '^COUNT=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
s_proto=$(grep '^PROTOCOL=' "$state_file" | cut -d= -f2 || true)
|
|
||||||
s_reason="${s_reason:-rate-limit}"
|
s_reason="${s_reason:-rate-limit}"
|
||||||
s_proto="${s_proto:-?}"
|
s_proto="${s_proto:-?}"
|
||||||
|
|
||||||
@@ -1078,7 +1002,7 @@ show_status() {
|
|||||||
[[ "$s_reason" == "dns-flood-watchlist" ]] && reason_tag=" (DNS-Flood-Watchlist)"
|
[[ "$s_reason" == "dns-flood-watchlist" ]] && reason_tag=" (DNS-Flood-Watchlist)"
|
||||||
|
|
||||||
local count_info=""
|
local count_info=""
|
||||||
if [[ -n "$s_count" && "$s_count" != "-" ]]; then
|
if [[ -n "$s_count" && "$s_count" != "0" && "$s_count" != "-" ]]; then
|
||||||
if [[ "$s_reason" == "subdomain-flood" ]]; then
|
if [[ "$s_reason" == "subdomain-flood" ]]; then
|
||||||
count_info=", ${s_count} Subdomains"
|
count_info=", ${s_count} Subdomains"
|
||||||
else
|
else
|
||||||
@@ -1088,16 +1012,23 @@ show_status() {
|
|||||||
|
|
||||||
local proto_tag=" via ${s_proto}"
|
local proto_tag=" via ${s_proto}"
|
||||||
|
|
||||||
if [[ "$s_perm" == "true" && "$s_reason" == "dns-flood-watchlist" ]]; then
|
local s_until_display
|
||||||
|
if [[ "$s_ban_until_epoch" == "0" || "$s_perm_int" == "1" ]]; then
|
||||||
|
s_until_display="PERMANENT"
|
||||||
|
else
|
||||||
|
s_until_display=$(date -d "@$s_ban_until_epoch" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date -r "$s_ban_until_epoch" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "?")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$s_perm_int" == "1" && "$s_reason" == "dns-flood-watchlist" ]]; then
|
||||||
echo " 🚫 Gesperrt: $s_ip → $s_domain [PERMANENT${count_info}${proto_tag}]${reason_tag}"
|
echo " 🚫 Gesperrt: $s_ip → $s_domain [PERMANENT${count_info}${proto_tag}]${reason_tag}"
|
||||||
elif [[ "$s_perm" == "true" ]]; then
|
elif [[ "$s_perm_int" == "1" ]]; then
|
||||||
echo " 🚫 Gesperrt: $s_ip → $s_domain [PERMANENT, Stufe ${s_level:-?}${count_info}${proto_tag}]${reason_tag}"
|
echo " 🚫 Gesperrt: $s_ip → $s_domain [PERMANENT, Stufe ${s_level:-?}${count_info}${proto_tag}]${reason_tag}"
|
||||||
elif [[ -n "$s_level" && "$s_level" -gt 0 ]]; then
|
elif [[ -n "$s_level" && "$s_level" -gt 0 ]]; then
|
||||||
echo " 🚫 Gesperrt: $s_ip → $s_domain [Stufe ${s_level}, $(format_duration "${s_dur:-$BAN_DURATION}"), bis $s_until${count_info}${proto_tag}]${reason_tag}"
|
echo " 🚫 Gesperrt: $s_ip → $s_domain [Stufe ${s_level}, $(format_duration "${s_dur:-$BAN_DURATION}"), bis $s_until_display${count_info}${proto_tag}]${reason_tag}"
|
||||||
else
|
else
|
||||||
echo " 🚫 Gesperrt: $s_ip → $s_domain [bis $s_until${count_info}${proto_tag}]${reason_tag}"
|
echo " 🚫 Gesperrt: $s_ip → $s_domain [bis $s_until_display${count_info}${proto_tag}]${reason_tag}"
|
||||||
fi
|
fi
|
||||||
done
|
done <<< "$all_bans"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -1108,15 +1039,15 @@ show_status() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Offense-Informationen anzeigen (Wiederholungstäter)
|
# Offense-Informationen anzeigen (Wiederholungstäter)
|
||||||
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && -d "$STATE_DIR" ]]; then
|
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then
|
||||||
|
local offense_data
|
||||||
|
offense_data=$(db_offense_get_all)
|
||||||
local offense_count=0
|
local offense_count=0
|
||||||
local offense_output=""
|
local offense_output=""
|
||||||
for offense_file in "${STATE_DIR}"/*.offenses; do
|
|
||||||
[[ -f "$offense_file" ]] || continue
|
if [[ -n "$offense_data" ]]; then
|
||||||
local o_ip o_level o_last
|
while IFS='|' read -r o_ip o_level o_last_epoch o_last o_first; do
|
||||||
o_ip=$(grep '^CLIENT_IP=' "$offense_file" | cut -d= -f2 || true)
|
[[ -z "$o_ip" ]] && continue
|
||||||
o_level=$(grep '^OFFENSE_LEVEL=' "$offense_file" | cut -d= -f2 || true)
|
|
||||||
o_last=$(grep '^LAST_OFFENSE=' "$offense_file" | cut -d= -f2 || true)
|
|
||||||
offense_count=$((offense_count + 1))
|
offense_count=$((offense_count + 1))
|
||||||
local next_dur
|
local next_dur
|
||||||
next_dur=$(calculate_ban_duration "$((o_level + 1))")
|
next_dur=$(calculate_ban_duration "$((o_level + 1))")
|
||||||
@@ -1125,7 +1056,8 @@ show_status() {
|
|||||||
else
|
else
|
||||||
offense_output+=" ⚠ $o_ip: Stufe $o_level (letztes Vergehen: $o_last) → nächste Sperre: $(format_duration "$next_dur")\n"
|
offense_output+=" ⚠ $o_ip: Stufe $o_level (letztes Vergehen: $o_last) → nächste Sperre: $(format_duration "$next_dur")\n"
|
||||||
fi
|
fi
|
||||||
done
|
done <<< "$offense_data"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $offense_count -gt 0 ]]; then
|
if [[ $offense_count -gt 0 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
@@ -1156,29 +1088,34 @@ show_history() {
|
|||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
local total
|
||||||
|
total=$(db_history_count)
|
||||||
|
|
||||||
|
if [[ "${total:-0}" -eq 0 ]]; then
|
||||||
echo " Noch keine History vorhanden."
|
echo " Noch keine History vorhanden."
|
||||||
echo " Datei: $BAN_HISTORY_FILE"
|
|
||||||
echo ""
|
echo ""
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Header zeigen
|
echo " # Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | PROTOKOLL | GRUND"
|
||||||
head -3 "$BAN_HISTORY_FILE" | sed 's/^/ /'
|
echo " #──────────────────────────────────────────────────────────────────────────────────────────────────"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Letzte N Einträge (ohne Header-Zeilen)
|
local recent
|
||||||
grep -v '^#' "$BAN_HISTORY_FILE" | tail -n "$lines" | sed 's/^/ /'
|
recent=$(db_history_get_recent "$lines")
|
||||||
|
if [[ -n "$recent" ]]; then
|
||||||
|
while IFS='|' read -r ts action ip domain count duration protocol reason; do
|
||||||
|
printf " %-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
|
||||||
|
"$ts" "$action" "$ip" "$domain" "$count" "$duration" "$protocol" "$reason"
|
||||||
|
done <<< "$recent"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
local total
|
local bans unbans
|
||||||
total=$(grep -vc '^#' "$BAN_HISTORY_FILE" 2>/dev/null || echo "0")
|
bans=$(db_history_count_by_action "BAN")
|
||||||
local bans
|
unbans=$(db_history_count_by_action "UNBAN")
|
||||||
bans=$(grep -c '| BAN ' "$BAN_HISTORY_FILE" 2>/dev/null || echo "0")
|
|
||||||
local unbans
|
|
||||||
unbans=$(grep -c '| UNBAN ' "$BAN_HISTORY_FILE" 2>/dev/null || echo "0")
|
|
||||||
echo " Gesamt: $total Einträge ($bans Sperren, $unbans Entsperrungen)"
|
echo " Gesamt: $total Einträge ($bans Sperren, $unbans Entsperrungen)"
|
||||||
echo " Datei: $BAN_HISTORY_FILE"
|
echo " Datenbank: $DB_FILE"
|
||||||
echo ""
|
echo ""
|
||||||
echo "═══════════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
}
|
}
|
||||||
@@ -1187,12 +1124,14 @@ show_history() {
|
|||||||
flush_all_bans() {
|
flush_all_bans() {
|
||||||
log "INFO" "Alle Sperren werden aufgehoben..."
|
log "INFO" "Alle Sperren werden aufgehoben..."
|
||||||
|
|
||||||
for state_file in "${STATE_DIR}"/*.ban; do
|
local all_ips
|
||||||
[[ -f "$state_file" ]] || continue
|
all_ips=$(db_query "SELECT client_ip FROM active_bans;")
|
||||||
local client_ip
|
if [[ -n "$all_ips" ]]; then
|
||||||
client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2 || true)
|
while IFS= read -r client_ip; do
|
||||||
|
[[ -z "$client_ip" ]] && continue
|
||||||
unban_client "$client_ip" "manual-flush"
|
unban_client "$client_ip" "manual-flush"
|
||||||
done
|
done <<< "$all_ips"
|
||||||
|
fi
|
||||||
|
|
||||||
# Chain leeren
|
# Chain leeren
|
||||||
iptables -F "$IPTABLES_CHAIN" 2>/dev/null || true
|
iptables -F "$IPTABLES_CHAIN" 2>/dev/null || true
|
||||||
@@ -1203,15 +1142,8 @@ flush_all_bans() {
|
|||||||
|
|
||||||
# ─── Alle Offense-Zähler zurücksetzen ────────────────────────────────────────
|
# ─── Alle Offense-Zähler zurücksetzen ────────────────────────────────────────
|
||||||
flush_all_offenses() {
|
flush_all_offenses() {
|
||||||
local count=0
|
local count
|
||||||
for offense_file in "${STATE_DIR}"/*.offenses; do
|
count=$(db_offense_delete_all)
|
||||||
[[ -f "$offense_file" ]] || continue
|
|
||||||
local o_ip
|
|
||||||
o_ip=$(grep '^CLIENT_IP=' "$offense_file" | cut -d= -f2 || true)
|
|
||||||
log "INFO" "Offense-Zähler zurückgesetzt: $o_ip"
|
|
||||||
rm -f "$offense_file"
|
|
||||||
count=$((count + 1))
|
|
||||||
done
|
|
||||||
log "INFO" "$count Offense-Zähler zurückgesetzt"
|
log "INFO" "$count Offense-Zähler zurückgesetzt"
|
||||||
echo "$count Offense-Zähler zurückgesetzt"
|
echo "$count Offense-Zähler zurückgesetzt"
|
||||||
}
|
}
|
||||||
@@ -1630,7 +1562,7 @@ Interne Befehle (nicht direkt verwenden — nur über systemd):
|
|||||||
|
|
||||||
Konfiguration: $CONFIG_FILE
|
Konfiguration: $CONFIG_FILE
|
||||||
Log-Datei: $LOG_FILE
|
Log-Datei: $LOG_FILE
|
||||||
Ban-History: $BAN_HISTORY_FILE
|
Datenbank: $DB_FILE
|
||||||
State: $STATE_DIR
|
State: $STATE_DIR
|
||||||
|
|
||||||
USAGE
|
USAGE
|
||||||
|
|||||||
641
db.sh
Normal file
641
db.sh
Normal file
@@ -0,0 +1,641 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
###############################################################################
|
||||||
|
# AdGuard Shield - SQLite Datenbank-Bibliothek
|
||||||
|
# Zentrale Datenbankfunktionen fuer alle Scripte.
|
||||||
|
# Wird per "source db.sh" eingebunden.
|
||||||
|
#
|
||||||
|
# Autor: Patrick Asmus
|
||||||
|
# E-Mail: support@techniverse.net
|
||||||
|
# Lizenz: MIT
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
DB_FILE="${STATE_DIR}/adguard-shield.db"
|
||||||
|
DB_SCHEMA_VERSION=1
|
||||||
|
_DB_MIGRATION_MARKER="${STATE_DIR}/.migration_v1_complete"
|
||||||
|
|
||||||
|
# ─── SQL-Wert escapen (Single Quotes verdoppeln) ────────────────────────────
|
||||||
|
_db_escape() {
|
||||||
|
echo "${1//\'/\'\'}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── SQL ausfuehren (INSERT/UPDATE/DELETE) ───────────────────────────────────
|
||||||
|
db_exec() {
|
||||||
|
sqlite3 "$DB_FILE" <<EOF
|
||||||
|
.timeout 5000
|
||||||
|
$1
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── SQL-Abfrage mit Pipe-Separator ─────────────────────────────────────────
|
||||||
|
db_query() {
|
||||||
|
sqlite3 -separator '|' "$DB_FILE" <<EOF
|
||||||
|
.timeout 5000
|
||||||
|
$1
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Datenbank initialisieren ────────────────────────────────────────────────
|
||||||
|
db_init() {
|
||||||
|
mkdir -p "$(dirname "$DB_FILE")"
|
||||||
|
|
||||||
|
sqlite3 "$DB_FILE" <<'SCHEMA'
|
||||||
|
.timeout 5000
|
||||||
|
PRAGMA journal_mode=WAL;
|
||||||
|
PRAGMA busy_timeout=5000;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS schema_version (
|
||||||
|
version INTEGER PRIMARY KEY,
|
||||||
|
applied_at TEXT DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS active_bans (
|
||||||
|
client_ip TEXT PRIMARY KEY,
|
||||||
|
domain TEXT,
|
||||||
|
count INTEGER,
|
||||||
|
ban_time TEXT,
|
||||||
|
ban_until_epoch INTEGER DEFAULT 0,
|
||||||
|
ban_duration INTEGER DEFAULT 0,
|
||||||
|
offense_level INTEGER DEFAULT 0,
|
||||||
|
is_permanent INTEGER DEFAULT 0,
|
||||||
|
reason TEXT DEFAULT 'rate-limit',
|
||||||
|
protocol TEXT DEFAULT 'DNS',
|
||||||
|
source TEXT DEFAULT 'monitor',
|
||||||
|
geoip_country TEXT,
|
||||||
|
geoip_mode TEXT,
|
||||||
|
created_at TEXT DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS offense_tracking (
|
||||||
|
client_ip TEXT PRIMARY KEY,
|
||||||
|
offense_level INTEGER DEFAULT 0,
|
||||||
|
last_offense_epoch INTEGER,
|
||||||
|
last_offense TEXT,
|
||||||
|
first_offense TEXT,
|
||||||
|
created_at TEXT DEFAULT (datetime('now', 'localtime')),
|
||||||
|
updated_at TEXT DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ban_history (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
timestamp_epoch INTEGER NOT NULL,
|
||||||
|
timestamp_text TEXT NOT NULL,
|
||||||
|
action TEXT NOT NULL,
|
||||||
|
client_ip TEXT NOT NULL,
|
||||||
|
domain TEXT,
|
||||||
|
count TEXT,
|
||||||
|
duration TEXT,
|
||||||
|
protocol TEXT,
|
||||||
|
reason TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS whitelist_cache (
|
||||||
|
ip_address TEXT PRIMARY KEY,
|
||||||
|
source TEXT,
|
||||||
|
resolved_at TEXT DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indexes fuer Performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_bans_until ON active_bans(ban_until_epoch);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_bans_source ON active_bans(source);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_bans_reason ON active_bans(reason);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_history_timestamp ON ban_history(timestamp_epoch);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_history_action ON ban_history(action);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_history_ip ON ban_history(client_ip);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_offenses_last ON offense_tracking(last_offense_epoch);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO schema_version (version) VALUES (1);
|
||||||
|
SCHEMA
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Ban-Funktionen ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
db_ban_exists() {
|
||||||
|
local ip=$(_db_escape "$1")
|
||||||
|
local result
|
||||||
|
result=$(db_query "SELECT 1 FROM active_bans WHERE client_ip='$ip' LIMIT 1;")
|
||||||
|
[[ -n "$result" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ban_get() {
|
||||||
|
local ip=$(_db_escape "$1")
|
||||||
|
db_query "SELECT client_ip, domain, count, ban_time, ban_until_epoch, ban_duration, offense_level, is_permanent, reason, protocol, source, geoip_country, geoip_mode FROM active_bans WHERE client_ip='$ip' LIMIT 1;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ban_insert() {
|
||||||
|
local ip=$(_db_escape "$1")
|
||||||
|
local domain=$(_db_escape "$2")
|
||||||
|
local count="${3:-0}"
|
||||||
|
local ban_time=$(_db_escape "$4")
|
||||||
|
local ban_until_epoch="${5:-0}"
|
||||||
|
local ban_duration="${6:-0}"
|
||||||
|
local offense_level="${7:-0}"
|
||||||
|
local is_permanent="${8:-0}"
|
||||||
|
local reason=$(_db_escape "${9:-rate-limit}")
|
||||||
|
local protocol=$(_db_escape "${10:-DNS}")
|
||||||
|
local source=$(_db_escape "${11:-monitor}")
|
||||||
|
local geoip_country=$(_db_escape "${12:-}")
|
||||||
|
local geoip_mode=$(_db_escape "${13:-}")
|
||||||
|
|
||||||
|
db_exec "INSERT OR REPLACE INTO active_bans (client_ip, domain, count, ban_time, ban_until_epoch, ban_duration, offense_level, is_permanent, reason, protocol, source, geoip_country, geoip_mode) VALUES ('$ip', '$domain', $count, '$ban_time', $ban_until_epoch, $ban_duration, $offense_level, $is_permanent, '$reason', '$protocol', '$source', '$geoip_country', '$geoip_mode');"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ban_delete() {
|
||||||
|
local ip=$(_db_escape "$1")
|
||||||
|
db_exec "DELETE FROM active_bans WHERE client_ip='$ip';"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ban_get_field() {
|
||||||
|
local ip=$(_db_escape "$1")
|
||||||
|
local field=$(_db_escape "$2")
|
||||||
|
db_query "SELECT $field FROM active_bans WHERE client_ip='$ip' LIMIT 1;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ban_get_expired() {
|
||||||
|
local now
|
||||||
|
now=$(date '+%s')
|
||||||
|
db_query "SELECT client_ip FROM active_bans WHERE ban_until_epoch > 0 AND is_permanent = 0 AND ban_until_epoch <= $now;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ban_get_expired_by_source() {
|
||||||
|
local source=$(_db_escape "$1")
|
||||||
|
local now
|
||||||
|
now=$(date '+%s')
|
||||||
|
db_query "SELECT client_ip FROM active_bans WHERE source='$source' AND ban_until_epoch > 0 AND is_permanent = 0 AND ban_until_epoch <= $now;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ban_get_by_source() {
|
||||||
|
local source=$(_db_escape "$1")
|
||||||
|
db_query "SELECT client_ip FROM active_bans WHERE source='$source';"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ban_count() {
|
||||||
|
db_query "SELECT COUNT(*) FROM active_bans;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ban_count_by_source() {
|
||||||
|
local source=$(_db_escape "$1")
|
||||||
|
db_query "SELECT COUNT(*) FROM active_bans WHERE source='$source';"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ban_get_all() {
|
||||||
|
db_query "SELECT client_ip, domain, count, ban_time, ban_until_epoch, ban_duration, offense_level, is_permanent, reason, protocol, source, geoip_country, geoip_mode FROM active_bans ORDER BY created_at DESC;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ban_get_by_reason() {
|
||||||
|
local reason=$(_db_escape "$1")
|
||||||
|
db_query "SELECT client_ip, domain, count, ban_time, ban_until_epoch, ban_duration, offense_level, is_permanent, reason, protocol, source, geoip_country, geoip_mode FROM active_bans WHERE reason='$reason';"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Offense-Funktionen ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
db_offense_get_level() {
|
||||||
|
local ip=$(_db_escape "$1")
|
||||||
|
local reset_after="${2:-86400}"
|
||||||
|
local now
|
||||||
|
now=$(date '+%s')
|
||||||
|
|
||||||
|
local row
|
||||||
|
row=$(db_query "SELECT offense_level, last_offense_epoch FROM offense_tracking WHERE client_ip='$ip' LIMIT 1;")
|
||||||
|
|
||||||
|
if [[ -z "$row" ]]; then
|
||||||
|
echo "0"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local level last_epoch
|
||||||
|
IFS='|' read -r level last_epoch <<< "$row"
|
||||||
|
|
||||||
|
if [[ -n "$last_epoch" && $((now - last_epoch)) -gt "$reset_after" ]]; then
|
||||||
|
db_exec "DELETE FROM offense_tracking WHERE client_ip='$ip';"
|
||||||
|
echo "0"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${level:-0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_offense_increment() {
|
||||||
|
local ip=$(_db_escape "$1")
|
||||||
|
local current_level
|
||||||
|
current_level=$(db_offense_get_level "$1" "${PROGRESSIVE_BAN_RESET_AFTER:-86400}")
|
||||||
|
local new_level=$((current_level + 1))
|
||||||
|
local now
|
||||||
|
now=$(date '+%s')
|
||||||
|
local now_readable
|
||||||
|
now_readable=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
local first_offense
|
||||||
|
first_offense=$(db_query "SELECT first_offense FROM offense_tracking WHERE client_ip='$ip' LIMIT 1;")
|
||||||
|
[[ -z "$first_offense" ]] && first_offense="$now_readable"
|
||||||
|
|
||||||
|
db_exec "INSERT OR REPLACE INTO offense_tracking (client_ip, offense_level, last_offense_epoch, last_offense, first_offense, updated_at) VALUES ('$ip', $new_level, $now, '$now_readable', '$first_offense', '$now_readable');"
|
||||||
|
|
||||||
|
echo "$new_level"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_offense_delete() {
|
||||||
|
local ip=$(_db_escape "$1")
|
||||||
|
db_exec "DELETE FROM offense_tracking WHERE client_ip='$ip';"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_offense_delete_all() {
|
||||||
|
local count
|
||||||
|
count=$(db_query "SELECT COUNT(*) FROM offense_tracking;")
|
||||||
|
db_exec "DELETE FROM offense_tracking;"
|
||||||
|
echo "${count:-0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_offense_delete_expired() {
|
||||||
|
local reset_after="${1:-86400}"
|
||||||
|
local now
|
||||||
|
now=$(date '+%s')
|
||||||
|
local cutoff=$((now - reset_after))
|
||||||
|
|
||||||
|
local expired
|
||||||
|
expired=$(db_query "SELECT client_ip, offense_level, last_offense_epoch FROM offense_tracking WHERE last_offense_epoch <= $cutoff;")
|
||||||
|
local count=0
|
||||||
|
if [[ -n "$expired" ]]; then
|
||||||
|
count=$(echo "$expired" | wc -l)
|
||||||
|
db_exec "DELETE FROM offense_tracking WHERE last_offense_epoch <= $cutoff;"
|
||||||
|
fi
|
||||||
|
echo "$count"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_offense_get_all() {
|
||||||
|
db_query "SELECT client_ip, offense_level, last_offense_epoch, last_offense, first_offense FROM offense_tracking ORDER BY last_offense_epoch DESC;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_offense_count() {
|
||||||
|
db_query "SELECT COUNT(*) FROM offense_tracking;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_offense_count_expired() {
|
||||||
|
local reset_after="${1:-86400}"
|
||||||
|
local now
|
||||||
|
now=$(date '+%s')
|
||||||
|
local cutoff=$((now - reset_after))
|
||||||
|
db_query "SELECT COUNT(*) FROM offense_tracking WHERE last_offense_epoch <= $cutoff;"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Ban-History-Funktionen ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
db_history_add() {
|
||||||
|
local action=$(_db_escape "$1")
|
||||||
|
local client_ip=$(_db_escape "$2")
|
||||||
|
local domain=$(_db_escape "${3:--}")
|
||||||
|
local count=$(_db_escape "${4:--}")
|
||||||
|
local reason=$(_db_escape "${5:--}")
|
||||||
|
local duration=$(_db_escape "${6:--}")
|
||||||
|
local protocol=$(_db_escape "${7:--}")
|
||||||
|
local now_epoch
|
||||||
|
now_epoch=$(date '+%s')
|
||||||
|
local now_text
|
||||||
|
now_text=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
db_exec "INSERT INTO ban_history (timestamp_epoch, timestamp_text, action, client_ip, domain, count, duration, protocol, reason) VALUES ($now_epoch, '$now_text', '$action', '$client_ip', '$domain', '$count', '$duration', '$protocol', '$reason');"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_history_cleanup() {
|
||||||
|
local retention_days="${1:-0}"
|
||||||
|
[[ "$retention_days" == "0" || -z "$retention_days" ]] && return
|
||||||
|
|
||||||
|
local cutoff_epoch
|
||||||
|
cutoff_epoch=$(date -d "-${retention_days} days" '+%s' 2>/dev/null)
|
||||||
|
[[ -z "$cutoff_epoch" ]] && return
|
||||||
|
|
||||||
|
local before after removed
|
||||||
|
before=$(db_query "SELECT COUNT(*) FROM ban_history;")
|
||||||
|
db_exec "DELETE FROM ban_history WHERE timestamp_epoch < $cutoff_epoch;"
|
||||||
|
after=$(db_query "SELECT COUNT(*) FROM ban_history;")
|
||||||
|
removed=$((before - after))
|
||||||
|
echo "$removed"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_history_get_recent() {
|
||||||
|
local limit="${1:-50}"
|
||||||
|
db_query "SELECT timestamp_text, action, client_ip, domain, count, duration, protocol, reason FROM ban_history ORDER BY id DESC LIMIT $limit;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_history_count() {
|
||||||
|
db_query "SELECT COUNT(*) FROM ban_history;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_history_count_by_action() {
|
||||||
|
local action=$(_db_escape "$1")
|
||||||
|
db_query "SELECT COUNT(*) FROM ban_history WHERE action='$action';"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_history_stats_for_range() {
|
||||||
|
local start_epoch="$1"
|
||||||
|
local end_epoch="$2"
|
||||||
|
|
||||||
|
db_query "SELECT
|
||||||
|
COALESCE(SUM(CASE WHEN action='BAN' THEN 1 ELSE 0 END), 0),
|
||||||
|
COALESCE(SUM(CASE WHEN action='UNBAN' THEN 1 ELSE 0 END), 0),
|
||||||
|
COALESCE(COUNT(DISTINCT CASE WHEN action='BAN' THEN client_ip END), 0),
|
||||||
|
COALESCE(SUM(CASE WHEN action='BAN' AND (duration LIKE '%PERMANENT%' OR duration LIKE '%permanent%') THEN 1 ELSE 0 END), 0)
|
||||||
|
FROM ban_history
|
||||||
|
WHERE timestamp_epoch >= $start_epoch AND timestamp_epoch <= $end_epoch;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_history_report_stats() {
|
||||||
|
local start_epoch="$1"
|
||||||
|
local end_epoch="$2"
|
||||||
|
local busiest_start="$3"
|
||||||
|
|
||||||
|
db_query "SELECT
|
||||||
|
COALESCE(SUM(CASE WHEN action='BAN' THEN 1 ELSE 0 END), 0),
|
||||||
|
COALESCE(SUM(CASE WHEN action='UNBAN' THEN 1 ELSE 0 END), 0),
|
||||||
|
COALESCE(COUNT(DISTINCT CASE WHEN action='BAN' THEN client_ip END), 0),
|
||||||
|
COALESCE(SUM(CASE WHEN action='BAN' AND (duration LIKE '%PERMANENT%' OR duration LIKE '%permanent%') THEN 1 ELSE 0 END), 0),
|
||||||
|
COALESCE(SUM(CASE WHEN action='BAN' AND reason LIKE '%rate%limit%' THEN 1 ELSE 0 END), 0),
|
||||||
|
COALESCE(SUM(CASE WHEN action='BAN' AND reason LIKE '%subdomain%flood%' THEN 1 ELSE 0 END), 0),
|
||||||
|
COALESCE(SUM(CASE WHEN action='BAN' AND reason LIKE '%external%blocklist%' THEN 1 ELSE 0 END), 0)
|
||||||
|
FROM ban_history
|
||||||
|
WHERE timestamp_epoch >= $start_epoch AND timestamp_epoch <= $end_epoch;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_history_busiest_day() {
|
||||||
|
local start_epoch="$1"
|
||||||
|
local end_epoch="$2"
|
||||||
|
|
||||||
|
db_query "SELECT substr(timestamp_text, 1, 10), COUNT(*)
|
||||||
|
FROM ban_history
|
||||||
|
WHERE action='BAN' AND timestamp_epoch >= $start_epoch AND timestamp_epoch <= $end_epoch
|
||||||
|
GROUP BY substr(timestamp_text, 1, 10)
|
||||||
|
ORDER BY COUNT(*) DESC
|
||||||
|
LIMIT 1;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_history_top_ips() {
|
||||||
|
local start_epoch="$1"
|
||||||
|
local end_epoch="$2"
|
||||||
|
local limit="${3:-10}"
|
||||||
|
|
||||||
|
db_query "SELECT COUNT(*), client_ip
|
||||||
|
FROM ban_history
|
||||||
|
WHERE action='BAN' AND timestamp_epoch >= $start_epoch AND timestamp_epoch <= $end_epoch
|
||||||
|
GROUP BY client_ip
|
||||||
|
ORDER BY COUNT(*) DESC
|
||||||
|
LIMIT $limit;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_history_top_domains() {
|
||||||
|
local start_epoch="$1"
|
||||||
|
local end_epoch="$2"
|
||||||
|
local limit="${3:-10}"
|
||||||
|
|
||||||
|
db_query "SELECT COUNT(*), domain
|
||||||
|
FROM ban_history
|
||||||
|
WHERE action='BAN' AND domain != '-' AND domain != '' AND timestamp_epoch >= $start_epoch AND timestamp_epoch <= $end_epoch
|
||||||
|
GROUP BY domain
|
||||||
|
ORDER BY COUNT(*) DESC
|
||||||
|
LIMIT $limit;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_history_protocol_stats() {
|
||||||
|
local start_epoch="$1"
|
||||||
|
local end_epoch="$2"
|
||||||
|
|
||||||
|
db_query "SELECT COUNT(*), COALESCE(NULLIF(protocol, ''), 'unbekannt')
|
||||||
|
FROM ban_history
|
||||||
|
WHERE action='BAN' AND timestamp_epoch >= $start_epoch AND timestamp_epoch <= $end_epoch
|
||||||
|
GROUP BY COALESCE(NULLIF(protocol, ''), 'unbekannt')
|
||||||
|
ORDER BY COUNT(*) DESC;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_history_recent_bans() {
|
||||||
|
local start_epoch="$1"
|
||||||
|
local end_epoch="$2"
|
||||||
|
local limit="${3:-10}"
|
||||||
|
|
||||||
|
db_query "SELECT timestamp_text, action, client_ip, domain, count, duration, protocol, reason
|
||||||
|
FROM ban_history
|
||||||
|
WHERE action='BAN' AND timestamp_epoch >= $start_epoch AND timestamp_epoch <= $end_epoch
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT $limit;"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Whitelist-Funktionen ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
db_whitelist_contains() {
|
||||||
|
local ip=$(_db_escape "$1")
|
||||||
|
local result
|
||||||
|
result=$(db_query "SELECT 1 FROM whitelist_cache WHERE ip_address='$ip' LIMIT 1;")
|
||||||
|
[[ -n "$result" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
db_whitelist_sync() {
|
||||||
|
local source=$(_db_escape "${1:-external}")
|
||||||
|
local tmp_sql=""
|
||||||
|
tmp_sql="BEGIN TRANSACTION; DELETE FROM whitelist_cache;"
|
||||||
|
while IFS= read -r ip; do
|
||||||
|
[[ -z "$ip" ]] && continue
|
||||||
|
local safe_ip=$(_db_escape "$ip")
|
||||||
|
tmp_sql+=" INSERT OR IGNORE INTO whitelist_cache (ip_address, source) VALUES ('$safe_ip', '$source');"
|
||||||
|
done
|
||||||
|
tmp_sql+=" COMMIT;"
|
||||||
|
db_exec "$tmp_sql"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_whitelist_count() {
|
||||||
|
db_query "SELECT COUNT(*) FROM whitelist_cache;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_whitelist_get_all() {
|
||||||
|
db_query "SELECT ip_address FROM whitelist_cache;"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_whitelist_clear() {
|
||||||
|
db_exec "DELETE FROM whitelist_cache;"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Migration von Flat-Files ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
db_migrate_from_files() {
|
||||||
|
# Bereits migriert?
|
||||||
|
if [[ -f "$_DB_MIGRATION_MARKER" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local migrated=0
|
||||||
|
local backup_dir="${STATE_DIR}/.backup_pre_sqlite"
|
||||||
|
|
||||||
|
# ─── .ban-Dateien migrieren ──────────────────────────────────────────
|
||||||
|
local ban_sql="BEGIN TRANSACTION;"
|
||||||
|
local ban_count=0
|
||||||
|
|
||||||
|
for state_file in "${STATE_DIR}"/*.ban "${STATE_DIR}"/ext_*.ban; do
|
||||||
|
[[ -f "$state_file" ]] || continue
|
||||||
|
local basename_f
|
||||||
|
basename_f=$(basename "$state_file")
|
||||||
|
|
||||||
|
local s_ip s_domain s_count s_ban_time s_ban_until_epoch s_ban_duration
|
||||||
|
local s_offense_level s_is_permanent s_reason s_protocol s_source
|
||||||
|
local s_geoip_country s_geoip_mode
|
||||||
|
|
||||||
|
s_ip=$(grep '^CLIENT_IP=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
[[ -z "$s_ip" ]] && continue
|
||||||
|
|
||||||
|
s_domain=$(grep '^DOMAIN=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
s_count=$(grep '^COUNT=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
s_ban_time=$(grep '^BAN_TIME=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
s_ban_until_epoch=$(grep '^BAN_UNTIL_EPOCH=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
s_ban_duration=$(grep '^BAN_DURATION=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
s_offense_level=$(grep '^OFFENSE_LEVEL=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
s_is_permanent=$(grep '^IS_PERMANENT=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
s_reason=$(grep '^REASON=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
s_protocol=$(grep '^PROTOCOL=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
s_geoip_country=$(grep '^GEOIP_COUNTRY=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
s_geoip_mode=$(grep '^GEOIP_MODE=' "$state_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
|
||||||
|
# Source bestimmen
|
||||||
|
if [[ "$basename_f" == ext_* ]]; then
|
||||||
|
s_source="external-blocklist"
|
||||||
|
elif [[ "$s_reason" == "geoip" ]]; then
|
||||||
|
s_source="geoip"
|
||||||
|
else
|
||||||
|
s_source="monitor"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Boolean zu Integer
|
||||||
|
local perm_int=0
|
||||||
|
[[ "$s_is_permanent" == "true" ]] && perm_int=1
|
||||||
|
|
||||||
|
s_ip=$(_db_escape "$s_ip")
|
||||||
|
s_domain=$(_db_escape "${s_domain:--}")
|
||||||
|
s_ban_time=$(_db_escape "${s_ban_time:-}")
|
||||||
|
s_reason=$(_db_escape "${s_reason:-rate-limit}")
|
||||||
|
s_protocol=$(_db_escape "${s_protocol:-DNS}")
|
||||||
|
s_geoip_country=$(_db_escape "${s_geoip_country:-}")
|
||||||
|
s_geoip_mode=$(_db_escape "${s_geoip_mode:-}")
|
||||||
|
|
||||||
|
ban_sql+=" INSERT OR IGNORE INTO active_bans (client_ip, domain, count, ban_time, ban_until_epoch, ban_duration, offense_level, is_permanent, reason, protocol, source, geoip_country, geoip_mode) VALUES ('$s_ip', '$s_domain', ${s_count:-0}, '$s_ban_time', ${s_ban_until_epoch:-0}, ${s_ban_duration:-0}, ${s_offense_level:-0}, $perm_int, '$s_reason', '$s_protocol', '$s_source', '$s_geoip_country', '$s_geoip_mode');"
|
||||||
|
ban_count=$((ban_count + 1))
|
||||||
|
done
|
||||||
|
ban_sql+=" COMMIT;"
|
||||||
|
|
||||||
|
if [[ $ban_count -gt 0 ]]; then
|
||||||
|
db_exec "$ban_sql"
|
||||||
|
migrated=$((migrated + ban_count))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── .offenses-Dateien migrieren ─────────────────────────────────────
|
||||||
|
local offense_sql="BEGIN TRANSACTION;"
|
||||||
|
local offense_count=0
|
||||||
|
|
||||||
|
for offense_file in "${STATE_DIR}"/*.offenses; do
|
||||||
|
[[ -f "$offense_file" ]] || continue
|
||||||
|
|
||||||
|
local o_ip o_level o_last_epoch o_last o_first
|
||||||
|
o_ip=$(grep '^CLIENT_IP=' "$offense_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
[[ -z "$o_ip" ]] && continue
|
||||||
|
|
||||||
|
o_level=$(grep '^OFFENSE_LEVEL=' "$offense_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
o_last_epoch=$(grep '^LAST_OFFENSE_EPOCH=' "$offense_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
o_last=$(grep '^LAST_OFFENSE=' "$offense_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
o_first=$(grep '^FIRST_OFFENSE=' "$offense_file" 2>/dev/null | cut -d= -f2 || true)
|
||||||
|
|
||||||
|
o_ip=$(_db_escape "$o_ip")
|
||||||
|
o_last=$(_db_escape "${o_last:-}")
|
||||||
|
o_first=$(_db_escape "${o_first:-}")
|
||||||
|
|
||||||
|
offense_sql+=" INSERT OR IGNORE INTO offense_tracking (client_ip, offense_level, last_offense_epoch, last_offense, first_offense) VALUES ('$o_ip', ${o_level:-0}, ${o_last_epoch:-0}, '$o_last', '$o_first');"
|
||||||
|
offense_count=$((offense_count + 1))
|
||||||
|
done
|
||||||
|
offense_sql+=" COMMIT;"
|
||||||
|
|
||||||
|
if [[ $offense_count -gt 0 ]]; then
|
||||||
|
db_exec "$offense_sql"
|
||||||
|
migrated=$((migrated + offense_count))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── Ban-History-Log migrieren ───────────────────────────────────────
|
||||||
|
local history_count=0
|
||||||
|
if [[ -f "$BAN_HISTORY_FILE" ]]; then
|
||||||
|
local history_sql
|
||||||
|
history_sql=$(awk '
|
||||||
|
/^#/ || /^[[:space:]]*$/ { next }
|
||||||
|
{
|
||||||
|
n = split($0, f, "|")
|
||||||
|
if (n < 2) next
|
||||||
|
ts = f[1]; gsub(/^[[:space:]]+|[[:space:]]+$/, "", ts)
|
||||||
|
if (length(ts) < 19) next
|
||||||
|
ep = mktime(substr(ts,1,4) " " substr(ts,6,2) " " substr(ts,9,2) " " \
|
||||||
|
substr(ts,12,2) " " substr(ts,15,2) " " substr(ts,18,2))
|
||||||
|
if (ep < 0) next
|
||||||
|
for (i = 1; i <= n; i++) gsub(/^[[:space:]]+|[[:space:]]+$/, "", f[i])
|
||||||
|
# Single quotes escapen
|
||||||
|
gsub(/'\''/, "'\'''\''", f[1])
|
||||||
|
gsub(/'\''/, "'\'''\''", f[2])
|
||||||
|
gsub(/'\''/, "'\'''\''", f[3])
|
||||||
|
gsub(/'\''/, "'\'''\''", f[4])
|
||||||
|
gsub(/'\''/, "'\'''\''", f[5])
|
||||||
|
gsub(/'\''/, "'\'''\''", f[6])
|
||||||
|
gsub(/'\''/, "'\'''\''", f[7])
|
||||||
|
gsub(/'\''/, "'\'''\''", f[8])
|
||||||
|
printf "INSERT INTO ban_history (timestamp_epoch, timestamp_text, action, client_ip, domain, count, duration, protocol, reason) VALUES (%d, '\''%s'\'', '\''%s'\'', '\''%s'\'', '\''%s'\'', '\''%s'\'', '\''%s'\'', '\''%s'\'', '\''%s'\'');\n", \
|
||||||
|
ep, f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8]
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
END { print "-- migrated " count+0 " history entries" }
|
||||||
|
' "$BAN_HISTORY_FILE")
|
||||||
|
|
||||||
|
if [[ -n "$history_sql" ]]; then
|
||||||
|
echo "BEGIN TRANSACTION; $history_sql COMMIT;" | sqlite3 "$DB_FILE" 2>/dev/null
|
||||||
|
history_count=$(echo "$history_sql" | grep -c '^INSERT' || true)
|
||||||
|
migrated=$((migrated + history_count))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── Whitelist-Cache migrieren ───────────────────────────────────────
|
||||||
|
local wl_file="${EXTERNAL_WHITELIST_CACHE_DIR:-/var/lib/adguard-shield/external-whitelist}/resolved_ips.txt"
|
||||||
|
local wl_count=0
|
||||||
|
if [[ -f "$wl_file" ]]; then
|
||||||
|
local wl_sql="BEGIN TRANSACTION;"
|
||||||
|
while IFS= read -r ip; do
|
||||||
|
[[ -z "$ip" ]] && continue
|
||||||
|
local safe_ip=$(_db_escape "$ip")
|
||||||
|
wl_sql+=" INSERT OR IGNORE INTO whitelist_cache (ip_address, source) VALUES ('$safe_ip', 'external');"
|
||||||
|
wl_count=$((wl_count + 1))
|
||||||
|
done < "$wl_file"
|
||||||
|
wl_sql+=" COMMIT;"
|
||||||
|
|
||||||
|
if [[ $wl_count -gt 0 ]]; then
|
||||||
|
db_exec "$wl_sql"
|
||||||
|
migrated=$((migrated + wl_count))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── Alte Dateien in Backup verschieben ──────────────────────────────
|
||||||
|
if [[ $migrated -gt 0 ]]; then
|
||||||
|
mkdir -p "$backup_dir"
|
||||||
|
|
||||||
|
for f in "${STATE_DIR}"/*.ban "${STATE_DIR}"/ext_*.ban; do
|
||||||
|
[[ -f "$f" ]] || continue
|
||||||
|
mv "$f" "$backup_dir/" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
for f in "${STATE_DIR}"/*.offenses; do
|
||||||
|
[[ -f "$f" ]] || continue
|
||||||
|
mv "$f" "$backup_dir/" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -f "$BAN_HISTORY_FILE" ]]; then
|
||||||
|
cp "$BAN_HISTORY_FILE" "${backup_dir}/adguard-shield-bans.log.bak" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$wl_file" ]]; then
|
||||||
|
cp "$wl_file" "${backup_dir}/resolved_ips.txt.bak" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Migrations-Marker setzen
|
||||||
|
echo "migrated_at=$(date '+%Y-%m-%d %H:%M:%S')" > "$_DB_MIGRATION_MARKER"
|
||||||
|
echo "bans=$ban_count" >> "$_DB_MIGRATION_MARKER"
|
||||||
|
echo "offenses=$offense_count" >> "$_DB_MIGRATION_MARKER"
|
||||||
|
echo "history=$history_count" >> "$_DB_MIGRATION_MARKER"
|
||||||
|
echo "whitelist=$wl_count" >> "$_DB_MIGRATION_MARKER"
|
||||||
|
|
||||||
|
echo "$migrated"
|
||||||
|
}
|
||||||
@@ -93,48 +93,36 @@ ADGUARD_SHIELD Chain
|
|||||||
- Kann komplett geflusht werden ohne andere Regeln zu beeinflussen
|
- Kann komplett geflusht werden ohne andere Regeln zu beeinflussen
|
||||||
- Einfaches Debugging per `iptables -L ADGUARD_SHIELD`
|
- Einfaches Debugging per `iptables -L ADGUARD_SHIELD`
|
||||||
|
|
||||||
## State-Management
|
## State-Management (SQLite)
|
||||||
|
|
||||||
Jede aktive Sperre wird als Datei gespeichert:
|
Alle Laufzeitdaten werden in einer zentralen SQLite-Datenbank gespeichert:
|
||||||
|
|
||||||
```
|
```
|
||||||
/var/lib/adguard-shield/192.168.1.50.ban
|
/var/lib/adguard-shield/adguard-shield.db
|
||||||
```
|
```
|
||||||
|
|
||||||
Inhalt:
|
Die Datenbank enthält folgende Tabellen:
|
||||||
```
|
|
||||||
CLIENT_IP=192.168.1.50
|
|
||||||
DOMAIN=microsoft.com
|
|
||||||
COUNT=45
|
|
||||||
BAN_TIME=2026-03-03 14:30:00
|
|
||||||
BAN_UNTIL_EPOCH=1741012200
|
|
||||||
BAN_UNTIL=2026-03-03 15:30:00
|
|
||||||
BAN_DURATION=3600
|
|
||||||
OFFENSE_LEVEL=1
|
|
||||||
IS_PERMANENT=false
|
|
||||||
REASON=rate-limit
|
|
||||||
```
|
|
||||||
|
|
||||||
Zusätzlich wird für jede IP ein Offense-Tracker gespeichert:
|
| Tabelle | Beschreibung |
|
||||||
|
|---------|--------------|
|
||||||
|
| `active_bans` | Aktive Sperren (IP, Domain, Sperrdauer, Offense-Level, Grund, Quelle, GeoIP) |
|
||||||
|
| `offense_tracking` | Offense-Zähler für progressive Sperren (Level, letztes/erstes Vergehen) |
|
||||||
|
| `ban_history` | Vollständige Ban-History (alle Sperren und Entsperrungen) |
|
||||||
|
| `whitelist_cache` | Cache der aufgelösten externen Whitelist-IPs |
|
||||||
|
| `schema_version` | Datenbank-Schema-Version für zukünftige Migrationen |
|
||||||
|
|
||||||
```
|
**Vorteile gegenüber Flat-Files:**
|
||||||
/var/lib/adguard-shield/192.168.1.50.offenses
|
- Schnellere Abfragen, besonders bei vielen aktiven Sperren
|
||||||
```
|
- Atomare Transaktionen — kein Datenverlust bei Stromausfall
|
||||||
|
- WAL-Modus für parallelen Lese-/Schreibzugriff
|
||||||
|
- Indexierte Suche nach IP, Zeitstempel, Quelle und Aktion
|
||||||
|
- Kompakte Speicherung statt tausender Einzeldateien
|
||||||
|
|
||||||
Inhalt:
|
Die zentrale Datenbankbibliothek (`db.sh`) wird von allen Scripts per `source db.sh` eingebunden und stellt typisierte Funktionen für alle Tabellen bereit (z.B. `db_ban_insert`, `db_offense_get_level`, `db_history_add`).
|
||||||
```
|
|
||||||
CLIENT_IP=192.168.1.50
|
|
||||||
OFFENSE_LEVEL=2
|
|
||||||
LAST_OFFENSE_EPOCH=1741008600
|
|
||||||
LAST_OFFENSE=2026-03-03 14:30:00
|
|
||||||
FIRST_OFFENSE=2026-03-03 12:15:00
|
|
||||||
```
|
|
||||||
|
|
||||||
Das ermöglicht:
|
### Migration von Flat-Files
|
||||||
- Persistenz über Script-Neustarts hinweg
|
|
||||||
- Statusabfragen jederzeit möglich
|
Beim Update auf die SQLite-Version werden bestehende Flat-File-Daten (`.ban`, `.offenses`, Ban-History-Log, Whitelist-Cache) automatisch in die Datenbank migriert. Die alten Dateien werden als Backup nach `/var/lib/adguard-shield/.backup_pre_sqlite/` verschoben. Die Migration läuft einmalig beim Update und zeigt den Fortschritt im Terminal an.
|
||||||
- Automatisches Aufräumen per Cron-Job
|
|
||||||
- Progressive Sperrzeiten über mehrere Ban-Zyklen hinweg
|
|
||||||
|
|
||||||
## Dateistruktur nach Installation
|
## Dateistruktur nach Installation
|
||||||
|
|
||||||
@@ -149,6 +137,7 @@ Das ermöglicht:
|
|||||||
├── external-whitelist-worker.sh # Externer Whitelist-Worker (DNS-Auflösung)
|
├── external-whitelist-worker.sh # Externer Whitelist-Worker (DNS-Auflösung)
|
||||||
├── geoip-worker.sh # GeoIP-Länderfilter-Worker
|
├── geoip-worker.sh # GeoIP-Länderfilter-Worker
|
||||||
├── offense-cleanup-worker.sh # Aufräumen abgelaufener Offense-Zähler (nice 19, idle I/O)
|
├── offense-cleanup-worker.sh # Aufräumen abgelaufener Offense-Zähler (nice 19, idle I/O)
|
||||||
|
├── db.sh # SQLite Datenbank-Bibliothek (wird von allen Scripts eingebunden)
|
||||||
├── unban-expired.sh # Cron-basiertes Entsperren
|
├── unban-expired.sh # Cron-basiertes Entsperren
|
||||||
└── geoip/ # Auto-Download MaxMind GeoLite2 DB (optional)
|
└── geoip/ # Auto-Download MaxMind GeoLite2 DB (optional)
|
||||||
|
|
||||||
@@ -158,15 +147,16 @@ Das ermöglicht:
|
|||||||
└── adguard-shield-watchdog.timer # systemd Timer (alle 5 Min.)
|
└── adguard-shield-watchdog.timer # systemd Timer (alle 5 Min.)
|
||||||
|
|
||||||
/var/lib/adguard-shield/
|
/var/lib/adguard-shield/
|
||||||
├── *.ban # State-Dateien aktiver Sperren
|
├── adguard-shield.db # SQLite-Datenbank (Bans, Offenses, History, Whitelist-Cache)
|
||||||
├── *.offenses # Offense-Zähler (Progressive Sperren)
|
├── .migration_v1_complete # Marker: Flat-File-Migration abgeschlossen
|
||||||
|
├── .backup_pre_sqlite/ # Backup der alten Flat-Files nach Migration
|
||||||
├── external-blocklist/ # Cache für externe Blocklisten
|
├── external-blocklist/ # Cache für externe Blocklisten
|
||||||
├── external-whitelist/ # Cache für externe Whitelisten + aufgelöste IPs
|
├── external-whitelist/ # Cache für externe Whitelisten
|
||||||
└── geoip-cache/ # Cache für GeoIP-Lookups (24h)
|
└── geoip-cache/ # Cache für GeoIP-Lookups (24h)
|
||||||
|
|
||||||
/var/log/
|
/var/log/
|
||||||
├── adguard-shield.log # Laufzeit-Log
|
├── adguard-shield.log # Laufzeit-Log
|
||||||
└── adguard-shield-bans.log # Ban-History (alle Sperren/Entsperrungen)
|
└── adguard-shield-bans.log # Ban-History (Legacy, wird nach Migration nicht mehr geschrieben)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installer-Architektur
|
## Installer-Architektur
|
||||||
@@ -176,7 +166,7 @@ Der Installer (`install.sh`) bietet ein interaktives Menü und folgende Funktion
|
|||||||
| Befehl | Beschreibung |
|
| Befehl | Beschreibung |
|
||||||
|--------|--------------|
|
|--------|--------------|
|
||||||
| `install` | Vollständige Neuinstallation (Abhängigkeiten, Dateien, Konfiguration, Service, Watchdog) |
|
| `install` | Vollständige Neuinstallation (Abhängigkeiten, Dateien, Konfiguration, Service, Watchdog) |
|
||||||
| `update` | Update mit automatischer Konfigurations-Migration, Watchdog-Aktivierung und Service-Neustart |
|
| `update` | Update mit automatischer Konfigurations-Migration, Datenbank-Migration, Watchdog-Aktivierung und Service-Neustart |
|
||||||
| `uninstall` | Deinstallation mit optionalem Behalten der Konfiguration |
|
| `uninstall` | Deinstallation mit optionalem Behalten der Konfiguration |
|
||||||
| `status` | Installationsstatus, Version und Service-Status anzeigen |
|
| `status` | Installationsstatus, Version und Service-Status anzeigen |
|
||||||
| `--help` | Hilfe und Befehlsübersicht |
|
| `--help` | Hilfe und Befehlsübersicht |
|
||||||
@@ -206,15 +196,20 @@ Der Installer (`install.sh`) bietet ein interaktives Menü und folgende Funktion
|
|||||||
|
|
||||||
## Ban-History
|
## Ban-History
|
||||||
|
|
||||||
Jede Sperre und Entsperrung wird dauerhaft in der Ban-History protokolliert (`/var/log/adguard-shield-bans.log`). Das ermöglicht eine lückenlose Nachvollziehbarkeit, auch nachdem State-Dateien bereits gelöscht wurden.
|
Jede Sperre und Entsperrung wird dauerhaft in der SQLite-Datenbank protokolliert (Tabelle `ban_history`). Das ermöglicht eine lückenlose Nachvollziehbarkeit mit indexierter Suche nach IP, Zeitstempel und Aktion.
|
||||||
|
|
||||||
**Format:**
|
**Gespeicherte Felder pro Eintrag:**
|
||||||
```
|
| Feld | Beschreibung |
|
||||||
ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | GRUND
|
|------|--------------|
|
||||||
2026-03-03 14:30:12 | BAN | 192.168.1.50 | microsoft.com | 45 | 3600s | rate-limit
|
| `timestamp_epoch` | Unix-Zeitstempel |
|
||||||
2026-03-03 15:30:12 | UNBAN | 192.168.1.50 | microsoft.com | - | - | expired
|
| `timestamp_text` | Lesbarer Zeitstempel |
|
||||||
2026-03-03 16:10:33 | UNBAN | 10.0.0.25 | telemetry.example.com | - | - | manual
|
| `action` | `BAN` oder `UNBAN` |
|
||||||
```
|
| `client_ip` | Betroffene IP-Adresse |
|
||||||
|
| `domain` | Angefragte Domain |
|
||||||
|
| `count` | Anzahl der Anfragen |
|
||||||
|
| `duration` | Sperrdauer |
|
||||||
|
| `protocol` | Verwendetes DNS-Protokoll |
|
||||||
|
| `reason` | Sperrgrund |
|
||||||
|
|
||||||
**Mögliche Gründe (GRUND-Spalte):**
|
**Mögliche Gründe (GRUND-Spalte):**
|
||||||
| Grund | Bedeutung |
|
| Grund | Bedeutung |
|
||||||
|
|||||||
@@ -42,9 +42,10 @@ Beim Update passiert automatisch:
|
|||||||
2. Die bestehende Konfiguration wird als `adguard-shield.conf.old` gesichert
|
2. Die bestehende Konfiguration wird als `adguard-shield.conf.old` gesichert
|
||||||
3. Neue Konfigurationsparameter werden automatisch zur bestehenden Konfig hinzugefügt
|
3. Neue Konfigurationsparameter werden automatisch zur bestehenden Konfig hinzugefügt
|
||||||
4. Bestehende Einstellungen bleiben **immer** erhalten
|
4. Bestehende Einstellungen bleiben **immer** erhalten
|
||||||
5. Der systemd Service und Watchdog-Timer werden per `daemon-reload` neu geladen
|
5. Bestehende Flat-File-Daten werden einmalig (mit einem Update kommend von einer v0.9.0 oder älter) in die SQLite-Datenbank migriert (mit Fortschrittsanzeige und Backup)
|
||||||
6. Der Watchdog-Timer wird automatisch aktiviert (falls noch nicht aktiv)
|
6. Der systemd Service und Watchdog-Timer werden per `daemon-reload` neu geladen
|
||||||
7. Der Service wird automatisch neu gestartet (falls er lief)
|
7. Der Watchdog-Timer wird automatisch aktiviert (falls noch nicht aktiv)
|
||||||
|
8. Der Service wird automatisch neu gestartet (falls er lief)
|
||||||
|
|
||||||
### API-Verbindungstest nach Installation
|
### API-Verbindungstest nach Installation
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ Folgende Pakete werden bei der Installation automatisch installiert (via `apt`):
|
|||||||
- `iptables` — Firewall-Regeln für IP-Sperren
|
- `iptables` — Firewall-Regeln für IP-Sperren
|
||||||
- `gawk` — Textverarbeitung
|
- `gawk` — Textverarbeitung
|
||||||
- `systemd` — Service-Management
|
- `systemd` — Service-Management
|
||||||
|
- `sqlite3` — Datenbank für State-Management, Ban-History und Offense-Tracking
|
||||||
|
|
||||||
## systemd Service
|
## systemd Service
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ Wiederholungstäter werden wie bei fail2ban stufenweise länger gesperrt. Wird e
|
|||||||
| `LOG_FILE` | `/var/log/adguard-shield.log` | Pfad zur Log-Datei |
|
| `LOG_FILE` | `/var/log/adguard-shield.log` | Pfad zur Log-Datei |
|
||||||
| `LOG_LEVEL` | `INFO` | Log-Level: `DEBUG`, `INFO`, `WARN`, `ERROR` |
|
| `LOG_LEVEL` | `INFO` | Log-Level: `DEBUG`, `INFO`, `WARN`, `ERROR` |
|
||||||
| `LOG_MAX_SIZE_MB` | `50` | Max. Log-Größe bevor rotiert wird |
|
| `LOG_MAX_SIZE_MB` | `50` | Max. Log-Größe bevor rotiert wird |
|
||||||
| `BAN_HISTORY_FILE` | `/var/log/adguard-shield-bans.log` | Datei für die Ban-History (alle Sperren/Entsperrungen) |
|
| `BAN_HISTORY_FILE` | `/var/log/adguard-shield-bans.log` | Legacy: Pfad zur alten Ban-History-Datei (wird bei der SQLite-Migration als Quelle verwendet). Neue Einträge werden direkt in die SQLite-Datenbank geschrieben. |
|
||||||
| `BAN_HISTORY_RETENTION_DAYS` | `0` | Aufbewahrungsdauer der Ban-History in Tagen. `0` = unbegrenzt (niemals löschen). Alte Einträge werden beim nächsten Report automatisch entfernt. |
|
| `BAN_HISTORY_RETENTION_DAYS` | `0` | Aufbewahrungsdauer der Ban-History in Tagen. `0` = unbegrenzt (niemals löschen). Alte Einträge werden beim nächsten Report automatisch entfernt. |
|
||||||
|
|
||||||
### Benachrichtigungen
|
### Benachrichtigungen
|
||||||
@@ -175,7 +175,7 @@ Regelmäßige Statistik-Reports per E-Mail. Voraussetzung ist ein funktionierend
|
|||||||
|
|
||||||
| Parameter | Standard | Beschreibung |
|
| Parameter | Standard | Beschreibung |
|
||||||
|-----------|----------|--------------|
|
|-----------|----------|--------------|
|
||||||
| `STATE_DIR` | `/var/lib/adguard-shield` | Verzeichnis für State-Dateien |
|
| `STATE_DIR` | `/var/lib/adguard-shield` | Verzeichnis für die SQLite-Datenbank (`adguard-shield.db`) und Caches |
|
||||||
| `PID_FILE` | `/var/run/adguard-shield.pid` | PID-Datei |
|
| `PID_FILE` | `/var/run/adguard-shield.pid` | PID-Datei |
|
||||||
| `DRY_RUN` | `false` | Testmodus — nur loggen, nicht sperren |
|
| `DRY_RUN` | `false` | Testmodus — nur loggen, nicht sperren |
|
||||||
|
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ Die Test-Mail enthält eine Übersicht der aktuellen Konfiguration und bestätig
|
|||||||
|
|
||||||
### Report enthält keine Daten
|
### Report enthält keine Daten
|
||||||
|
|
||||||
Der Report basiert auf der Ban-History-Datei (`/var/log/adguard-shield-bans.log`). Wenn keine Sperren im Berichtszeitraum vorhanden sind, zeigt der Report „Keine Daten" an.
|
Der Report basiert auf der Ban-History in der SQLite-Datenbank (`/var/lib/adguard-shield/adguard-shield.db`). Wenn keine Sperren im Berichtszeitraum vorhanden sind, zeigt der Report „Keine Daten" an.
|
||||||
|
|
||||||
### Cron-Job wird nicht ausgeführt
|
### Cron-Job wird nicht ausgeführt
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ Wenn eine IP die maximale Stufe der progressiven Sperren erreicht hat, wird sie
|
|||||||
|
|
||||||
### Sperren überleben Reboot nicht
|
### Sperren überleben Reboot nicht
|
||||||
|
|
||||||
Das ist normal — iptables-Regeln sind flüchtig. Der **Service** erstellt die Chain beim Start automatisch neu. Aktive Sperren aus dem State-Verzeichnis werden aber nicht automatisch wiederhergestellt.
|
Das ist normal — iptables-Regeln sind flüchtig. Der **Service** erstellt die Chain beim Start automatisch neu. Aktive Sperren aus der SQLite-Datenbank werden aber nicht automatisch als iptables-Regeln wiederhergestellt.
|
||||||
|
|
||||||
**Optionen:**
|
**Optionen:**
|
||||||
- `iptables-persistent` installieren (`apt install iptables-persistent`)
|
- `iptables-persistent` installieren (`apt install iptables-persistent`)
|
||||||
@@ -240,6 +240,7 @@ sudo bash install.sh update
|
|||||||
- Konfiguration wird als `adguard-shield.conf.old` gesichert
|
- Konfiguration wird als `adguard-shield.conf.old` gesichert
|
||||||
- Neue Konfigurationsparameter werden automatisch zur bestehenden Konfig ergänzt
|
- Neue Konfigurationsparameter werden automatisch zur bestehenden Konfig ergänzt
|
||||||
- Bestehende Einstellungen bleiben erhalten
|
- Bestehende Einstellungen bleiben erhalten
|
||||||
|
- Bestehende Flat-File-Daten werden einmalig in die SQLite-Datenbank migriert (mit Fortschrittsanzeige)
|
||||||
- Service wird per `daemon-reload` neu geladen und automatisch neu gestartet
|
- Service wird per `daemon-reload` neu geladen und automatisch neu gestartet
|
||||||
|
|
||||||
## Deinstallation
|
## Deinstallation
|
||||||
@@ -281,5 +282,6 @@ Folgende Pakete werden für den Betrieb benötigt und bei der Installation autom
|
|||||||
| `iptables` | Firewall-Regeln (IPv4 + IPv6) |
|
| `iptables` | Firewall-Regeln (IPv4 + IPv6) |
|
||||||
| `gawk` | Textverarbeitung in Scripts |
|
| `gawk` | Textverarbeitung in Scripts |
|
||||||
| `systemd` | Service-Management und Autostart |
|
| `systemd` | Service-Management und Autostart |
|
||||||
|
| `sqlite3` | Datenbank für State-Management, Ban-History und Offense-Tracking |
|
||||||
|
|
||||||
Diese werden bei `sudo bash install.sh install` automatisch geprüft und bei Bedarf über den Paketmanager (`apt`, `dnf`, `yum`, `pacman`) nachinstalliert.
|
Diese werden bei `sudo bash install.sh install` automatisch geprüft und bei Bedarf über den Paketmanager (`apt`, `dnf`, `yum`, `pacman`) nachinstalliert.
|
||||||
|
|||||||
@@ -31,13 +31,14 @@ sudo bash install.sh update
|
|||||||
|
|
||||||
Das Update-Script macht automatisch folgendes:
|
Das Update-Script macht automatisch folgendes:
|
||||||
|
|
||||||
1. **Abhängigkeiten prüfen** — Fehlende Pakete werden nachinstalliert
|
1. **Abhängigkeiten prüfen** — Fehlende Pakete (inkl. `sqlite3`) werden nachinstalliert
|
||||||
2. **Scripts aktualisieren** — Alle `.sh`-Dateien werden nach `/opt/adguard-shield/` kopiert
|
2. **Scripts aktualisieren** — Alle `.sh`-Dateien werden nach `/opt/adguard-shield/` kopiert
|
||||||
3. **Konfigurations-Migration** — Neue Parameter werden automatisch zur bestehenden Konfiguration hinzugefügt, bestehende Einstellungen bleiben **unverändert**
|
3. **Konfigurations-Migration** — Neue Parameter werden automatisch zur bestehenden Konfiguration hinzugefügt, bestehende Einstellungen bleiben **unverändert**
|
||||||
4. **Backup erstellen** — Die alte Konfiguration wird als `adguard-shield.conf.old` gesichert
|
4. **Backup erstellen** — Die alte Konfiguration wird als `adguard-shield.conf.old` gesichert
|
||||||
5. **Service aktualisieren** — Die systemd Service-Datei und Watchdog-Dateien werden aktualisiert und `daemon-reload` ausgeführt
|
5. **Datenbank-Migration (in der v1.0.0)** — Bestehende Flat-File-Daten (`.ban`, `.offenses`, Ban-History-Log) werden einmalig in die SQLite-Datenbank migriert. Die alten Dateien werden als Backup gesichert. Der Fortschritt und das Ergebnis werden im Terminal angezeigt.
|
||||||
6. **Watchdog aktivieren** — Der Watchdog-Timer wird automatisch aktiviert (falls noch nicht aktiv)
|
6. **Service aktualisieren** — Die systemd Service-Datei und Watchdog-Dateien werden aktualisiert und `daemon-reload` ausgeführt
|
||||||
7. **Service neustarten** — Der Service wird automatisch neu gestartet (falls er vorher lief)
|
7. **Watchdog aktivieren** — Der Watchdog-Timer wird automatisch aktiviert (falls noch nicht aktiv)
|
||||||
|
8. **Service neustarten** — Der Service wird automatisch neu gestartet (falls er vorher lief)
|
||||||
|
|
||||||
### 3. Neue Parameter prüfen (optional)
|
### 3. Neue Parameter prüfen (optional)
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ fi
|
|||||||
# shellcheck source=adguard-shield.conf
|
# shellcheck source=adguard-shield.conf
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
|
# shellcheck source=db.sh
|
||||||
|
source "${SCRIPT_DIR}/db.sh"
|
||||||
|
|
||||||
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
||||||
WORKER_PID_FILE="/var/run/adguard-blocklist-worker.pid"
|
WORKER_PID_FILE="/var/run/adguard-blocklist-worker.pid"
|
||||||
|
|
||||||
@@ -48,21 +51,11 @@ log_ban_history() {
|
|||||||
local action="$1"
|
local action="$1"
|
||||||
local client_ip="$2"
|
local client_ip="$2"
|
||||||
local reason="${3:-external-blocklist}"
|
local reason="${3:-external-blocklist}"
|
||||||
local timestamp
|
|
||||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
||||||
|
|
||||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
|
||||||
echo "# AdGuard Shield - Ban History" > "$BAN_HISTORY_FILE"
|
|
||||||
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | PROTOKOLL | GRUND" >> "$BAN_HISTORY_FILE"
|
|
||||||
echo "#──────────────────────────────────────────────────────────────────────────────────────────────────" >> "$BAN_HISTORY_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local duration="permanent"
|
local duration="permanent"
|
||||||
[[ "$EXTERNAL_BLOCKLIST_BAN_DURATION" -gt 0 ]] && duration="${EXTERNAL_BLOCKLIST_BAN_DURATION}s"
|
[[ "$EXTERNAL_BLOCKLIST_BAN_DURATION" -gt 0 ]] && duration="${EXTERNAL_BLOCKLIST_BAN_DURATION}s"
|
||||||
|
|
||||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
|
db_history_add "$action" "$client_ip" "-" "-" "$reason" "$duration" "-"
|
||||||
"$timestamp" "$action" "$client_ip" "-" "-" "$duration" "-" "$reason" \
|
|
||||||
>> "$BAN_HISTORY_FILE"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
||||||
@@ -70,6 +63,7 @@ init_directories() {
|
|||||||
mkdir -p "$EXTERNAL_BLOCKLIST_CACHE_DIR"
|
mkdir -p "$EXTERNAL_BLOCKLIST_CACHE_DIR"
|
||||||
mkdir -p "$STATE_DIR"
|
mkdir -p "$STATE_DIR"
|
||||||
mkdir -p "$(dirname "$LOG_FILE")"
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
db_init
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Whitelist Prüfung ───────────────────────────────────────────────────────
|
# ─── Whitelist Prüfung ───────────────────────────────────────────────────────
|
||||||
@@ -77,15 +71,13 @@ is_whitelisted() {
|
|||||||
local ip="$1"
|
local ip="$1"
|
||||||
IFS=',' read -ra wl_entries <<< "$WHITELIST"
|
IFS=',' read -ra wl_entries <<< "$WHITELIST"
|
||||||
for entry in "${wl_entries[@]}"; do
|
for entry in "${wl_entries[@]}"; do
|
||||||
entry=$(echo "$entry" | xargs) # trim
|
entry=$(echo "$entry" | xargs)
|
||||||
if [[ "$ip" == "$entry" ]]; then
|
if [[ "$ip" == "$entry" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Externe Whitelist prüfen (aufgelöste IPs aus dem Whitelist-Worker)
|
if db_whitelist_contains "$ip"; then
|
||||||
local ext_wl_file="${EXTERNAL_WHITELIST_CACHE_DIR:-/var/lib/adguard-shield/external-whitelist}/resolved_ips.txt"
|
|
||||||
if [[ -f "$ext_wl_file" ]] && grep -qxF "$ip" "$ext_wl_file" 2>/dev/null; then
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -118,11 +110,10 @@ setup_iptables_chain() {
|
|||||||
# ─── IP sperren ──────────────────────────────────────────────────────────────
|
# ─── IP sperren ──────────────────────────────────────────────────────────────
|
||||||
ban_ip() {
|
ban_ip() {
|
||||||
local ip="$1"
|
local ip="$1"
|
||||||
local state_file="${STATE_DIR}/ext_${ip//[:\/]/_}.ban"
|
|
||||||
|
|
||||||
# Bereits gesperrt?
|
# Bereits gesperrt?
|
||||||
if [[ -f "$state_file" ]]; then
|
if db_ban_exists "$ip"; then
|
||||||
# iptables-Regel prüfen und ggf. nachziehen (z.B. nach Neustart verloren gegangen)
|
# iptables-Regel pruefen und ggf. nachziehen
|
||||||
if [[ "$ip" == *:* ]]; then
|
if [[ "$ip" == *:* ]]; then
|
||||||
if ! ip6tables -C "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null; then
|
if ! ip6tables -C "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null; then
|
||||||
ip6tables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
ip6tables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
||||||
@@ -132,14 +123,7 @@ ban_ip() {
|
|||||||
iptables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
iptables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
log "DEBUG" "IP $ip bereits über externe Blocklist gesperrt"
|
log "DEBUG" "IP $ip bereits gesperrt"
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Nicht auch vom Hauptscript gesperrt? (State-Datei ohne ext_ Prefix)
|
|
||||||
local main_state_file="${STATE_DIR}/${ip//[:\/]/_}.ban"
|
|
||||||
if [[ -f "$main_state_file" ]]; then
|
|
||||||
log "DEBUG" "IP $ip bereits vom Rate-Limiter gesperrt - überspringe"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -151,36 +135,24 @@ ban_ip() {
|
|||||||
|
|
||||||
log "WARN" "SPERRE IP (externe Blocklist): $ip"
|
log "WARN" "SPERRE IP (externe Blocklist): $ip"
|
||||||
|
|
||||||
# iptables-Regel setzen
|
|
||||||
if [[ "$ip" == *:* ]]; then
|
if [[ "$ip" == *:* ]]; then
|
||||||
ip6tables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
ip6tables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
||||||
else
|
else
|
||||||
iptables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
iptables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# State speichern
|
|
||||||
local ban_until_epoch="0"
|
local ban_until_epoch="0"
|
||||||
local ban_until_display="permanent"
|
local is_permanent=1
|
||||||
if [[ "$EXTERNAL_BLOCKLIST_BAN_DURATION" -gt 0 ]]; then
|
if [[ "$EXTERNAL_BLOCKLIST_BAN_DURATION" -gt 0 ]]; then
|
||||||
ban_until_epoch=$(date -d "+${EXTERNAL_BLOCKLIST_BAN_DURATION} seconds" '+%s' 2>/dev/null \
|
ban_until_epoch=$(date -d "+${EXTERNAL_BLOCKLIST_BAN_DURATION} seconds" '+%s' 2>/dev/null \
|
||||||
|| date -v "+${EXTERNAL_BLOCKLIST_BAN_DURATION}S" '+%s')
|
|| date -v "+${EXTERNAL_BLOCKLIST_BAN_DURATION}S" '+%s')
|
||||||
ban_until_display=$(date -d "@$ban_until_epoch" '+%Y-%m-%d %H:%M:%S' 2>/dev/null \
|
is_permanent=0
|
||||||
|| date -r "$ban_until_epoch" '+%Y-%m-%d %H:%M:%S')
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat > "$state_file" << EOF
|
db_ban_insert "$ip" "-" "0" "$(date '+%Y-%m-%d %H:%M:%S')" "$ban_until_epoch" "${EXTERNAL_BLOCKLIST_BAN_DURATION:-0}" "0" "$is_permanent" "external-blocklist" "-" "external-blocklist"
|
||||||
CLIENT_IP=$ip
|
|
||||||
DOMAIN=-
|
|
||||||
COUNT=-
|
|
||||||
BAN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
BAN_UNTIL_EPOCH=$ban_until_epoch
|
|
||||||
BAN_UNTIL=$ban_until_display
|
|
||||||
SOURCE=external-blocklist
|
|
||||||
EOF
|
|
||||||
|
|
||||||
log_ban_history "BAN" "$ip" "external-blocklist"
|
log_ban_history "BAN" "$ip" "external-blocklist"
|
||||||
|
|
||||||
# Benachrichtigung senden (nur wenn EXTERNAL_BLOCKLIST_NOTIFY=true)
|
|
||||||
if [[ "$NOTIFY_ENABLED" == "true" && "${EXTERNAL_BLOCKLIST_NOTIFY:-false}" == "true" ]]; then
|
if [[ "$NOTIFY_ENABLED" == "true" && "${EXTERNAL_BLOCKLIST_NOTIFY:-false}" == "true" ]]; then
|
||||||
send_notification "ban" "$ip"
|
send_notification "ban" "$ip"
|
||||||
fi
|
fi
|
||||||
@@ -190,9 +162,8 @@ EOF
|
|||||||
unban_ip() {
|
unban_ip() {
|
||||||
local ip="$1"
|
local ip="$1"
|
||||||
local reason="${2:-external-blocklist-removed}"
|
local reason="${2:-external-blocklist-removed}"
|
||||||
local state_file="${STATE_DIR}/ext_${ip//[:\/]/_}.ban"
|
|
||||||
|
|
||||||
[[ -f "$state_file" ]] || return 0
|
db_ban_exists "$ip" || return 0
|
||||||
|
|
||||||
log "INFO" "ENTSPERRE IP (externe Blocklist entfernt): $ip"
|
log "INFO" "ENTSPERRE IP (externe Blocklist entfernt): $ip"
|
||||||
|
|
||||||
@@ -202,7 +173,7 @@ unban_ip() {
|
|||||||
iptables -D "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
iptables -D "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$state_file"
|
db_ban_delete "$ip"
|
||||||
log_ban_history "UNBAN" "$ip" "$reason"
|
log_ban_history "UNBAN" "$ip" "$reason"
|
||||||
|
|
||||||
if [[ "$NOTIFY_ENABLED" == "true" && "${EXTERNAL_BLOCKLIST_NOTIFY:-false}" == "true" ]]; then
|
if [[ "$NOTIFY_ENABLED" == "true" && "${EXTERNAL_BLOCKLIST_NOTIFY:-false}" == "true" ]]; then
|
||||||
@@ -542,31 +513,21 @@ parse_blocklist_ips() {
|
|||||||
|
|
||||||
# ─── Aktuelle externe Sperren ermitteln ──────────────────────────────────────
|
# ─── Aktuelle externe Sperren ermitteln ──────────────────────────────────────
|
||||||
get_currently_banned_external_ips() {
|
get_currently_banned_external_ips() {
|
||||||
for state_file in "${STATE_DIR}"/ext_*.ban; do
|
db_ban_get_by_source "external-blocklist"
|
||||||
[[ -f "$state_file" ]] || continue
|
|
||||||
grep '^CLIENT_IP=' "$state_file" | cut -d= -f2
|
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Abgelaufene externe Sperren prüfen ─────────────────────────────────────
|
# ─── Abgelaufene externe Sperren prüfen ─────────────────────────────────────
|
||||||
check_expired_external_bans() {
|
check_expired_external_bans() {
|
||||||
[[ "$EXTERNAL_BLOCKLIST_BAN_DURATION" -gt 0 ]] || return
|
[[ "$EXTERNAL_BLOCKLIST_BAN_DURATION" -gt 0 ]] || return
|
||||||
|
|
||||||
local now
|
local expired_ips
|
||||||
now=$(date '+%s')
|
expired_ips=$(db_ban_get_expired_by_source "external-blocklist")
|
||||||
|
[[ -z "$expired_ips" ]] && return
|
||||||
|
|
||||||
for state_file in "${STATE_DIR}"/ext_*.ban; do
|
while IFS= read -r client_ip; do
|
||||||
[[ -f "$state_file" ]] || continue
|
[[ -z "$client_ip" ]] && continue
|
||||||
|
|
||||||
local ban_until_epoch
|
|
||||||
ban_until_epoch=$(grep '^BAN_UNTIL_EPOCH=' "$state_file" | cut -d= -f2)
|
|
||||||
local client_ip
|
|
||||||
client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2)
|
|
||||||
|
|
||||||
if [[ -n "$ban_until_epoch" && "$ban_until_epoch" -gt 0 && "$now" -ge "$ban_until_epoch" ]]; then
|
|
||||||
unban_ip "$client_ip" "external-blocklist-expired"
|
unban_ip "$client_ip" "external-blocklist-expired"
|
||||||
fi
|
done <<< "$expired_ips"
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Blocklisten synchronisieren ─────────────────────────────────────────────
|
# ─── Blocklisten synchronisieren ─────────────────────────────────────────────
|
||||||
@@ -615,9 +576,8 @@ sync_blocklists() {
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local _state_file_before="${STATE_DIR}/ext_${ip//[:/]/_}.ban"
|
|
||||||
local _was_new=false
|
local _was_new=false
|
||||||
[[ ! -f "$_state_file_before" ]] && _was_new=true
|
db_ban_exists "$ip" || _was_new=true
|
||||||
|
|
||||||
ban_ip "$ip"
|
ban_ip "$ip"
|
||||||
[[ "$_was_new" == "true" ]] && new_bans=$((new_bans + 1))
|
[[ "$_was_new" == "true" ]] && new_bans=$((new_bans + 1))
|
||||||
@@ -729,12 +689,9 @@ show_status() {
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Aktive externe Sperren
|
# Aktive externe Sperren
|
||||||
local ext_ban_count=0
|
local ext_ban_count
|
||||||
for state_file in "${STATE_DIR}"/ext_*.ban; do
|
ext_ban_count=$(db_ban_count_by_source "external-blocklist")
|
||||||
[[ -f "$state_file" ]] || continue
|
echo " Aktive Sperren (externe Blocklist): ${ext_ban_count:-0}"
|
||||||
ext_ban_count=$((ext_ban_count + 1))
|
|
||||||
done
|
|
||||||
echo " Aktive Sperren (externe Blocklist): $ext_ban_count"
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " Prüfintervall: ${EXTERNAL_BLOCKLIST_INTERVAL}s"
|
echo " Prüfintervall: ${EXTERNAL_BLOCKLIST_INTERVAL}s"
|
||||||
@@ -817,11 +774,14 @@ case "${1:-start}" in
|
|||||||
flush)
|
flush)
|
||||||
init_directories
|
init_directories
|
||||||
echo "Entferne alle externen Blocklist-Sperren..."
|
echo "Entferne alle externen Blocklist-Sperren..."
|
||||||
for state_file in "${STATE_DIR}"/ext_*.ban; do
|
local flush_ips
|
||||||
[[ -f "$state_file" ]] || continue
|
flush_ips=$(db_ban_get_by_source "external-blocklist")
|
||||||
_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2)
|
if [[ -n "$flush_ips" ]]; then
|
||||||
|
while IFS= read -r _ip; do
|
||||||
|
[[ -z "$_ip" ]] && continue
|
||||||
unban_ip "$_ip" "manual-flush"
|
unban_ip "$_ip" "manual-flush"
|
||||||
done
|
done <<< "$flush_ips"
|
||||||
|
fi
|
||||||
echo "Alle externen Blocklist-Sperren aufgehoben"
|
echo "Alle externen Blocklist-Sperren aufgehoben"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|||||||
@@ -25,9 +25,11 @@ fi
|
|||||||
# shellcheck source=adguard-shield.conf
|
# shellcheck source=adguard-shield.conf
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
|
# shellcheck source=db.sh
|
||||||
|
source "${SCRIPT_DIR}/db.sh"
|
||||||
|
|
||||||
# ─── Standardwerte ────────────────────────────────────────────────────────────
|
# ─── Standardwerte ────────────────────────────────────────────────────────────
|
||||||
EXTERNAL_WHITELIST_CACHE_DIR="${EXTERNAL_WHITELIST_CACHE_DIR:-/var/lib/adguard-shield/external-whitelist}"
|
EXTERNAL_WHITELIST_CACHE_DIR="${EXTERNAL_WHITELIST_CACHE_DIR:-/var/lib/adguard-shield/external-whitelist}"
|
||||||
EXTERNAL_WHITELIST_RESOLVED_FILE="${EXTERNAL_WHITELIST_CACHE_DIR}/resolved_ips.txt"
|
|
||||||
|
|
||||||
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
||||||
WORKER_PID_FILE="/var/run/adguard-whitelist-worker.pid"
|
WORKER_PID_FILE="/var/run/adguard-whitelist-worker.pid"
|
||||||
@@ -53,6 +55,7 @@ log() {
|
|||||||
init_directories() {
|
init_directories() {
|
||||||
mkdir -p "$EXTERNAL_WHITELIST_CACHE_DIR"
|
mkdir -p "$EXTERNAL_WHITELIST_CACHE_DIR"
|
||||||
mkdir -p "$(dirname "$LOG_FILE")"
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
db_init
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Eintrag-Validierung ─────────────────────────────────────────────────────
|
# ─── Eintrag-Validierung ─────────────────────────────────────────────────────
|
||||||
@@ -271,7 +274,7 @@ sync_whitelists() {
|
|||||||
index=$((index + 1))
|
index=$((index + 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
# Alle Einträge aus Cache-Dateien parsen und IPs auflösen
|
# Alle Eintraege aus Cache-Dateien parsen und IPs aufloesen
|
||||||
local all_ips_file="${EXTERNAL_WHITELIST_CACHE_DIR}/.all_ips.tmp"
|
local all_ips_file="${EXTERNAL_WHITELIST_CACHE_DIR}/.all_ips.tmp"
|
||||||
> "$all_ips_file"
|
> "$all_ips_file"
|
||||||
|
|
||||||
@@ -280,56 +283,42 @@ sync_whitelists() {
|
|||||||
parse_whitelist_entries "$cache_file" >> "$all_ips_file"
|
parse_whitelist_entries "$cache_file" >> "$all_ips_file"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Duplikate entfernen und in die resolved-Datei schreiben
|
# Duplikate entfernen und in SQLite-Whitelist schreiben (atomar)
|
||||||
|
local unique_file="${EXTERNAL_WHITELIST_CACHE_DIR}/.all_ips_unique.tmp"
|
||||||
|
sort -u "$all_ips_file" > "$unique_file"
|
||||||
local unique_count
|
local unique_count
|
||||||
sort -u "$all_ips_file" > "${EXTERNAL_WHITELIST_RESOLVED_FILE}.tmp"
|
unique_count=$(wc -l < "$unique_file" | xargs)
|
||||||
mv "${EXTERNAL_WHITELIST_RESOLVED_FILE}.tmp" "$EXTERNAL_WHITELIST_RESOLVED_FILE"
|
|
||||||
unique_count=$(wc -l < "$EXTERNAL_WHITELIST_RESOLVED_FILE" | xargs)
|
|
||||||
|
|
||||||
rm -f "$all_ips_file"
|
db_whitelist_sync "external" < "$unique_file"
|
||||||
|
|
||||||
|
rm -f "$all_ips_file" "$unique_file"
|
||||||
|
|
||||||
log "DEBUG" "Externe Whitelist: $unique_count eindeutige IPs aufgelöst"
|
log "DEBUG" "Externe Whitelist: $unique_count eindeutige IPs aufgelöst"
|
||||||
|
|
||||||
# Prüfe ob gesperrte IPs jetzt auf der Whitelist stehen und entsperrt werden müssen
|
# Pruefen ob gesperrte IPs jetzt auf der Whitelist stehen
|
||||||
check_banned_whitelist_ips
|
check_banned_whitelist_ips
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Gesperrte IPs prüfen die jetzt gewhitelistet sind ──────────────────────
|
# ─── Gesperrte IPs prüfen die jetzt gewhitelistet sind ──────────────────────
|
||||||
# Wenn eine IP nach einer Whitelist-Aktualisierung nun auf der externen
|
|
||||||
# Whitelist steht, wird sie automatisch entsperrt.
|
|
||||||
check_banned_whitelist_ips() {
|
check_banned_whitelist_ips() {
|
||||||
local state_dir="${STATE_DIR:-/var/lib/adguard-shield}"
|
# Alle gesperrten IPs pruefen, ob sie jetzt auf der Whitelist stehen
|
||||||
[[ -d "$state_dir" ]] || return
|
local banned_ips
|
||||||
[[ -f "$EXTERNAL_WHITELIST_RESOLVED_FILE" ]] || return
|
banned_ips=$(db_query "SELECT a.client_ip FROM active_bans a INNER JOIN whitelist_cache w ON a.client_ip = w.ip_address;")
|
||||||
|
[[ -z "$banned_ips" ]] && return
|
||||||
|
|
||||||
for state_file in "${state_dir}"/*.ban "${state_dir}"/ext_*.ban; do
|
while IFS= read -r client_ip; do
|
||||||
[[ -f "$state_file" ]] || continue
|
|
||||||
local client_ip
|
|
||||||
client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2)
|
|
||||||
[[ -z "$client_ip" ]] && continue
|
[[ -z "$client_ip" ]] && continue
|
||||||
|
|
||||||
if grep -qxF "$client_ip" "$EXTERNAL_WHITELIST_RESOLVED_FILE" 2>/dev/null; then
|
|
||||||
log "INFO" "Gesperrte IP $client_ip ist jetzt auf externer Whitelist – entsperre automatisch"
|
log "INFO" "Gesperrte IP $client_ip ist jetzt auf externer Whitelist – entsperre automatisch"
|
||||||
|
|
||||||
# iptables-Regel entfernen
|
|
||||||
if [[ "$client_ip" == *:* ]]; then
|
if [[ "$client_ip" == *:* ]]; then
|
||||||
ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
else
|
else
|
||||||
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$state_file"
|
db_ban_delete "$client_ip"
|
||||||
|
db_history_add "UNBAN" "$client_ip" "-" "-" "external-whitelist" "-" "-"
|
||||||
# Ban-History Eintrag
|
done <<< "$banned_ips"
|
||||||
if [[ -f "${BAN_HISTORY_FILE:-/var/log/adguard-shield-bans.log}" ]]; then
|
|
||||||
local timestamp
|
|
||||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
||||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
|
|
||||||
"$timestamp" "UNBAN" "$client_ip" "-" "-" "-" "-" "external-whitelist" \
|
|
||||||
>> "${BAN_HISTORY_FILE:-/var/log/adguard-shield-bans.log}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── PID-Management ──────────────────────────────────────────────────────────
|
# ─── PID-Management ──────────────────────────────────────────────────────────
|
||||||
@@ -409,27 +398,29 @@ show_status() {
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Aufgelöste IPs
|
# Aufgelöste IPs aus Datenbank
|
||||||
if [[ -f "$EXTERNAL_WHITELIST_RESOLVED_FILE" ]]; then
|
|
||||||
local resolved_count
|
local resolved_count
|
||||||
resolved_count=$(wc -l < "$EXTERNAL_WHITELIST_RESOLVED_FILE" | xargs)
|
resolved_count=$(db_whitelist_count)
|
||||||
local last_resolved
|
|
||||||
last_resolved=$(date -r "$EXTERNAL_WHITELIST_RESOLVED_FILE" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unbekannt")
|
|
||||||
echo " Aufgelöste IPs: $resolved_count"
|
|
||||||
echo " Letzte Auflösung: $last_resolved"
|
|
||||||
|
|
||||||
if [[ "$resolved_count" -gt 0 && "$resolved_count" -le 20 ]]; then
|
if [[ "${resolved_count:-0}" -gt 0 ]]; then
|
||||||
|
echo " Aufgelöste IPs: $resolved_count"
|
||||||
|
|
||||||
|
if [[ "$resolved_count" -le 20 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo " Aktuelle IPs:"
|
echo " Aktuelle IPs:"
|
||||||
|
local all_wl_ips
|
||||||
|
all_wl_ips=$(db_whitelist_get_all)
|
||||||
while IFS= read -r ip; do
|
while IFS= read -r ip; do
|
||||||
echo " ✅ $ip"
|
echo " ✅ $ip"
|
||||||
done < "$EXTERNAL_WHITELIST_RESOLVED_FILE"
|
done <<< "$all_wl_ips"
|
||||||
elif [[ "$resolved_count" -gt 20 ]]; then
|
else
|
||||||
echo ""
|
echo ""
|
||||||
echo " Erste 20 IPs:"
|
echo " Erste 20 IPs:"
|
||||||
head -20 "$EXTERNAL_WHITELIST_RESOLVED_FILE" | while IFS= read -r ip; do
|
local first_wl_ips
|
||||||
|
first_wl_ips=$(db_query "SELECT ip_address FROM whitelist_cache LIMIT 20;")
|
||||||
|
while IFS= read -r ip; do
|
||||||
echo " ✅ $ip"
|
echo " ✅ $ip"
|
||||||
done
|
done <<< "$first_wl_ips"
|
||||||
echo " ... ($((resolved_count - 20)) weitere)"
|
echo " ... ($((resolved_count - 20)) weitere)"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
@@ -508,7 +499,7 @@ case "${1:-start}" in
|
|||||||
flush)
|
flush)
|
||||||
init_directories
|
init_directories
|
||||||
echo "Entferne aufgelöste externe Whitelist-IPs..."
|
echo "Entferne aufgelöste externe Whitelist-IPs..."
|
||||||
rm -f "$EXTERNAL_WHITELIST_RESOLVED_FILE"
|
db_whitelist_clear
|
||||||
echo "Externe Whitelist-IPs entfernt"
|
echo "Externe Whitelist-IPs entfernt"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|||||||
122
geoip-worker.sh
122
geoip-worker.sh
@@ -22,6 +22,9 @@ fi
|
|||||||
# shellcheck source=adguard-shield.conf
|
# shellcheck source=adguard-shield.conf
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
|
||||||
|
# shellcheck source=db.sh
|
||||||
|
source "${SCRIPT_DIR}/db.sh"
|
||||||
|
|
||||||
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
||||||
WORKER_PID_FILE="/var/run/adguard-geoip-worker.pid"
|
WORKER_PID_FILE="/var/run/adguard-geoip-worker.pid"
|
||||||
|
|
||||||
@@ -56,20 +59,8 @@ log_ban_history() {
|
|||||||
local client_ip="$2"
|
local client_ip="$2"
|
||||||
local country="${3:-}"
|
local country="${3:-}"
|
||||||
local reason="${4:-geoip}"
|
local reason="${4:-geoip}"
|
||||||
local timestamp
|
|
||||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
||||||
|
|
||||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
db_history_add "$action" "$client_ip" "Land: ${country:-?}" "-" "$reason" "permanent" "-"
|
||||||
echo "# AdGuard Shield - Ban History" > "$BAN_HISTORY_FILE"
|
|
||||||
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | PROTOKOLL | GRUND" >> "$BAN_HISTORY_FILE"
|
|
||||||
echo "#──────────────────────────────────────────────────────────────────────────────────────────────────" >> "$BAN_HISTORY_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local duration="permanent"
|
|
||||||
|
|
||||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
|
|
||||||
"$timestamp" "$action" "$client_ip" "Land: ${country:-?}" "-" "$duration" "-" "$reason" \
|
|
||||||
>> "$BAN_HISTORY_FILE"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
||||||
@@ -78,6 +69,7 @@ init_directories() {
|
|||||||
mkdir -p "$GEOIP_DB_DIR"
|
mkdir -p "$GEOIP_DB_DIR"
|
||||||
mkdir -p "$STATE_DIR"
|
mkdir -p "$STATE_DIR"
|
||||||
mkdir -p "$(dirname "$LOG_FILE")"
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
db_init
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Private IP-Adressen erkennen ────────────────────────────────────────────
|
# ─── Private IP-Adressen erkennen ────────────────────────────────────────────
|
||||||
@@ -107,15 +99,13 @@ is_whitelisted() {
|
|||||||
local ip="$1"
|
local ip="$1"
|
||||||
IFS=',' read -ra wl_entries <<< "$WHITELIST"
|
IFS=',' read -ra wl_entries <<< "$WHITELIST"
|
||||||
for entry in "${wl_entries[@]}"; do
|
for entry in "${wl_entries[@]}"; do
|
||||||
entry=$(echo "$entry" | xargs) # trim
|
entry=$(echo "$entry" | xargs)
|
||||||
if [[ "$ip" == "$entry" ]]; then
|
if [[ "$ip" == "$entry" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Externe Whitelist prüfen
|
if db_whitelist_contains "$ip"; then
|
||||||
local ext_wl_file="${EXTERNAL_WHITELIST_CACHE_DIR:-/var/lib/adguard-shield/external-whitelist}/resolved_ips.txt"
|
|
||||||
if [[ -f "$ext_wl_file" ]] && grep -qxF "$ip" "$ext_wl_file" 2>/dev/null; then
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -324,8 +314,7 @@ ban_ip_geoip() {
|
|||||||
local mode="${GEOIP_MODE:-blocklist}"
|
local mode="${GEOIP_MODE:-blocklist}"
|
||||||
|
|
||||||
# Prüfen ob bereits gesperrt
|
# Prüfen ob bereits gesperrt
|
||||||
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
|
if db_ban_exists "$client_ip"; then
|
||||||
if [[ -f "$state_file" ]]; then
|
|
||||||
log "DEBUG" "GeoIP: $client_ip ist bereits gesperrt"
|
log "DEBUG" "GeoIP: $client_ip ist bereits gesperrt"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -350,22 +339,8 @@ ban_ip_geoip() {
|
|||||||
iptables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
iptables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# State-Datei erstellen
|
# State in Datenbank speichern
|
||||||
cat > "$state_file" << EOF
|
db_ban_insert "$client_ip" "GeoIP:${country_code}" "0" "$(date '+%Y-%m-%d %H:%M:%S')" "0" "0" "0" "1" "geoip" "-" "geoip" "$country_code" "$mode"
|
||||||
CLIENT_IP=$client_ip
|
|
||||||
DOMAIN=GeoIP:${country_code}
|
|
||||||
COUNT=-
|
|
||||||
BAN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
BAN_UNTIL_EPOCH=0
|
|
||||||
BAN_UNTIL=PERMANENT
|
|
||||||
BAN_DURATION=0
|
|
||||||
OFFENSE_LEVEL=0
|
|
||||||
IS_PERMANENT=true
|
|
||||||
REASON=geoip
|
|
||||||
PROTOCOL=-
|
|
||||||
GEOIP_COUNTRY=$country_code
|
|
||||||
GEOIP_MODE=$mode
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Ban-History
|
# Ban-History
|
||||||
log_ban_history "BAN" "$client_ip" "$country_code" "$reason_text"
|
log_ban_history "BAN" "$client_ip" "$country_code" "$reason_text"
|
||||||
@@ -516,47 +491,37 @@ get_active_clients() {
|
|||||||
# - GeoIP deaktiviert wurde
|
# - GeoIP deaktiviert wurde
|
||||||
auto_unban_geoip() {
|
auto_unban_geoip() {
|
||||||
local unban_count=0
|
local unban_count=0
|
||||||
|
local geoip_bans
|
||||||
|
geoip_bans=$(db_ban_get_by_reason "geoip")
|
||||||
|
[[ -z "$geoip_bans" ]] && return
|
||||||
|
|
||||||
for f in "${STATE_DIR}"/*.ban; do
|
while IFS='|' read -r client_ip domain count ban_time ban_until_epoch ban_duration offense_level is_permanent reason protocol source geoip_country geoip_mode; do
|
||||||
[[ -f "$f" ]] || continue
|
[[ -z "$client_ip" ]] && continue
|
||||||
|
|
||||||
local reason
|
|
||||||
reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true)
|
|
||||||
[[ "$reason" != "geoip" ]] && continue
|
|
||||||
|
|
||||||
local client_ip country_code old_mode
|
|
||||||
client_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true)
|
|
||||||
country_code=$(grep '^GEOIP_COUNTRY=' "$f" | cut -d= -f2 || true)
|
|
||||||
old_mode=$(grep '^GEOIP_MODE=' "$f" | cut -d= -f2 || true)
|
|
||||||
|
|
||||||
local should_unban=false
|
local should_unban=false
|
||||||
|
|
||||||
# GeoIP deaktiviert → alle GeoIP-Sperren aufheben
|
|
||||||
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
if [[ "${GEOIP_ENABLED:-false}" != "true" ]]; then
|
||||||
should_unban=true
|
should_unban=true
|
||||||
# Modus gewechselt → alle GeoIP-Sperren aufheben und neu prüfen
|
elif [[ -n "$geoip_mode" && "$geoip_mode" != "${GEOIP_MODE:-blocklist}" ]]; then
|
||||||
elif [[ -n "$old_mode" && "$old_mode" != "${GEOIP_MODE:-blocklist}" ]]; then
|
|
||||||
should_unban=true
|
should_unban=true
|
||||||
# Prüfen ob das Land nach aktueller Konfiguration noch gesperrt sein soll
|
elif [[ -n "$geoip_country" ]] && ! should_block_by_geoip "$geoip_country"; then
|
||||||
elif [[ -n "$country_code" ]] && ! should_block_by_geoip "$country_code"; then
|
|
||||||
should_unban=true
|
should_unban=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$should_unban" == "true" ]]; then
|
if [[ "$should_unban" == "true" ]]; then
|
||||||
log "INFO" "GeoIP Auto-Unban: $client_ip (Land: ${country_code:-?}, war: ${old_mode:-?})"
|
log "INFO" "GeoIP Auto-Unban: $client_ip (Land: ${geoip_country:-?}, war: ${geoip_mode:-?})"
|
||||||
|
|
||||||
# iptables Regel entfernen
|
|
||||||
if [[ "$client_ip" == *:* ]]; then
|
if [[ "$client_ip" == *:* ]]; then
|
||||||
ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
else
|
else
|
||||||
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$f"
|
db_ban_delete "$client_ip"
|
||||||
log_ban_history "UNBAN" "$client_ip" "$country_code" "geoip-auto-unban"
|
log_ban_history "UNBAN" "$client_ip" "$geoip_country" "geoip-auto-unban"
|
||||||
unban_count=$((unban_count + 1))
|
unban_count=$((unban_count + 1))
|
||||||
fi
|
fi
|
||||||
done
|
done <<< "$geoip_bans"
|
||||||
|
|
||||||
if [[ $unban_count -gt 0 ]]; then
|
if [[ $unban_count -gt 0 ]]; then
|
||||||
log "INFO" "GeoIP Auto-Unban: $unban_count Sperren aufgehoben (Länderliste/Modus geändert)"
|
log "INFO" "GeoIP Auto-Unban: $unban_count Sperren aufgehoben (Länderliste/Modus geändert)"
|
||||||
@@ -618,8 +583,7 @@ sync_geoip() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Bereits gesperrt?
|
# Bereits gesperrt?
|
||||||
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
|
if db_ban_exists "$client_ip"; then
|
||||||
if [[ -f "$state_file" ]]; then
|
|
||||||
skipped=$((skipped + 1))
|
skipped=$((skipped + 1))
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -741,21 +705,20 @@ show_status() {
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# GeoIP-Sperren anzeigen
|
# GeoIP-Sperren anzeigen
|
||||||
|
local geoip_bans_data
|
||||||
|
geoip_bans_data=$(db_ban_get_by_reason "geoip")
|
||||||
local geoip_bans=0
|
local geoip_bans=0
|
||||||
if [[ -d "$STATE_DIR" ]]; then
|
|
||||||
for f in "${STATE_DIR}"/*.ban; do
|
if [[ -n "$geoip_bans_data" ]]; then
|
||||||
[[ -f "$f" ]] || continue
|
while IFS='|' read -r s_ip s_domain _ _ s_ban_until_epoch _ _ s_perm_int _ _ _ s_country _; do
|
||||||
local reason
|
[[ -z "$s_ip" ]] && continue
|
||||||
reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true)
|
|
||||||
if [[ "$reason" == "geoip" ]]; then
|
|
||||||
geoip_bans=$((geoip_bans + 1))
|
geoip_bans=$((geoip_bans + 1))
|
||||||
local s_ip s_country s_until
|
local s_until_display="PERMANENT"
|
||||||
s_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true)
|
if [[ "$s_ban_until_epoch" != "0" && "$s_perm_int" != "1" ]]; then
|
||||||
s_country=$(grep '^GEOIP_COUNTRY=' "$f" | cut -d= -f2 || true)
|
s_until_display=$(date -d "@$s_ban_until_epoch" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "?")
|
||||||
s_until=$(grep '^BAN_UNTIL=' "$f" | cut -d= -f2 || true)
|
|
||||||
echo " 🌍 $s_ip → Land: ${s_country:-?} (bis: ${s_until:-?})"
|
|
||||||
fi
|
fi
|
||||||
done
|
echo " 🌍 $s_ip → Land: ${s_country:-?} (bis: $s_until_display)"
|
||||||
|
done <<< "$geoip_bans_data"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $geoip_bans -eq 0 ]]; then
|
if [[ $geoip_bans -eq 0 ]]; then
|
||||||
@@ -831,14 +794,12 @@ flush_cache() {
|
|||||||
# ─── GeoIP-Sperren aufheben ─────────────────────────────────────────────────
|
# ─── GeoIP-Sperren aufheben ─────────────────────────────────────────────────
|
||||||
flush_geoip_bans() {
|
flush_geoip_bans() {
|
||||||
local count=0
|
local count=0
|
||||||
if [[ -d "$STATE_DIR" ]]; then
|
local geoip_ips
|
||||||
for f in "${STATE_DIR}"/*.ban; do
|
geoip_ips=$(db_query "SELECT client_ip FROM active_bans WHERE reason='geoip';")
|
||||||
[[ -f "$f" ]] || continue
|
|
||||||
local reason
|
if [[ -n "$geoip_ips" ]]; then
|
||||||
reason=$(grep '^REASON=' "$f" | cut -d= -f2 || true)
|
while IFS= read -r client_ip; do
|
||||||
if [[ "$reason" == "geoip" ]]; then
|
[[ -z "$client_ip" ]] && continue
|
||||||
local client_ip
|
|
||||||
client_ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2 || true)
|
|
||||||
|
|
||||||
# iptables Regel entfernen
|
# iptables Regel entfernen
|
||||||
if [[ "$client_ip" == *:* ]]; then
|
if [[ "$client_ip" == *:* ]]; then
|
||||||
@@ -847,11 +808,10 @@ flush_geoip_bans() {
|
|||||||
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$f"
|
db_ban_delete "$client_ip"
|
||||||
log_ban_history "UNBAN" "$client_ip" "" "geoip-flush"
|
log_ban_history "UNBAN" "$client_ip" "" "geoip-flush"
|
||||||
count=$((count + 1))
|
count=$((count + 1))
|
||||||
fi
|
done <<< "$geoip_ips"
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✅ $count GeoIP-Sperren aufgehoben"
|
echo "✅ $count GeoIP-Sperren aufgehoben"
|
||||||
|
|||||||
100
install.sh
100
install.sh
@@ -6,7 +6,7 @@
|
|||||||
# Lizenz: MIT
|
# Lizenz: MIT
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
VERSION="v0.9.0"
|
VERSION="v1.0.0"
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -65,6 +65,7 @@ print_help() {
|
|||||||
echo -e " Aktualisiert alle Scripts, führt eine automatische"
|
echo -e " Aktualisiert alle Scripts, führt eine automatische"
|
||||||
echo -e " Konfigurations-Migration durch (neue Parameter werden"
|
echo -e " Konfigurations-Migration durch (neue Parameter werden"
|
||||||
echo -e " hinzugefügt, bestehende Einstellungen bleiben erhalten),"
|
echo -e " hinzugefügt, bestehende Einstellungen bleiben erhalten),"
|
||||||
|
echo -e " migriert bestehende Daten nach SQLite (einmalig)"
|
||||||
echo -e " und startet den Service automatisch neu."
|
echo -e " und startet den Service automatisch neu."
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${GREEN}uninstall${NC} Vollständige Deinstallation"
|
echo -e " ${GREEN}uninstall${NC} Vollständige Deinstallation"
|
||||||
@@ -142,7 +143,7 @@ print_help() {
|
|||||||
echo " - Linux Server (Debian/Ubuntu empfohlen)"
|
echo " - Linux Server (Debian/Ubuntu empfohlen)"
|
||||||
echo " - Root-Zugriff (sudo)"
|
echo " - Root-Zugriff (sudo)"
|
||||||
echo " - AdGuard Home installiert und erreichbar"
|
echo " - AdGuard Home installiert und erreichbar"
|
||||||
echo " - Pakete: curl, jq, iptables, gawk (werden bei Installation automatisch installiert)"
|
echo " - Pakete: curl, jq, iptables, gawk, sqlite3 (werden bei Installation automatisch installiert)"
|
||||||
echo " - GeoIP (optional): geoip-bin + geoip-database oder MaxMind GeoLite2 DB"
|
echo " - GeoIP (optional): geoip-bin + geoip-database oder MaxMind GeoLite2 DB"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BOLD}Dokumentation:${NC}"
|
echo -e "${BOLD}Dokumentation:${NC}"
|
||||||
@@ -197,9 +198,10 @@ check_dependencies() {
|
|||||||
[ip6tables]="iptables"
|
[ip6tables]="iptables"
|
||||||
[gawk]="gawk"
|
[gawk]="gawk"
|
||||||
[systemctl]="systemd"
|
[systemctl]="systemd"
|
||||||
|
[sqlite3]="sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
for cmd in curl jq iptables ip6tables gawk systemctl; do
|
for cmd in curl jq iptables ip6tables gawk systemctl sqlite3; do
|
||||||
if command -v "$cmd" &>/dev/null; then
|
if command -v "$cmd" &>/dev/null; then
|
||||||
echo -e " ✅ $cmd"
|
echo -e " ✅ $cmd"
|
||||||
else
|
else
|
||||||
@@ -266,6 +268,7 @@ install_files() {
|
|||||||
cp "$SCRIPT_DIR/uninstall.sh" "$INSTALL_DIR/"
|
cp "$SCRIPT_DIR/uninstall.sh" "$INSTALL_DIR/"
|
||||||
cp "$SCRIPT_DIR/geoip-worker.sh" "$INSTALL_DIR/"
|
cp "$SCRIPT_DIR/geoip-worker.sh" "$INSTALL_DIR/"
|
||||||
cp "$SCRIPT_DIR/offense-cleanup-worker.sh" "$INSTALL_DIR/"
|
cp "$SCRIPT_DIR/offense-cleanup-worker.sh" "$INSTALL_DIR/"
|
||||||
|
cp "$SCRIPT_DIR/db.sh" "$INSTALL_DIR/"
|
||||||
|
|
||||||
# Templates kopieren
|
# Templates kopieren
|
||||||
mkdir -p "$INSTALL_DIR/templates"
|
mkdir -p "$INSTALL_DIR/templates"
|
||||||
@@ -283,6 +286,7 @@ install_files() {
|
|||||||
chmod +x "$INSTALL_DIR/uninstall.sh"
|
chmod +x "$INSTALL_DIR/uninstall.sh"
|
||||||
chmod +x "$INSTALL_DIR/geoip-worker.sh"
|
chmod +x "$INSTALL_DIR/geoip-worker.sh"
|
||||||
chmod +x "$INSTALL_DIR/offense-cleanup-worker.sh"
|
chmod +x "$INSTALL_DIR/offense-cleanup-worker.sh"
|
||||||
|
chmod +x "$INSTALL_DIR/db.sh"
|
||||||
|
|
||||||
echo -e " ✅ Dateien installiert"
|
echo -e " ✅ Dateien installiert"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -671,6 +675,92 @@ do_install() {
|
|||||||
print_summary
|
print_summary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ─── SQLite-Datenbank-Migration ──────────────────────────────────────────────
|
||||||
|
# Migriert bestehende Flat-File-Daten (*.ban, *.offenses, History-Log) nach SQLite.
|
||||||
|
# Läuft synchron im Vordergrund mit sichtbarer Fortschrittsanzeige.
|
||||||
|
migrate_database() {
|
||||||
|
echo -e "${YELLOW}Prüfe Datenbank-Migration...${NC}"
|
||||||
|
|
||||||
|
# Konfiguration laden für STATE_DIR und BAN_HISTORY_FILE
|
||||||
|
local conf="$INSTALL_DIR/adguard-shield.conf"
|
||||||
|
if [[ ! -f "$conf" ]]; then
|
||||||
|
echo -e " ${RED}Konfiguration nicht gefunden — Migration übersprungen${NC}"
|
||||||
|
echo ""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Nur die benötigten Variablen aus der Konfig laden
|
||||||
|
STATE_DIR=$(grep '^STATE_DIR=' "$conf" | cut -d= -f2 | tr -d '"')
|
||||||
|
STATE_DIR="${STATE_DIR:-/var/lib/adguard-shield}"
|
||||||
|
BAN_HISTORY_FILE=$(grep '^BAN_HISTORY_FILE=' "$conf" | cut -d= -f2 | tr -d '"')
|
||||||
|
BAN_HISTORY_FILE="${BAN_HISTORY_FILE:-/var/log/adguard-shield-bans.log}"
|
||||||
|
export STATE_DIR BAN_HISTORY_FILE
|
||||||
|
|
||||||
|
# db.sh aus dem Installationsverzeichnis laden
|
||||||
|
source "$INSTALL_DIR/db.sh"
|
||||||
|
|
||||||
|
# Datenbank initialisieren (Schema anlegen falls nötig)
|
||||||
|
db_init
|
||||||
|
|
||||||
|
# Prüfen ob Migration bereits durchgeführt wurde
|
||||||
|
if [[ -f "$_DB_MIGRATION_MARKER" ]]; then
|
||||||
|
echo -e " ✅ Datenbank ist aktuell — Migration bereits abgeschlossen"
|
||||||
|
echo ""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prüfen ob überhaupt Flat-Files vorhanden sind
|
||||||
|
local has_files=false
|
||||||
|
for f in "${STATE_DIR}"/*.ban "${STATE_DIR}"/ext_*.ban "${STATE_DIR}"/*.offenses; do
|
||||||
|
if [[ -f "$f" ]]; then
|
||||||
|
has_files=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "$has_files" == "false" && ! -f "$BAN_HISTORY_FILE" ]]; then
|
||||||
|
# Keine alten Daten vorhanden — Marker setzen und fertig
|
||||||
|
echo "migrated_at=$(date '+%Y-%m-%d %H:%M:%S')" > "$_DB_MIGRATION_MARKER"
|
||||||
|
echo "bans=0" >> "$_DB_MIGRATION_MARKER"
|
||||||
|
echo "offenses=0" >> "$_DB_MIGRATION_MARKER"
|
||||||
|
echo "history=0" >> "$_DB_MIGRATION_MARKER"
|
||||||
|
echo "whitelist=0" >> "$_DB_MIGRATION_MARKER"
|
||||||
|
echo -e " ✅ Keine bestehenden Daten gefunden — Datenbank bereit"
|
||||||
|
echo ""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e " ${CYAN}Migriere bestehende Daten nach SQLite...${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
local migrated
|
||||||
|
migrated=$(db_migrate_from_files)
|
||||||
|
|
||||||
|
if [[ "${migrated:-0}" -gt 0 ]]; then
|
||||||
|
# Details aus dem Marker lesen
|
||||||
|
local m_bans m_offenses m_history m_whitelist
|
||||||
|
m_bans=$(grep '^bans=' "$_DB_MIGRATION_MARKER" 2>/dev/null | cut -d= -f2)
|
||||||
|
m_offenses=$(grep '^offenses=' "$_DB_MIGRATION_MARKER" 2>/dev/null | cut -d= -f2)
|
||||||
|
m_history=$(grep '^history=' "$_DB_MIGRATION_MARKER" 2>/dev/null | cut -d= -f2)
|
||||||
|
m_whitelist=$(grep '^whitelist=' "$_DB_MIGRATION_MARKER" 2>/dev/null | cut -d= -f2)
|
||||||
|
|
||||||
|
echo -e " ${GREEN}═══════════════════════════════════════════════════════════${NC}"
|
||||||
|
echo -e " ${GREEN} SQLite-Migration erfolgreich abgeschlossen!${NC}"
|
||||||
|
echo -e " ${GREEN}═══════════════════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " Migrierte Einträge gesamt: ${BOLD}${migrated}${NC}"
|
||||||
|
[[ "${m_bans:-0}" -gt 0 ]] && echo -e " • Aktive Bans: ${m_bans}"
|
||||||
|
[[ "${m_offenses:-0}" -gt 0 ]] && echo -e " • Offense-Tracking: ${m_offenses}"
|
||||||
|
[[ "${m_history:-0}" -gt 0 ]] && echo -e " • Ban-History: ${m_history}"
|
||||||
|
[[ "${m_whitelist:-0}" -gt 0 ]] && echo -e " • Whitelist-Cache: ${m_whitelist}"
|
||||||
|
echo ""
|
||||||
|
echo -e " 📦 Backup der alten Dateien: ${STATE_DIR}/.backup_pre_sqlite/"
|
||||||
|
echo -e " 📂 Neue Datenbank: ${STATE_DIR}/adguard-shield.db"
|
||||||
|
else
|
||||||
|
echo -e " ✅ Migration abgeschlossen — keine Daten zum Migrieren"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# ─── Update ──────────────────────────────────────────────────────────────────
|
# ─── Update ──────────────────────────────────────────────────────────────────
|
||||||
do_update() {
|
do_update() {
|
||||||
check_root
|
check_root
|
||||||
@@ -691,6 +781,9 @@ do_update() {
|
|||||||
# Konfigurations-Migration durchführen
|
# Konfigurations-Migration durchführen
|
||||||
migrate_config
|
migrate_config
|
||||||
|
|
||||||
|
# SQLite-Datenbank-Migration durchführen
|
||||||
|
migrate_database
|
||||||
|
|
||||||
# Service-Datei aktualisieren
|
# Service-Datei aktualisieren
|
||||||
echo -e "${YELLOW}Aktualisiere systemd Service...${NC}"
|
echo -e "${YELLOW}Aktualisiere systemd Service...${NC}"
|
||||||
cp "$SCRIPT_DIR/adguard-shield.service" "$SERVICE_FILE"
|
cp "$SCRIPT_DIR/adguard-shield.service" "$SERVICE_FILE"
|
||||||
@@ -816,6 +909,7 @@ do_uninstall() {
|
|||||||
rm -f "$INSTALL_DIR/geoip-worker.sh"
|
rm -f "$INSTALL_DIR/geoip-worker.sh"
|
||||||
rm -f "$INSTALL_DIR/report-generator.sh"
|
rm -f "$INSTALL_DIR/report-generator.sh"
|
||||||
rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh"
|
rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh"
|
||||||
|
rm -f "$INSTALL_DIR/db.sh"
|
||||||
rm -f "$INSTALL_DIR/uninstall.sh"
|
rm -f "$INSTALL_DIR/uninstall.sh"
|
||||||
rm -rf "$INSTALL_DIR/templates"
|
rm -rf "$INSTALL_DIR/templates"
|
||||||
rm -rf "$INSTALL_DIR/geoip"
|
rm -rf "$INSTALL_DIR/geoip"
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ if [[ ! -f "$CONFIG_FILE" ]]; then
|
|||||||
fi
|
fi
|
||||||
# shellcheck source=adguard-shield.conf
|
# shellcheck source=adguard-shield.conf
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
# shellcheck source=db.sh
|
||||||
|
source "${SCRIPT_DIR}/db.sh"
|
||||||
|
|
||||||
# ─── Niedrigste Priorität setzen (CPU + I/O) ─────────────────────────────────
|
# ─── Niedrigste Priorität setzen (CPU + I/O) ─────────────────────────────────
|
||||||
# Stellt sicher, dass der Worker auch bei manuellem Start nie andere Dienste
|
# Stellt sicher, dass der Worker auch bei manuellem Start nie andere Dienste
|
||||||
@@ -77,6 +79,7 @@ format_duration() {
|
|||||||
init_directories() {
|
init_directories() {
|
||||||
mkdir -p "${STATE_DIR}"
|
mkdir -p "${STATE_DIR}"
|
||||||
mkdir -p "$(dirname "$LOG_FILE")"
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
db_init
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Abgelaufene Offense-Zähler aufräumen ────────────────────────────────────
|
# ─── Abgelaufene Offense-Zähler aufräumen ────────────────────────────────────
|
||||||
@@ -84,39 +87,23 @@ cleanup_expired_offenses() {
|
|||||||
local reset_after="${PROGRESSIVE_BAN_RESET_AFTER:-86400}"
|
local reset_after="${PROGRESSIVE_BAN_RESET_AFTER:-86400}"
|
||||||
local now
|
local now
|
||||||
now=$(date '+%s')
|
now=$(date '+%s')
|
||||||
local cleaned=0
|
local cutoff=$((now - reset_after))
|
||||||
|
|
||||||
local batch_count=0
|
local expired_rows
|
||||||
for offense_file in "${STATE_DIR}"/*.offenses; do
|
expired_rows=$(db_query "SELECT client_ip, offense_level, last_offense_epoch FROM offense_tracking WHERE last_offense_epoch <= $cutoff;")
|
||||||
[[ -f "$offense_file" ]] || continue
|
|
||||||
|
|
||||||
local last_offense_epoch client_ip offense_level
|
if [[ -n "$expired_rows" ]]; then
|
||||||
last_offense_epoch=$(grep '^LAST_OFFENSE_EPOCH=' "$offense_file" | cut -d= -f2 || true)
|
while IFS='|' read -r client_ip offense_level last_epoch; do
|
||||||
client_ip=$(grep '^CLIENT_IP=' "$offense_file" | cut -d= -f2 || true)
|
[[ -z "$client_ip" ]] && continue
|
||||||
offense_level=$(grep '^OFFENSE_LEVEL=' "$offense_file" | cut -d= -f2 || true)
|
local elapsed=$((now - last_epoch))
|
||||||
|
|
||||||
# Kein Zeitstempel vorhanden → überspringen
|
|
||||||
if [[ -z "$last_offense_epoch" ]]; then
|
|
||||||
log "DEBUG" "Offense-Datei ohne Zeitstempel übersprungen: $offense_file"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
local elapsed=$((now - last_offense_epoch))
|
|
||||||
|
|
||||||
if [[ $elapsed -gt $reset_after ]]; then
|
|
||||||
log "INFO" "Offense-Zähler abgelaufen: $client_ip (Stufe $offense_level, letztes Vergehen vor $(format_duration $elapsed)) → entfernt"
|
log "INFO" "Offense-Zähler abgelaufen: $client_ip (Stufe $offense_level, letztes Vergehen vor $(format_duration $elapsed)) → entfernt"
|
||||||
rm -f "$offense_file"
|
done <<< "$expired_rows"
|
||||||
cleaned=$((cleaned + 1))
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Alle 10 Dateien kurz pausieren, um I/O-Bursts zu vermeiden
|
local cleaned
|
||||||
batch_count=$((batch_count + 1))
|
cleaned=$(db_offense_delete_expired "$reset_after")
|
||||||
if (( batch_count % 10 == 0 )); then
|
|
||||||
sleep 0.1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ $cleaned -gt 0 ]]; then
|
if [[ "$cleaned" -gt 0 ]]; then
|
||||||
log "INFO" "Offense-Cleanup: $cleaned abgelaufene Zähler entfernt"
|
log "INFO" "Offense-Cleanup: $cleaned abgelaufene Zähler entfernt"
|
||||||
else
|
else
|
||||||
log "DEBUG" "Offense-Cleanup: keine abgelaufenen Zähler gefunden"
|
log "DEBUG" "Offense-Cleanup: keine abgelaufenen Zähler gefunden"
|
||||||
@@ -179,22 +166,11 @@ show_status() {
|
|||||||
echo " Reset-Zeitraum: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}")"
|
echo " Reset-Zeitraum: $(format_duration "${PROGRESSIVE_BAN_RESET_AFTER:-86400}")"
|
||||||
echo " Prüfintervall: $(format_duration "$OFFENSE_CLEANUP_INTERVAL")"
|
echo " Prüfintervall: $(format_duration "$OFFENSE_CLEANUP_INTERVAL")"
|
||||||
|
|
||||||
# Aktuelle Offense-Dateien zählen
|
|
||||||
local total=0
|
|
||||||
local expired=0
|
|
||||||
local now
|
|
||||||
now=$(date '+%s')
|
|
||||||
local reset_after="${PROGRESSIVE_BAN_RESET_AFTER:-86400}"
|
local reset_after="${PROGRESSIVE_BAN_RESET_AFTER:-86400}"
|
||||||
|
local total
|
||||||
for offense_file in "${STATE_DIR}"/*.offenses; do
|
total=$(db_offense_count)
|
||||||
[[ -f "$offense_file" ]] || continue
|
local expired
|
||||||
total=$((total + 1))
|
expired=$(db_offense_count_expired "$reset_after")
|
||||||
local last_epoch
|
|
||||||
last_epoch=$(grep '^LAST_OFFENSE_EPOCH=' "$offense_file" | cut -d= -f2 || true)
|
|
||||||
if [[ -n "$last_epoch" && $((now - last_epoch)) -gt $reset_after ]]; then
|
|
||||||
expired=$((expired + 1))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " Offense-Zähler gesamt: $total"
|
echo " Offense-Zähler gesamt: $total"
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ if [[ ! -f "$CONFIG_FILE" ]]; then
|
|||||||
fi
|
fi
|
||||||
# shellcheck source=adguard-shield.conf
|
# shellcheck source=adguard-shield.conf
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
# shellcheck source=db.sh
|
||||||
|
source "${SCRIPT_DIR}/db.sh"
|
||||||
|
|
||||||
# ─── Standardwerte ────────────────────────────────────────────────────────────
|
# ─── Standardwerte ────────────────────────────────────────────────────────────
|
||||||
REPORT_ENABLED="${REPORT_ENABLED:-false}"
|
REPORT_ENABLED="${REPORT_ENABLED:-false}"
|
||||||
@@ -179,143 +181,48 @@ get_period_end_epoch() {
|
|||||||
echo $((today_midnight - 1))
|
echo $((today_midnight - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── History-Cache (einmaliges Einlesen der Ban-History) ─────────────────────
|
|
||||||
# Die Datei wird genau einmal mit awk geparst; alle Funktionen lesen danach
|
|
||||||
# nur noch aus diesem In-Memory-Cache – keine date-Subprozesse pro Zeile mehr.
|
|
||||||
#
|
|
||||||
# Cache-Format pro Zeile (Pipe-separiert, alle Felder getrimmt):
|
|
||||||
# EPOCH|TIMESTAMP|ACTION|IP|DOMAIN|COUNT|DURATION|PROTOCOL|REASON
|
|
||||||
HISTORY_CACHE=""
|
|
||||||
HISTORY_CACHE_LOADED=false
|
|
||||||
|
|
||||||
_load_history_cache() {
|
|
||||||
[[ "$HISTORY_CACHE_LOADED" == "true" ]] && return
|
|
||||||
HISTORY_CACHE_LOADED=true
|
|
||||||
[[ ! -f "$BAN_HISTORY_FILE" ]] && return
|
|
||||||
HISTORY_CACHE=$(awk '
|
|
||||||
/^#/ || /^[[:space:]]*$/ { next }
|
|
||||||
{
|
|
||||||
n = split($0, f, "|")
|
|
||||||
if (n < 2) next
|
|
||||||
ts = f[1]; gsub(/^[[:space:]]+|[[:space:]]+$/, "", ts)
|
|
||||||
if (length(ts) < 19) next
|
|
||||||
ep = mktime(substr(ts,1,4) " " substr(ts,6,2) " " substr(ts,9,2) " " \
|
|
||||||
substr(ts,12,2) " " substr(ts,15,2) " " substr(ts,18,2))
|
|
||||||
if (ep < 0) next
|
|
||||||
for (i = 1; i <= n; i++) gsub(/^[[:space:]]+|[[:space:]]+$/, "", f[i])
|
|
||||||
print ep "|" f[1] "|" f[2] "|" f[3] "|" f[4] "|" f[5] "|" f[6] "|" f[7] "|" f[8]
|
|
||||||
}
|
|
||||||
' "$BAN_HISTORY_FILE")
|
|
||||||
}
|
|
||||||
|
|
||||||
# ─── Ban-History filtern nach Zeitraum ────────────────────────────────────────
|
|
||||||
# Gibt nur Zeilen zurück, deren Zeitstempel im Berichtszeitraum liegen.
|
|
||||||
# Liest intern aus dem Cache – keine erneuten date-Subprozesse.
|
|
||||||
filter_history_by_period() {
|
|
||||||
local start_epoch="$1"
|
|
||||||
local end_epoch="$2"
|
|
||||||
|
|
||||||
[[ ! -f "$BAN_HISTORY_FILE" ]] && return
|
|
||||||
_load_history_cache
|
|
||||||
[[ -z "$HISTORY_CACHE" ]] && return
|
|
||||||
|
|
||||||
# Aus dem Cache filtern und im Original-Format ausgeben (Abwärtskompatibilität)
|
|
||||||
echo "$HISTORY_CACHE" | awk -F'|' -v s="$start_epoch" -v e="$end_epoch" '
|
|
||||||
$1 >= s && $1 <= e {
|
|
||||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n",
|
|
||||||
$2, $3, $4, $5, $6, $7, $8, $9
|
|
||||||
}
|
|
||||||
'
|
|
||||||
}
|
|
||||||
|
|
||||||
# ─── Ban-History bereinigen ────────────────────────────────────────────────────
|
# ─── Ban-History bereinigen ────────────────────────────────────────────────────
|
||||||
# Entfernt Einträge älter als BAN_HISTORY_RETENTION_DAYS (0 = deaktiviert).
|
|
||||||
# Nutzt einen einzelnen awk-Durchlauf mit mktime() – kein date-Subprocess pro Zeile.
|
|
||||||
cleanup_ban_history() {
|
cleanup_ban_history() {
|
||||||
[[ ! -f "$BAN_HISTORY_FILE" ]] && return
|
|
||||||
[[ "$BAN_HISTORY_RETENTION_DAYS" == "0" || -z "$BAN_HISTORY_RETENTION_DAYS" ]] && return
|
[[ "$BAN_HISTORY_RETENTION_DAYS" == "0" || -z "$BAN_HISTORY_RETENTION_DAYS" ]] && return
|
||||||
|
|
||||||
local cutoff_epoch
|
local removed
|
||||||
cutoff_epoch=$(date -d "-${BAN_HISTORY_RETENTION_DAYS} days" '+%s' 2>/dev/null)
|
removed=$(db_history_cleanup "$BAN_HISTORY_RETENTION_DAYS")
|
||||||
[[ -z "$cutoff_epoch" ]] && return
|
if [[ "${removed:-0}" -gt 0 ]]; then
|
||||||
|
|
||||||
local tmp_file="${BAN_HISTORY_FILE}.tmp"
|
|
||||||
local lines_before lines_after
|
|
||||||
lines_before=$(wc -l < "$BAN_HISTORY_FILE")
|
|
||||||
|
|
||||||
awk -v cutoff="$cutoff_epoch" '
|
|
||||||
/^#/ || /^[[:space:]]*$/ { print; next }
|
|
||||||
{
|
|
||||||
n = split($0, f, "|")
|
|
||||||
if (n < 2) { print; next }
|
|
||||||
ts = f[1]; gsub(/^[[:space:]]+|[[:space:]]+$/, "", ts)
|
|
||||||
if (length(ts) < 19) { print; next }
|
|
||||||
ep = mktime(substr(ts,1,4) " " substr(ts,6,2) " " substr(ts,9,2) " " \
|
|
||||||
substr(ts,12,2) " " substr(ts,15,2) " " substr(ts,18,2))
|
|
||||||
if (ep >= cutoff) print
|
|
||||||
}
|
|
||||||
' "$BAN_HISTORY_FILE" > "$tmp_file"
|
|
||||||
|
|
||||||
lines_after=$(wc -l < "$tmp_file")
|
|
||||||
local removed=$(( lines_before - lines_after ))
|
|
||||||
|
|
||||||
if [[ $removed -gt 0 ]]; then
|
|
||||||
mv "$tmp_file" "$BAN_HISTORY_FILE"
|
|
||||||
# Cache invalidieren, damit Folgeaufrufe die bereinigte Datei neu lesen
|
|
||||||
HISTORY_CACHE=""
|
|
||||||
HISTORY_CACHE_LOADED=false
|
|
||||||
log "INFO" "Ban-History bereinigt: $removed Einträge älter als ${BAN_HISTORY_RETENTION_DAYS} Tage entfernt"
|
log "INFO" "Ban-History bereinigt: $removed Einträge älter als ${BAN_HISTORY_RETENTION_DAYS} Tage entfernt"
|
||||||
else
|
|
||||||
rm -f "$tmp_file"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Statistiken für beliebigen Zeitraum berechnen ──────────────────────────
|
# ─── Statistiken für beliebigen Zeitraum berechnen ──────────────────────────
|
||||||
# Gibt "bans|unbans|unique_ips|permanent" für einen Epochen-Bereich zurück.
|
|
||||||
# Liest direkt aus dem Cache in einem einzigen awk-Durchlauf.
|
|
||||||
get_stats_for_epoch_range() {
|
get_stats_for_epoch_range() {
|
||||||
local start_epoch="$1"
|
local start_epoch="$1"
|
||||||
local end_epoch="$2"
|
local end_epoch="$2"
|
||||||
|
|
||||||
_load_history_cache
|
local result
|
||||||
if [[ -z "$HISTORY_CACHE" ]]; then
|
result=$(db_history_stats_for_range "$start_epoch" "$end_epoch")
|
||||||
|
if [[ -z "$result" ]]; then
|
||||||
echo "0|0|0|0"
|
echo "0|0|0|0"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
echo "$result"
|
||||||
echo "$HISTORY_CACHE" | awk -F'|' -v s="$start_epoch" -v e="$end_epoch" '
|
|
||||||
$1 >= s && $1 <= e {
|
|
||||||
if ($3 == "BAN") {
|
|
||||||
bans++
|
|
||||||
ip_seen[$4] = 1
|
|
||||||
if (tolower($7) ~ /permanent/) perm++
|
|
||||||
} else if ($3 == "UNBAN") {
|
|
||||||
unbans++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
END {
|
|
||||||
for (ip in ip_seen) unique++
|
|
||||||
print (bans+0) "|" (unbans+0) "|" (unique+0) "|" (perm+0)
|
|
||||||
}
|
|
||||||
'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ─── Statistiken berechnen ────────────────────────────────────────────────────
|
# ─── Statistiken berechnen ────────────────────────────────────────────────────
|
||||||
# Liest die Ban-History genau einmal aus dem Cache und berechnet alle
|
|
||||||
# Kennzahlen in einem einzigen awk-Durchlauf – keine Subprozesse pro Zeile.
|
|
||||||
calculate_stats() {
|
calculate_stats() {
|
||||||
# Ban-History bereinigen (falls Retention konfiguriert)
|
# Ban-History bereinigen (falls Retention konfiguriert)
|
||||||
cleanup_ban_history
|
cleanup_ban_history
|
||||||
|
|
||||||
|
# Datenbank initialisieren
|
||||||
|
db_init
|
||||||
|
|
||||||
local start_epoch
|
local start_epoch
|
||||||
start_epoch=$(get_period_start_epoch)
|
start_epoch=$(get_period_start_epoch)
|
||||||
local end_epoch
|
local end_epoch
|
||||||
end_epoch=$(get_period_end_epoch)
|
end_epoch=$(get_period_end_epoch)
|
||||||
|
|
||||||
_load_history_cache
|
local total_history
|
||||||
|
total_history=$(db_history_count)
|
||||||
|
|
||||||
# Wenn keine History-Datei vorhanden, Standardwerte setzen
|
if [[ "${total_history:-0}" -eq 0 ]]; then
|
||||||
if [[ -z "$HISTORY_CACHE" ]]; then
|
|
||||||
TOTAL_BANS=0
|
TOTAL_BANS=0
|
||||||
TOTAL_UNBANS=0
|
TOTAL_UNBANS=0
|
||||||
UNIQUE_IPS=0
|
UNIQUE_IPS=0
|
||||||
@@ -334,8 +241,12 @@ calculate_stats() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Einen einzigen awk-Pass über den Cache: alle Statistiken auf einmal
|
# Haupt-Statistiken per SQL
|
||||||
# Busiest-Day-Bereich berechnen (konfigurierbar, Standard: 30 Tage)
|
local stats_row
|
||||||
|
stats_row=$(db_history_report_stats "$start_epoch" "$end_epoch")
|
||||||
|
IFS='|' read -r TOTAL_BANS TOTAL_UNBANS UNIQUE_IPS PERMANENT_BANS RATELIMIT_BANS SUBDOMAIN_FLOOD_BANS EXTERNAL_BLOCKLIST_BANS <<< "$stats_row"
|
||||||
|
|
||||||
|
# Busiest-Day-Bereich berechnen
|
||||||
local busiest_start_epoch
|
local busiest_start_epoch
|
||||||
if [[ "$REPORT_BUSIEST_DAY_RANGE" == "0" || -z "$REPORT_BUSIEST_DAY_RANGE" ]]; then
|
if [[ "$REPORT_BUSIEST_DAY_RANGE" == "0" || -z "$REPORT_BUSIEST_DAY_RANGE" ]]; then
|
||||||
busiest_start_epoch="$start_epoch"
|
busiest_start_epoch="$start_epoch"
|
||||||
@@ -345,75 +256,11 @@ calculate_stats() {
|
|||||||
busiest_start_epoch=$((today_midnight - REPORT_BUSIEST_DAY_RANGE * 86400))
|
busiest_start_epoch=$((today_midnight - REPORT_BUSIEST_DAY_RANGE * 86400))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local awk_result
|
local busiest_row
|
||||||
awk_result=$(echo "$HISTORY_CACHE" | awk -F'|' -v s="$start_epoch" -v e="$end_epoch" -v bs="$busiest_start_epoch" '
|
busiest_row=$(db_history_busiest_day "$busiest_start_epoch" "$end_epoch")
|
||||||
$1 >= s && $1 <= e {
|
if [[ -n "$busiest_row" ]]; then
|
||||||
action = $3
|
local busiest_raw busiest_cnt
|
||||||
if (action == "BAN") {
|
IFS='|' read -r busiest_raw busiest_cnt <<< "$busiest_row"
|
||||||
bans++
|
|
||||||
ip_count[$4]++
|
|
||||||
ip_seen[$4] = 1
|
|
||||||
dom = $5
|
|
||||||
if (dom != "" && dom != "-") dom_count[dom]++
|
|
||||||
proto = $8
|
|
||||||
if (proto == "" || proto == "-") proto = "unbekannt"
|
|
||||||
proto_count[proto]++
|
|
||||||
if (tolower($7) ~ /permanent/) perm++
|
|
||||||
rsn = tolower($9)
|
|
||||||
if (rsn ~ /rate.limit/) rl++
|
|
||||||
if (rsn ~ /subdomain.flood/) sf++
|
|
||||||
if (rsn ~ /external.blocklist/) eb++
|
|
||||||
# Zirkulärer Puffer für die letzten 10 Sperren
|
|
||||||
recent[bans % 10] = $2 "|" $3 "|" $4 "|" $5 "|" $6 "|" $7 "|" $8 "|" $9
|
|
||||||
} else if (action == "UNBAN") {
|
|
||||||
unbans++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Aktivster Tag: separater Zeitraum (konfigurierbar, z.B. letzte 30 Tage)
|
|
||||||
$1 >= bs && $1 <= e && $3 == "BAN" {
|
|
||||||
bday = substr($2, 1, 10)
|
|
||||||
bday_count[bday]++
|
|
||||||
}
|
|
||||||
END {
|
|
||||||
for (ip in ip_seen) unique++
|
|
||||||
busiest = ""; max_d = 0
|
|
||||||
for (d in bday_count) {
|
|
||||||
if (bday_count[d] > max_d) { max_d = bday_count[d]; busiest = d; busiest_cnt = bday_count[d] }
|
|
||||||
}
|
|
||||||
print "BANS=" (bans+0)
|
|
||||||
print "UNBANS=" (unbans+0)
|
|
||||||
print "UNIQUE=" (unique+0)
|
|
||||||
print "PERM=" (perm+0)
|
|
||||||
print "RL=" (rl+0)
|
|
||||||
print "SF=" (sf+0)
|
|
||||||
print "EB=" (eb+0)
|
|
||||||
print "BUSIEST=" busiest
|
|
||||||
print "BUSIEST_CNT=" (busiest_cnt+0)
|
|
||||||
for (ip in ip_count) print "IP\t" ip_count[ip] "\t" ip
|
|
||||||
for (d in dom_count) print "DOMAIN\t" dom_count[d] "\t" d
|
|
||||||
for (p in proto_count) print "PROTO\t" proto_count[p] "\t" p
|
|
||||||
n = (bans < 10) ? bans : 10
|
|
||||||
for (i = 0; i < n; i++) {
|
|
||||||
idx = (bans - i) % 10
|
|
||||||
print "RECENT\t" recent[idx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
')
|
|
||||||
|
|
||||||
# Einfache Kennzahlen aus dem awk-Ergebnis extrahieren
|
|
||||||
TOTAL_BANS=$( echo "$awk_result" | awk -F= '$1=="BANS" {print $2; exit}')
|
|
||||||
TOTAL_UNBANS=$( echo "$awk_result" | awk -F= '$1=="UNBANS" {print $2; exit}')
|
|
||||||
UNIQUE_IPS=$( echo "$awk_result" | awk -F= '$1=="UNIQUE" {print $2; exit}')
|
|
||||||
PERMANENT_BANS=$(echo "$awk_result" | awk -F= '$1=="PERM" {print $2; exit}')
|
|
||||||
RATELIMIT_BANS=$( echo "$awk_result" | awk -F= '$1=="RL" {print $2; exit}')
|
|
||||||
SUBDOMAIN_FLOOD_BANS=$( echo "$awk_result" | awk -F= '$1=="SF" {print $2; exit}')
|
|
||||||
EXTERNAL_BLOCKLIST_BANS=$(echo "$awk_result" | awk -F= '$1=="EB" {print $2; exit}')
|
|
||||||
|
|
||||||
local busiest_raw
|
|
||||||
busiest_raw=$(echo "$awk_result" | awk -F= '$1=="BUSIEST" {print $2; exit}')
|
|
||||||
local busiest_cnt
|
|
||||||
busiest_cnt=$(echo "$awk_result" | awk -F= '$1=="BUSIEST_CNT" {print $2; exit}')
|
|
||||||
if [[ -n "$busiest_raw" ]]; then
|
|
||||||
local busiest_formatted
|
local busiest_formatted
|
||||||
busiest_formatted=$(date -d "$busiest_raw" '+%d.%m.%Y' 2>/dev/null || echo "$busiest_raw")
|
busiest_formatted=$(date -d "$busiest_raw" '+%d.%m.%Y' 2>/dev/null || echo "$busiest_raw")
|
||||||
BUSIEST_DAY="${busiest_formatted} (${busiest_cnt})"
|
BUSIEST_DAY="${busiest_formatted} (${busiest_cnt})"
|
||||||
@@ -421,28 +268,22 @@ calculate_stats() {
|
|||||||
BUSIEST_DAY="–"
|
BUSIEST_DAY="–"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Dynamisches Label für den aktivsten Tag
|
|
||||||
if [[ "$REPORT_BUSIEST_DAY_RANGE" == "0" || -z "$REPORT_BUSIEST_DAY_RANGE" ]]; then
|
if [[ "$REPORT_BUSIEST_DAY_RANGE" == "0" || -z "$REPORT_BUSIEST_DAY_RANGE" ]]; then
|
||||||
BUSIEST_DAY_LABEL="Aktivster Tag"
|
BUSIEST_DAY_LABEL="Aktivster Tag"
|
||||||
else
|
else
|
||||||
BUSIEST_DAY_LABEL="Aktivster Tag (${REPORT_BUSIEST_DAY_RANGE} Tage)"
|
BUSIEST_DAY_LABEL="Aktivster Tag (${REPORT_BUSIEST_DAY_RANGE} Tage)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Top-Listen: Tab-getrennte Felder sortieren und in das erwartete Format bringen
|
# Top-Listen per SQL (Ausgabe: "count|value" → umformatieren zu "count value")
|
||||||
TOP10_IPS=$( echo "$awk_result" | awk -F'\t' '$1=="IP" {print $2 " " $3}' | sort -rn | head -10)
|
TOP10_IPS=$(db_history_top_ips "$start_epoch" "$end_epoch" 10 | sed 's/|/ /')
|
||||||
TOP10_DOMAINS=$(echo "$awk_result" | awk -F'\t' '$1=="DOMAIN" {print $2 " " $3}' | sort -rn | head -10)
|
TOP10_DOMAINS=$(db_history_top_domains "$start_epoch" "$end_epoch" 10 | sed 's/|/ /')
|
||||||
PROTOCOL_STATS=$(echo "$awk_result" | awk -F'\t' '$1=="PROTO" {print $2 " " $3}' | sort -rn)
|
PROTOCOL_STATS=$(db_history_protocol_stats "$start_epoch" "$end_epoch" | sed 's/|/ /')
|
||||||
RECENT_BANS=$( echo "$awk_result" | awk -F'\t' '$1=="RECENT" {print $2}')
|
RECENT_BANS=$(db_history_recent_bans "$start_epoch" "$end_epoch" 10)
|
||||||
|
|
||||||
# Aktuell aktive Sperren (aus State-Dateien)
|
# Aktuell aktive Sperren aus der Datenbank
|
||||||
ACTIVE_BANS=0
|
ACTIVE_BANS=$(db_ban_count)
|
||||||
if [[ -d "$STATE_DIR" ]]; then
|
|
||||||
for f in "${STATE_DIR}"/*.ban; do
|
|
||||||
[[ -f "$f" ]] && ACTIVE_BANS=$((ACTIVE_BANS + 1))
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# AbuseIPDB Reports – zeitraum-gefiltert aus der Logdatei via awk+mktime
|
# AbuseIPDB Reports – zeitraum-gefiltert aus der Logdatei
|
||||||
ABUSEIPDB_REPORTS=0
|
ABUSEIPDB_REPORTS=0
|
||||||
if [[ -f "$LOG_FILE" ]]; then
|
if [[ -f "$LOG_FILE" ]]; then
|
||||||
ABUSEIPDB_REPORTS=$(grep "AbuseIPDB:.*erfolgreich gemeldet" "$LOG_FILE" 2>/dev/null | \
|
ABUSEIPDB_REPORTS=$(grep "AbuseIPDB:.*erfolgreich gemeldet" "$LOG_FILE" 2>/dev/null | \
|
||||||
@@ -1081,12 +922,12 @@ send_test_email() {
|
|||||||
errors=$((errors + 1))
|
errors=$((errors + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 5. Ban-History prüfen
|
# 5. Datenbank prüfen
|
||||||
echo -n " 5) Ban-History ... "
|
echo -n " 5) Datenbank ... "
|
||||||
if [[ -f "$BAN_HISTORY_FILE" ]]; then
|
if [[ -f "$DB_FILE" ]]; then
|
||||||
local lines
|
local entries
|
||||||
lines=$(grep -vc '^#' "$BAN_HISTORY_FILE" 2>/dev/null || echo "0")
|
entries=$(db_history_count 2>/dev/null || echo "0")
|
||||||
echo "✅ vorhanden ($lines Einträge)"
|
echo "✅ vorhanden ($entries History-Einträge)"
|
||||||
else
|
else
|
||||||
echo "⚠️ nicht vorhanden (Report wird leer sein – das ist OK für einen Test)"
|
echo "⚠️ nicht vorhanden (Report wird leer sein – das ist OK für einen Test)"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -17,52 +17,29 @@ if [[ ! -f "$CONFIG_FILE" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
|
# shellcheck source=db.sh
|
||||||
|
source "${SCRIPT_DIR}/db.sh"
|
||||||
|
|
||||||
BAN_HISTORY_FILE="${BAN_HISTORY_FILE:-/var/log/adguard-shield-bans.log}"
|
|
||||||
LOG_PREFIX="[$(date '+%Y-%m-%d %H:%M:%S')] [UNBAN-TIMER]"
|
LOG_PREFIX="[$(date '+%Y-%m-%d %H:%M:%S')] [UNBAN-TIMER]"
|
||||||
NOW=$(date '+%s')
|
|
||||||
|
|
||||||
# History-Eintrag schreiben
|
# Datenbank initialisieren
|
||||||
log_ban_history() {
|
mkdir -p "${STATE_DIR}"
|
||||||
local action="$1"
|
db_init
|
||||||
local client_ip="$2"
|
|
||||||
local domain="${3:-}"
|
|
||||||
local count="${4:-}"
|
|
||||||
local reason="${5:-}"
|
|
||||||
local protocol="${6:-}"
|
|
||||||
local timestamp
|
|
||||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
||||||
|
|
||||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
|
||||||
echo "# AdGuard Shield - Ban History" > "$BAN_HISTORY_FILE"
|
|
||||||
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | PROTOKOLL | GRUND" >> "$BAN_HISTORY_FILE"
|
|
||||||
echo "#────────────────────────────────────────────────────────────────────────────────────────────────" >> "$BAN_HISTORY_FILE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[[ -z "$protocol" ]] && protocol="-"
|
|
||||||
|
|
||||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
|
|
||||||
"$timestamp" "$action" "$client_ip" "${domain:--}" "${count:--}" "-" "$protocol" "${reason:-expired}" \
|
|
||||||
>> "$BAN_HISTORY_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
unban_count=0
|
unban_count=0
|
||||||
|
|
||||||
for state_file in "${STATE_DIR}"/*.ban; do
|
# Abgelaufene Sperren aus der Datenbank abfragen
|
||||||
[[ -f "$state_file" ]] || continue
|
expired_ips=$(db_ban_get_expired)
|
||||||
|
|
||||||
ban_until_epoch=$(grep '^BAN_UNTIL_EPOCH=' "$state_file" | cut -d= -f2)
|
if [[ -n "$expired_ips" ]]; then
|
||||||
client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2)
|
while IFS= read -r client_ip; do
|
||||||
domain=$(grep '^DOMAIN=' "$state_file" | cut -d= -f2)
|
[[ -z "$client_ip" ]] && continue
|
||||||
is_permanent=$(grep '^IS_PERMANENT=' "$state_file" | cut -d= -f2)
|
|
||||||
protocol=$(grep '^PROTOCOL=' "$state_file" | cut -d= -f2)
|
|
||||||
|
|
||||||
# Permanente Sperren nicht automatisch aufheben
|
# Domain und Protokoll für History-Eintrag holen
|
||||||
if [[ "$is_permanent" == "true" || "$ban_until_epoch" == "0" ]]; then
|
local_ban_data=$(db_ban_get "$client_ip")
|
||||||
continue
|
domain=$(echo "$local_ban_data" | cut -d'|' -f2)
|
||||||
fi
|
protocol=$(echo "$local_ban_data" | cut -d'|' -f10)
|
||||||
|
|
||||||
if [[ -n "$ban_until_epoch" && "$NOW" -ge "$ban_until_epoch" ]]; then
|
|
||||||
echo "$LOG_PREFIX Entsperre abgelaufene Sperre: $client_ip" >> "$LOG_FILE"
|
echo "$LOG_PREFIX Entsperre abgelaufene Sperre: $client_ip" >> "$LOG_FILE"
|
||||||
|
|
||||||
# iptables Regel entfernen
|
# iptables Regel entfernen
|
||||||
@@ -73,12 +50,12 @@ for state_file in "${STATE_DIR}"/*.ban; do
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Ban-History Eintrag
|
# Ban-History Eintrag
|
||||||
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "expired-cron" "${protocol:-}"
|
db_history_add "UNBAN" "$client_ip" "${domain:--}" "-" "expired-cron" "-" "${protocol:-}"
|
||||||
|
|
||||||
rm -f "$state_file"
|
db_ban_delete "$client_ip"
|
||||||
unban_count=$((unban_count + 1))
|
unban_count=$((unban_count + 1))
|
||||||
|
done <<< "$expired_ips"
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
if [[ $unban_count -gt 0 ]]; then
|
if [[ $unban_count -gt 0 ]]; then
|
||||||
echo "$LOG_PREFIX $unban_count Sperren aufgehoben" >> "$LOG_FILE"
|
echo "$LOG_PREFIX $unban_count Sperren aufgehoben" >> "$LOG_FILE"
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ do_uninstall() {
|
|||||||
rm -f "$INSTALL_DIR/report-generator.sh"
|
rm -f "$INSTALL_DIR/report-generator.sh"
|
||||||
rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh"
|
rm -f "$INSTALL_DIR/adguard-shield-watchdog.sh"
|
||||||
rm -f "$INSTALL_DIR/geoip-worker.sh"
|
rm -f "$INSTALL_DIR/geoip-worker.sh"
|
||||||
|
rm -f "$INSTALL_DIR/db.sh"
|
||||||
rm -f "$INSTALL_DIR/uninstall.sh"
|
rm -f "$INSTALL_DIR/uninstall.sh"
|
||||||
rm -rf "$INSTALL_DIR/templates"
|
rm -rf "$INSTALL_DIR/templates"
|
||||||
rm -rf "$INSTALL_DIR/geoip"
|
rm -rf "$INSTALL_DIR/geoip"
|
||||||
|
|||||||
Reference in New Issue
Block a user