TP23 Stufe 5: HA-Self-Service + Dashboard-Draft + Doku-Nachzug
- agh-exception.py: Creds aus Env/Datei (Mac ~/.secrets + HAOS /config), Domain/IP-Validierung - HA-Self-Service dokumentiert: shell_command + Helper/Timer/2 Automations (adguard/ha-self-service.md) - Eigenes Dashboard 'dns-ausnahmen' (1. Draft, sections) inkl. Repro-JSON - teilprojekte.md: TP23 Quick-Index + dated Update + Ist-Stand-Block nachgezogen (Stufe 1-5 durch; offen nur Stufe 6 Bypass-Sperre -> separate Session) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,19 +14,40 @@ Aufrufer (HA-Timer) bzw. der RouterOS-Backstop (Stufe 6). Diese CLI ist reines a
|
||||
|
||||
Auth: AGH_AUTOMATION_USER/_PASS aus ~/.secrets/credentials.env (nie in argv).
|
||||
"""
|
||||
import sys, os, json, base64, urllib.request, urllib.error, pathlib
|
||||
import sys, os, json, base64, urllib.request, urllib.error, pathlib, re
|
||||
|
||||
DOMAIN_RE = re.compile(r"^(?=.{1,253}$)([a-z0-9](-?[a-z0-9]+)*\.)+[a-z]{2,}$", re.I)
|
||||
IP_RE = re.compile(r"^\d{1,3}(\.\d{1,3}){3}$")
|
||||
|
||||
INST = ["10.40.20.36", "10.40.20.37"]
|
||||
SECRETS = pathlib.Path.home() / ".secrets/credentials.env"
|
||||
# Creds-Quellen in Reihenfolge: erste, die User+Pass liefert, gewinnt.
|
||||
# So läuft derselbe Code auf dem Mac (~/.secrets) und auf HAOS (/config).
|
||||
CRED_FILES = [
|
||||
os.environ.get("AGH_CREDS_FILE"),
|
||||
str(pathlib.Path.home() / ".secrets/credentials.env"),
|
||||
"/config/agh-exception.secrets",
|
||||
]
|
||||
|
||||
|
||||
def _from_file(path):
|
||||
u = p = None
|
||||
try:
|
||||
for line in open(path).read().splitlines():
|
||||
if line.startswith("AGH_AUTOMATION_USER="): u = line.split("=", 1)[1].strip()
|
||||
if line.startswith("AGH_AUTOMATION_PASS="): p = line.split("=", 1)[1].strip()
|
||||
except OSError:
|
||||
return None
|
||||
return (u, p) if (u and p) else None
|
||||
|
||||
|
||||
def creds():
|
||||
u = p = None
|
||||
for line in SECRETS.read_text().splitlines():
|
||||
if line.startswith("AGH_AUTOMATION_USER="): u = line.split("=", 1)[1].strip()
|
||||
if line.startswith("AGH_AUTOMATION_PASS="): p = line.split("=", 1)[1].strip()
|
||||
if not (u and p): sys.exit("AGH_AUTOMATION_USER/_PASS fehlen in ~/.secrets")
|
||||
return u, p
|
||||
eu, ep = os.environ.get("AGH_AUTOMATION_USER"), os.environ.get("AGH_AUTOMATION_PASS")
|
||||
if eu and ep:
|
||||
return eu, ep
|
||||
for f in CRED_FILES:
|
||||
if f and (r := _from_file(f)):
|
||||
return r
|
||||
sys.exit("AGH_AUTOMATION_USER/_PASS: weder Env noch eine Creds-Datei gefunden")
|
||||
|
||||
|
||||
AUTH = "Basic " + base64.b64encode(":".join(creds()).encode()).decode()
|
||||
@@ -53,6 +74,9 @@ def rule(domain):
|
||||
|
||||
|
||||
def allow(domain, remove=False):
|
||||
domain = domain.strip().lower()
|
||||
if not DOMAIN_RE.match(domain):
|
||||
sys.exit(f"ungültige Domain: {domain!r}")
|
||||
r = rule(domain)
|
||||
for ip in INST:
|
||||
st = api(ip, "/control/filtering/status")
|
||||
@@ -66,6 +90,8 @@ def allow(domain, remove=False):
|
||||
|
||||
|
||||
def set_filter(ip_addr, enabled):
|
||||
if not IP_RE.match(ip_addr):
|
||||
sys.exit(f"ungültige IP: {ip_addr!r}")
|
||||
for ip in INST:
|
||||
data = api(ip, "/control/clients")
|
||||
c = None
|
||||
|
||||
Reference in New Issue
Block a user