sync: add cross-subsystem event bus for config consistency

Add EventBus with loop guards to keep firewall, dnsmasq, wireguard,
and network configs consistent. Handlers emit SyncEvent after mutations;
subscribers compute diffs and write JSON without manual cascade loops.
This commit is contained in:
2026-06-29 20:44:36 +00:00
parent 348bbfbca6
commit 9088f34345
15 changed files with 2117 additions and 59 deletions
+36 -8
View File
@@ -25,6 +25,7 @@ from daemon.iface import (
)
from daemon.server import NotFoundError, refresh_state, registry
from lib.common import deep_merge, load_json, run, run_proc, save_json
from lib.sync import SyncEvent, bus
logger = logging.getLogger(__name__)
@@ -126,7 +127,10 @@ def save_config_handler(_request: Any, body: dict[str, Any] | None) -> dict[str,
if current_key:
body.setdefault("interface", {})["private_key"] = current_key
_save_config(body)
refresh_state(["wireguard"])
sync_result = bus.emit(
SyncEvent("wireguard", "config_saved", {"action": "config_saved"})
)
refresh_state(["wireguard", *sync_result.affected_subsystems])
return {"config_saved": True}
@@ -146,7 +150,10 @@ def patch_config(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
current = _get_config()
merged = deep_merge(current, body)
_save_config(merged)
refresh_state(["wireguard"])
sync_result = bus.emit(
SyncEvent("wireguard", "config_saved", {"action": "config_patched"})
)
refresh_state(["wireguard", *sync_result.affected_subsystems])
return {"config_saved": True}
@@ -167,8 +174,11 @@ def apply(_request: Any, _body: Any) -> dict[str, Any]:
local_tmp.unlink(missing_ok=True)
run([WG_QUICK_BIN, "up", cfg["interface"]["name"]], sudo=True)
logger.info("WireGuard tunnel '%s' brought up", cfg["interface"]["name"])
refresh_state(["wireguard"])
return {"applied": True}
sync_result = bus.emit(
SyncEvent("wireguard", "config_saved", {"action": "config_applied"})
)
refresh_state(["wireguard", *sync_result.affected_subsystems])
return {"applied": True, "synced": sync_result.affected_subsystems}
@registry.register(POST_WIREGUARD_DOWN)
@@ -178,7 +188,10 @@ def down(_request: Any, _body: Any) -> dict[str, Any]:
name = cfg["interface"]["name"]
run([WG_QUICK_BIN, "down", name], sudo=True)
logger.info("WireGuard tunnel '%s' brought down", name)
refresh_state(["wireguard"])
sync_result = bus.emit(
SyncEvent("wireguard", "config_saved", {"action": "tunnel_down"})
)
refresh_state(["wireguard", *sync_result.affected_subsystems])
return {"down": True}
@@ -205,7 +218,10 @@ def initialize(_request: Any, _body: Any) -> dict[str, Any]:
cfg["interface"]["public_key"] = public_key
_save_config(cfg)
logger.info("WireGuard initialised (pubkey=%s...)", public_key[:16])
refresh_state(["wireguard"])
sync_result = bus.emit(
SyncEvent("wireguard", "config_saved", {"action": "initialized"})
)
refresh_state(["wireguard", *sync_result.affected_subsystems])
safe = dict(cfg)
safe["interface"] = dict(safe["interface"])
safe["interface"].pop("private_key", None)
@@ -235,6 +251,7 @@ def add_peer(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
if body.get("preshared_key") is not None:
peer["preshared_key"] = body["preshared_key"]
logger.info("WireGuard peer '%s' updated", name)
_peer_action = "peer_updated"
else:
res = run_proc([WG_BIN, "genkey"], sudo=True)
priv = res.stdout.strip()
@@ -249,8 +266,14 @@ def add_peer(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
"preshared_key": body.get("preshared_key"),
}
logger.info("WireGuard peer '%s' added", name)
_peer_action = "peer_added"
_save_config(cfg)
refresh_state(["wireguard"])
sync_result = bus.emit(
SyncEvent(
"wireguard", "config_saved", {"action": _peer_action, "peer_name": name}
)
)
refresh_state(["wireguard", *sync_result.affected_subsystems])
peer_out = dict(peers[name])
peer_out.pop("private_key", None)
return peer_out
@@ -276,7 +299,12 @@ def remove_peer(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
del peers[name]
_save_config(cfg)
logger.info("WireGuard peer '%s' removed", name)
refresh_state(["wireguard"])
sync_result = bus.emit(
SyncEvent(
"wireguard", "config_saved", {"action": "peer_removed", "peer_name": name}
)
)
refresh_state(["wireguard", *sync_result.affected_subsystems])
return {"name": name}