6ce61e862a
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
4.1 KiB
4.1 KiB
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_versionin 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: exportedGracefulSelfRestart(reuse; the unexported one calls it).cmd/controller/main.go: wiredConfigRefresherintoOnPushResponsebeside 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 == appliedguard makesTestReconcile_SameVersion_NoOpFAIL (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).
- First-run baseline (no restart): first report after deploy recorded
applied_config_version=1withRestartCount=0— baseline recorded, no restart. - Config edit converges: bumped demo-felhom
config_version1→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 byweb.session_secretchanging (e1ed2dd3…→f456197e…, regenerated by the hub each pull);local_apiblock preserved. - No restart storm: 4 further report cycles at config_version=2 → zero config-refresh activity, container stayed up (no further restart).
- Apps unaffected: during the config-apply restart only
felhom-controllerrestarted — every customer app (nextcloud, gitea, calcom, rallly, sparkyfitness, …) stayedUp (healthy). - 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.MaybeIngestnever-clobber is untouched; the refresh is a separate explicit re-pull keyed on a version change.