feat: pre-computed state store and async ACME issuance (fixes timeout mismatch)

- Add lib/state.py: in-memory state store with subsystem collectors
  (firewall, dnsmasq, nginx, acme, wireguard)
- Refactor all handlers: read from state on GET, call refresh_state()
  after mutations instead of invoking subprocesses per request
- daemon/server.py: add refresh_state(), /status/all, /status/refresh;
  populate state at startup
- webui/api/certs.py: async step-by-step ACME issuance (validate,
  issue with request_id, poll status) replacing blocking endpoint
- webui/server.py: render pages from state instead of direct lib calls
- Update templates, JS for async cert issuance with polling UI
- Update tests for state-based mocking; add test_state.py
- Fix SIM105 lint issue (contextlib.suppress)
- Add TODO.md with certificate issuance issue tracking

Resolves: WebUI 30s timeout freeze during cert issuance (Problem 1)
This commit is contained in:
2026-05-30 05:45:40 +00:00
parent c091063248
commit dc96e15643
19 changed files with 1960 additions and 986 deletions
+47
View File
@@ -0,0 +1,47 @@
# TODO — Certificate Issuance Issues
## Problem 1: Timeout Mismatch Causes WebUI Freeze → FIXED
**Date Found:** May 30, 2026
**Date Fixed:** May 30, 2026
**Symptom:** Clicking "Issue Certificate" in the WebUI freezes for ~30 seconds, then returns a 500 error with "Daemon request timed out". Meanwhile the daemon silently runs `acme.sh` in the background for up to 120s before timing out itself.
**Root Cause:**
- `daemon/client.py` — WebUI client uses `timeout=30` for all daemon requests
- `daemon/handlers/acme.py` — Daemon allows `acme.sh` subprocess `timeout=120`
- The WebUI gives up at 30s while the daemon is still legitimately processing
**Fix Applied:** Replaced blocking issue endpoint with async step-by-step issuance:
- `POST /acme/validate` — Pre-flight checks (instant): acme.sh installed, email configured, webroot ready, DNS resolves, challenge configured
- `POST /acme/issue` — Returns immediately with `request_id`, spawns background task
- `GET /acme/issue/status` — Client polls for step-by-step progress
- UI shows pre-check results, then step progress with polling (no timeout issues)
- Blocking DNS check prevents wasted acme.sh calls when domain doesn't resolve
**Files Changed:** `daemon/handlers/acme.py`, `webui/api/certs.py`, `webui/templates/certs.html`, `webui/static/app.js`
---
## Problem 2: ZeroSSL Rate Limits Block Certificate Issuance
**Date Found:** May 30, 2026
**Symptom:** `acme.sh` fails to issue a certificate for `218broad.vacuum.network` with:
```
The retryafter=86400 value is too large (> 600), will not retry anymore.
```
**Root Cause:** ZeroSSL CA returns a `retry-after` of 86400 seconds (24 hours), likely from a prior failed challenge. `acme.sh` has a hard cap of 600s on retry-after values and refuses to proceed when the CA requests a longer wait.
**Files:** N/A (acme.sh behavior, not a project code issue)
**Workarounds:**
- Switch CA to Let's Encrypt: `acme.sh --set-default-ca --server letsencrypt`
- Wait 24 hours and retry
- Investigate and clean up prior failed challenges for the domain on ZeroSSL's side
## Status
- **Problem 1:** ✅ FIXED (2026-05-30) — Refactored to async step-by-step issuance with polling. State store collectors replace blocking subprocess calls on every request.
- **Problem 2:** ️ N/A — ZeroSSL rate limit is external to the project. Workarounds: switch CA to Let's Encrypt, wait 24h, or clean up failed challenges on ZeroSSL's side.