6ce61e862a
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
61 lines
4.1 KiB
Markdown
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.
|