Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TtXesNa2LGbMmE4DNL6SE7
4.3 KiB
REPORT — v0.85.0: self-update reworked — in-guest pull + agent swap (Phase 1)
Repo: felhom-controller · Version: v0.84.0 → v0.85.0 (+ v0.85.1 version-only validation
target) · Date: 2026-06-26
What & why
The self-update button was dead in the LXC architecture: selfupdate/updater.go drove the old bare-metal
docker compose -f /opt/docker/felhom-controller/docker-compose.yml up -d — a path that doesn't exist in
the guest ("docker-compose.yml nem elérhető"). The stranded 0.77.0 demo could detect 0.84 but not install
it. Phase 1: the controller pulls the target image in-guest, then delegates the container swap to the
host agent (which owns restart + verify + rollback). The controller never docker rm/recreates itself.
Baseline
felhom-controller e0cf78b v0.84.0 → v0.85.0. Cross-repo: felhom-agent v0.42.0 (POST /controller/swap).
Changes
internal/selfupdate/updater.go— rewrote the install path. KeptCheckForUpdate/queryRegistry/VerifyStartup/state.performUpdatenow:pullImage(docker login--password-stdin→ pull → logout, in-guest over the shared socket) →agent.SwapController(targetImage). RemovedupdateComposeFile+composePath+ thedocker compose up -dflow.NewUpdatertakes anAgentSwapper(nil → update unavailable) instead of a compose path.DryRunreportsagent_reachable+pull_capable. Added small test seams (queryFn/pullFn).internal/agentapi/client.go—SwapController(POST/controller/swap, 202) +SwapStatus.cmd/controller/main.go— build a (nil-able) agent client from the provisioned local-API config and pass it toNewUpdater.- UI + router unchanged: the existing button (
triggerUpdate→ POST/api/selfupdate/update) + poll (pollUntilBack) already drive it; success/rollback is detected by the existingVerifyStartup(running version vs target). Latest-only.
Tests (internal/selfupdate/updater_test.go) — all green (was 0 → 4)
up-to-date → no pull / no agent (Scenario C); pull-fails → agent never called, state failed (Scenario D);
happy → pull then exactly one SwapController with the right ref; no-agent → unavailable.
go build ./... && go vet ./... && go test ./... — all ok.
Build / deploy
Built + pushed gitea.dooplex.hu/admin/felhom-controller:0.85.0 (and :0.85.1, a version-only build, as
the live update target). The demo was not manually deployed — the update feature itself brought 9201
to latest.
Live test on demo 9201 (operator-authorized; method per check)
- Demo started at controller 0.77.0 (old dead self-update). Brought to 0.85.0 (new code) via the agent
primitive (
felhom-agent --selftest=controller-swap), each hop verified healthy. - E2E via the real Settings button (claude-in-chrome on
felhom.demo-felhom.eu/settings): clicked "Frissítés keresése" → the new controller'squeryRegistrydetected 0.85.1 • Frissítés elérhető; clicked "Frissítés telepítése" → the controller pulled 0.85.1 in-guest and called the agent → the page polled/api/healthand reloaded showing current 0.85.1 and "Utolsó frissítés: ✅ Sikeres (0.85.0 → 0.85.1)"./api/selfupdate/status→last_state.status = "success". No compose error. - Host-side (authoritative): running container
…:0.85.1 Up (healthy),/etc/felhom-controller-image= 0.85.1, agent log "controller-swap: new controller healthy". - Hub: the controller's report path shows
0.84.0 → 0.85.0 → 0.85.1.
Observations (out of scope — not acted on)
- Phase 2 (next task): hub per-customer version floor (default global) + operator desired-version fleet-push. An existing 0.84.0 customer still has the OLD dead button — the first hop onto new-code is operator/agent-driven (the agent primitive), which Phase 2 formalizes.
- Golden freshness: the golden bakes 0.77.0; a golden rebuild at current is wanted so fresh provisions start current (independent of this feature).
DryRun'sauto_updatescheduled path is unchanged structurally; this task covered the manual button.
Commits
3c1e91b— v0.85.0 self-update rework + agentapi + tests.
No secrets committed; the registry token is used only for the in-guest docker login --password-stdin
(logged out after the pull) and is referenced out-of-band.