Files
felhom-controller/REPORT.md
T

61 lines
4.1 KiB
Markdown

# REPORT — pull-based config-refresh (controller v0.94.0)
**Date:** 2026-06-30
**Task:** retire the hub's inbound controls + add pull-based config delivery. Controller half (companion:
hub v0.26.0). The controller re-pulls `controller.yaml` + self-restarts when the report ACK's
`config_version` changes — the box-pulls-config replacement for the retired "Push Config".
## Baselines
- felhom-controller `464b14f` (v0.93.0) → `419d3d0` (**v0.94.0**, live on guest 9201 / felhom-pve).
- Companion hub v0.26.0 live on k3s (advertises `config_version` in the report ACK).
## What changed
- `internal/report/pusher.go`: `PushResponse.ConfigVersion` (`json:"config_version"`).
- `internal/report/config_refresh.go` (NEW): `ConfigRefresher.Reconcile` — testable reconcile with
injected side effects. change → Refresh→Record→Restart; first-run records baseline (no restart);
unchanged = no-op; failed pull keeps config (no record/restart, retries); record-before-restart.
- `internal/bootstrap/bootstrap.go`: `RefreshConfig` — re-pull + re-write controller.yaml, re-merging
local_api from bootstrap.json (reuses pullWithRetry/mergeLocalAPI/writeFileAtomic). Overwrites
controller.yaml (hub = source of truth); never touches settings.json; fail-safe.
- `internal/settings/settings.go`: `applied_config_version` + Get/Set.
- `internal/api/selfrestart.go`: exported `GracefulSelfRestart` (reuse; the unexported one calls it).
- `cmd/controller/main.go`: wired `ConfigRefresher` into `OnPushResponse` beside the floor reconcile.
## Tests & green gate
- `go build ./... && go vet ./... && go test ./...`**all green**.
- `Reconcile`: change→refresh+record+restart; **same-version NO-OP red-proof**; baseline-no-restart;
failed-pull no-record/no-restart; zero-version no-op; record-fail skips restart.
- `RefreshConfig`: re-pull overwrites + re-merges local_api; failed pull leaves config unchanged;
absent bootstrap errors without writing.
- **Red-proof (no-restart-storm):** removing the `ackVersion == applied` guard makes
`TestReconcile_SameVersion_NoOp` FAIL (`refreshes=1/restarts=1, want 0`) — proving the guard prevents
a restart-every-report loop. Restored → green.
## Live validation (guest 9201 on felhom-pve, hub.felhom.eu)
Deployed 0.94.0 via the golden/bootstrap mechanism; push_interval temporarily 1m for fast cycles
(restored to 15m after).
1. **First-run baseline (no restart):** first report after deploy recorded `applied_config_version=1`
with `RestartCount=0` — baseline recorded, no restart.
2. **Config edit converges:** bumped demo-felhom `config_version` 1→2 in the hub DB (equivalent to a
config save). Next report ACK carried 2; controller logged:
`config-refresh: hub config_version=2 != applied=1 — re-pulling controller.yaml`
`re-pulled … merged local_api (192.168.0.162:8443)`
`applied config_version=2 — self-restarting``Graceful self-restart: exiting (0)`.
Container restarted (`RestartCount 0→1`, healthy); `applied_config_version=2`; the re-pull is proven
by `web.session_secret` changing (`e1ed2dd3…``f456197e…`, regenerated by the hub each pull);
`local_api` block preserved.
3. **No restart storm:** 4 further report cycles at config_version=2 → zero config-refresh activity,
container stayed up (no further restart).
4. **Apps unaffected:** during the config-apply restart only `felhom-controller` restarted — every
customer app (nextcloud, gitea, calcom, rallly, sparkyfitness, …) stayed `Up (healthy)`.
5. **No inbound:** the whole flow ran with the hub never connecting into the box (it only answers the
box's report); hub `grep client.Do internal/web/` is clean of ControllerURL targets.
## Notes
- A config change rotates `web.session_secret` (configgen regenerates it per pull) → controller
dashboard sessions are invalidated on a config apply. This is identical to the retired Push Config
behaviour (same configgen path) — no regression; only on actual config changes, not every report.
- The first-boot `bootstrap.MaybeIngest` never-clobber is untouched; the refresh is a separate explicit
re-pull keyed on a version change.