UI improvements and input validations
This commit is contained in:
parent
b8c4914a52
commit
270856b391
22 changed files with 1548 additions and 302 deletions
108
router/core.py
108
router/core.py
|
|
@ -111,9 +111,18 @@ DNSMASQ_CONF_DIR = Path("/etc/dnsmasq-router")
|
|||
LEASES_DIR = Path("/var/lib/misc")
|
||||
NETWORKD_DIR = Path("/etc/systemd/network")
|
||||
SYSTEMD_DIR = Path("/etc/systemd/system")
|
||||
TIMER_NAME = "dns-blocklists-update"
|
||||
TIMER_FILE = SYSTEMD_DIR / f"{TIMER_NAME}.timer"
|
||||
TIMER_SVC_FILE = SYSTEMD_DIR / f"{TIMER_NAME}.service"
|
||||
BLIST_TIMER_NAME = "dns-blocklists-update"
|
||||
BLIST_TIMER_FILE = SYSTEMD_DIR / f"{BLIST_TIMER_NAME}.timer"
|
||||
BLIST_TIMER_SVC_FILE = SYSTEMD_DIR / f"{BLIST_TIMER_NAME}.service"
|
||||
DASHB_TIMER_NAME = "router-dashboard-queue"
|
||||
DASHB_TIMER_FILE = SYSTEMD_DIR / f"{DASHB_TIMER_NAME}.timer"
|
||||
DASHB_TIMER_SVC_FILE = SYSTEMD_DIR / f"{DASHB_TIMER_NAME}.service"
|
||||
DASHB_TIMER_INTERVAL_SEC = 60
|
||||
DASHB_QUEUE_FILE = SCRIPT_DIR / ".dashboard-queue"
|
||||
DASHB_DONE_FILE = SCRIPT_DIR / ".dashboard-done"
|
||||
DASHB_LAST_RUN_FILE = SCRIPT_DIR / ".dashboard-last-run"
|
||||
DASHB_LOCK_FILE = SCRIPT_DIR / ".dashboard-lock"
|
||||
DASHB_SCRIPT_FILE = SCRIPT_DIR / "do_dashboard_queue.sh"
|
||||
RESOLV_CONF = Path("/etc/resolv.conf")
|
||||
NAT_SERVICE_NAME = "core-nat"
|
||||
NAT_SERVICE_FILE = SYSTEMD_DIR / f"{NAT_SERVICE_NAME}.service"
|
||||
|
|
@ -1729,20 +1738,77 @@ def install_timer(data):
|
|||
"",
|
||||
])
|
||||
|
||||
for path, content in ((TIMER_FILE, timer_content), (TIMER_SVC_FILE, service_content)):
|
||||
for path, content in ((BLIST_TIMER_FILE, timer_content), (BLIST_TIMER_SVC_FILE, service_content)):
|
||||
if not path.exists() or path.read_text() != content:
|
||||
path.write_text(content)
|
||||
print(f"Written: {path}")
|
||||
|
||||
subprocess.run(["systemctl", "daemon-reload"], capture_output=True, text=True)
|
||||
subprocess.run(["systemctl", "enable", "--now", f"{TIMER_NAME}.timer"],
|
||||
subprocess.run(["systemctl", "enable", "--now", f"{BLIST_TIMER_NAME}.timer"],
|
||||
capture_output=True, text=True)
|
||||
print(f"Timer {TIMER_NAME}.timer enabled (runs daily at {execute_time}).")
|
||||
print(f"Timer {BLIST_TIMER_NAME}.timer enabled (runs daily at {execute_time}).")
|
||||
|
||||
def install_dashboard_timer():
|
||||
"""Install the 1-minute dashboard-queue timer that processes .dashboard-queue."""
|
||||
timer_content = "\n".join([
|
||||
"# Generated by core.py -- do not edit manually.",
|
||||
"",
|
||||
"[Unit]",
|
||||
"Description=Router dashboard pending-update processor",
|
||||
"",
|
||||
"[Timer]",
|
||||
f"OnActiveSec={DASHB_TIMER_INTERVAL_SEC}s",
|
||||
f"OnUnitActiveSec={DASHB_TIMER_INTERVAL_SEC}s",
|
||||
"AccuracySec=10s",
|
||||
"",
|
||||
"[Install]",
|
||||
"WantedBy=timers.target",
|
||||
"",
|
||||
])
|
||||
|
||||
service_content = "\n".join([
|
||||
"# Generated by core.py -- do not edit manually.",
|
||||
"",
|
||||
"[Unit]",
|
||||
"Description=Router dashboard update processor",
|
||||
"",
|
||||
"[Service]",
|
||||
"Type=oneshot",
|
||||
f"ExecStart=/bin/bash {DASHB_SCRIPT_FILE}",
|
||||
"",
|
||||
])
|
||||
|
||||
for path, content in ((DASHB_TIMER_FILE, timer_content), (DASHB_TIMER_SVC_FILE, service_content)):
|
||||
if not path.exists() or path.read_text() != content:
|
||||
path.write_text(content)
|
||||
print(f"Written: {path}")
|
||||
|
||||
subprocess.run(["systemctl", "daemon-reload"], capture_output=True, text=True)
|
||||
subprocess.run(["systemctl", "enable", f"{DASHB_TIMER_NAME}.timer"],
|
||||
capture_output=True, text=True)
|
||||
active = subprocess.run(
|
||||
["systemctl", "is-active", f"{DASHB_TIMER_NAME}.timer"],
|
||||
capture_output=True, text=True
|
||||
).stdout.strip() == "active"
|
||||
verb = "restart" if active else "start"
|
||||
subprocess.run(["systemctl", verb, f"{DASHB_TIMER_NAME}.timer"],
|
||||
capture_output=True, text=True)
|
||||
print(f"Timer {DASHB_TIMER_NAME}.timer enabled (runs every {DASHB_TIMER_INTERVAL_SEC}s).")
|
||||
|
||||
def remove_dashboard_timer():
|
||||
subprocess.run(["systemctl", "disable", "--now", f"{DASHB_TIMER_NAME}.timer"],
|
||||
capture_output=True, text=True)
|
||||
for f in (DASHB_TIMER_FILE, DASHB_TIMER_SVC_FILE):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
print(f"Removed: {f}")
|
||||
else:
|
||||
print(f"Not found, skipping: {f}")
|
||||
|
||||
def remove_timer():
|
||||
subprocess.run(["systemctl", "disable", "--now", f"{TIMER_NAME}.timer"],
|
||||
subprocess.run(["systemctl", "disable", "--now", f"{BLIST_TIMER_NAME}.timer"],
|
||||
capture_output=True, text=True)
|
||||
for f in (TIMER_FILE, TIMER_SVC_FILE):
|
||||
for f in (BLIST_TIMER_FILE, BLIST_TIMER_SVC_FILE):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
print(f"Removed: {f}")
|
||||
|
|
@ -2252,12 +2318,12 @@ def apply_nftables(data, dry_run=False):
|
|||
dst = r.get("dst_ip_or_subnet") or r.get("dst_ip", "")
|
||||
try:
|
||||
# Single IP -- check if it's in an active subnet
|
||||
addr = _ipaddress.IPv4Address(dst)
|
||||
addr = ipaddress.IPv4Address(dst)
|
||||
return any(addr in net for net in active_subnets)
|
||||
except ValueError:
|
||||
try:
|
||||
# Subnet -- check if it overlaps with any active subnet
|
||||
net = _ipaddress.IPv4Network(dst, strict=False)
|
||||
net = ipaddress.IPv4Network(dst, strict=False)
|
||||
return any(net.overlaps(s) for s in active_subnets)
|
||||
except ValueError:
|
||||
return True
|
||||
|
|
@ -2636,7 +2702,7 @@ def show_status(data):
|
|||
units.append((vlan_service_name(vlan), "(wg0 not up)", "active"))
|
||||
else:
|
||||
units.append((vlan_service_name(vlan), None, "active"))
|
||||
units.append((f"{TIMER_NAME}.timer", None, "active"))
|
||||
units.append((f"{BLIST_TIMER_NAME}.timer", None, "active"))
|
||||
units.append((NAT_SERVICE_NAME, None, "inactive")) # oneshot - exits after running
|
||||
units.append(("freeradius", None, "active"))
|
||||
units.append(("avahi-daemon", None, "active"))
|
||||
|
|
@ -2652,12 +2718,12 @@ def show_status(data):
|
|||
|
||||
# Timer next trigger
|
||||
r = subprocess.run(
|
||||
["systemctl", "show", f"{TIMER_NAME}.timer", "--property=NextElapseUSecRealtime,NextElapseUSecMonotonic"],
|
||||
["systemctl", "show", f"{BLIST_TIMER_NAME}.timer", "--property=NextElapseUSecRealtime,NextElapseUSecMonotonic"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
# Fall back to human-readable 'Trigger' field from status output
|
||||
r2 = subprocess.run(
|
||||
["systemctl", "status", f"{TIMER_NAME}.timer", "--no-pager"],
|
||||
["systemctl", "status", f"{BLIST_TIMER_NAME}.timer", "--no-pager"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
for line in r2.stdout.splitlines():
|
||||
|
|
@ -2983,6 +3049,7 @@ def show_metrics(data):
|
|||
def stop_instances(data):
|
||||
"""Remove timer and stop all per-VLAN instances (config files preserved)."""
|
||||
remove_timer()
|
||||
remove_dashboard_timer()
|
||||
print()
|
||||
for vlan in data["vlans"]:
|
||||
svc = vlan_service_name(vlan)
|
||||
|
|
@ -3193,7 +3260,7 @@ def _dry_run_timer(data):
|
|||
print("-- Timer (dry-run) ---------------------------------------------------")
|
||||
general = data.get("general", {})
|
||||
execute_time = general.get("daily_execute_time_24hr_local", "02:30")
|
||||
for path, label in [(TIMER_FILE, "timer unit"), (TIMER_SVC_FILE, "service unit")]:
|
||||
for path, label in [(BLIST_TIMER_FILE, "timer unit"), (BLIST_TIMER_SVC_FILE, "service unit")]:
|
||||
action = "update" if path.exists() else "create and enable"
|
||||
print(f" Would {action}: {path}")
|
||||
print(f" Schedule: daily at {execute_time} local time (Persistent=true - catches up if missed)")
|
||||
|
|
@ -3213,7 +3280,7 @@ def _dry_run_disable(data, iface, use_dhcp, static_cidr, resolv_ok, dns_choice,
|
|||
print()
|
||||
|
||||
print("-- Stopping router services (dry-run) --------------------------------")
|
||||
print(f" Would disable and stop: {TIMER_NAME}.timer")
|
||||
print(f" Would disable and stop: {BLIST_TIMER_NAME}.timer")
|
||||
for vlan in data["vlans"]:
|
||||
svc = vlan_service_name(vlan)
|
||||
conf = vlan_conf_file(vlan)
|
||||
|
|
@ -3484,6 +3551,13 @@ def cmd_install(data):
|
|||
check_root()
|
||||
check_dependencies()
|
||||
print("All required packages are installed.")
|
||||
install_dashboard_timer()
|
||||
# Create blank dotfiles for dashboard updates
|
||||
for dotfile in (DASHB_QUEUE_FILE, DASHB_DONE_FILE, DASHB_LAST_RUN_FILE, DASHB_LOCK_FILE):
|
||||
if not dotfile.exists():
|
||||
dotfile.touch()
|
||||
chown_to_script_dir_owner(dotfile)
|
||||
print(f"Created: {dotfile}")
|
||||
|
||||
|
||||
def cmd_apply(data, dry_run=False):
|
||||
|
|
@ -3580,6 +3654,10 @@ def cmd_apply(data, dry_run=False):
|
|||
install_timer(data)
|
||||
print()
|
||||
|
||||
print("-- Dashboard timer ---------------------------------------------------")
|
||||
install_dashboard_timer()
|
||||
print()
|
||||
|
||||
print("-- Boot service ------------------------------------------------------")
|
||||
install_nat_service()
|
||||
print()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue