refactor: introduce two-user daemon architecture with socket-based communication
- Add daemon/ module with aiohttp server, sync client, and handler registry - Add daemon/handlers/ for privileged operations (acme, dnsmasq, firewall, logs, nginx, wireguard) - Add system/acme-deploy.py, vacuum-walld sudoers and systemd service - Update API routes to use daemon client instead of lib/ directly - Update lib/, tests/, and webui/ for new architecture - Update docs and deployment scripts
This commit is contained in:
+42
-56
@@ -14,25 +14,8 @@ from pathlib import Path
|
||||
|
||||
from flask import Flask, render_template, request
|
||||
|
||||
from lib.acme import get_email, list_certs
|
||||
from lib.dnsmasq import get_config as dnsmasq_config
|
||||
from lib.dnsmasq import get_lease_table
|
||||
from lib.dnsmasq import get_status as dnsmasq_status
|
||||
from lib.firewall import (
|
||||
config_pending,
|
||||
get_active_zones,
|
||||
get_interfaces,
|
||||
get_services,
|
||||
get_zone_info,
|
||||
)
|
||||
from lib.firewall import (
|
||||
get_config as fw_config_get,
|
||||
)
|
||||
from daemon.client import get
|
||||
from lib.logging import setup_logging
|
||||
from lib.nginx import get_config as nginx_config
|
||||
from lib.nginx import get_domains
|
||||
from lib.wireguard import get_config as wg_config
|
||||
from lib.wireguard import status as wg_status
|
||||
from webui.api.certs import bp as certs_bp
|
||||
from webui.api.dhcp import bp as dhcp_bp
|
||||
from webui.api.firewall import bp as firewall_bp
|
||||
@@ -117,7 +100,6 @@ def _log_request_finish(response):
|
||||
|
||||
@app.template_filter("timestamp")
|
||||
def timestamp_filter(value):
|
||||
"""Convert an ISO timestamp string to a human-readable date."""
|
||||
if not value:
|
||||
return ""
|
||||
try:
|
||||
@@ -129,7 +111,6 @@ def timestamp_filter(value):
|
||||
|
||||
@app.template_filter("bytes")
|
||||
def bytes_filter(value):
|
||||
"""Format a byte count to a human-readable string (KB / MB / GB)."""
|
||||
try:
|
||||
num = float(value)
|
||||
except (ValueError, TypeError):
|
||||
@@ -145,7 +126,6 @@ def bytes_filter(value):
|
||||
|
||||
@app.template_filter("duration")
|
||||
def duration_filter(value):
|
||||
"""Format a duration in seconds to a human-readable string."""
|
||||
try:
|
||||
total = int(float(value))
|
||||
except (ValueError, TypeError):
|
||||
@@ -168,7 +148,6 @@ def duration_filter(value):
|
||||
|
||||
@app.template_filter("json_pretty")
|
||||
def json_pretty_filter(value):
|
||||
"""Pretty-print a JSON-serialisable value for debug displays."""
|
||||
import json
|
||||
|
||||
try:
|
||||
@@ -205,14 +184,21 @@ def _get_service_status(dnsmasq_info, wg_info):
|
||||
return services
|
||||
|
||||
|
||||
def _fw_config_get():
|
||||
"""Read firewall config via daemon."""
|
||||
return get("/firewall/config")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def dashboard():
|
||||
active_zones = _safely(get_active_zones, {})
|
||||
interfaces = _safely(get_interfaces, [])
|
||||
dnsmasq = _safely(dnsmasq_status, {})
|
||||
domains = _safely(get_domains, [])
|
||||
certs = _safely(list_certs, [])
|
||||
wg = _safely(wg_status, {})
|
||||
active_zones = _safely(
|
||||
lambda: {k: v for k, v in get("/firewall/zones").get("active", {}).items()}, {}
|
||||
)
|
||||
interfaces = _safely(lambda: get("/firewall/interfaces"), [])
|
||||
dnsmasq = _safely(lambda: get("/dnsmasq/status"), {})
|
||||
domains = _safely(lambda: get("/nginx/domains"), [])
|
||||
certs = _safely(lambda: get("/acme/list"), [])
|
||||
wg = _safely(lambda: get("/wireguard/status"), {})
|
||||
|
||||
return render_template(
|
||||
"dashboard.html",
|
||||
@@ -223,36 +209,30 @@ def dashboard():
|
||||
certs=certs,
|
||||
wg_status=wg,
|
||||
services=_get_service_status(dnsmasq, wg),
|
||||
firewall_config=_safely(fw_config_get, {}),
|
||||
firewall_pending=_safely(config_pending, {}),
|
||||
firewall_config=_safely(_fw_config_get, {}),
|
||||
firewall_pending=_safely(lambda: get("/firewall/config/pending"), {}),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/interfaces")
|
||||
def interfaces_page():
|
||||
firewall_config = _safely(fw_config_get, {})
|
||||
firewall_pending = _safely(config_pending, {})
|
||||
return render_template(
|
||||
"interfaces.html",
|
||||
interfaces=_safely(get_interfaces, []),
|
||||
active_zones=_safely(get_active_zones, {}),
|
||||
firewall_config=firewall_config,
|
||||
firewall_pending=firewall_pending,
|
||||
interfaces=_safely(lambda: get("/firewall/interfaces"), []),
|
||||
zones=_safely(lambda: get("/firewall/zones").get("available", []), []),
|
||||
firewall_config=_safely(_fw_config_get, {}),
|
||||
firewall_pending=_safely(lambda: get("/firewall/config/pending"), {}),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/zones")
|
||||
def zones_page():
|
||||
firewall_config = _safely(fw_config_get, {})
|
||||
firewall_pending = _safely(config_pending, {})
|
||||
zones_data = {}
|
||||
for name in _safely(get_active_zones, {}):
|
||||
zones_data[name] = _safely(lambda n=name: get_zone_info(n), {})
|
||||
firewall_config = _safely(_fw_config_get, {})
|
||||
firewall_pending = _safely(lambda: get("/firewall/config/pending"), {})
|
||||
return render_template(
|
||||
"zones.html",
|
||||
zones=zones_data,
|
||||
interfaces=_safely(get_interfaces, []),
|
||||
services=_safely(get_services, []),
|
||||
zones=_safely(lambda: get("/firewall/zones/all"), []),
|
||||
services=_safely(lambda: get("/firewall/services"), []),
|
||||
firewall_config=firewall_config,
|
||||
firewall_pending=firewall_pending,
|
||||
)
|
||||
@@ -260,8 +240,8 @@ def zones_page():
|
||||
|
||||
@app.route("/rules")
|
||||
def rules_page():
|
||||
zones = list(_safely(get_active_zones, {}).keys())
|
||||
raw = _safely(fw_config_get, {})
|
||||
zones = list(_safely(lambda: get("/firewall/zones").get("active", {}).keys(), []))
|
||||
raw = _safely(_fw_config_get, {})
|
||||
rules = {}
|
||||
for zname, zcfg in raw.get("zones", {}).items():
|
||||
rr = zcfg.get("rich_rules", [])
|
||||
@@ -272,40 +252,46 @@ def rules_page():
|
||||
|
||||
@app.route("/nat")
|
||||
def nat_page():
|
||||
zones = {}
|
||||
for name in _safely(get_active_zones, {}):
|
||||
zones[name] = _safely(lambda n=name: get_zone_info(n), {})
|
||||
return render_template("nat.html", zones=zones)
|
||||
return render_template(
|
||||
"nat.html", zones=_safely(lambda: get("/firewall/zones/all"), [])
|
||||
)
|
||||
|
||||
|
||||
@app.route("/dhcp")
|
||||
def dhcp_page():
|
||||
return render_template(
|
||||
"dhcp.html",
|
||||
config=_safely(dnsmasq_config, {}),
|
||||
status=_safely(dnsmasq_status, {}),
|
||||
leases=_safely(get_lease_table, []),
|
||||
config=_safely(lambda: get("/dnsmasq/config"), {}),
|
||||
status=_safely(lambda: get("/dnsmasq/status"), {}),
|
||||
leases=_safely(lambda: get("/dnsmasq/leases"), []),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/proxy")
|
||||
def proxy_page():
|
||||
return render_template(
|
||||
"proxy.html", domains=_safely(get_domains, []), config=_safely(nginx_config, {})
|
||||
"proxy.html",
|
||||
domains=_safely(lambda: get("/nginx/domains"), []),
|
||||
config=_safely(lambda: get("/nginx/config"), {}),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/certs")
|
||||
def certs_page():
|
||||
email_data = _safely(lambda: get("/acme/email"), {"email": ""})
|
||||
return render_template(
|
||||
"certs.html", certs=_safely(list_certs, []), email=_safely(get_email, "")
|
||||
"certs.html",
|
||||
certs=_safely(lambda: get("/acme/list"), []),
|
||||
email=email_data.get("email", ""),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/wireguard")
|
||||
def wireguard_page():
|
||||
return render_template(
|
||||
"wireguard.html", config=_safely(wg_config, {}), status=_safely(wg_status, {})
|
||||
"wireguard.html",
|
||||
config=_safely(lambda: get("/wireguard/config"), {}),
|
||||
status=_safely(lambda: get("/wireguard/status"), {}),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user