WireGuard access classes, firewall nftables fixes, network sync event refactor

- WireGuard: refactor to multi-interface 'access classes' model; extract config
  generation and helpers into lib/wireguard.py; add per-class up/down endpoints
  and API routes; update UI with class management pages and QR code component
- Firewall: fix zone creation with --new-zone before --set-target; skip
  masquerade on public zone; add masquerade propagation for nftables backend
  so NAT works when internal zones exit via public
- Network: rename sync event subsystem 'network' -> 'networkd'; always stamp
  config hash even when deployment fails (fixes pending-changes detection)
- DHCP: add new API endpoint and update frontend page
- State/Sync: update state collectors and sync buses for new subsystems
- Docs: update API and config documentation for new endpoints and schemas
This commit is contained in:
2026-07-20 03:57:16 +00:00
parent dadabd7954
commit 04417cf05c
19 changed files with 2688 additions and 455 deletions
+12 -10
View File
@@ -214,16 +214,18 @@ def save_interface(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]
)
logger.info("Interface '%s' config saved (applied=%s)", name, deployed)
if deployed:
cfg_after = get_config()
cfg_after[_APPLY_HASH_KEY] = config_hash(cfg_after)
save_config(cfg_after)
# Always stamp the hash so pending-changes detection stays current
# even when deployment fails (e.g. in containerized environments).
# The hash represents the JSON config state, not the system state.
cfg_after = get_config()
cfg_after[_APPLY_HASH_KEY] = config_hash(cfg_after)
save_config(cfg_after)
sync_result = bus.emit(
SyncEvent(
"network", "config_saved", {"action": "interface_saved", "interface": name}
"networkd", "config_saved", {"action": "interface_saved", "interface": name}
)
)
refresh_state(["network", *sync_result.affected_subsystems])
refresh_state(["networkd", *sync_result.affected_subsystems])
return {
"name": name,
"applied": deployed,
@@ -296,9 +298,9 @@ def apply_all(_request: Any, _body: Any) -> dict[str, Any]:
cfg_after[_APPLY_HASH_KEY] = config_hash(cfg_after)
save_config(cfg_after)
sync_result = bus.emit(
SyncEvent("network", "config_saved", {"action": "config_applied"})
SyncEvent("networkd", "config_saved", {"action": "config_applied"})
)
refresh_state(["network", *sync_result.affected_subsystems])
refresh_state(["networkd", *sync_result.affected_subsystems])
logger.info(
"Network config applied: %d interfaces, %d stale cleaned",
@@ -364,7 +366,7 @@ def set_sysctl(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
logger.info("sysctl %s set to %s", name, value)
sync_result = bus.emit(
SyncEvent("network", "config_saved", {"action": "sysctl_set", "name": name})
SyncEvent("networkd", "config_saved", {"action": "sysctl_set", "name": name})
)
refresh_state(["network", *sync_result.affected_subsystems])
refresh_state(["networkd", *sync_result.affected_subsystems])
return {"name": name, "value": value}