refactor: daemon collectors, thin webui proxies, pure config reads
- move state collectors from lib/state.py to daemon/collectors/ (7 modules, registration side-effect; daemon/server.py imports the package before the first populate()) - webui/api: new daemon_route() decorator factory in common.py collapses the try/except daemon-proxy boilerplate in all 8 blueprints (rules/params/body/transform keep responses identical) - firewall: interface-coverage invariant — config is the source of truth for zone interfaces (absent key = empty, no hands-off zones); pure validate_coverage() enforced at save (400) and apply (409, force: true overrides), top-level `unmanaged` exemption - lib: get_config() reads are now pure (no dir creation or writes); new lib/bootstrap.py creates runtime dirs and persists the one-shot nginx legacy migration at daemon start, after system_import (lib.nginx.migrate_config_file) - lib/common: compute_pending() apply-bookkeeping helper - daemon: emit_and_refresh() handler helper; refresh_state(bump=) so /status/refresh no longer bumps versions (poll/mutation only) - acme: move --log last so acme.sh never treats a real arg as the log-file argument - docs: AGENTS.md, config.md, state-model.md, api.md updated; HARDEN.md dropped (plan implemented); apply-confirm force wording Tests: 917 passed; ruff check + format clean.
This commit is contained in:
+29
-7
@@ -433,7 +433,9 @@ POST /api/firewall/config
|
||||
|
||||
Replace the declarative config. Returns pending changes summary.
|
||||
|
||||
**Request Body:** Request body must contain `zones`.
|
||||
**Request Body:** Request body must contain `zones`. An optional top-level `unmanaged` array (list of interface names) exempts those interfaces from the interface-coverage invariant.
|
||||
|
||||
**Errors:** Returns HTTP `400` when the body is malformed (missing/non-dict `zones`, non-list `unmanaged`) or when the config would leave a network-managed interface without zone coverage (the interface-coverage invariant — see `docs/config.md`).
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -452,6 +454,10 @@ POST /api/firewall/config/apply
|
||||
|
||||
Apply the declarative config to live firewalld. Applies targets, services, interfaces, masquerade, rich rules, and forward ports.
|
||||
|
||||
**Request Body:** Optional. Send `{"force": true}` to override the management-lockout and interface-coverage guards.
|
||||
|
||||
**Errors:** Returns HTTP `409` when the apply is refused by the management-lockout guard (https+ssh stripped from the default zone) or the interface-coverage invariant (a network-managed interface has no zone coverage and is not `unmanaged`). See `docs/config.md`.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -478,6 +484,10 @@ PATCH /api/firewall/config
|
||||
|
||||
Deep-merge the provided fields into the existing config. Returns pending changes summary.
|
||||
|
||||
**Request Body:** Partial config object; a provided `unmanaged` array replaces the existing one.
|
||||
|
||||
**Errors:** Returns HTTP `400` when the merged config is malformed or would leave a network-managed interface without zone coverage (interface-coverage invariant — see `docs/config.md`).
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
@@ -1983,8 +1993,8 @@ Apply pending changes for all subsystems in dependency order.
|
||||
```
|
||||
|
||||
`force` is forwarded to the firewall apply only — it overrides the
|
||||
management-lockout and interface-coverage guards. Other subsystems
|
||||
ignore it.
|
||||
management-lockout guard and the interface-coverage invariant. Other
|
||||
subsystems ignore it.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -1996,10 +2006,14 @@ ignore it.
|
||||
The endpoint returns `200` even when some subsystems failed — per-subsystem
|
||||
failures are reported in `errors`, so clients must check `errors` (not just
|
||||
the HTTP status) before reporting success. Without `force`, the firewall
|
||||
apply refuses if an interface would be left without zone coverage (the
|
||||
coverage guard) or both https/ssh would be stripped from the default zone
|
||||
(lockout guard); the `ConflictError` surfaces in `errors` under
|
||||
`"Firewall"` while the other subsystems proceed.
|
||||
apply is refused when a network-managed interface has no zone coverage in
|
||||
the config and is not `unmanaged` (the interface-coverage invariant) or when
|
||||
the config would strip both https/ssh from the default zone (lockout guard);
|
||||
the `ConflictError` surfaces in `errors` under `"Firewall"` while the other
|
||||
subsystems proceed. Pending state comes from
|
||||
the last state poll (firewall 30s, dnsmasq 10s, nginx 60s, wireguard 10s,
|
||||
networkd 10s), so an edit saved within the last poll interval may not be
|
||||
picked up by this call.
|
||||
|
||||
---
|
||||
|
||||
@@ -2016,6 +2030,14 @@ Subsystems without a recorded baseline (config never applied) are
|
||||
reported as skipped and left untouched. No live-system commands run —
|
||||
only the declarative config files are written.
|
||||
|
||||
Notes: pending state comes from the last state poll (firewall 30s,
|
||||
dnsmasq 10s, nginx 60s, wireguard 10s, networkd 10s), so an edit saved
|
||||
within the last poll interval is not yet flagged pending and is left in
|
||||
place. For the firewall, pending is a config-vs-live diff: cancel
|
||||
restores only the config file, so live firewalld drift made outside the
|
||||
declarative config (manual `firewall-cmd`) is not reverted and the
|
||||
firewall may still report pending after a cancel.
|
||||
|
||||
**Request Body:** none.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
Reference in New Issue
Block a user