faa076370d
- 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.
67 lines
2.1 KiB
Python
67 lines
2.1 KiB
Python
"""Nginx state collector."""
|
|
|
|
from copy import deepcopy
|
|
from typing import Any
|
|
|
|
from lib import schema
|
|
from lib.common import compute_pending, strip_apply_meta
|
|
from lib.nginx import DEFAULT_CONFIG, SITES_DIR, _resolve_paths, get_config
|
|
from lib.state import _now_iso, register_collector
|
|
|
|
|
|
def _collect_nginx() -> schema.NginxState:
|
|
"""Collect nginx config and domains list.
|
|
|
|
Returns:
|
|
Dict containing config, domains, and timestamp.
|
|
"""
|
|
# Load config via lib.nginx — a pure read that applies the legacy-format
|
|
# migration in memory (the one-shot on-disk migration runs at daemon
|
|
# startup, see lib.bootstrap).
|
|
try:
|
|
cfg = get_config()
|
|
except Exception:
|
|
cfg = deepcopy(DEFAULT_CONFIG)
|
|
|
|
# Build flattened domains list (one entry per path)
|
|
backends = cfg.get("backends", {})
|
|
domains: list[dict[str, Any]] = []
|
|
for name, dom in cfg.get("domains", {}).items():
|
|
if "backend" not in dom:
|
|
continue
|
|
site = SITES_DIR / f"{name}.conf"
|
|
paths = _resolve_paths(dom, backends)
|
|
if not paths:
|
|
continue
|
|
for ppath, pcfg in paths.items():
|
|
entry: dict[str, Any] = {
|
|
"domain": name,
|
|
"path": ppath,
|
|
"backend": pcfg.get("backend", {}),
|
|
"online": site.exists() if SITES_DIR.exists() else False,
|
|
"force_ssl": dom.get("force_ssl", True),
|
|
"backend_name": dom["backend"],
|
|
"cert": dom.get("cert"),
|
|
}
|
|
if pcfg.get("is_management"):
|
|
entry["is_management"] = True
|
|
if pcfg.get("is_websocket"):
|
|
entry["is_websocket"] = True
|
|
domains.append(entry)
|
|
|
|
pending_changes, nginx_pending_diff = compute_pending(cfg)
|
|
safe_cfg = strip_apply_meta(cfg)
|
|
|
|
return {
|
|
"config": safe_cfg,
|
|
"domains": domains,
|
|
"status": {
|
|
"pending_changes": pending_changes,
|
|
"pending_diff": nginx_pending_diff,
|
|
},
|
|
"timestamp": _now_iso(),
|
|
}
|
|
|
|
|
|
register_collector("nginx", _collect_nginx)
|