#!/usr/bin/env python3 """ GeoIP Shop Blocker Manager - DACH Version 2-Component System: PHP blocking + Python watcher (systemd service) Blocks all IPs outside Germany, Austria, and Switzerland (DACH region) """ import os import sys import shutil import subprocess import json import time from datetime import datetime, timedelta from pathlib import Path # Configuration VHOSTS_DIR = "/var/www/vhosts" BACKUP_SUFFIX = ".geoip_backup" BLOCKING_FILE = "geoip_blocking.php" CACHE_FILE = "dach_ip_ranges.cache" LOG_FILE = "geoip_blocked.log" CROWDSEC_QUEUE_FILE = "geoip_crowdsec_queue.log" WATCHER_SCRIPT = "/usr/local/bin/geoip_crowdsec_watcher.py" SYSTEMD_SERVICE = "/etc/systemd/system/geoip-crowdsec-watcher.service" ACTIVE_SHOPS_FILE = "/var/lib/crowdsec/geoip_active_shops.json" # PHP GeoIP blocking script (no exec, just logging) GEOIP_SCRIPT = ''' $expiry_date) {{ return; // Script expired, allow all traffic }} // Get visitor IP $visitor_ip = $_SERVER['REMOTE_ADDR'] ?? ''; if (empty($visitor_ip)) {{ return; }} // Skip private IPs if (filter_var($visitor_ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {{ return; }} // Files $cache_file = __DIR__ . '/{cache_file}'; $cache_duration = 86400; // 24 hours $log_file = __DIR__ . '/{log_file}'; $crowdsec_queue = __DIR__ . '/{crowdsec_queue}'; // Function to download DACH IP ranges (Germany, Austria, Switzerland) function download_dach_ranges() {{ $ranges = []; $countries = ['de', 'at', 'ch']; // Germany, Austria, Switzerland foreach ($countries as $country) {{ $url = "https://www.ipdeny.com/ipblocks/data/aggregated/$country-aggregated.zone"; $content = @file_get_contents($url); if ($content !== false) {{ $lines = explode("\\n", trim($content)); foreach ($lines as $line) {{ $line = trim($line); if (!empty($line) && strpos($line, '/') !== false) {{ $ranges[] = $line; }} }} }} }} return $ranges; }} // Function to check if IP is in CIDR range function ip_in_range($ip, $cidr) {{ list($subnet, $mask) = explode('/', $cidr); $ip_long = ip2long($ip); $subnet_long = ip2long($subnet); $mask_long = -1 << (32 - (int)$mask); return ($ip_long & $mask_long) == ($subnet_long & $mask_long); }} // Load or download IP ranges $dach_ranges = []; if (file_exists($cache_file) && (time() - filemtime($cache_file)) < $cache_duration) {{ $dach_ranges = unserialize(file_get_contents($cache_file)); }} else {{ $dach_ranges = download_dach_ranges(); if (!empty($dach_ranges)) {{ @file_put_contents($cache_file, serialize($dach_ranges)); }} }} // Check if visitor IP is from DACH region $is_dach = false; foreach ($dach_ranges as $range) {{ if (ip_in_range($visitor_ip, $range)) {{ $is_dach = true; break; }} }} // Block non-DACH IPs if (!$is_dach) {{ $timestamp = date('Y-m-d H:i:s'); $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'; $request_uri = $_SERVER['REQUEST_URI'] ?? '/'; // Log for humans $log_entry = "[$timestamp] IP: $visitor_ip | UA: $user_agent | URI: $request_uri\\n"; @file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX); // Queue for CrowdSec (simple format) $queue_entry = "$timestamp|$visitor_ip|{shop_name}\\n"; @file_put_contents($crowdsec_queue, $queue_entry, FILE_APPEND | LOCK_EX); header('HTTP/1.1 403 Forbidden'); exit; }} ''' # PHP GeoIP blocking script - PHP ONLY (no CrowdSec queue) GEOIP_SCRIPT_PHP_ONLY = ''' $expiry_date) {{ return; // Script expired, allow all traffic }} // Get visitor IP $visitor_ip = $_SERVER['REMOTE_ADDR'] ?? ''; if (empty($visitor_ip)) {{ return; }} // Skip private IPs if (filter_var($visitor_ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {{ return; }} // Files $cache_file = __DIR__ . '/{cache_file}'; $cache_duration = 86400; // 24 hours $log_file = __DIR__ . '/{log_file}'; // Function to download DACH IP ranges (Germany, Austria, Switzerland) function download_dach_ranges() {{ $ranges = []; $countries = ['de', 'at', 'ch']; // Germany, Austria, Switzerland foreach ($countries as $country) {{ $url = "https://www.ipdeny.com/ipblocks/data/aggregated/$country-aggregated.zone"; $content = @file_get_contents($url); if ($content !== false) {{ $lines = explode("\\n", trim($content)); foreach ($lines as $line) {{ $line = trim($line); if (!empty($line) && strpos($line, '/') !== false) {{ $ranges[] = $line; }} }} }} }} return $ranges; }} // Function to check if IP is in CIDR range function ip_in_range($ip, $cidr) {{ list($subnet, $mask) = explode('/', $cidr); $ip_long = ip2long($ip); $subnet_long = ip2long($subnet); $mask_long = -1 << (32 - (int)$mask); return ($ip_long & $mask_long) == ($subnet_long & $mask_long); }} // Load or download IP ranges $dach_ranges = []; if (file_exists($cache_file) && (time() - filemtime($cache_file)) < $cache_duration) {{ $dach_ranges = unserialize(file_get_contents($cache_file)); }} else {{ $dach_ranges = download_dach_ranges(); if (!empty($dach_ranges)) {{ @file_put_contents($cache_file, serialize($dach_ranges)); }} }} // Check if visitor IP is from DACH region $is_dach = false; foreach ($dach_ranges as $range) {{ if (ip_in_range($visitor_ip, $range)) {{ $is_dach = true; break; }} }} // Block non-DACH IPs if (!$is_dach) {{ $timestamp = date('Y-m-d H:i:s'); $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'; $request_uri = $_SERVER['REQUEST_URI'] ?? '/'; // Log for humans $log_entry = "[$timestamp] IP: $visitor_ip | UA: $user_agent | URI: $request_uri\\n"; @file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX); header('HTTP/1.1 403 Forbidden'); exit; }} ''' # Python watcher script (runs as systemd service) WATCHER_SCRIPT_CONTENT = '''#!/usr/bin/env python3 """ GeoIP CrowdSec Watcher Service Monitors queue files and adds blocked IPs to CrowdSec """ import os import sys import time import subprocess import json from pathlib import Path from datetime import datetime VHOSTS_DIR = "/var/www/vhosts" QUEUE_FILE = "geoip_crowdsec_queue.log" ACTIVE_SHOPS_FILE = "/var/lib/crowdsec/geoip_active_shops.json" PROCESSED_IPS = {} # In-memory cache to avoid re-adding same IP CHECK_INTERVAL = 5 # Check every 5 seconds def log(msg): print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}", flush=True) def get_active_shops(): """Get list of shops with active GeoIP blocking""" if not os.path.isfile(ACTIVE_SHOPS_FILE): return {} try: with open(ACTIVE_SHOPS_FILE, 'r') as f: return json.load(f) except: return {} def add_to_crowdsec(ip, shop): """Add IP to CrowdSec with 72h ban""" # Check if already processed recently (within last hour) now = time.time() if ip in PROCESSED_IPS and (now - PROCESSED_IPS[ip]) < 3600: return True cmd = [ 'cscli', 'decisions', 'add', '--ip', ip, '--duration', '72h', '--type', 'ban', '--reason', f'GeoIP: Non-DACH IP blocked by {shop}' ] try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) if result.returncode == 0: PROCESSED_IPS[ip] = now log(f"✅ Added {ip} to CrowdSec (from {shop})") return True else: log(f"⚠️ Failed to add {ip}: {result.stderr.strip()}") return False except Exception as e: log(f"❌ Error adding {ip}: {e}") return False def process_queue_file(shop_path, shop): """Process queue file for a shop""" queue_file = os.path.join(shop_path, 'httpdocs', QUEUE_FILE) if not os.path.isfile(queue_file): return 0 processed = 0 try: # Read all lines with open(queue_file, 'r') as f: lines = f.readlines() if not lines: return 0 # Process each line for line in lines: line = line.strip() if not line: continue try: parts = line.split('|') if len(parts) >= 2: timestamp = parts[0] ip = parts[1] if add_to_crowdsec(ip, shop): processed += 1 except: continue # Clear the file after processing if processed > 0: with open(queue_file, 'w') as f: f.write('') except Exception as e: log(f"❌ Error processing {shop}: {e}") return processed def main(): log("🚀 GeoIP CrowdSec Watcher started (DACH mode)") while True: try: active_shops = get_active_shops() if not active_shops: time.sleep(CHECK_INTERVAL) continue total_processed = 0 for shop, info in active_shops.items(): shop_path = os.path.join(VHOSTS_DIR, shop) if os.path.isdir(shop_path): count = process_queue_file(shop_path, shop) total_processed += count if total_processed > 0: log(f"📊 Processed {total_processed} IPs in this cycle") time.sleep(CHECK_INTERVAL) except KeyboardInterrupt: log("👋 Shutting down...") break except Exception as e: log(f"❌ Error in main loop: {e}") time.sleep(CHECK_INTERVAL) if __name__ == "__main__": main() ''' # Systemd service file SYSTEMD_SERVICE_CONTENT = '''[Unit] Description=GeoIP CrowdSec Watcher Service (DACH) After=network.target crowdsec.service Wants=crowdsec.service [Service] Type=simple ExecStart=/usr/bin/python3 /usr/local/bin/geoip_crowdsec_watcher.py Restart=always RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target ''' def run_command(cmd, capture_output=True): """Run a shell command""" try: if capture_output: result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) return result.returncode, result.stdout, result.stderr else: result = subprocess.run(cmd, shell=True, timeout=30) return result.returncode, "", "" except Exception as e: return -1, "", str(e) def check_crowdsec(): """Check if CrowdSec is running""" code, stdout, stderr = run_command("systemctl is-active crowdsec") return code == 0 and stdout.strip() == "active" def install_watcher_service(): """Install the watcher script and systemd service""" print(" 📦 Installiere CrowdSec-Watcher-Service...") # Create watcher script with open(WATCHER_SCRIPT, 'w') as f: f.write(WATCHER_SCRIPT_CONTENT) os.chmod(WATCHER_SCRIPT, 0o755) print(f" ✅ Watcher-Script erstellt: {WATCHER_SCRIPT}") # Create systemd service with open(SYSTEMD_SERVICE, 'w') as f: f.write(SYSTEMD_SERVICE_CONTENT) print(f" ✅ Systemd-Service erstellt: {SYSTEMD_SERVICE}") # Reload systemd and start service run_command("systemctl daemon-reload") run_command("systemctl enable geoip-crowdsec-watcher.service") run_command("systemctl start geoip-crowdsec-watcher.service") # Check if started time.sleep(1) code, stdout, _ = run_command("systemctl is-active geoip-crowdsec-watcher.service") if code == 0 and stdout.strip() == "active": print(" ✅ Service gestartet und läuft") return True else: print(" ⚠️ Service konnte nicht gestartet werden") return False def uninstall_watcher_service(): """Uninstall the watcher script and systemd service""" print(" 📦 Deinstalliere CrowdSec-Watcher-Service...") # Stop and disable service run_command("systemctl stop geoip-crowdsec-watcher.service") run_command("systemctl disable geoip-crowdsec-watcher.service") # Remove files if os.path.isfile(SYSTEMD_SERVICE): os.remove(SYSTEMD_SERVICE) print(f" 🗑️ Service-Datei gelöscht") if os.path.isfile(WATCHER_SCRIPT): os.remove(WATCHER_SCRIPT) print(f" 🗑️ Watcher-Script gelöscht") run_command("systemctl daemon-reload") print(" ✅ Service deinstalliert") def add_shop_to_active(shop, mode="php+crowdsec"): """Add shop to active shops tracking with mode""" os.makedirs(os.path.dirname(ACTIVE_SHOPS_FILE), exist_ok=True) shops = {} if os.path.isfile(ACTIVE_SHOPS_FILE): with open(ACTIVE_SHOPS_FILE, 'r') as f: shops = json.load(f) shops[shop] = { "activated": datetime.now().isoformat(), "expiry": (datetime.now() + timedelta(hours=72)).isoformat(), "mode": mode # "php+crowdsec" or "php-only" } with open(ACTIVE_SHOPS_FILE, 'w') as f: json.dump(shops, f, indent=2) def get_shop_mode(shop): """Get the blocking mode for a shop""" if not os.path.isfile(ACTIVE_SHOPS_FILE): return "php+crowdsec" try: with open(ACTIVE_SHOPS_FILE, 'r') as f: shops = json.load(f) return shops.get(shop, {}).get("mode", "php+crowdsec") except: return "php+crowdsec" def remove_shop_from_active(shop): """Remove shop from active shops tracking""" if not os.path.isfile(ACTIVE_SHOPS_FILE): return with open(ACTIVE_SHOPS_FILE, 'r') as f: shops = json.load(f) if shop in shops: del shops[shop] with open(ACTIVE_SHOPS_FILE, 'w') as f: json.dump(shops, f, indent=2) def cleanup_crowdsec_decisions(shop): """Remove CrowdSec decisions for a shop""" if not check_crowdsec(): return print(f" 🔍 Entferne CrowdSec-Decisions für {shop}...") total_removed = 0 max_iterations = 50 # Safety limit iteration = 0 while iteration < max_iterations: iteration += 1 # Get all decisions with --limit 0 (no pagination) list_cmd = f"cscli decisions list -o raw --limit 0 | grep '{shop}'" code, stdout, stderr = run_command(list_cmd) if code != 0 or not stdout.strip(): break # No more decisions found # Extract IPs and delete them (process in batches of 100) lines = stdout.strip().split('\n') batch_count = 0 for line in lines[:100]: # Process max 100 per iteration try: parts = line.split(',') if len(parts) >= 3: ip_field = parts[2].strip() if ':' in ip_field: ip = ip_field.split(':', 1)[1] else: ip = ip_field if ip: del_cmd = f"cscli decisions delete --ip {ip}" del_code, _, _ = run_command(del_cmd) if del_code == 0: batch_count += 1 except: continue total_removed += batch_count if batch_count == 0: break # Nothing deleted in this iteration # Small progress indicator for large cleanups if iteration > 1: print(f" ... {total_removed} Decisions entfernt (Durchlauf {iteration})") if total_removed > 0: print(f" ✅ {total_removed} Decisions entfernt") else: print(" ℹ️ Keine Decisions gefunden") def get_available_shops(): """Get list of all shops""" shops = [] if not os.path.exists(VHOSTS_DIR): return shops for entry in os.listdir(VHOSTS_DIR): shop_path = os.path.join(VHOSTS_DIR, entry) if os.path.isdir(shop_path) and entry not in ['chroot', 'system', 'default']: httpdocs = os.path.join(shop_path, 'httpdocs') if os.path.isdir(httpdocs): index_php = os.path.join(httpdocs, 'index.php') if os.path.isfile(index_php): shops.append(entry) return sorted(shops) def get_active_shops(): """Get list of shops with active blocking""" active = [] shops = get_available_shops() for shop in shops: httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs') blocking_file = os.path.join(httpdocs, BLOCKING_FILE) backup_file = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}') if os.path.isfile(blocking_file) or os.path.isfile(backup_file): active.append(shop) return active def activate_blocking(shop, silent=False, mode="php+crowdsec"): """Activate GeoIP blocking for a single shop""" httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs') index_php = os.path.join(httpdocs, 'index.php') backup_php = os.path.join(httpdocs, f'index.php{BACKUP_SUFFIX}') blocking_file = os.path.join(httpdocs, BLOCKING_FILE) if os.path.isfile(backup_php): if not silent: print(f"⚠️ GeoIP-Blocking bereits aktiv für {shop}") return False if not os.path.isfile(index_php): if not silent: print(f"❌ index.php nicht gefunden") return False if not silent: print(f"\n🔧 Aktiviere DACH GeoIP-Blocking für: {shop}") print(" (Erlaubt: Deutschland, Österreich, Schweiz)") print(f" Modus: {'PHP + CrowdSec' if mode == 'php+crowdsec' else 'Nur PHP'}") print("=" * 60) # Step 1: Install watcher service if not exists (only for php+crowdsec mode) if mode == "php+crowdsec": active_shops = get_active_shops() # Check if any shop uses crowdsec mode crowdsec_shops = [s for s in active_shops if get_shop_mode(s) == "php+crowdsec"] if not crowdsec_shops: # First shop with crowdsec if not silent: print("\n[1/3] Installiere CrowdSec-Watcher-Service...") if check_crowdsec(): install_watcher_service() else: if not silent: print(" ⚠️ CrowdSec nicht verfügbar - nur PHP-Blocking") else: if not silent: print("\n[1/3] CrowdSec-Watcher-Service bereits aktiv") else: if not silent: print("\n[1/3] CrowdSec-Synchronisation deaktiviert (nur PHP-Modus)") # Step 2: PHP blocking if not silent: print("\n[2/3] Aktiviere PHP-Blocking...") print(" 📋 Backup erstellen...") shutil.copy2(index_php, backup_php) with open(index_php, 'r', encoding='utf-8') as f: content = f.read() lines = content.split('\n') insert_line = 0 for i, line in enumerate(lines): if 'declare(strict_types' in line: insert_line = i + 1 break elif ' 0 else "" print(f" ├─ {shop}: {count} {bar}") # Display CrowdSec bans print(f"\n🛡️ CrowdSec-Bans gesamt: {total_crowdsec}") if crowdsec_stats: for shop in sorted(crowdsec_stats.keys()): count = crowdsec_stats[shop] bar = "█" * min(count // 10, 20) if count > 0 else "" print(f" ├─ {shop}: {count} {bar}") elif check_crowdsec(): print(" └─ Keine aktiven Bans") else: print(" └─ CrowdSec nicht verfügbar") # Top blocked IPs if all_ips: print(f"\n🔥 Top 100 blockierte IPs (alle Shops):") sorted_ips = sorted(all_ips.items(), key=lambda x: x[1], reverse=True)[:100] for ip, count in sorted_ips: bar = "█" * min(count // 5, 20) if count > 0 else "█" print(f" {ip}: {count} {bar}") print(f"\n{'═' * 60}") # Wait for user input("\nDrücke Enter um fortzufahren...") def show_logs(shop): """Show logs for a single shop""" httpdocs = os.path.join(VHOSTS_DIR, shop, 'httpdocs') log_file = os.path.join(httpdocs, LOG_FILE) shop_mode = get_shop_mode(shop) mode_display = "PHP + CrowdSec 🛡️" if shop_mode == "php+crowdsec" else "Nur PHP 📝" print(f"\n📊 Logs für {shop} [{mode_display}]") if os.path.isfile(log_file): print(f"\n📝 PHP-Blocks:") print("=" * 80) with open(log_file, 'r') as f: lines = f.readlines() for line in lines[-50:]: print(line.rstrip()) print("=" * 80) print(f"Gesamt: {len(lines)}") else: print(f"ℹ️ Keine PHP-Logs für {shop}") # Only show CrowdSec decisions if mode is php+crowdsec if shop_mode == "php+crowdsec" and check_crowdsec(): print(f"\n🛡️ CrowdSec Decisions:") print("=" * 80) # Use raw output with --limit 0 (no pagination) code, stdout, _ = run_command("cscli decisions list -o raw --limit 0") if code == 0 and stdout: lines = stdout.strip().split('\n') shop_decisions = [] for line in lines[1:]: # Skip header if shop in line: shop_decisions.append(line) if shop_decisions: print(f"Aktive Bans: {len(shop_decisions)}") print("\nLetzte 20 Bans:") for line in shop_decisions[:20]: parts = line.split(',') if len(parts) > 8: # Column 2: ip (format "Ip:1.2.3.4") ip_field = parts[2].strip() if ':' in ip_field: ip = ip_field.split(':', 1)[1] else: ip = ip_field # Column 8: expiration expiry = parts[8].strip() print(f" 🚫 {ip} (bis {expiry})") if len(shop_decisions) > 20: print(f" ... und {len(shop_decisions) - 20} weitere") else: print("Keine aktiven CrowdSec-Bans für diesen Shop") else: print("Konnte Decisions nicht abrufen") print("=" * 80) elif shop_mode == "php-only": print(f"\n📝 CrowdSec-Synchronisation ist für diesen Shop deaktiviert (PHP-only Modus)") def main(): """Main menu""" print("\n" + "=" * 60) print(" GeoIP Shop Blocker Manager - DACH Version") print(" Erlaubt: 🇩🇪 Deutschland | 🇦🇹 Österreich | 🇨🇭 Schweiz") print(" PHP + CrowdSec Watcher (systemd service)") print("=" * 60) if check_crowdsec(): print(" ✅ CrowdSec: Aktiv") else: print(" ⚠️ CrowdSec: Nicht verfügbar") # Check service status code, stdout, _ = run_command("systemctl is-active geoip-crowdsec-watcher.service") if code == 0 and stdout.strip() == "active": print(" ✅ Watcher-Service: Läuft") else: print(" ⚠️ Watcher-Service: Nicht aktiv") while True: print("\n" + "-" * 40) print("[1] GeoIP-Blocking AKTIVIEREN (einzeln)") print("[2] GeoIP-Blocking DEAKTIVIEREN (einzeln)") print("[3] Logs anzeigen") print("[4] Status anzeigen") print("-" * 40) print("[5] 🚀 ALLE Shops aktivieren") print("[6] 🛑 ALLE Shops deaktivieren") print("-" * 40) print("[0] Beenden") choice = input("\nWähle eine Option: ").strip() if choice == "1": shops = get_available_shops() active_shops = get_active_shops() available_shops = [s for s in shops if s not in active_shops] if not available_shops: print("\n⚠️ Keine Shops verfügbar") continue print("\n📋 Verfügbare Shops:") for i, shop in enumerate(available_shops, 1): print(f" [{i}] {shop}") shop_choice = input("\nWähle einen Shop: ").strip() try: shop_idx = int(shop_choice) - 1 if 0 <= shop_idx < len(available_shops): selected_shop = available_shops[shop_idx] # Ask for mode print(f"\n🔧 Wähle den Blocking-Modus:") print(f" [1] PHP + CrowdSec (IPs werden an CrowdSec gemeldet)") print(f" [2] Nur PHP (keine CrowdSec-Synchronisation)") mode_choice = input(f"\nModus wählen [1/2]: ").strip() if mode_choice == "2": mode = "php-only" mode_display = "Nur PHP" else: mode = "php+crowdsec" mode_display = "PHP + CrowdSec" confirm = input(f"\n⚠️ DACH-Blocking ({mode_display}) aktivieren für '{selected_shop}'? (ja/nein): ").strip().lower() if confirm in ['ja', 'j', 'yes', 'y']: activate_blocking(selected_shop, mode=mode) else: print("❌ Ungültig") except ValueError: print("❌ Ungültig") elif choice == "2": active_shops = get_active_shops() if not active_shops: print("\n⚠️ Keine aktiven Shops") continue print("\n📋 Aktive Shops:") for i, shop in enumerate(active_shops, 1): mode = get_shop_mode(shop) mode_icon = "🛡️" if mode == "php+crowdsec" else "📝" print(f" [{i}] {shop} {mode_icon}") shop_choice = input("\nWähle einen Shop: ").strip() try: shop_idx = int(shop_choice) - 1 if 0 <= shop_idx < len(active_shops): selected_shop = active_shops[shop_idx] confirm = input(f"\n⚠️ Deaktivieren für '{selected_shop}'? (ja/nein): ").strip().lower() if confirm in ['ja', 'j', 'yes', 'y']: deactivate_blocking(selected_shop) else: print("❌ Ungültig") except ValueError: print("❌ Ungültig") elif choice == "3": active_shops = get_active_shops() if not active_shops: print("\n⚠️ Keine aktiven Shops") continue print("\n📋 Logs anzeigen für:") print(f" [0] 📊 ALLE Shops (Zusammenfassung)") for i, shop in enumerate(active_shops, 1): mode = get_shop_mode(shop) mode_icon = "🛡️" if mode == "php+crowdsec" else "📝" print(f" [{i}] {shop} {mode_icon}") shop_choice = input("\nWähle eine Option: ").strip() try: shop_idx = int(shop_choice) if shop_idx == 0: show_all_logs() elif 1 <= shop_idx <= len(active_shops): show_logs(active_shops[shop_idx - 1]) else: print("❌ Ungültig") except ValueError: print("❌ Ungültig") elif choice == "4": shops = get_available_shops() active_shops = get_active_shops() print(f"\n📊 Status:") print(f" Shops gesamt: {len(shops)}") print(f" Aktive DACH-Blockings: {len(active_shops)}") if active_shops: for shop in active_shops: mode = get_shop_mode(shop) mode_icon = "🛡️" if mode == "php+crowdsec" else "📝" mode_text = "PHP+CS" if mode == "php+crowdsec" else "PHP" print(f" ✓ {shop} [{mode_text}] {mode_icon}") elif choice == "5": activate_all_shops() elif choice == "6": deactivate_all_shops() elif choice == "0": print("\n👋 Auf Wiedersehen!") break if __name__ == "__main__": if os.geteuid() != 0: print("❌ Als root ausführen!") sys.exit(1) try: main() except KeyboardInterrupt: print("\n\n👋 Abgebrochen") sys.exit(0)