Compare commits
2 Commits
35fe9c8c25
...
98f8479625
Author | SHA1 | Date | |
---|---|---|---|
98f8479625 | |||
1e46757de8 |
19
README.md
19
README.md
@ -1,4 +1,4 @@
|
|||||||
# AdGuard Scheduler
|
# AdGuard Rule Scheduler
|
||||||
|
|
||||||
Skrypt do zarządzania dynamicznymi regułami filtrowania AdGuard na podstawie harmonogramów i konfiguracji klientów.
|
Skrypt do zarządzania dynamicznymi regułami filtrowania AdGuard na podstawie harmonogramów i konfiguracji klientów.
|
||||||
|
|
||||||
@ -13,6 +13,23 @@ Zainstaluj:
|
|||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
lub:
|
||||||
|
|
||||||
|
Debian/Ubuntu
|
||||||
|
```bash
|
||||||
|
apt install python3-requests
|
||||||
|
```
|
||||||
|
|
||||||
|
Arch Linux
|
||||||
|
```bash
|
||||||
|
pacman -S python-requests
|
||||||
|
```
|
||||||
|
|
||||||
|
OpenSuse Tumbleweed
|
||||||
|
```bash
|
||||||
|
zypper in python313-requests
|
||||||
|
```
|
||||||
|
|
||||||
## ⚙️ Konfiguracja
|
## ⚙️ Konfiguracja
|
||||||
|
|
||||||
Skonfiguruj serwery i klientów w pliku `.ini`. Przykład:
|
Skonfiguruj serwery i klientów w pliku `.ini`. Przykład:
|
||||||
|
@ -6,6 +6,7 @@ from requests.auth import HTTPBasicAuth
|
|||||||
import subprocess
|
import subprocess
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import re # Dodane dla walidacji regexów
|
||||||
|
|
||||||
REQUIRED_SERVER_KEYS = {"url", "username", "password"}
|
REQUIRED_SERVER_KEYS = {"url", "username", "password"}
|
||||||
REQUIRED_CLIENT_KEYS = {"ip", "services", "schedule"}
|
REQUIRED_CLIENT_KEYS = {"ip", "services", "schedule"}
|
||||||
@ -69,6 +70,32 @@ def validate_config(config):
|
|||||||
if section.startswith("client:") and not REQUIRED_CLIENT_KEYS.issubset(keys):
|
if section.startswith("client:") and not REQUIRED_CLIENT_KEYS.issubset(keys):
|
||||||
raise ValueError(f"Błędna konfiguracja w {section}: wymagane {REQUIRED_CLIENT_KEYS}")
|
raise ValueError(f"Błędna konfiguracja w {section}: wymagane {REQUIRED_CLIENT_KEYS}")
|
||||||
|
|
||||||
|
def is_valid_regex(pattern):
|
||||||
|
"""Walidacja regexów (opcjonalne)"""
|
||||||
|
try:
|
||||||
|
re.compile(pattern)
|
||||||
|
return True
|
||||||
|
except re.error:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def expand_aliases(config):
|
||||||
|
aliases = {}
|
||||||
|
for section in config.sections():
|
||||||
|
if section.startswith("alias:"):
|
||||||
|
alias_name = section.split(":")[1]
|
||||||
|
domains = []
|
||||||
|
for d in config.get(section, "domains").split(","):
|
||||||
|
d = d.strip()
|
||||||
|
if d:
|
||||||
|
# Opcjonalna walidacja regexów (tylko dla /.../)
|
||||||
|
if d.startswith("/") and d.endswith("/"):
|
||||||
|
if not is_valid_regex(d[1:-1]):
|
||||||
|
print(f"⚠️ Nieprawidłowy regex w aliasie {alias_name}: {d}")
|
||||||
|
continue
|
||||||
|
domains.append(d)
|
||||||
|
aliases[alias_name] = domains
|
||||||
|
return aliases
|
||||||
|
|
||||||
def load_configs(files):
|
def load_configs(files):
|
||||||
merged = configparser.ConfigParser()
|
merged = configparser.ConfigParser()
|
||||||
for file in files:
|
for file in files:
|
||||||
@ -89,6 +116,7 @@ def test_server_connection(url, auth):
|
|||||||
|
|
||||||
def main(config_paths, silent=False):
|
def main(config_paths, silent=False):
|
||||||
config = load_configs(config_paths)
|
config = load_configs(config_paths)
|
||||||
|
aliases = expand_aliases(config)
|
||||||
|
|
||||||
servers = {
|
servers = {
|
||||||
section: {
|
section: {
|
||||||
@ -99,14 +127,22 @@ def main(config_paths, silent=False):
|
|||||||
for section in config.sections() if section.startswith("server")
|
for section in config.sections() if section.startswith("server")
|
||||||
}
|
}
|
||||||
|
|
||||||
clients = {
|
clients = {}
|
||||||
section: {
|
for section in config.sections():
|
||||||
"ip": config.get(section, "ip"),
|
if section.startswith("client:"):
|
||||||
"services": [s.strip() for s in config.get(section, "services").split(",")],
|
raw_services = [s.strip() for s in config.get(section, "services").split(",")]
|
||||||
"schedule": config.get(section, "schedule"),
|
expanded_services = []
|
||||||
}
|
for service in raw_services:
|
||||||
for section in config.sections() if section.startswith("client:")
|
if service in aliases:
|
||||||
}
|
expanded_services.extend(aliases[service])
|
||||||
|
else:
|
||||||
|
expanded_services.append(service)
|
||||||
|
|
||||||
|
clients[section] = {
|
||||||
|
"ip": config.get(section, "ip"),
|
||||||
|
"services": expanded_services,
|
||||||
|
"schedule": config.get(section, "schedule"),
|
||||||
|
}
|
||||||
|
|
||||||
for srv_name, srv in servers.items():
|
for srv_name, srv in servers.items():
|
||||||
if not silent:
|
if not silent:
|
||||||
@ -123,7 +159,11 @@ def main(config_paths, silent=False):
|
|||||||
for cdata in clients.values():
|
for cdata in clients.values():
|
||||||
if should_block(cdata["schedule"]):
|
if should_block(cdata["schedule"]):
|
||||||
for service in cdata["services"]:
|
for service in cdata["services"]:
|
||||||
rule = f"||{service}^$client={cdata['ip']}"
|
# Zachowaj oryginalną składnię (regex, wyjątki, itp.)
|
||||||
|
if service.startswith(("||", "@@", "/", "!", "#", "127.0.0.1")):
|
||||||
|
rule = f"{service}$client={cdata['ip']}"
|
||||||
|
else:
|
||||||
|
rule = f"||{service}^$client={cdata['ip']}" # Domyślna reguła
|
||||||
rules_set.add(rule)
|
rules_set.add(rule)
|
||||||
if not silent:
|
if not silent:
|
||||||
show_block_details(cdata["ip"], service, cdata["schedule"])
|
show_block_details(cdata["ip"], service, cdata["schedule"])
|
||||||
@ -131,7 +171,7 @@ def main(config_paths, silent=False):
|
|||||||
payload = {"rules": sorted(rules_set)}
|
payload = {"rules": sorted(rules_set)}
|
||||||
endpoint = f"{srv['url']}/control/filtering/set_rules"
|
endpoint = f"{srv['url']}/control/filtering/set_rules"
|
||||||
response = requests.post(endpoint, json=payload, auth=auth,
|
response = requests.post(endpoint, json=payload, auth=auth,
|
||||||
headers={"Content-Type": "application/json"})
|
headers={"Content-Type": "application/json"})
|
||||||
|
|
||||||
if not silent:
|
if not silent:
|
||||||
if response.status_code in [200, 204]:
|
if response.status_code in [200, 204]:
|
||||||
|
@ -3,11 +3,27 @@ url = http://192.168.1.1:3000
|
|||||||
username = admin
|
username = admin
|
||||||
password = mypass
|
password = mypass
|
||||||
|
|
||||||
[server:office]
|
[server:office-dns1]
|
||||||
url = http://10.0.0.1:3000
|
url = http://10.0.0.1:3000
|
||||||
username = admin
|
username = admin
|
||||||
password = secret123
|
password = secret123
|
||||||
|
|
||||||
|
[server:office-dns2]
|
||||||
|
url = http://10.0.0.1:3000
|
||||||
|
username = admin
|
||||||
|
password = secret123
|
||||||
|
|
||||||
|
[alias:tiktok]
|
||||||
|
domains = tiktok.com, www.tiktok.com, m.tiktok.com, vm.tiktok.com, vt.tiktok.com,
|
||||||
|
api.tiktok.com, log.tiktok.com, tiktokcdn.com, tiktokv.com,
|
||||||
|
musical.ly, bytedance.com
|
||||||
|
|
||||||
|
[alias:social]
|
||||||
|
domains = /(facebook|twitter|instagram)\.com/,@@||linkedin.com^ # Regex + wyjątek
|
||||||
|
|
||||||
|
[alias:custom]
|
||||||
|
domains = 127.0.0.1 malware.com, ! Komentarz, /^ads?\./
|
||||||
|
|
||||||
[client:tv]
|
[client:tv]
|
||||||
ip = 192.168.1.101
|
ip = 192.168.1.101
|
||||||
services = youtube.com, netflix.com
|
services = youtube.com, netflix.com
|
||||||
@ -25,7 +41,7 @@ schedule = custom:8-20
|
|||||||
|
|
||||||
[client:guest]
|
[client:guest]
|
||||||
ip = 192.168.1.200
|
ip = 192.168.1.200
|
||||||
services = tiktok.com
|
services = tiktok # alias
|
||||||
schedule = custom:0-0 ; zero godzin = nie blokuj
|
schedule = custom:0-0 ; zero godzin = nie blokuj
|
||||||
|
|
||||||
[client:office-laptop]
|
[client:office-laptop]
|
||||||
|
Reference in New Issue
Block a user