1755 lines
52 KiB
Bash
1755 lines
52 KiB
Bash
#!/bin/bash
|
||
# ============================================================================
|
||
# CrowdSec Manager
|
||
# ============================================================================
|
||
# Verwaltet automatisch eine CrowdSec-Allowlist und bietet interaktive
|
||
# CrowdSec-Administration über ein Menü.
|
||
#
|
||
# Autor: Patrick Asmus
|
||
# www.patrick-asmus.de
|
||
# Lizenz: MIT
|
||
# ============================================================================
|
||
|
||
set -euo pipefail
|
||
|
||
# ----------------------------------------------------------------------------
|
||
# Globale Variablen
|
||
# ----------------------------------------------------------------------------
|
||
SCRIPT_NAME="$(basename "$0")"
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
SCRIPT_VERSION="0.2.1"
|
||
CONFIG_FILE="${SCRIPT_DIR}/config/crowdsec-manager.conf"
|
||
|
||
# Farben (werden ggf. deaktiviert wenn nicht im Terminal)
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[0;33m'
|
||
BLUE='\033[0;34m'
|
||
CYAN='\033[0;36m'
|
||
MAGENTA='\033[0;35m'
|
||
BOLD='\033[1m'
|
||
DIM='\033[2m'
|
||
NC='\033[0m'
|
||
|
||
# Statistiken
|
||
STAT_ADDED=0
|
||
STAT_REMOVED=0
|
||
STAT_UNCHANGED=0
|
||
STAT_ERRORS=0
|
||
STAT_RESOLVED_DOMAINS=0
|
||
STAT_TOTAL_IPS=0
|
||
REPORT_MESSAGES=()
|
||
|
||
# Temporäre Dateien
|
||
TEMP_DIR=""
|
||
CURRENT_IPS_FILE=""
|
||
NEW_IPS_FILE=""
|
||
|
||
# ============================================================================
|
||
# TEIL 1: HILFSFUNKTIONEN
|
||
# ============================================================================
|
||
|
||
# Farben deaktivieren wenn nicht im Terminal oder wenn als Cron ausgeführt
|
||
disable_colors_if_needed() {
|
||
if [[ ! -t 1 ]] || [[ -n "${CRON:-}" ]]; then
|
||
RED=""
|
||
GREEN=""
|
||
YELLOW=""
|
||
BLUE=""
|
||
CYAN=""
|
||
MAGENTA=""
|
||
BOLD=""
|
||
DIM=""
|
||
NC=""
|
||
fi
|
||
}
|
||
|
||
# Trennlinie zeichnen
|
||
draw_line() {
|
||
local char="${1:--}"
|
||
local width="${2:-60}"
|
||
printf '%*s\n' "$width" '' | tr ' ' "$char"
|
||
}
|
||
|
||
# Logging-Funktion
|
||
log() {
|
||
local level="$1"
|
||
shift
|
||
local message="$*"
|
||
local timestamp
|
||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||
|
||
local -A levels=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
|
||
local current_level="${levels[${LOG_LEVEL:-INFO}]:-1}"
|
||
local msg_level="${levels[$level]:-1}"
|
||
|
||
if [[ $msg_level -lt $current_level ]]; then
|
||
return 0
|
||
fi
|
||
|
||
local color=""
|
||
case "$level" in
|
||
DEBUG) color="$CYAN" ;;
|
||
INFO) color="$GREEN" ;;
|
||
WARN) color="$YELLOW" ;;
|
||
ERROR) color="$RED" ;;
|
||
esac
|
||
|
||
local log_line="[${timestamp}] [${level}] ${message}"
|
||
local colored_line="${color}[${timestamp}] [${BOLD}${level}${NC}${color}] ${message}${NC}"
|
||
|
||
echo -e "$colored_line"
|
||
|
||
if [[ -n "${LOG_FILE:-}" ]]; then
|
||
echo "$log_line" >> "$LOG_FILE" 2>/dev/null || true
|
||
fi
|
||
}
|
||
|
||
# Log-Rotation
|
||
rotate_logs() {
|
||
if [[ -z "${LOG_FILE:-}" ]] || [[ "${LOG_MAX_SIZE_KB:-0}" -eq 0 ]]; then
|
||
return 0
|
||
fi
|
||
|
||
if [[ ! -f "$LOG_FILE" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
local size_kb
|
||
size_kb=$(du -k "$LOG_FILE" 2>/dev/null | cut -f1)
|
||
|
||
if [[ "$size_kb" -ge "$LOG_MAX_SIZE_KB" ]]; then
|
||
log "INFO" "Log-Rotation wird durchgeführt (${size_kb}KB >= ${LOG_MAX_SIZE_KB}KB)"
|
||
|
||
for ((i = LOG_ROTATE_COUNT - 1; i >= 1; i--)); do
|
||
local prev=$((i - 1))
|
||
if [[ -f "${LOG_FILE}.${prev}" ]]; then
|
||
mv "${LOG_FILE}.${prev}" "${LOG_FILE}.${i}"
|
||
fi
|
||
done
|
||
|
||
if [[ -f "$LOG_FILE" ]]; then
|
||
mv "$LOG_FILE" "${LOG_FILE}.0"
|
||
fi
|
||
|
||
touch "$LOG_FILE"
|
||
fi
|
||
}
|
||
|
||
# Temporäre Dateien
|
||
setup_temp() {
|
||
TEMP_DIR="$(mktemp -d /tmp/crowdsec-manager-XXXXXX)"
|
||
CURRENT_IPS_FILE="${TEMP_DIR}/current_ips.txt"
|
||
NEW_IPS_FILE="${TEMP_DIR}/new_ips.txt"
|
||
touch "$CURRENT_IPS_FILE" "$NEW_IPS_FILE"
|
||
}
|
||
|
||
cleanup_temp() {
|
||
if [[ -n "${TEMP_DIR:-}" ]] && [[ -d "$TEMP_DIR" ]]; then
|
||
rm -rf "$TEMP_DIR"
|
||
fi
|
||
}
|
||
|
||
# Lock-File Management
|
||
acquire_lock() {
|
||
if [[ "${LOCK_ENABLED:-false}" != "true" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
local lock_file="${LOCK_FILE:-/tmp/crowdsec-manager.lock}"
|
||
local timeout="${LOCK_TIMEOUT:-300}"
|
||
local waited=0
|
||
|
||
while [[ -f "$lock_file" ]]; do
|
||
local lock_pid
|
||
lock_pid=$(cat "$lock_file" 2>/dev/null || echo "")
|
||
|
||
if [[ -n "$lock_pid" ]] && ! kill -0 "$lock_pid" 2>/dev/null; then
|
||
log "WARN" "Verwaistes Lock-File gefunden (PID: $lock_pid). Wird entfernt."
|
||
rm -f "$lock_file"
|
||
break
|
||
fi
|
||
|
||
if [[ "$timeout" -eq 0 ]] || [[ "$waited" -ge "$timeout" ]]; then
|
||
log "ERROR" "Lock-File existiert bereits (PID: $lock_pid). Abbruch."
|
||
return 1
|
||
fi
|
||
|
||
log "DEBUG" "Warte auf Lock-Freigabe... (${waited}s/${timeout}s)"
|
||
sleep 5
|
||
waited=$((waited + 5))
|
||
done
|
||
|
||
echo $$ > "$lock_file"
|
||
log "DEBUG" "Lock erworben (PID: $$)"
|
||
}
|
||
|
||
release_lock() {
|
||
if [[ "${LOCK_ENABLED:-false}" != "true" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
local lock_file="${LOCK_FILE:-/tmp/crowdsec-manager.lock}"
|
||
if [[ -f "$lock_file" ]]; then
|
||
local lock_pid
|
||
lock_pid=$(cat "$lock_file" 2>/dev/null || echo "")
|
||
if [[ "$lock_pid" == "$$" ]]; then
|
||
rm -f "$lock_file"
|
||
log "DEBUG" "Lock freigegeben (PID: $$)"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# TEIL 2: VALIDIERUNG
|
||
# ============================================================================
|
||
|
||
is_valid_ipv4() {
|
||
local ip="$1"
|
||
local regex='^([0-9]{1,3}\.){3}[0-9]{1,3}$'
|
||
if [[ "$ip" =~ $regex ]]; then
|
||
local IFS='.'
|
||
read -ra octets <<< "$ip"
|
||
for octet in "${octets[@]}"; do
|
||
if [[ "$octet" -gt 255 ]]; then
|
||
return 1
|
||
fi
|
||
done
|
||
return 0
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
is_valid_ipv6() {
|
||
local ip="$1"
|
||
if [[ "$ip" =~ ^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$ ]] || \
|
||
[[ "$ip" =~ ^([0-9a-fA-F]{0,4}:){1,7}:$ ]] || \
|
||
[[ "$ip" =~ ^::([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$ ]] || \
|
||
[[ "$ip" =~ ^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$ ]]; then
|
||
return 0
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
is_cidr() {
|
||
local entry="$1"
|
||
[[ "$entry" =~ / ]]
|
||
}
|
||
|
||
is_ip_or_cidr() {
|
||
local entry="$1"
|
||
|
||
if is_cidr "$entry"; then
|
||
local ip="${entry%/*}"
|
||
local mask="${entry#*/}"
|
||
if is_valid_ipv4 "$ip"; then
|
||
[[ "$mask" -ge 0 && "$mask" -le 32 ]] 2>/dev/null && return 0
|
||
elif is_valid_ipv6 "$ip"; then
|
||
[[ "$mask" -ge 0 && "$mask" -le 128 ]] 2>/dev/null && return 0
|
||
fi
|
||
return 1
|
||
fi
|
||
|
||
is_valid_ipv4 "$entry" || is_valid_ipv6 "$entry"
|
||
}
|
||
|
||
is_domain() {
|
||
local entry="$1"
|
||
if ! is_valid_ipv4 "$entry" && ! is_valid_ipv6 "$entry" && ! is_cidr "$entry"; then
|
||
if [[ "$entry" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$ ]]; then
|
||
return 0
|
||
fi
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
# ============================================================================
|
||
# TEIL 3: DNS-AUFLÖSUNG
|
||
# ============================================================================
|
||
|
||
resolve_domain() {
|
||
local domain="$1"
|
||
local resolved_ips=()
|
||
|
||
log "DEBUG" "Löse Domain auf: $domain"
|
||
|
||
local dig_opts="+short +timeout=${DNS_TIMEOUT:-5} +retry=${DNS_RETRIES:-3}"
|
||
if [[ -n "${DNS_SERVER:-}" ]]; then
|
||
dig_opts+=" @${DNS_SERVER}"
|
||
fi
|
||
|
||
# IPv4 (A Records)
|
||
local ipv4_results
|
||
ipv4_results=$(dig $dig_opts A "$domain" 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' || true)
|
||
|
||
if [[ -n "$ipv4_results" ]]; then
|
||
while IFS= read -r ip; do
|
||
if is_valid_ipv4 "$ip"; then
|
||
resolved_ips+=("$ip")
|
||
log "DEBUG" " IPv4 aufgelöst: $domain -> $ip"
|
||
fi
|
||
done <<< "$ipv4_results"
|
||
fi
|
||
|
||
# IPv6 (AAAA Records)
|
||
if [[ "${RESOLVE_IPV6:-true}" == "true" ]]; then
|
||
local ipv6_results
|
||
ipv6_results=$(dig $dig_opts AAAA "$domain" 2>/dev/null | grep -E '^[0-9a-fA-F:]+$' | grep ':' || true)
|
||
|
||
if [[ -n "$ipv6_results" ]]; then
|
||
while IFS= read -r ip; do
|
||
if is_valid_ipv6 "$ip"; then
|
||
resolved_ips+=("$ip")
|
||
log "DEBUG" " IPv6 aufgelöst: $domain -> $ip"
|
||
fi
|
||
done <<< "$ipv6_results"
|
||
fi
|
||
fi
|
||
|
||
if [[ ${#resolved_ips[@]} -eq 0 ]]; then
|
||
log "WARN" "Keine IPs für Domain '$domain' aufgelöst"
|
||
STAT_ERRORS=$((STAT_ERRORS + 1))
|
||
REPORT_MESSAGES+=("WARN: Keine Auflösung für $domain")
|
||
return 1
|
||
fi
|
||
|
||
STAT_RESOLVED_DOMAINS=$((STAT_RESOLVED_DOMAINS + 1))
|
||
printf '%s\n' "${resolved_ips[@]}"
|
||
}
|
||
|
||
resolve_all_entries() {
|
||
log "INFO" "Starte DNS-Auflösung für ${#ALLOWLIST_ENTRIES[@]} Einträge..."
|
||
|
||
for entry in "${ALLOWLIST_ENTRIES[@]}"; do
|
||
entry=$(echo "$entry" | sed 's/#.*$//' | xargs)
|
||
[[ -z "$entry" ]] && continue
|
||
|
||
if is_ip_or_cidr "$entry"; then
|
||
log "DEBUG" "Direkter Eintrag: $entry"
|
||
echo "$entry" >> "$NEW_IPS_FILE"
|
||
STAT_TOTAL_IPS=$((STAT_TOTAL_IPS + 1))
|
||
elif is_domain "$entry"; then
|
||
local resolved
|
||
if resolved=$(resolve_domain "$entry"); then
|
||
while IFS= read -r ip; do
|
||
echo "$ip" >> "$NEW_IPS_FILE"
|
||
STAT_TOTAL_IPS=$((STAT_TOTAL_IPS + 1))
|
||
done <<< "$resolved"
|
||
fi
|
||
else
|
||
log "WARN" "Unbekannter Eintragstyp: '$entry' - wird übersprungen"
|
||
STAT_ERRORS=$((STAT_ERRORS + 1))
|
||
fi
|
||
done
|
||
|
||
if [[ -f "$NEW_IPS_FILE" ]]; then
|
||
sort -u "$NEW_IPS_FILE" -o "$NEW_IPS_FILE"
|
||
STAT_TOTAL_IPS=$(wc -l < "$NEW_IPS_FILE")
|
||
fi
|
||
|
||
log "INFO" "${STAT_TOTAL_IPS} eindeutige IPs/CIDRs aus ${STAT_RESOLVED_DOMAINS} aufgelösten Domains ermittelt"
|
||
}
|
||
|
||
# ============================================================================
|
||
# TEIL 4: CROWDSEC INTERAKTION (Kern-Funktionen)
|
||
# ============================================================================
|
||
|
||
# CrowdSec CLI Wrapper
|
||
cscli_exec() {
|
||
eval "$CSCLI_CMD $*"
|
||
}
|
||
|
||
crowdsec_health_check() {
|
||
if [[ "${HEALTH_CHECK:-true}" != "true" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
log "INFO" "Prüfe CrowdSec-Erreichbarkeit..."
|
||
|
||
if ! cscli_exec "version" &>/dev/null; then
|
||
log "ERROR" "CrowdSec ist nicht erreichbar! Befehl: '$CSCLI_CMD version'"
|
||
REPORT_MESSAGES+=("ERROR: CrowdSec nicht erreichbar")
|
||
return 1
|
||
fi
|
||
|
||
log "INFO" "CrowdSec ist erreichbar"
|
||
return 0
|
||
}
|
||
|
||
ensure_allowlist_exists() {
|
||
# Prüfe ob die Allowlist bereits existiert
|
||
if cscli_exec "allowlists inspect ${ALLOWLIST_NAME}" &>/dev/null; then
|
||
log "DEBUG" "Allowlist '${ALLOWLIST_NAME}' existiert bereits"
|
||
return 0
|
||
fi
|
||
|
||
# Allowlist erstellen
|
||
log "INFO" "Erstelle Allowlist '${ALLOWLIST_NAME}'..."
|
||
if cscli_exec "allowlists create ${ALLOWLIST_NAME} -d '${ALLOWLIST_DESCRIPTION}'" 2>&1; then
|
||
log "INFO" "Allowlist '${ALLOWLIST_NAME}' erfolgreich erstellt"
|
||
return 0
|
||
else
|
||
log "ERROR" "Fehler beim Erstellen der Allowlist '${ALLOWLIST_NAME}'"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
get_current_allowlist() {
|
||
log "INFO" "Rufe aktuelle Allowlist '${ALLOWLIST_NAME}' ab..."
|
||
|
||
if ! ensure_allowlist_exists; then
|
||
return 1
|
||
fi
|
||
|
||
# Aktuelle IPs aus der Allowlist auslesen
|
||
local inspect_output
|
||
inspect_output=$(cscli_exec "allowlists inspect ${ALLOWLIST_NAME} -o json" 2>/dev/null || echo "null")
|
||
|
||
if [[ "$inspect_output" == "null" ]] || [[ -z "$inspect_output" ]]; then
|
||
log "DEBUG" "Keine bestehenden Einträge in der Allowlist gefunden"
|
||
return 0
|
||
fi
|
||
|
||
# IPs/CIDRs aus dem JSON extrahieren (Feld "items" -> "value")
|
||
echo "$inspect_output" | grep -oP '"value"\s*:\s*"\K[^"]+' | sort -u > "$CURRENT_IPS_FILE" 2>/dev/null || true
|
||
|
||
local count
|
||
count=$(wc -l < "$CURRENT_IPS_FILE" 2>/dev/null || echo "0")
|
||
log "INFO" "${count} bestehende Einträge in der Allowlist gefunden"
|
||
}
|
||
|
||
add_to_allowlist() {
|
||
local ip="$1"
|
||
|
||
log "DEBUG" "Füge hinzu: $ip"
|
||
|
||
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
||
log "INFO" "[DRY-RUN] Würde hinzufügen: $ip"
|
||
return 0
|
||
fi
|
||
|
||
local result
|
||
if result=$(cscli_exec "allowlists add ${ALLOWLIST_NAME} $ip -d '${ALLOWLIST_REASON}'" 2>&1); then
|
||
log "DEBUG" "Erfolgreich hinzugefügt: $ip"
|
||
STAT_ADDED=$((STAT_ADDED + 1))
|
||
return 0
|
||
else
|
||
log "ERROR" "Fehler beim Hinzufügen von $ip: $result"
|
||
STAT_ERRORS=$((STAT_ERRORS + 1))
|
||
REPORT_MESSAGES+=("ERROR: Konnte $ip nicht hinzufügen: $result")
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
remove_from_allowlist() {
|
||
local ip="$1"
|
||
|
||
log "DEBUG" "Entferne: $ip"
|
||
|
||
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
||
log "INFO" "[DRY-RUN] Würde entfernen: $ip"
|
||
return 0
|
||
fi
|
||
|
||
local result
|
||
if result=$(cscli_exec "allowlists remove ${ALLOWLIST_NAME} $ip" 2>&1); then
|
||
log "DEBUG" "Erfolgreich entfernt: $ip"
|
||
STAT_REMOVED=$((STAT_REMOVED + 1))
|
||
return 0
|
||
else
|
||
log "ERROR" "Fehler beim Entfernen von $ip: $result"
|
||
STAT_ERRORS=$((STAT_ERRORS + 1))
|
||
REPORT_MESSAGES+=("ERROR: Konnte $ip nicht entfernen: $result")
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
sync_allowlist() {
|
||
log "INFO" "Synchronisiere Allowlist..."
|
||
|
||
local to_add
|
||
to_add=$(comm -23 "$NEW_IPS_FILE" "$CURRENT_IPS_FILE" 2>/dev/null || true)
|
||
|
||
local to_remove=""
|
||
if [[ "${AUTO_CLEANUP:-true}" == "true" ]]; then
|
||
to_remove=$(comm -13 "$NEW_IPS_FILE" "$CURRENT_IPS_FILE" 2>/dev/null || true)
|
||
fi
|
||
|
||
local unchanged
|
||
unchanged=$(comm -12 "$NEW_IPS_FILE" "$CURRENT_IPS_FILE" 2>/dev/null || true)
|
||
if [[ -n "$unchanged" ]]; then
|
||
STAT_UNCHANGED=$(echo "$unchanged" | wc -l)
|
||
fi
|
||
|
||
# Keine Änderungen erkannt → frühzeitig zurückkehren
|
||
if [[ -z "$to_add" ]] && [[ -z "$to_remove" ]]; then
|
||
log "INFO" "Keine Änderungen erkannt - Allowlist ist bereits aktuell"
|
||
return 1
|
||
fi
|
||
|
||
if [[ -n "$to_add" ]]; then
|
||
log "INFO" "Neue IPs zum Hinzufügen gefunden:"
|
||
while IFS= read -r ip; do
|
||
[[ -z "$ip" ]] && continue
|
||
log "INFO" " + $ip"
|
||
add_to_allowlist "$ip"
|
||
done <<< "$to_add"
|
||
fi
|
||
|
||
if [[ -n "$to_remove" ]]; then
|
||
log "INFO" "Veraltete IPs zum Entfernen gefunden:"
|
||
while IFS= read -r ip; do
|
||
[[ -z "$ip" ]] && continue
|
||
log "INFO" " - $ip"
|
||
remove_from_allowlist "$ip"
|
||
done <<< "$to_remove"
|
||
fi
|
||
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# TEIL 5: BACKUP
|
||
# ============================================================================
|
||
|
||
create_backup() {
|
||
if [[ "${BACKUP_ENABLED:-false}" != "true" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
log "INFO" "Erstelle Backup..."
|
||
|
||
local backup_dir="${BACKUP_DIR:-/var/backup/crowdsec-manager}"
|
||
mkdir -p "$backup_dir"
|
||
|
||
local backup_file="${backup_dir}/allowlist_$(date '+%Y%m%d_%H%M%S').bak"
|
||
|
||
if [[ -f "$CURRENT_IPS_FILE" ]]; then
|
||
cp "$CURRENT_IPS_FILE" "$backup_file"
|
||
log "INFO" "Backup erstellt: $backup_file"
|
||
fi
|
||
|
||
local retain=${BACKUP_RETAIN_COUNT:-7}
|
||
local backups
|
||
backups=$(find "$backup_dir" -name "allowlist_*.bak" -type f 2>/dev/null | sort -r)
|
||
local count=0
|
||
|
||
while IFS= read -r file; do
|
||
count=$((count + 1))
|
||
if [[ $count -gt $retain ]]; then
|
||
rm -f "$file"
|
||
log "DEBUG" "Altes Backup gelöscht: $file"
|
||
fi
|
||
done <<< "$backups"
|
||
}
|
||
|
||
# ============================================================================
|
||
# TEIL 6: BENACHRICHTIGUNGEN (Ntfy, Gotify, E-Mail, Desktop)
|
||
# ============================================================================
|
||
|
||
build_notification_message() {
|
||
local status="OK"
|
||
if [[ $STAT_ERRORS -gt 0 ]]; then
|
||
status="FEHLER"
|
||
fi
|
||
|
||
local msg=""
|
||
msg+="=== CrowdSec Manager Report ===\n"
|
||
msg+="Status: $status\n"
|
||
msg+="Zeitpunkt: $(date '+%Y-%m-%d %H:%M:%S')\n"
|
||
msg+="---\n"
|
||
msg+="Domains aufgelöst: $STAT_RESOLVED_DOMAINS\n"
|
||
msg+="IPs gesamt: $STAT_TOTAL_IPS\n"
|
||
msg+="Hinzugefügt: $STAT_ADDED\n"
|
||
msg+="Entfernt: $STAT_REMOVED\n"
|
||
msg+="Unverändert: $STAT_UNCHANGED\n"
|
||
msg+="Fehler: $STAT_ERRORS\n"
|
||
|
||
if [[ ${#REPORT_MESSAGES[@]} -gt 0 ]]; then
|
||
msg+="---\n"
|
||
msg+="Details:\n"
|
||
for m in "${REPORT_MESSAGES[@]}"; do
|
||
msg+=" $m\n"
|
||
done
|
||
fi
|
||
|
||
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
||
msg+="---\n"
|
||
msg+="HINWEIS: Dry-Run Modus - Keine Änderungen durchgeführt\n"
|
||
fi
|
||
|
||
echo -e "$msg"
|
||
}
|
||
|
||
should_notify() {
|
||
local notify_on="${NOTIFY_ON:-changes}"
|
||
case "$notify_on" in
|
||
always) return 0 ;;
|
||
changes)
|
||
if [[ $STAT_ADDED -gt 0 ]] || [[ $STAT_REMOVED -gt 0 ]] || [[ $STAT_ERRORS -gt 0 ]]; then
|
||
return 0
|
||
fi
|
||
return 1
|
||
;;
|
||
errors)
|
||
[[ $STAT_ERRORS -gt 0 ]] && return 0
|
||
return 1
|
||
;;
|
||
esac
|
||
return 1
|
||
}
|
||
|
||
send_notify_desktop() {
|
||
if [[ "${NOTIFY_DESKTOP_ENABLED:-false}" != "true" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
if ! command -v notify-send &>/dev/null; then
|
||
log "WARN" "notify-send nicht gefunden. Desktop-Benachrichtigung übersprungen."
|
||
return 1
|
||
fi
|
||
|
||
local urgency="normal"
|
||
[[ $STAT_ERRORS -gt 0 ]] && urgency="critical"
|
||
|
||
local summary="CrowdSec Allowlist: +${STAT_ADDED} -${STAT_REMOVED} (${STAT_ERRORS} Fehler)"
|
||
notify-send -u "$urgency" "CrowdSec Manager" "$summary"
|
||
log "DEBUG" "Desktop-Benachrichtigung gesendet"
|
||
}
|
||
|
||
send_notify_ntfy() {
|
||
if [[ "${NOTIFY_NTFY_ENABLED:-false}" != "true" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
if ! command -v curl &>/dev/null; then
|
||
log "WARN" "curl nicht gefunden. Ntfy-Benachrichtigung übersprungen."
|
||
return 1
|
||
fi
|
||
|
||
local message
|
||
message=$(build_notification_message)
|
||
|
||
local title="CrowdSec Manager"
|
||
local priority="${NOTIFY_NTFY_PRIORITY:-default}"
|
||
local tags="${NOTIFY_NTFY_TAGS:-shield}"
|
||
|
||
if [[ $STAT_ERRORS -gt 0 ]]; then
|
||
priority="high"
|
||
tags="warning,shield"
|
||
fi
|
||
|
||
local curl_opts=(
|
||
-s
|
||
-H "Title: $title"
|
||
-H "Priority: $priority"
|
||
-H "Tags: $tags"
|
||
)
|
||
|
||
if [[ -n "${NOTIFY_NTFY_TOKEN:-}" ]]; then
|
||
curl_opts+=(-H "Authorization: Bearer ${NOTIFY_NTFY_TOKEN}")
|
||
fi
|
||
|
||
local url="${NOTIFY_NTFY_URL:-https://ntfy.sh}/${NOTIFY_NTFY_TOPIC:-crowdsec-manager}"
|
||
|
||
if curl "${curl_opts[@]}" -d "$message" "$url" &>/dev/null; then
|
||
log "DEBUG" "Ntfy-Benachrichtigung gesendet"
|
||
else
|
||
log "WARN" "Ntfy-Benachrichtigung fehlgeschlagen"
|
||
fi
|
||
}
|
||
|
||
send_notify_gotify() {
|
||
if [[ "${NOTIFY_GOTIFY_ENABLED:-false}" != "true" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
if ! command -v curl &>/dev/null; then
|
||
log "WARN" "curl nicht gefunden. Gotify-Benachrichtigung übersprungen."
|
||
return 1
|
||
fi
|
||
|
||
local gotify_url="${NOTIFY_GOTIFY_URL:-}"
|
||
local gotify_token="${NOTIFY_GOTIFY_TOKEN:-}"
|
||
|
||
if [[ -z "$gotify_url" ]] || [[ -z "$gotify_token" ]]; then
|
||
log "WARN" "Gotify URL oder Token nicht konfiguriert."
|
||
return 1
|
||
fi
|
||
|
||
local message
|
||
message=$(build_notification_message)
|
||
|
||
local title="CrowdSec Manager"
|
||
local priority="${NOTIFY_GOTIFY_PRIORITY:-5}"
|
||
|
||
# Bei Fehlern höhere Priorität
|
||
if [[ $STAT_ERRORS -gt 0 ]]; then
|
||
priority=8
|
||
fi
|
||
|
||
local api_url="${gotify_url}/message?token=${gotify_token}"
|
||
|
||
# JSON-Payload erstellen – Nachricht für JSON escapen
|
||
local escaped_message
|
||
escaped_message=$(echo "$message" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null || \
|
||
echo "$message" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/^/"/;s/$/"/')
|
||
|
||
local escaped_title
|
||
escaped_title=$(echo "$title" | sed 's/"/\\"/g')
|
||
|
||
if curl -s -X POST "$api_url" \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"title\":\"${escaped_title}\",\"message\":${escaped_message},\"priority\":${priority}}" \
|
||
&>/dev/null; then
|
||
log "DEBUG" "Gotify-Benachrichtigung gesendet an ${gotify_url}"
|
||
else
|
||
log "WARN" "Gotify-Benachrichtigung fehlgeschlagen"
|
||
fi
|
||
}
|
||
|
||
send_notify_email() {
|
||
if [[ "${NOTIFY_EMAIL_ENABLED:-false}" != "true" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
local message
|
||
message=$(build_notification_message)
|
||
|
||
local to="${NOTIFY_EMAIL_TO:-}"
|
||
local from="${NOTIFY_EMAIL_FROM:-crowdsec@$(hostname)}"
|
||
local subject="${NOTIFY_EMAIL_SUBJECT:-CrowdSec Manager Report}"
|
||
|
||
if command -v curl &>/dev/null && [[ -n "${NOTIFY_EMAIL_SMTP_SERVER:-}" ]]; then
|
||
local smtp_url="smtp://${NOTIFY_EMAIL_SMTP_SERVER}:${NOTIFY_EMAIL_SMTP_PORT:-587}"
|
||
|
||
if [[ "${NOTIFY_EMAIL_SMTP_TLS:-true}" == "true" ]]; then
|
||
smtp_url="smtps://${NOTIFY_EMAIL_SMTP_SERVER}:${NOTIFY_EMAIL_SMTP_PORT:-465}"
|
||
fi
|
||
|
||
local email_body
|
||
email_body="From: ${from}\r\nTo: ${to}\r\nSubject: ${subject}\r\n\r\n${message}"
|
||
|
||
local curl_opts=(
|
||
-s
|
||
--url "$smtp_url"
|
||
--mail-from "$from"
|
||
--mail-rcpt "$to"
|
||
)
|
||
|
||
if [[ -n "${NOTIFY_EMAIL_SMTP_USER:-}" ]]; then
|
||
curl_opts+=(--user "${NOTIFY_EMAIL_SMTP_USER}:${NOTIFY_EMAIL_SMTP_PASS:-}")
|
||
fi
|
||
|
||
if echo -e "$email_body" | curl "${curl_opts[@]}" -T - 2>/dev/null; then
|
||
log "DEBUG" "E-Mail-Benachrichtigung gesendet an $to"
|
||
else
|
||
log "WARN" "E-Mail-Benachrichtigung fehlgeschlagen"
|
||
fi
|
||
elif command -v mail &>/dev/null; then
|
||
echo -e "$message" | mail -s "$subject" -r "$from" "$to"
|
||
log "DEBUG" "E-Mail-Benachrichtigung gesendet an $to (via mail)"
|
||
else
|
||
log "WARN" "Weder curl noch mail verfügbar. E-Mail-Benachrichtigung übersprungen."
|
||
fi
|
||
}
|
||
|
||
send_notifications() {
|
||
if [[ "${NOTIFY_ENABLED:-false}" != "true" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
if ! should_notify; then
|
||
log "DEBUG" "Keine Benachrichtigung erforderlich (Bedingung: ${NOTIFY_ON:-changes})"
|
||
return 0
|
||
fi
|
||
|
||
log "INFO" "Sende Benachrichtigungen..."
|
||
send_notify_desktop
|
||
send_notify_ntfy
|
||
send_notify_gotify
|
||
send_notify_email
|
||
}
|
||
|
||
# ============================================================================
|
||
# TEIL 7: CROWDSEC ADMIN-FUNKTIONEN (Interaktiv)
|
||
# ============================================================================
|
||
|
||
# ---[ Metriken anzeigen ]---
|
||
admin_show_metrics() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== CrowdSec Metriken ===${NC}"
|
||
draw_line "=" 60
|
||
echo ""
|
||
cscli_exec "metrics" 2>/dev/null || {
|
||
echo -e "${RED}Fehler beim Abrufen der Metriken.${NC}"
|
||
}
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Alle Decisions anzeigen ]---
|
||
admin_list_decisions() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Aktuelle Decisions ===${NC}"
|
||
draw_line "=" 60
|
||
echo ""
|
||
cscli_exec "decisions list" 2>/dev/null || {
|
||
echo -e "${YELLOW}Keine Decisions vorhanden oder Fehler beim Abrufen.${NC}"
|
||
}
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Decision für eine IP suchen ]---
|
||
admin_search_decision() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Decision suchen ===${NC}"
|
||
echo -n -e "${CYAN}IP-Adresse eingeben: ${NC}"
|
||
read -r search_ip
|
||
|
||
if [[ -z "$search_ip" ]]; then
|
||
echo -e "${YELLOW}Keine IP eingegeben.${NC}"
|
||
return
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${DIM}Suche nach Decisions für: $search_ip${NC}"
|
||
draw_line "-" 60
|
||
cscli_exec "decisions list -i '$search_ip'" 2>/dev/null || {
|
||
echo -e "${YELLOW}Keine Decisions für $search_ip gefunden.${NC}"
|
||
}
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Decision manuell hinzufügen ]---
|
||
admin_add_decision() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Decision manuell hinzufügen ===${NC}"
|
||
echo ""
|
||
|
||
# IP eingeben
|
||
echo -n -e "${CYAN}IP-Adresse oder CIDR: ${NC}"
|
||
read -r add_ip
|
||
|
||
if [[ -z "$add_ip" ]]; then
|
||
echo -e "${YELLOW}Keine IP eingegeben. Abbruch.${NC}"
|
||
return
|
||
fi
|
||
|
||
# Typ wählen
|
||
echo ""
|
||
echo -e "${BOLD}Typ auswählen:${NC}"
|
||
echo " 1) ban - IP sperren"
|
||
echo " 2) allow - IP erlauben (Allowlist)"
|
||
echo " 3) captcha - Captcha anzeigen"
|
||
echo " 4) throttle - Drosselung"
|
||
echo -n -e "${CYAN}Auswahl [1-4]: ${NC}"
|
||
read -r type_choice
|
||
|
||
local decision_type
|
||
case "$type_choice" in
|
||
1) decision_type="ban" ;;
|
||
2) decision_type="allow" ;;
|
||
3) decision_type="captcha" ;;
|
||
4) decision_type="throttle" ;;
|
||
*)
|
||
echo -e "${YELLOW}Ungültige Auswahl. Verwende 'ban'.${NC}"
|
||
decision_type="ban"
|
||
;;
|
||
esac
|
||
|
||
# Dauer eingeben
|
||
echo -n -e "${CYAN}Dauer (z.B. 10m, 1h, 24h, 7d) [Standard: 4h]: ${NC}"
|
||
read -r duration
|
||
duration="${duration:-4h}"
|
||
|
||
# Grund eingeben
|
||
echo -n -e "${CYAN}Grund (optional) [Standard: manual via script]: ${NC}"
|
||
read -r reason
|
||
reason="${reason:-manual via script}"
|
||
|
||
# Scope bestimmen
|
||
local scope="ip"
|
||
if is_cidr "$add_ip"; then
|
||
scope="range"
|
||
fi
|
||
|
||
# Zusammenfassung
|
||
echo ""
|
||
draw_line "-" 60
|
||
echo -e "${BOLD}Zusammenfassung:${NC}"
|
||
echo -e " IP/Range: ${CYAN}$add_ip${NC}"
|
||
echo -e " Typ: ${CYAN}$decision_type${NC}"
|
||
echo -e " Scope: ${CYAN}$scope${NC}"
|
||
echo -e " Dauer: ${CYAN}$duration${NC}"
|
||
echo -e " Grund: ${CYAN}$reason${NC}"
|
||
draw_line "-" 60
|
||
echo ""
|
||
|
||
echo -n -e "${YELLOW}Decision hinzufügen? [j/N]: ${NC}"
|
||
read -r confirm
|
||
if [[ "$confirm" =~ ^[jJyY]$ ]]; then
|
||
echo ""
|
||
if cscli_exec "decisions add --type '$decision_type' --scope '$scope' --value '$add_ip' --duration '$duration' --reason '$reason'" 2>&1; then
|
||
echo -e "${GREEN}Decision erfolgreich hinzugefügt!${NC}"
|
||
else
|
||
echo -e "${RED}Fehler beim Hinzufügen der Decision.${NC}"
|
||
fi
|
||
else
|
||
echo -e "${YELLOW}Abgebrochen.${NC}"
|
||
fi
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Decision entfernen ]---
|
||
admin_remove_decision() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Decision entfernen ===${NC}"
|
||
echo ""
|
||
echo -e "${BOLD}Entfernen nach:${NC}"
|
||
echo " 1) IP-Adresse"
|
||
echo " 2) Decision-ID"
|
||
echo -n -e "${CYAN}Auswahl [1-2]: ${NC}"
|
||
read -r remove_choice
|
||
|
||
case "$remove_choice" in
|
||
1)
|
||
echo -n -e "${CYAN}IP-Adresse: ${NC}"
|
||
read -r remove_ip
|
||
if [[ -z "$remove_ip" ]]; then
|
||
echo -e "${YELLOW}Keine IP eingegeben.${NC}"
|
||
return
|
||
fi
|
||
echo -n -e "${YELLOW}Decision für $remove_ip wirklich entfernen? [j/N]: ${NC}"
|
||
read -r confirm
|
||
if [[ "$confirm" =~ ^[jJyY]$ ]]; then
|
||
if cscli_exec "decisions delete --ip '$remove_ip'" 2>&1; then
|
||
echo -e "${GREEN}Decision entfernt!${NC}"
|
||
else
|
||
echo -e "${RED}Fehler beim Entfernen.${NC}"
|
||
fi
|
||
else
|
||
echo -e "${YELLOW}Abgebrochen.${NC}"
|
||
fi
|
||
;;
|
||
2)
|
||
echo -n -e "${CYAN}Decision-ID: ${NC}"
|
||
read -r remove_id
|
||
if [[ -z "$remove_id" ]]; then
|
||
echo -e "${YELLOW}Keine ID eingegeben.${NC}"
|
||
return
|
||
fi
|
||
if ! [[ "$remove_id" =~ ^[0-9]+$ ]]; then
|
||
echo -e "${RED}Ungültige Decision-ID (nur Zahlen erlaubt).${NC}"
|
||
return
|
||
fi
|
||
echo -n -e "${YELLOW}Decision #$remove_id wirklich entfernen? [j/N]: ${NC}"
|
||
read -r confirm
|
||
if [[ "$confirm" =~ ^[jJyY]$ ]]; then
|
||
if cscli_exec "decisions delete --id '$remove_id'" 2>&1; then
|
||
echo -e "${GREEN}Decision #$remove_id entfernt!${NC}"
|
||
else
|
||
echo -e "${RED}Fehler beim Entfernen.${NC}"
|
||
fi
|
||
else
|
||
echo -e "${YELLOW}Abgebrochen.${NC}"
|
||
fi
|
||
;;
|
||
*)
|
||
echo -e "${YELLOW}Ungültige Auswahl.${NC}"
|
||
;;
|
||
esac
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Alerts anzeigen ]---
|
||
admin_list_alerts() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Aktuelle Alerts ===${NC}"
|
||
draw_line "=" 60
|
||
echo ""
|
||
cscli_exec "alerts list" 2>/dev/null || {
|
||
echo -e "${YELLOW}Keine Alerts vorhanden oder Fehler beim Abrufen.${NC}"
|
||
}
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Alert Detail-Inspektion ]---
|
||
admin_inspect_alert() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Alert inspizieren ===${NC}"
|
||
echo -n -e "${CYAN}Alert-ID eingeben: ${NC}"
|
||
read -r alert_id
|
||
|
||
if [[ -z "$alert_id" ]]; then
|
||
echo -e "${YELLOW}Keine Alert-ID eingegeben.${NC}"
|
||
return
|
||
fi
|
||
|
||
if ! [[ "$alert_id" =~ ^[0-9]+$ ]]; then
|
||
echo -e "${RED}Ungültige Alert-ID (nur Zahlen erlaubt).${NC}"
|
||
return
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${DIM}Inspiziere Alert #$alert_id...${NC}"
|
||
draw_line "-" 60
|
||
cscli_exec "alerts inspect '$alert_id' -d" 2>/dev/null || {
|
||
echo -e "${RED}Fehler beim Inspizieren von Alert #$alert_id.${NC}"
|
||
}
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Alert löschen ]---
|
||
admin_delete_alert() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Alert löschen ===${NC}"
|
||
echo ""
|
||
echo -e "${BOLD}Löschen nach:${NC}"
|
||
echo " 1) Einzelne Alert-ID"
|
||
echo " 2) Alle Alerts für eine IP"
|
||
echo " 3) Alle Alerts für einen Zeitraum"
|
||
echo -n -e "${CYAN}Auswahl [1-3]: ${NC}"
|
||
read -r delete_choice
|
||
|
||
case "$delete_choice" in
|
||
1)
|
||
echo -n -e "${CYAN}Alert-ID: ${NC}"
|
||
read -r del_id
|
||
if [[ -z "$del_id" ]] || ! [[ "$del_id" =~ ^[0-9]+$ ]]; then
|
||
echo -e "${RED}Ungültige Alert-ID.${NC}"
|
||
return
|
||
fi
|
||
echo -n -e "${YELLOW}Alert #$del_id wirklich löschen? [j/N]: ${NC}"
|
||
read -r confirm
|
||
if [[ "$confirm" =~ ^[jJyY]$ ]]; then
|
||
cscli_exec "alerts delete --id '$del_id'" 2>&1
|
||
else
|
||
echo -e "${YELLOW}Abgebrochen.${NC}"
|
||
fi
|
||
;;
|
||
2)
|
||
echo -n -e "${CYAN}IP-Adresse: ${NC}"
|
||
read -r del_ip
|
||
if [[ -z "$del_ip" ]]; then
|
||
echo -e "${YELLOW}Keine IP eingegeben.${NC}"
|
||
return
|
||
fi
|
||
echo -n -e "${YELLOW}Alle Alerts für $del_ip löschen? [j/N]: ${NC}"
|
||
read -r confirm
|
||
if [[ "$confirm" =~ ^[jJyY]$ ]]; then
|
||
cscli_exec "alerts delete --ip '$del_ip'" 2>&1
|
||
else
|
||
echo -e "${YELLOW}Abgebrochen.${NC}"
|
||
fi
|
||
;;
|
||
3)
|
||
echo -n -e "${CYAN}Zeitraum (z.B. 24h, 7d): ${NC}"
|
||
read -r del_range
|
||
if [[ -z "$del_range" ]]; then
|
||
echo -e "${YELLOW}Kein Zeitraum eingegeben.${NC}"
|
||
return
|
||
fi
|
||
echo -n -e "${YELLOW}Alle Alerts älter als $del_range löschen? [j/N]: ${NC}"
|
||
read -r confirm
|
||
if [[ "$confirm" =~ ^[jJyY]$ ]]; then
|
||
cscli_exec "alerts delete --range '$del_range'" 2>&1
|
||
else
|
||
echo -e "${YELLOW}Abgebrochen.${NC}"
|
||
fi
|
||
;;
|
||
*)
|
||
echo -e "${YELLOW}Ungültige Auswahl.${NC}"
|
||
;;
|
||
esac
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Bouncers verwalten ]---
|
||
admin_list_bouncers() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Registrierte Bouncers ===${NC}"
|
||
draw_line "=" 60
|
||
echo ""
|
||
cscli_exec "bouncers list" 2>/dev/null || {
|
||
echo -e "${RED}Fehler beim Abrufen der Bouncers.${NC}"
|
||
}
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Machines verwalten ]---
|
||
admin_list_machines() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Registrierte Machines ===${NC}"
|
||
draw_line "=" 60
|
||
echo ""
|
||
cscli_exec "machines list" 2>/dev/null || {
|
||
echo -e "${RED}Fehler beim Abrufen der Machines.${NC}"
|
||
}
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Parsers anzeigen ]---
|
||
admin_list_parsers() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Installierte Parsers ===${NC}"
|
||
draw_line "=" 60
|
||
echo ""
|
||
cscli_exec "parsers list" 2>/dev/null || {
|
||
echo -e "${RED}Fehler beim Abrufen der Parsers.${NC}"
|
||
}
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Scenarios anzeigen ]---
|
||
admin_list_scenarios() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Installierte Scenarios ===${NC}"
|
||
draw_line "=" 60
|
||
echo ""
|
||
cscli_exec "scenarios list" 2>/dev/null || {
|
||
echo -e "${RED}Fehler beim Abrufen der Scenarios.${NC}"
|
||
}
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Collections anzeigen ]---
|
||
admin_list_collections() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Installierte Collections ===${NC}"
|
||
draw_line "=" 60
|
||
echo ""
|
||
cscli_exec "collections list" 2>/dev/null || {
|
||
echo -e "${RED}Fehler beim Abrufen der Collections.${NC}"
|
||
}
|
||
echo ""
|
||
}
|
||
|
||
# ---[ CrowdSec Version & Hub Status ]---
|
||
admin_show_status() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== CrowdSec Status ===${NC}"
|
||
draw_line "=" 60
|
||
echo ""
|
||
|
||
echo -e "${BOLD}Version:${NC}"
|
||
cscli_exec "version" 2>/dev/null || echo -e "${RED}Nicht erreichbar${NC}"
|
||
|
||
echo ""
|
||
echo -e "${BOLD}Hub Status:${NC}"
|
||
cscli_exec "hub list" 2>/dev/null || echo -e "${RED}Fehler${NC}"
|
||
echo ""
|
||
}
|
||
|
||
# ---[ Eigenen cscli-Befehl ausführen ]---
|
||
admin_custom_command() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== Eigenen cscli-Befehl ausführen ===${NC}"
|
||
echo -e "${DIM}Basis: ${CSCLI_CMD}${NC}"
|
||
echo -e "${DIM}Gib nur die Argumente nach 'cscli' ein.${NC}"
|
||
echo -e "${DIM}Beispiel: decisions list -o json${NC}"
|
||
echo ""
|
||
echo -n -e "${CYAN}cscli> ${NC}"
|
||
read -r custom_args
|
||
|
||
if [[ -z "$custom_args" ]]; then
|
||
echo -e "${YELLOW}Kein Befehl eingegeben.${NC}"
|
||
return
|
||
fi
|
||
|
||
echo ""
|
||
draw_line "-" 60
|
||
cscli_exec "$custom_args" 2>&1 || true
|
||
draw_line "-" 60
|
||
echo ""
|
||
}
|
||
|
||
# ---[ IP-Info: Alle Infos zu einer IP sammeln ]---
|
||
admin_ip_info() {
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}=== IP-Informationen ===${NC}"
|
||
echo -n -e "${CYAN}IP-Adresse eingeben: ${NC}"
|
||
read -r info_ip
|
||
|
||
if [[ -z "$info_ip" ]]; then
|
||
echo -e "${YELLOW}Keine IP eingegeben.${NC}"
|
||
return
|
||
fi
|
||
|
||
echo ""
|
||
draw_line "=" 60
|
||
echo -e "${BOLD}Informationen für: ${CYAN}$info_ip${NC}"
|
||
draw_line "=" 60
|
||
|
||
# DNS Reverse-Lookup
|
||
echo ""
|
||
echo -e "${BOLD}[1/4] Reverse-DNS:${NC}"
|
||
local rdns
|
||
rdns=$(dig +short -x "$info_ip" 2>/dev/null || true)
|
||
if [[ -n "$rdns" ]]; then
|
||
echo -e " ${GREEN}$rdns${NC}"
|
||
else
|
||
echo -e " ${DIM}Kein PTR-Record gefunden${NC}"
|
||
fi
|
||
|
||
# Decisions für diese IP
|
||
echo ""
|
||
echo -e "${BOLD}[2/4] Aktive Decisions:${NC}"
|
||
cscli_exec "decisions list -i '$info_ip'" 2>/dev/null || {
|
||
echo -e " ${DIM}Keine Decisions gefunden${NC}"
|
||
}
|
||
|
||
# Alerts für diese IP
|
||
echo ""
|
||
echo -e "${BOLD}[3/4] Alerts:${NC}"
|
||
local alerts_output
|
||
alerts_output=$(cscli_exec "alerts list -i '$info_ip'" 2>/dev/null || true)
|
||
if [[ -n "$alerts_output" ]]; then
|
||
echo "$alerts_output"
|
||
|
||
# Alert-IDs extrahieren für Detail-Inspektion
|
||
echo ""
|
||
echo -e "${BOLD}[4/4] Alert-Details:${NC}"
|
||
local alert_ids
|
||
alert_ids=$(echo "$alerts_output" | grep -oP '^\s*\K[0-9]+' | head -5)
|
||
|
||
if [[ -n "$alert_ids" ]]; then
|
||
echo -e "${DIM}Zeige Details für die letzten Alerts (max. 5):${NC}"
|
||
while IFS= read -r aid; do
|
||
[[ -z "$aid" ]] && continue
|
||
echo ""
|
||
echo -e "${MAGENTA}--- Alert #$aid ---${NC}"
|
||
cscli_exec "alerts inspect '$aid' -d" 2>/dev/null || true
|
||
done <<< "$alert_ids"
|
||
fi
|
||
else
|
||
echo -e " ${DIM}Keine Alerts gefunden${NC}"
|
||
echo ""
|
||
echo -e "${BOLD}[4/4] Alert-Details:${NC}"
|
||
echo -e " ${DIM}Übersprungen (keine Alerts)${NC}"
|
||
fi
|
||
|
||
draw_line "=" 60
|
||
echo ""
|
||
}
|
||
|
||
# ============================================================================
|
||
# TEIL 8: INTERAKTIVES MENÜ
|
||
# ============================================================================
|
||
|
||
show_menu_banner() {
|
||
clear 2>/dev/null || true
|
||
echo ""
|
||
echo -e "${BOLD}${BLUE}"
|
||
echo " ╔═══════════════════════════════════════════════════════╗"
|
||
echo " ║ ║"
|
||
echo " ║ CrowdSec Manager v${SCRIPT_VERSION} ║"
|
||
echo " ║ ║"
|
||
echo " ║ Patrick Asmus ║"
|
||
echo " ║ www.patrick-asmus.de ║"
|
||
echo " ║ ║"
|
||
echo " ╚═══════════════════════════════════════════════════════╝"
|
||
echo -e "${NC}"
|
||
echo -e " ${DIM}Basisbefehl: ${CSCLI_CMD}${NC}"
|
||
echo ""
|
||
}
|
||
|
||
show_main_menu() {
|
||
echo -e "${BOLD}${GREEN} ── ALLOWLIST MANAGEMENT ──────────────────────────────${NC}"
|
||
echo -e " ${CYAN} 1)${NC} Allowlist synchronisieren (DNS auflösen & updaten)"
|
||
echo -e " ${CYAN} 2)${NC} Allowlist synchronisieren (Dry-Run)"
|
||
echo -e " ${CYAN} 3)${NC} Aktuelle Allowlist anzeigen"
|
||
echo -e " ${CYAN} 4)${NC} Konfiguration testen"
|
||
echo -e " ${CYAN} 5)${NC} Alle verwalteten Einträge entfernen (Flush)"
|
||
echo ""
|
||
echo -e "${BOLD}${GREEN} ── DECISIONS ─────────────────────────────────────────${NC}"
|
||
echo -e " ${CYAN}10)${NC} Alle Decisions anzeigen"
|
||
echo -e " ${CYAN}11)${NC} Decision suchen (nach IP)"
|
||
echo -e " ${CYAN}12)${NC} Decision manuell hinzufügen"
|
||
echo -e " ${CYAN}13)${NC} Decision entfernen"
|
||
echo ""
|
||
echo -e "${BOLD}${GREEN} ── ALERTS ────────────────────────────────────────────${NC}"
|
||
echo -e " ${CYAN}20)${NC} Alle Alerts anzeigen"
|
||
echo -e " ${CYAN}21)${NC} Alert inspizieren (Detail-Ansicht)"
|
||
echo -e " ${CYAN}22)${NC} Alerts löschen"
|
||
echo ""
|
||
echo -e "${BOLD}${GREEN} ── INFORMATIONEN ─────────────────────────────────────${NC}"
|
||
echo -e " ${CYAN}30)${NC} IP-Informationen (Komplett-Check)"
|
||
echo -e " ${CYAN}31)${NC} CrowdSec Metriken"
|
||
echo -e " ${CYAN}32)${NC} CrowdSec Status & Version"
|
||
echo -e " ${CYAN}33)${NC} Bouncers anzeigen"
|
||
echo -e " ${CYAN}34)${NC} Machines anzeigen"
|
||
echo -e " ${CYAN}35)${NC} Parsers anzeigen"
|
||
echo -e " ${CYAN}36)${NC} Scenarios anzeigen"
|
||
echo -e " ${CYAN}37)${NC} Collections anzeigen"
|
||
echo ""
|
||
echo -e "${BOLD}${GREEN} ── ERWEITERT ─────────────────────────────────────────${NC}"
|
||
echo -e " ${CYAN}40)${NC} Eigenen cscli-Befehl ausführen"
|
||
echo ""
|
||
echo -e " ${CYAN} 0)${NC} Beenden"
|
||
echo ""
|
||
draw_line "─" 60
|
||
}
|
||
|
||
interactive_menu() {
|
||
load_config
|
||
disable_colors_if_needed
|
||
|
||
while true; do
|
||
show_menu_banner
|
||
show_main_menu
|
||
|
||
echo -n -e " ${BOLD}Auswahl: ${NC}"
|
||
read -r choice
|
||
|
||
case "$choice" in
|
||
# Allowlist Management
|
||
1) run_allowlist_sync false ;;
|
||
2) run_allowlist_sync true ;;
|
||
3) show_current_list ;;
|
||
4) test_config ;;
|
||
5)
|
||
echo -n -e " ${YELLOW}Wirklich ALLE verwalteten Einträge entfernen? [j/N]: ${NC}"
|
||
read -r confirm
|
||
if [[ "$confirm" =~ ^[jJyY]$ ]]; then
|
||
flush_allowlist
|
||
else
|
||
echo -e " ${YELLOW}Abgebrochen.${NC}"
|
||
fi
|
||
;;
|
||
|
||
# Decisions
|
||
10) admin_list_decisions ;;
|
||
11) admin_search_decision ;;
|
||
12) admin_add_decision ;;
|
||
13) admin_remove_decision ;;
|
||
|
||
# Alerts
|
||
20) admin_list_alerts ;;
|
||
21) admin_inspect_alert ;;
|
||
22) admin_delete_alert ;;
|
||
|
||
# Informationen
|
||
30) admin_ip_info ;;
|
||
31) admin_show_metrics ;;
|
||
32) admin_show_status ;;
|
||
33) admin_list_bouncers ;;
|
||
34) admin_list_machines ;;
|
||
35) admin_list_parsers ;;
|
||
36) admin_list_scenarios ;;
|
||
37) admin_list_collections ;;
|
||
|
||
# Erweitert
|
||
40) admin_custom_command ;;
|
||
|
||
# Beenden
|
||
0|q|Q|exit)
|
||
echo ""
|
||
echo -e " ${GREEN}Auf Wiedersehen!${NC}"
|
||
echo ""
|
||
exit 0
|
||
;;
|
||
|
||
*)
|
||
echo -e " ${RED}Ungültige Auswahl: $choice${NC}"
|
||
;;
|
||
esac
|
||
|
||
echo ""
|
||
echo -n -e " ${DIM}Drücke Enter um fortzufahren...${NC}"
|
||
read -r
|
||
done
|
||
}
|
||
|
||
# Allowlist Sync als Funktion (wird aus Menü und CLI aufgerufen)
|
||
run_allowlist_sync() {
|
||
local dry_run_override="${1:-false}"
|
||
local orig_dry_run="${DRY_RUN:-false}"
|
||
|
||
if [[ "$dry_run_override" == "true" ]]; then
|
||
DRY_RUN=true
|
||
fi
|
||
|
||
# Statistiken zurücksetzen
|
||
STAT_ADDED=0
|
||
STAT_REMOVED=0
|
||
STAT_UNCHANGED=0
|
||
STAT_ERRORS=0
|
||
STAT_RESOLVED_DOMAINS=0
|
||
STAT_TOTAL_IPS=0
|
||
REPORT_MESSAGES=()
|
||
|
||
echo ""
|
||
log "INFO" "============================================"
|
||
log "INFO" " CrowdSec Manager v${SCRIPT_VERSION}"
|
||
log "INFO" " Patrick Asmus - www.patrick-asmus.de"
|
||
log "INFO" "============================================"
|
||
|
||
if [[ "${DRY_RUN}" == "true" ]]; then
|
||
log "WARN" "*** DRY-RUN MODUS AKTIV ***"
|
||
fi
|
||
|
||
rotate_logs
|
||
|
||
if ! check_dependencies; then
|
||
DRY_RUN="$orig_dry_run"
|
||
return 1
|
||
fi
|
||
|
||
if ! acquire_lock; then
|
||
DRY_RUN="$orig_dry_run"
|
||
return 1
|
||
fi
|
||
|
||
trap 'release_lock; cleanup_temp' EXIT INT TERM
|
||
|
||
setup_temp
|
||
|
||
if ! crowdsec_health_check; then
|
||
cleanup_temp
|
||
release_lock
|
||
DRY_RUN="$orig_dry_run"
|
||
return 1
|
||
fi
|
||
|
||
get_current_allowlist
|
||
resolve_all_entries
|
||
|
||
if sync_allowlist; then
|
||
# Änderungen erkannt → Backup erstellen & benachrichtigen
|
||
create_backup
|
||
send_notifications
|
||
fi
|
||
|
||
print_summary
|
||
|
||
cleanup_temp
|
||
release_lock
|
||
|
||
trap - EXIT INT TERM
|
||
|
||
DRY_RUN="$orig_dry_run"
|
||
|
||
if [[ $STAT_ERRORS -gt 0 ]]; then
|
||
return 2
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# TEIL 9: Report / Zusammenfassung
|
||
# ============================================================================
|
||
|
||
print_summary() {
|
||
echo ""
|
||
log "INFO" "============================================"
|
||
log "INFO" " ZUSAMMENFASSUNG"
|
||
log "INFO" "============================================"
|
||
log "INFO" "Domains aufgelöst: $STAT_RESOLVED_DOMAINS"
|
||
log "INFO" "IPs/CIDRs gesamt: $STAT_TOTAL_IPS"
|
||
log "INFO" "Hinzugefügt: $STAT_ADDED"
|
||
log "INFO" "Entfernt: $STAT_REMOVED"
|
||
log "INFO" "Unverändert: $STAT_UNCHANGED"
|
||
|
||
if [[ $STAT_ERRORS -gt 0 ]]; then
|
||
log "WARN" "Fehler: $STAT_ERRORS"
|
||
else
|
||
log "INFO" "Fehler: $STAT_ERRORS"
|
||
fi
|
||
|
||
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
||
log "WARN" "DRY-RUN MODUS - Keine Änderungen vorgenommen"
|
||
fi
|
||
|
||
log "INFO" "============================================"
|
||
echo ""
|
||
}
|
||
|
||
# ============================================================================
|
||
# TEIL 10: CLI-FUNKTIONEN (nicht-interaktiv)
|
||
# ============================================================================
|
||
|
||
show_help() {
|
||
cat << EOF
|
||
${BOLD}CrowdSec Manager v${SCRIPT_VERSION}${NC}
|
||
|
||
Verwaltet automatisch eine CrowdSec-Allowlist und bietet interaktive
|
||
CrowdSec-Administration.
|
||
|
||
${BOLD}Verwendung:${NC}
|
||
$SCRIPT_NAME [OPTIONEN]
|
||
|
||
${BOLD}Modi:${NC}
|
||
(ohne Optionen) Startet das interaktive Menü
|
||
--sync-allowlist Allowlist synchronisieren (für Cron)
|
||
|
||
${BOLD}Optionen:${NC}
|
||
-c, --config FILE Konfigurationsdatei angeben
|
||
-d, --dry-run Dry-Run: Zeigt was passieren würde
|
||
-v, --verbose Ausführliche Ausgabe (DEBUG)
|
||
-q, --quiet Minimale Ausgabe (ERROR)
|
||
-l, --list Aktuelle Allowlist anzeigen
|
||
-f, --flush Alle verwalteten Einträge entfernen
|
||
-t, --test Konfiguration testen
|
||
-i, --interactive Interaktives Menü starten
|
||
--sync-allowlist Allowlist-Sync ausführen (nicht-interaktiv)
|
||
-h, --help Diese Hilfe anzeigen
|
||
-V, --version Version anzeigen
|
||
|
||
${BOLD}Beispiele:${NC}
|
||
$SCRIPT_NAME # Interaktives Menü
|
||
$SCRIPT_NAME --sync-allowlist # Allowlist-Sync (Cron-Modus)
|
||
$SCRIPT_NAME --sync-allowlist --dry-run # Testlauf
|
||
$SCRIPT_NAME --sync-allowlist --quiet # Sync ohne Ausgabe (nur Fehler)
|
||
$SCRIPT_NAME --interactive # Interaktives Menü
|
||
$SCRIPT_NAME --list # Allowlist anzeigen
|
||
$SCRIPT_NAME --config /etc/conf.conf # Eigene Config
|
||
|
||
${BOLD}Cron-Beispiel:${NC}
|
||
*/30 * * * * ${SCRIPT_DIR}/${SCRIPT_NAME} --sync-allowlist --quiet 2>&1
|
||
|
||
${BOLD}Konfiguration:${NC}
|
||
Standard: config/crowdsec-manager.conf
|
||
|
||
EOF
|
||
}
|
||
|
||
show_version() {
|
||
echo "CrowdSec Manager v${SCRIPT_VERSION}"
|
||
}
|
||
|
||
show_current_list() {
|
||
echo ""
|
||
echo -e "${BOLD}Aktuelle CrowdSec Allowlist (${ALLOWLIST_NAME}):${NC}"
|
||
draw_line "-" 60
|
||
cscli_exec "allowlists inspect ${ALLOWLIST_NAME}" 2>/dev/null || echo "Keine Einträge oder Allowlist nicht gefunden."
|
||
draw_line "-" 60
|
||
}
|
||
|
||
flush_allowlist() {
|
||
log "WARN" "Entferne ALLE verwalteten Einträge aus der Allowlist..."
|
||
|
||
if [[ "${DRY_RUN:-false}" == "true" ]]; then
|
||
log "INFO" "[DRY-RUN] Würde Allowlist '${ALLOWLIST_NAME}' löschen und neu erstellen"
|
||
return 0
|
||
fi
|
||
|
||
# Allowlist komplett löschen und leer neu erstellen
|
||
if cscli_exec "allowlists delete ${ALLOWLIST_NAME}" 2>/dev/null; then
|
||
log "INFO" "Allowlist '${ALLOWLIST_NAME}' gelöscht."
|
||
else
|
||
log "WARN" "Allowlist '${ALLOWLIST_NAME}' konnte nicht gelöscht werden (existiert möglicherweise nicht)."
|
||
fi
|
||
|
||
# Leer neu erstellen
|
||
if cscli_exec "allowlists create ${ALLOWLIST_NAME} -d '${ALLOWLIST_DESCRIPTION}'" 2>&1; then
|
||
log "INFO" "Leere Allowlist '${ALLOWLIST_NAME}' neu erstellt."
|
||
else
|
||
log "ERROR" "Fehler beim Neuerstellen der Allowlist."
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
test_config() {
|
||
echo -e "\n${BOLD}Konfigurationstest:${NC}\n"
|
||
|
||
echo -e "${GREEN}[OK]${NC} Konfigurationsdatei geladen: $CONFIG_FILE"
|
||
|
||
echo -n "CrowdSec-Erreichbarkeit... "
|
||
if cscli_exec "version" &>/dev/null; then
|
||
echo -e "${GREEN}[OK]${NC}"
|
||
else
|
||
echo -e "${RED}[FEHLER]${NC} Befehl: '$CSCLI_CMD version'"
|
||
fi
|
||
|
||
echo -n "DNS-Auflösung... "
|
||
if command -v dig &>/dev/null; then
|
||
echo -e "${GREEN}[OK]${NC} dig gefunden"
|
||
else
|
||
echo -e "${RED}[FEHLER]${NC} dig nicht gefunden (dnsutils installieren)"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BOLD}Konfigurierte Einträge (${#ALLOWLIST_ENTRIES[@]}):${NC}"
|
||
for entry in "${ALLOWLIST_ENTRIES[@]}"; do
|
||
entry=$(echo "$entry" | sed 's/#.*$//' | xargs)
|
||
[[ -z "$entry" ]] && continue
|
||
|
||
if is_ip_or_cidr "$entry"; then
|
||
echo -e " ${CYAN}[IP/CIDR]${NC} $entry"
|
||
elif is_domain "$entry"; then
|
||
echo -n " [Domain] $entry -> "
|
||
local ips
|
||
ips=$(resolve_domain "$entry" 2>/dev/null)
|
||
if [[ -n "$ips" ]]; then
|
||
echo -e "${GREEN}$(echo "$ips" | tr '\n' ', ' | sed 's/,$//')${NC}"
|
||
else
|
||
echo -e "${RED}Nicht auflösbar${NC}"
|
||
fi
|
||
else
|
||
echo -e " ${RED}[UNBEKANNT]${NC} $entry"
|
||
fi
|
||
done
|
||
|
||
echo ""
|
||
echo -e "${BOLD}Benachrichtigungen:${NC}"
|
||
echo " Aktiviert: ${NOTIFY_ENABLED}"
|
||
echo " Desktop: ${NOTIFY_DESKTOP_ENABLED:-false}"
|
||
echo " Ntfy: ${NOTIFY_NTFY_ENABLED:-false}"
|
||
echo " Gotify: ${NOTIFY_GOTIFY_ENABLED:-false}"
|
||
echo " E-Mail: ${NOTIFY_EMAIL_ENABLED:-false}"
|
||
echo ""
|
||
}
|
||
|
||
# ============================================================================
|
||
# TEIL 11: KONFIGURATION & ABHÄNGIGKEITEN
|
||
# ============================================================================
|
||
|
||
load_config() {
|
||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||
echo "FEHLER: Konfigurationsdatei nicht gefunden: $CONFIG_FILE" >&2
|
||
echo "Erstelle eine Konfigurationsdatei basierend auf der Vorlage:" >&2
|
||
echo " cp config/crowdsec-manager.conf.example config/crowdsec-manager.conf" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# shellcheck source=/dev/null
|
||
source "$CONFIG_FILE"
|
||
|
||
if [[ -n "${LOG_FILE:-}" ]]; then
|
||
local log_dir
|
||
log_dir="$(dirname "$LOG_FILE")"
|
||
if [[ ! -d "$log_dir" ]]; then
|
||
mkdir -p "$log_dir" 2>/dev/null || true
|
||
fi
|
||
fi
|
||
}
|
||
|
||
check_dependencies() {
|
||
local missing=()
|
||
|
||
if ! command -v dig &>/dev/null; then
|
||
missing+=("dig (dnsutils/bind-utils)")
|
||
fi
|
||
|
||
if ! command -v curl &>/dev/null; then
|
||
if [[ "${NOTIFY_NTFY_ENABLED:-false}" == "true" ]] || \
|
||
[[ "${NOTIFY_GOTIFY_ENABLED:-false}" == "true" ]] || \
|
||
[[ "${NOTIFY_EMAIL_ENABLED:-false}" == "true" ]]; then
|
||
missing+=("curl (für Benachrichtigungen)")
|
||
fi
|
||
fi
|
||
|
||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||
log "ERROR" "Fehlende Abhängigkeiten:"
|
||
for dep in "${missing[@]}"; do
|
||
log "ERROR" " - $dep"
|
||
done
|
||
return 1
|
||
fi
|
||
|
||
return 0
|
||
}
|
||
|
||
# ============================================================================
|
||
# TEIL 12: HAUPTPROGRAMM
|
||
# ============================================================================
|
||
|
||
main() {
|
||
local action="interactive"
|
||
|
||
# Argumente parsen
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
-c|--config)
|
||
CONFIG_FILE="$2"
|
||
shift 2
|
||
;;
|
||
-d|--dry-run)
|
||
DRY_RUN=true
|
||
shift
|
||
;;
|
||
-v|--verbose)
|
||
LOG_LEVEL="DEBUG"
|
||
shift
|
||
;;
|
||
-q|--quiet)
|
||
LOG_LEVEL="ERROR"
|
||
shift
|
||
;;
|
||
-l|--list)
|
||
action="list"
|
||
shift
|
||
;;
|
||
-f|--flush)
|
||
action="flush"
|
||
shift
|
||
;;
|
||
-t|--test)
|
||
action="test"
|
||
shift
|
||
;;
|
||
-i|--interactive)
|
||
action="interactive"
|
||
shift
|
||
;;
|
||
--sync-allowlist)
|
||
action="sync-allowlist"
|
||
shift
|
||
;;
|
||
-h|--help)
|
||
disable_colors_if_needed
|
||
show_help
|
||
exit 0
|
||
;;
|
||
-V|--version)
|
||
show_version
|
||
exit 0
|
||
;;
|
||
*)
|
||
echo "Unbekannte Option: $1" >&2
|
||
echo "Verwende '$SCRIPT_NAME --help' für Hilfe" >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
disable_colors_if_needed
|
||
|
||
# Spezielle Aktionen
|
||
case "$action" in
|
||
list)
|
||
load_config
|
||
show_current_list
|
||
exit 0
|
||
;;
|
||
test)
|
||
load_config
|
||
test_config
|
||
exit 0
|
||
;;
|
||
flush)
|
||
load_config
|
||
flush_allowlist
|
||
exit 0
|
||
;;
|
||
interactive)
|
||
interactive_menu
|
||
exit 0
|
||
;;
|
||
sync-allowlist)
|
||
;;
|
||
esac
|
||
|
||
# === Automatischer Sync (--sync-allowlist) ===
|
||
load_config
|
||
[[ -n "${DRY_RUN:-}" ]] || DRY_RUN=false
|
||
|
||
run_allowlist_sync false
|
||
local rc=$?
|
||
|
||
exit $rc
|
||
}
|
||
|
||
# Script ausführen
|
||
main "$@"
|