docs: add comprehensive docstrings and inline comments

Add docstrings to all handler functions in daemon/handlers/firewall.py, covering
params, return values, and raised exceptions. Add inline comments to
_config_apply() reconciliation steps and the request body merge order.

Add docstrings across lib/ modules for emit helpers (_emit_str, _emit_int, etc.),
volatile stripping logic, two-layer diff strategy, sync event dispatch, and all
cross-subsystem sync subscribers (DnsToFirewall, WgToFirewall, FirewallToDhcp,
NetworkToAllSync).

Document WireGuard/networkd config parsers and key-value mappers in
system_import.py. Add docstrings to _ep(), Registry.decorator,
setup_logging, and _replace helper across daemon/ and lib/.
This commit is contained in:
2026-07-13 17:26:45 +00:00
parent 2e49dec633
commit c21639b7f1
10 changed files with 510 additions and 17 deletions
+77
View File
@@ -147,10 +147,14 @@ class EventBus:
"""Core dispatch logic (called within try/finally of _dispatch)."""
result = SyncResult()
# Iterate subscribers for this (subsystem, action) pair.
# Each subscriber is called in registration order.
for handler in self._subscribers.get((event.subsystem, event.action), []):
try:
sub_result = handler(event)
except Exception:
# Error containment: subscriber failures are logged, never abort
# the originating handler or other subscribers.
logger.warning(
"Sync subscriber %s failed for %s.%s",
_safe_name(handler),
@@ -163,10 +167,14 @@ class EventBus:
if sub_result is None:
continue
# Accumulate results from this subscriber into the aggregate.
result.affected_subsystems.extend(sub_result.affected_subsystems)
result.changes.extend(sub_result.changes)
result.applied = result.applied or sub_result.applied
# Cascade: for each affected subsystem (that isn't the source),
# emit a new event so downstream subscribers react. The cascade
# event carries _cascade=source so subscribers can detect loops.
for affected in sub_result.affected_subsystems:
if affected == event.subsystem:
continue
@@ -180,6 +188,8 @@ class EventBus:
result.changes.extend(cascaded.changes)
result.applied = result.applied or cascaded.applied
# Dedupe affected list before returning (cascade events may cause
# the same subsystem to appear multiple times).
result.affected_subsystems = _dedupe(result.affected_subsystems)
return result
@@ -247,6 +257,25 @@ class DnsToFirewallSync:
@classmethod
def on_dnsmasq_config_saved(cls, event: SyncEvent) -> SyncResult | None:
"""Sync subscriber: dnsmasq config_saved → update firewall zone services.
When DHCP ranges are added/removed on interfaces, this subscriber
ensures the corresponding firewall zones have ``dhcp`` and ``dns``
services enabled/disabled to match. Back-propagates: ensures
DHCP ranges carry the gateway (interface IP) so clients get
their default route.
Skips processing if event originated as a cascade from ``firewall``
to prevent infinite loops.
Args:
event: Sync event with ``config_saved`` action from dnsmasq.
Returns:
SyncResult listing firewall and dnsmasq as affected subsystems,
with human-readable change descriptions. ``None`` if skipped
due to cascade guard.
"""
if event.payload.get("_cascade") == "firewall":
return None
@@ -438,6 +467,23 @@ class WgToFirewallSync:
@classmethod
def on_wireguard_config_saved(cls, event: SyncEvent) -> SyncResult | None:
"""Sync subscriber: wireguard config_saved → update firewall config.
When WireGuard is active (has peers and interface), this subscriber
ensures the ``vpn`` zone exists with the WG interface assigned,
masquerade enabled, UDP 51820 accept rule, and inter-zone rich rules
for peer allowed_ips subnets. When WireGuard becomes inactive,
cleans up WireGuard-created entries from the vpn zone.
Skips processing if event originated as a cascade from ``firewall``.
Args:
event: Sync event with ``config_saved`` action from wireguard.
Returns:
SyncResult listing firewall as affected subsystem with change
descriptions. ``None`` if skipped due to cascade guard.
"""
if event.payload.get("_cascade") == "firewall":
return None
@@ -556,6 +602,23 @@ class FirewallToDhcpSync:
@classmethod
def on_firewall_config_saved(cls, event: SyncEvent) -> SyncResult | None:
"""Sync subscriber: firewall config_saved → sync dnsmasq DHCP ranges.
Removes DHCP ranges whose interface no longer belongs to any firewall
zone. When masquerade is enabled on a zone, ensures DHCP ranges on
that zone's interfaces carry the gateway (interface IP). Logs warnings
for zones with dhcp service but no range.
Skips processing if event originated as a cascade from ``dnsmasq``.
Args:
event: Sync event with ``config_saved`` action from firewall.
Returns:
SyncResult listing dnsmasq as affected subsystem when ranges were
modified, with change descriptions. ``None`` if skipped due to
cascade guard.
"""
if event.payload.get("_cascade") == "dnsmasq":
return None
@@ -665,6 +728,20 @@ class NetworkToAllSync:
@classmethod
def on_network_config_saved(cls, event: SyncEvent) -> SyncResult | None:
"""Sync subscriber: network config_saved → update firewall zone interfaces.
Suggests DHCP ranges for static-IP interfaces without ranges.
Removes interfaces from firewall zones that are no longer present
in the network config. Logs warnings for interfaces not assigned
to any zone.
Args:
event: Sync event with ``config_saved`` action from network.
Returns:
SyncResult listing firewall as affected subsystem when zone
interfaces were modified, with change descriptions.
"""
try:
from lib.dnsmasq import get_config as _get_dnsmasq_cfg
from lib.firewall import get_config as _get_fw_cfg