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:
+28
-5
@@ -86,7 +86,13 @@ POST /api/firewall/config/apply
|
||||
|
||||
Apply the declarative config to live firewalld. Applies targets, services, interfaces, masquerade, rich rules, and forward ports.
|
||||
|
||||
**Response:** `data` contains `applied_zones` list and backup path.
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `applied_zones` | `[string, ...]` | List of zone names that were applied |
|
||||
| `backup` | `string` | Path to the firewall state backup file |
|
||||
| `synced` | `[string, ...]` | Subsystems that were automatically updated by the sync event bus |
|
||||
|
||||
#### Check Pending Changes
|
||||
|
||||
@@ -470,7 +476,12 @@ POST /api/dhcp/apply
|
||||
|
||||
Write the in-memory configuration to `/etc/dnsmasq.d/vacuum-wall.conf` and reload the dnsmasq service.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `applied` | `boolean` | Always `true` on success |
|
||||
| `synced` | `[string, ...]` | Subsystems that were automatically updated by the sync event bus |
|
||||
|
||||
### Status
|
||||
|
||||
@@ -1114,7 +1125,12 @@ POST /api/wireguard/apply
|
||||
|
||||
Write the current configuration to `wg0.conf` and bring the tunnel up.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `applied` | `boolean` | Always `true` on success |
|
||||
| `synced` | `[string, ...]` | Subsystems that were automatically updated by the sync event bus |
|
||||
|
||||
---
|
||||
|
||||
@@ -1138,7 +1154,12 @@ POST /api/wireguard/down
|
||||
|
||||
Bring down the WireGuard tunnel interface (`wg0`).
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `down` | `boolean` | Always `true` on success |
|
||||
| `synced` | `[string, ...]` | Subsystems auto-synced as a result |
|
||||
|
||||
### Status
|
||||
|
||||
@@ -1329,7 +1350,8 @@ Save network config for an interface, render the `.network` file, copy it to `/e
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | `string` | Interface name |
|
||||
| `applied` | `boolean` | Always `true` on success |
|
||||
| `applied` | `boolean` | `true` if deploy to systemd-networkd succeeded, `false` if the system call was unavailable |
|
||||
| `synced` | `[string, ...]` | Subsystems that were automatically updated by the sync event bus |
|
||||
|
||||
Returns HTTP `400` if the interface name is invalid.
|
||||
|
||||
@@ -1369,6 +1391,7 @@ Full sync: generate all `.network` files, remove stale files, copy to `/etc/syst
|
||||
| `applied` | `number` | Number of interfaces applied |
|
||||
| `files` | `[string, ...]` | Paths of generated files |
|
||||
| `cleaned` | `[string, ...]` | Paths of removed stale files |
|
||||
| `synced` | `[string, ...]` | Subsystems that were automatically updated by the sync event bus |
|
||||
|
||||
### Helpers
|
||||
|
||||
|
||||
@@ -105,6 +105,46 @@ Poll intervals are configurable via `VACUUM_WALL_POLL_INTERVALS` env var (`firew
|
||||
|
||||
On collector failure during a poll, no broadcast is sent (avoids noisy ticks). State data is set to `None`.
|
||||
|
||||
## Cross-Subsystem Sync Event Bus
|
||||
|
||||
When a subsystem's configuration changes, related subsystems are automatically
|
||||
updated to stay consistent. An in-process event bus (`lib/sync.py`) decouples
|
||||
subsystems — no handler calls into another handler's logic directly.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. A mutation handler saves its config (e.g., adding a DHCP range).
|
||||
2. The handler emits a `SyncEvent` on the event bus.
|
||||
3. Subscribers react by updating related subsystem configs:
|
||||
- **DnsToFirewallSync**: Adds `dhcp`, `dns` services and `masquerade` to the
|
||||
firewall zone for each interface serving a DHCP range.
|
||||
- **WgToFirewallSync**: Creates or updates a `vpn` firewall zone with
|
||||
WireGuard interface and UDP 51820 rich rule.
|
||||
- **FirewallToDhcpSync**: Detects stale DHCP ranges for interfaces not in
|
||||
any zone (logs warnings, does not auto-remove).
|
||||
- **NetworkToAllSync**: Suggests DHCP ranges and syncs firewall zone
|
||||
interface assignments when network config changes.
|
||||
4. The handler refreshes state for the originating subsystem plus all
|
||||
transitively affected subsystems.
|
||||
|
||||
### Guard Rails
|
||||
|
||||
- **Idempotency**: Each subscriber reads current state, computes desired state,
|
||||
writes the diff. Running twice is safe.
|
||||
- **No loops**: The event bus tracks `(subsystem, action)` per dispatch cycle.
|
||||
Re-entrant emits for the same key are silently dropped.
|
||||
- **Firewall-cmd separation**: Sync subscribers only write JSON config. They
|
||||
do NOT call `firewall-cmd`. The user clicks "Apply" on the firewall page to
|
||||
push to firewalld.
|
||||
- **Error handling**: Subscriber exceptions are caught, logged as warnings,
|
||||
and do NOT abort the originating handler.
|
||||
|
||||
### Frontend Impact
|
||||
|
||||
Minimal. The sync happens transparently in the backend. The "pending changes"
|
||||
indicator on the firewall page will show pending when DHCP or WireGuard saves
|
||||
(since sync writes JSON but does not call firewall-cmd).
|
||||
|
||||
## Directory Structure
|
||||
|
||||
### Config — Declarative Settings
|
||||
|
||||
+17
-1
@@ -478,4 +478,20 @@ When `POST /api/network/apply` is called, the handler automatically collects pub
|
||||
|
||||
### Generated Files
|
||||
|
||||
Each interface config entry produces a `50-<name>.network` file in `data/networkd/`. During apply, these are copied to `/etc/systemd/network/` and stale files (not matching any config entry) are removed. File generation uses the `systemd.syntax(7)` naming convention: first section is bare (`[Address]`, `[Route]`), subsequent sections use `#` suffix (`[Address#1]`, `[Route#2]`).
|
||||
Each interface config entry produces a `50-<name>.network` file in `data/networkd/`. During apply, these are copied to `/etc/systemd/network/` and stale files (not matching any config entry) are removed. File generation uses the `systemd.syntax(7)` naming convention: first section is bare (`[Address]`, `[Route]`), subsequent sections use `#` suffix (`[Address#1]`, `[Route#2]`).
|
||||
|
||||
## Cross-Subsystem Dependencies
|
||||
|
||||
Some subsystems depend on each other. When you modify one, related subsystems
|
||||
are updated automatically through the event bus.
|
||||
|
||||
| Trigger Subsystem | Affected Subsystem | What Happens |
|
||||
|-------------------|-------------------|--------------|
|
||||
| dnsmasq (DHCP range) | firewall | Zone gains `dhcp`/`dns` services and `masquerade`. Removing the last range removes them. |
|
||||
| wireguard (peer add/remove) | firewall | `vpn` zone is created or maintained with `wg0` interface, masquerade, and UDP 51820 rule. |
|
||||
| firewall (zone changes) | dnsmasq | Stale DHCP ranges (for interfaces no longer in any zone) are automatically removed. Zones with dhcp service but no range are logged as warnings. |
|
||||
| network (interface config) | firewall | Zone interface assignments in firewall config are updated to match. |
|
||||
| network (interface config) | dnsmasq | Suggested DHCP ranges are logged when an interface has a static IP but no DHCP range. |
|
||||
|
||||
Note: The firewall "Apply" button is still needed to push config changes to
|
||||
firewalld. Sync only updates the declarative JSON.
|
||||
Reference in New Issue
Block a user