# 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. Kept `CheckForUpdate`/`queryRegistry`/ `VerifyStartup`/state. `performUpdate` now: `pullImage` (docker login `--password-stdin` → pull → logout, in-guest over the shared socket) → `agent.SwapController(targetImage)`. **Removed** `updateComposeFile` + `composePath` + the `docker compose up -d` flow. `NewUpdater` takes an **`AgentSwapper`** (nil → update unavailable) instead of a compose path. `DryRun` reports `agent_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 to `NewUpdater`. - **UI + router unchanged:** the existing button (`triggerUpdate` → POST `/api/selfupdate/update`) + poll (`pollUntilBack`) already drive it; success/rollback is detected by the existing `VerifyStartup` (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's `queryRegistry` detected **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/health` and 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`'s `auto_update` scheduled 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.