From 535aed129eb7fe1929ba6df10afd0ae7bce294e1 Mon Sep 17 00:00:00 2001 From: gru Date: Wed, 11 Jun 2025 14:01:26 +0200 Subject: [PATCH] push to repo --- .gitignore | 4 + README.md | 69 +++++++++++++++++ adguard_scheduler.py | 175 +++++++++++++++++++++++++++++++++++++++++++ config.example.ini | 34 +++++++++ cron.txt | 2 + requirements.txt | 3 + 6 files changed, 287 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 adguard_scheduler.py create mode 100644 config.example.ini create mode 100644 cron.txt create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..395970b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +config.ini +venv +env +.venv diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e19711 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# AdGuard Scheduler + +Skrypt do zarządzania dynamicznymi regułami filtrowania AdGuard na podstawie harmonogramów i konfiguracji klientów. + +## 📦 Wymagania + +- Python 3.6+ +- Biblioteki: + - `requests` + +Zainstaluj: +```bash +pip install -r requirements.txt +``` + +## ⚙️ Konfiguracja + +Skonfiguruj serwery i klientów w pliku `.ini`. Przykład: +```ini +[server:home] +url = http://192.168.1.1:3000 +username = admin +password = haslo + +[client:tv] +ip = 192.168.1.101 +services = youtube.com, netflix.com +schedule = whole +``` + +Obsługiwane harmonogramy: +- `whole` — zawsze blokuj +- `custom:HH-HH` — tylko w określonych godzinach (np. `custom:22-6`) + +## 🚀 Użycie + +### Uruchomienie +```bash +python adguard_scheduler.py +``` + +### Z własnym plikiem config: +```bash +python adguard_scheduler.py /ścieżka/do/config.ini +``` + +### Z katalogiem: +```bash +python adguard_scheduler.py --config-dir /etc/adguard_configs +``` + +### Tryb cron (bez outputu): +```bash +python adguard_scheduler.py --cron +``` + +### Instalacja zadania cron co godzinę: +```bash +python adguard_scheduler.py --install-cron +``` + +### Pomoc: +```bash +python adguard_scheduler.py --help +``` + +## 📁 Przykład konfiguracji + +Zobacz plik: [`config.example.ini`](./config.example.ini) diff --git a/adguard_scheduler.py b/adguard_scheduler.py new file mode 100644 index 0000000..b08234b --- /dev/null +++ b/adguard_scheduler.py @@ -0,0 +1,175 @@ +import sys +import os +import configparser +import requests +from requests.auth import HTTPBasicAuth +import subprocess +from glob import glob +from datetime import datetime + +REQUIRED_SERVER_KEYS = {"url", "username", "password"} +REQUIRED_CLIENT_KEYS = {"ip", "services", "schedule"} + +def should_block(schedule): + if schedule == "whole": + return True + elif schedule.startswith("custom:"): + try: + hours = schedule.split(":")[1] + start, end = map(int, hours.split("-")) + now = datetime.now().hour + if start <= end: + return start <= now < end + else: + return now >= start or now < end + except: + return False + return False + +def show_block_details(ip, service, schedule): + print(f"Blokada: IP={ip} | Serwis={service} | Harmonogram={schedule}") + +def install_cron(): + python_exec = sys.executable + script_path = os.path.abspath(__file__) + config_path = os.path.join(os.path.dirname(script_path), "config.ini") + cron_line = f"0 * * * * {python_exec} {script_path} {config_path} --cron > /dev/null 2>&1" + + result = subprocess.run(["crontab", "-l"], capture_output=True, text=True) + existing_cron = result.stdout if result.returncode == 0 else "" + + if cron_line in existing_cron: + print("Cron już istnieje.") + return + + updated_cron = existing_cron.strip() + f"\n{cron_line}\n" + subprocess.run(["crontab", "-"], input=updated_cron, text=True) + print("Dodano do crona.") + +def print_help(): + print("Użycie:") + print(" python adguard_scheduler.py [config.ini] [--config-dir DIR] [--install-cron|--cron|--help|-h]") + print("\nOpcje:") + print(" --install-cron Dodaje zadanie do crona co godzinę") + print(" --cron Uruchomienie bez outputu (do crona)") + print(" --config-dir DIR Wczytaj wszystkie pliki .ini z katalogu DIR") + print(" --help, -h Pokazuje pomoc") + print("\nPrzykłady:") + print(" python adguard_scheduler.py") + print(" python adguard_scheduler.py /ścieżka/config.ini") + print(" python adguard_scheduler.py --config-dir /etc/adguard_configs") + print(" python adguard_scheduler.py --install-cron") + print(" python adguard_scheduler.py /cfg.ini --cron") + +def validate_config(config): + for section in config.sections(): + keys = set(config[section].keys()) + if section.startswith("server") and not REQUIRED_SERVER_KEYS.issubset(keys): + raise ValueError(f"Błędna konfiguracja w {section}: wymagane {REQUIRED_SERVER_KEYS}") + if section.startswith("client:") and not REQUIRED_CLIENT_KEYS.issubset(keys): + raise ValueError(f"Błędna konfiguracja w {section}: wymagane {REQUIRED_CLIENT_KEYS}") + +def load_configs(files): + merged = configparser.ConfigParser() + for file in files: + if os.path.isfile(file): + conf = configparser.ConfigParser() + conf.read(file) + for section in conf.sections(): + merged[section] = conf[section] + validate_config(merged) + return merged + +def test_server_connection(url, auth): + try: + r = requests.get(f"{url}/control/status", auth=auth, timeout=5) + return r.status_code == 200 + except: + return False + +def main(config_paths, silent=False): + config = load_configs(config_paths) + + servers = { + section: { + "url": config.get(section, "url").rstrip("/"), + "username": config.get(section, "username"), + "password": config.get(section, "password"), + } + for section in config.sections() if section.startswith("server") + } + + clients = { + section: { + "ip": config.get(section, "ip"), + "services": [s.strip() for s in config.get(section, "services").split(",")], + "schedule": config.get(section, "schedule"), + } + for section in config.sections() if section.startswith("client:") + } + + for srv_name, srv in servers.items(): + if not silent: + print(f"--- Przetwarzanie serwera {srv_name} ---") + + auth = HTTPBasicAuth(srv["username"], srv["password"]) + if not test_server_connection(srv["url"], auth): + if not silent: + print(f"[{srv['url']}] Niedostępny / brak połączenia.") + continue + + try: + rules_set = set() + for cdata in clients.values(): + if should_block(cdata["schedule"]): + for service in cdata["services"]: + rule = f"||{service}^$client={cdata['ip']}" + rules_set.add(rule) + if not silent: + show_block_details(cdata["ip"], service, cdata["schedule"]) + + payload = {"rules": sorted(rules_set)} + endpoint = f"{srv['url']}/control/filtering/set_rules" + response = requests.post(endpoint, json=payload, auth=auth, + headers={"Content-Type": "application/json"}) + + if not silent: + if response.status_code in [200, 204]: + print(f"[{srv['url']}] Reguły zaktualizowane.") + else: + print(f"[{srv['url']}] Błąd HTTP: {response.status_code} {response.text}") + + except Exception as e: + if not silent: + print(f"[{srv['url']}] Błąd: {e}") + +if __name__ == "__main__": + args = sys.argv[1:] + + if "--help" in args or "-h" in args: + print_help() + sys.exit(0) + + if "--install-cron" in args: + install_cron() + sys.exit(0) + + silent = "--cron" in args + args = [a for a in args if not a.startswith("--")] + + config_files = [] + + if "--config-dir" in sys.argv: + try: + dir_index = sys.argv.index("--config-dir") + 1 + config_dir = sys.argv[dir_index] + config_files = glob(os.path.join(config_dir, "*.ini")) + except IndexError: + print("Brak ścieżki po --config-dir") + sys.exit(1) + elif args: + config_files = [args[0]] + else: + config_files = [os.path.join(os.path.dirname(__file__), "config.ini")] + + main(config_files, silent=silent) diff --git a/config.example.ini b/config.example.ini new file mode 100644 index 0000000..dd1f7b6 --- /dev/null +++ b/config.example.ini @@ -0,0 +1,34 @@ +[server:home] +url = http://192.168.1.1:3000 +username = admin +password = mypass + +[server:office] +url = http://10.0.0.1:3000 +username = admin +password = secret123 + +[client:tv] +ip = 192.168.1.101 +services = youtube.com, netflix.com +schedule = whole + +[client:tablet] +ip = 192.168.1.102 +services = facebook.com, instagram.com +schedule = custom:22-6 + +[client:child-pc] +ip = 192.168.1.150 +services = steamcommunity.com, epicgames.com +schedule = custom:8-20 + +[client:guest] +ip = 192.168.1.200 +services = tiktok.com +schedule = custom:0-0 ; zero godzin = nie blokuj + +[client:office-laptop] +ip = 10.0.0.55 +services = reddit.com, twitter.com +schedule = whole diff --git a/cron.txt b/cron.txt new file mode 100644 index 0000000..93dd437 --- /dev/null +++ b/cron.txt @@ -0,0 +1,2 @@ +@hourly /root/adguard_blocker/venv/bin/python3.13 /root/adguard_blocker/adguard_scheduler.py /root/adguard_blocker/config.ini + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4b91f61 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +requests +croniter +adguardhome