diff --git a/routlin/core.py b/routlin/core.py index 8debb38..5db2091 100644 --- a/routlin/core.py +++ b/routlin/core.py @@ -118,20 +118,11 @@ BLIST_TIMER_SVC_FILE = SYSTEMD_DIR / f"{BLIST_TIMER_NAME}.service" DDNS_TIMER_NAME = f"{PRODUCT_NAME}-ddns-update" DDNS_TIMER_FILE = SYSTEMD_DIR / f"{DDNS_TIMER_NAME}.timer" DDNS_TIMER_SVC_FILE = SYSTEMD_DIR / f"{DDNS_TIMER_NAME}.service" -DASHB_TIMER_NAME = f"{PRODUCT_NAME}-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" HEALTH_TIMER_NAME = f"{PRODUCT_NAME}-health-check" HEALTH_TIMER_FILE = SYSTEMD_DIR / f"{HEALTH_TIMER_NAME}.timer" HEALTH_TIMER_SVC_FILE = SYSTEMD_DIR / f"{HEALTH_TIMER_NAME}.service" HEALTH_TIMER_INTERVAL_SEC = 300 HEALTH_FILE = SCRIPT_DIR / ".health" -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 = f"{PRODUCT_NAME}-nat" NAT_SERVICE_FILE = SYSTEMD_DIR / f"{NAT_SERVICE_NAME}.service" @@ -2384,9 +2375,9 @@ def show_metrics(data): def stop_instances(data): """Remove timers and stop all per-VLAN instances (config files preserved).""" _remove_timers( - names=[BLIST_TIMER_NAME, DASHB_TIMER_NAME, HEALTH_TIMER_NAME, DDNS_TIMER_NAME], - timer_files=[BLIST_TIMER_FILE, DASHB_TIMER_FILE, HEALTH_TIMER_FILE, DDNS_TIMER_FILE], - svc_files=[BLIST_TIMER_SVC_FILE, DASHB_TIMER_SVC_FILE, HEALTH_TIMER_SVC_FILE, DDNS_TIMER_SVC_FILE], + names=[BLIST_TIMER_NAME, HEALTH_TIMER_NAME, DDNS_TIMER_NAME], + timer_files=[BLIST_TIMER_FILE, HEALTH_TIMER_FILE, DDNS_TIMER_FILE], + svc_files=[BLIST_TIMER_SVC_FILE, HEALTH_TIMER_SVC_FILE, DDNS_TIMER_SVC_FILE], daemon_reload=True, ) print() @@ -2980,21 +2971,14 @@ def cmd_apply(data, dry_run=False): print() print("Interval timers =====================================================") - # build parallel lists; dashboard timer only installed when queue file exists - t_names = [HEALTH_TIMER_NAME] - t_files = [HEALTH_TIMER_FILE] - s_files = [HEALTH_TIMER_SVC_FILE] - t_descs = ["Router status health check"] - t_execs = [f"/usr/bin/python3 {SCRIPT_DIR / 'health.py'}"] - t_intervals = [HEALTH_TIMER_INTERVAL_SEC] - if DASHB_QUEUE_FILE.exists(): - t_names += [DASHB_TIMER_NAME] - t_files += [DASHB_TIMER_FILE] - s_files += [DASHB_TIMER_SVC_FILE] - t_descs += ["Router dashboard pending-update processor"] - t_execs += [f"/bin/bash {DASHB_SCRIPT_FILE}"] - t_intervals += [DASHB_TIMER_INTERVAL_SEC] - _install_interval_timers(t_names, t_files, s_files, t_descs, t_execs, t_intervals) + _install_interval_timers( + names=[HEALTH_TIMER_NAME], + timer_files=[HEALTH_TIMER_FILE], + svc_files=[HEALTH_TIMER_SVC_FILE], + descriptions=["Router status health check"], + exec_starts=[f"/usr/bin/python3 {SCRIPT_DIR / 'health.py'}"], + interval_secs=[HEALTH_TIMER_INTERVAL_SEC], + ) print() print("DDNS timer ==========================================================") diff --git a/routlin/install.py b/routlin/install.py index 064288d..cc2d3ff 100644 --- a/routlin/install.py +++ b/routlin/install.py @@ -23,14 +23,23 @@ SCRIPT_DIR = Path(__file__).parent.resolve() COMPOSE_FILE = SCRIPT_DIR.parent / "docker" / "routlin-dash" / "docker-compose.yml" CADDYFILE = Path("/etc/caddy/Caddyfile") FLASK_PORT = 25327 +PRODUCT_NAME = os.environ.get('PRODUCT_NAME', 'routlin') +SYSTEMD_DIR = Path("/etc/systemd/system") -# Per-VLAN dnsmasq dotfiles (parallel to core.py constants) -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_PENDING_FILE = SCRIPT_DIR / ".dashboard-pending" -HEALTH_FILE = SCRIPT_DIR / ".health" +# Dashboard dotfiles +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_PENDING_FILE = SCRIPT_DIR / ".dashboard-pending" +DASHB_SCRIPT_FILE = SCRIPT_DIR / "do_dashboard_queue.sh" +HEALTH_FILE = SCRIPT_DIR / ".health" + +# Dashboard systemd timer +DASHB_TIMER_NAME = f"{PRODUCT_NAME}-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 # =================================================================== @@ -339,6 +348,62 @@ def create_dotfiles(): os.chown(f, stat.st_uid, stat.st_gid) +# =================================================================== +# Dashboard systemd timer +# =================================================================== + +def install_dashboard_timer(): + description = "Routlin dashboard pending-update processor" + timer_content = "\n".join([ + "# Generated by install.py -- do not edit manually.", + "", + "[Unit]", + f"Description={description}", + "", + "[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 install.py -- do not edit manually.", + "", + "[Unit]", + f"Description={description}", + "", + "[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)): + path.write_text(content) + print(f" Written: {path}") + subprocess.run(["systemctl", "daemon-reload"], capture_output=True, text=True) + active = subprocess.run( + ["systemctl", "is-active", f"{DASHB_TIMER_NAME}.timer"], + capture_output=True, text=True, + ).stdout.strip() == "active" + subprocess.run(["systemctl", "enable", f"{DASHB_TIMER_NAME}.timer"], capture_output=True, text=True) + subprocess.run(["systemctl", "restart" if active else "start", 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}") + subprocess.run(["systemctl", "daemon-reload"], capture_output=True, text=True) + + # =================================================================== # Caddy # =================================================================== @@ -518,6 +583,7 @@ def main(): if not want_dashboard: print() print(" Skipping dashboard setup.") + remove_dashboard_timer() print() print("Done.") print(next_step) @@ -543,6 +609,10 @@ def main(): setup_docker_compose(reuse_config=reuse_config) create_dotfiles() + # -- Dashboard timer ------------------------------------------- + header("Dashboard Timer") + install_dashboard_timer() + # -- External access ------------------------------------------- header("External Access (optional)") ext_domain = _external_access_domain()