v0.308.0 (R-554, held-back-able): recovery-info.txt points at the dashboard's Restore page and the recovery code; the first-boot setup wizard is deleted

Operator ruling 2026-10-12 (S27, 09 §3 decision 222). The operator approves the Hungarian text before
release. A box whose first ingestion failed now serves one bilingual waiting page and retries every
minute instead of the wizard. Own commit so the lead can hold it back.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-12 15:38:28 +02:00
parent fd28d70bff
commit faef8f8a91
36 changed files with 421 additions and 1916 deletions
+24 -104
View File
@@ -112,7 +112,7 @@ backups, monitoring and notifications. All Proxmox/disk operations are delegated
component; state chips are `.tag`; informational pills are `.metarow`. **Fonts (Plus Jakarta Sans +
JetBrains Mono, variable woff2, latin+latin-ext) and a 30-icon Lucide sprite are vendored in the
binary** (`internal/web/static/fonts/` served at `/static/fonts/`; `templates/icons.html`) — no CDN,
no emoji. The setup wizard serves the same embedded stylesheet via `web.StyleCSS()`.
no emoji.
- **Guest RAM resize (v0.143.0, R-24; MinAgent 0.90.0)** — the Rendszer page's "Szerver memória (RAM)"
card shows the guest's current/used memory + the allowed range and lets the customer resize it. The
controller only proxies + maps the agent's machine `code` to Hungarian; **the agent (felhom-agent
@@ -3364,77 +3364,18 @@ The hub service (separate Go app in the `felhom.eu` repo) provides:
- Color coding: green (<30min), yellow (30-60min), red (>60min since last report)
- 90-day report + event retention with daily prune at 04:30 Budapest time
### 10. First-Run Setup Wizard
### 10. Not configured yet — the waiting page (the setup wizard is deleted, v0.308.0, R-554)
When the controller starts with no valid customer configuration (`customer.id` empty), it enters **setup mode** — a web-based wizard that handles all initial configuration. This replaces the old interactive shell wizard in `docker-setup.sh`.
#### Setup Mode Detection (`internal/setup/setup.go`)
`NeedsSetup(cfg)` returns true when `customer.id` is empty or a `.needs-setup` marker file exists. In setup mode, the controller skips normal startup (no scheduler, no backup, no stacks) and serves only the wizard UI on two listeners:
- `:8080` — behind Traefik (accessible via domain, e.g. `https://felhom.example.com`)
- `:8081` — direct HTTP (accessible via LAN IP, e.g. `http://192.168.0.100:8081`)
#### Wizard Flow
```
┌──────────────────────────────────┐
│ 1. Welcome │
│ Choose: Restore / Fresh install │
└─────────┬───────────┬────────────┘
│ │
┌─────▼─────┐ ┌──▼───────────────┐
│ 2a. Scan │ │ 2b. Hub download │
│ drives for│ │ (customer ID + │
│ local │ │ password) │
│ backups │ │ │
└─────┬─────┘ └──────┬────────────┘
│ │
┌─────▼─────┐ │
│ 2a.2 Hub │ │
│ recovery │ │
│ (fallback)│ │
└─────┬─────┘ │
│ │
┌─────▼─────┐ ┌──────▼───────────┐
│ Execute │ │ Execute fresh │
│ restore │ │ install │
└─────┬─────┘ └──────┬───────────┘
│ │
└───────┬───────┘
▼
os.Exit(0) → Docker restarts
→ normal mode
```
#### Hub Pre-Seeding
When `docker-setup.sh` is run with `--hub-customer` / `--hub-password`, the controller receives
pre-seeded credentials via environment variables:
| Env var | Purpose |
|---------|---------|
| `FELHOM_SETUP_CUSTOMER_ID` | Pre-fills customer ID in wizard forms |
| `FELHOM_SETUP_PASSWORD` | Pre-fills retrieval password for auto-processing |
In hub mode, the welcome page shows three cards instead of two:
1. **"Visszaállítás a Hub-ról"** — auto-calls `PullRecovery()`, shows infra backup details
2. **"Visszaállítás helyi meghajtóról"** — standard drive scan
3. **"Friss telepítés"** — auto-calls `PullConfig()`, downloads config only
Both hub paths auto-process when credentials are pre-seeded (no form entry needed).
On error, the wizard falls back to the manual form with the error displayed.
#### Key Components
| File | Purpose |
|------|---------|
| `setup/setup.go` | `NeedsSetup()` detection, `SetupState` persistence to `setup-state.json` |
| `setup/handlers.go` | HTTP handlers for each wizard step (welcome, scan, hub-restore, fresh, manual) |
| `setup/scanner.go` | Scans all block devices for `.felhom-infra-backup/` directories (current + `history/`) via `lsblk` + temp mounts; returns rich info (app names, disk count) |
| `setup/hub.go` | Hub recovery pull (`GET /api/v1/recovery/{id}`) and config download |
| `setup/csrf.go` | Lightweight CSRF protection (cookie + hidden field, `SameSite=Strict`) |
| `setup/network.go` | Detects local IPs for LAN access URL display |
| `setup/templates/` | 8 embedded HTML templates (Hungarian, dark theme matching main UI) — includes `setup_hub_versions.html` for Hub backup version picker |
The host agent provisions every box: at first boot the controller ingests its configuration from the hub
(`internal/bootstrap`, `MaybeIngest`). The first-boot **setup wizard** (`internal/setup/`, the docker-setup.sh era,
port 8081) was obsolete by design (`02-controller-module-map.md`) and is **deleted** (`09` §3 decision 222). A box whose
ingestion failed (no or bad `bootstrap.json`, a hub it cannot reach, a failed write) — `bootstrap.Unconfigured(cfg)`,
i.e. `customer.id` empty — now serves ONE page on its listener, in both languages („A Felhom még nem érte el a
központot" / "Felhom has not reached the central service yet"), answers `/api/health` with
`waiting_for_hub: true`, and retries the ingestion every minute (`bootstrap.WaitForConfig`). Once configured it closes
the page and starts normally on the same listener. The `.needs-setup` marker and the debug page's „DR / Telepítő
varázsló" trigger are gone; `web.setup_listen` is still read so an old `controller.yaml` loads, and is unused.
Pinned by `TestWaitForConfig_ServesWaitingPageThenStarts`.
#### Local Infra Backup (`internal/backup/local_infra.go`)
@@ -3445,11 +3386,15 @@ The controller writes infrastructure snapshots to **every connected drive** afte
- `{timestamp}-backup.json` + `{timestamp}-metadata.json` pairs (timestamp format: `20060102T150405Z`)
- Oldest entries pruned when count exceeds 5
During setup wizard drive scan, both current and historical backups are discovered, integrity-verified, and offered for one-click restore. The scan results table shows app names/count, disk count, and a "korábbi" badge for historical versions.
(The setup wizard's drive scan that offered these for one-click restore is deleted, v0.308.0, R-554.)
#### Recovery Info (`internal/recovery/info.go`)
Generates `recovery-info.txt` on the system data partition with customer ID, Hub URL, retrieval password, and recovery instructions in Hungarian. Updated on startup and after config changes. Also displayed on the Settings page in a "Vészhelyzeti információk" section.
Generates `recovery-info.txt` (0600) in the data directory at every start when a retrieval password is set: customer
ID, Hub URL, retrieval password, support contacts, and — since v0.308.0 (R-554) — what to do: the dashboard's
**Biztonsági mentés → Visszaállítás** page, and after a reinstall the **Adatok visszaszerzése** page with the
household's recovery code (ten words). Written in the box's language from the bundle key `recovery_info.body`; it no
longer points at the deleted setup wizard. Pinned by `TestRecoveryInfo_PointsAtDashboardNotWizard`.
**No secret is rendered into a page's response body (v0.207.0 + v0.208.0, R-249/R-254).** Three endpoints implement one rule — the page carries a BOOLEAN, the value comes from an explicit authenticated POST with `Cache-Control: no-store`, and the reveal is LOGGED (reading a value off markup left no trace at all):
@@ -3465,29 +3410,11 @@ They are deliberately **per-secret**, not one generic "reveal any named secret"
### 11. Disaster Recovery
When a system drive fails and is replaced, the recovery flow uses the setup wizard:
```
1. docker-setup.sh deploys fresh controller with minimal config
- With --hub-customer: credentials pre-seeded via env vars
- Without: user enters credentials manually in wizard
2. Controller detects empty customer.id → enters setup mode
3. User opens wizard at http://<LAN-IP>:8081
4. Hub mode: welcome page shows Hub restore / local scan / fresh install
Non-hub mode: welcome page shows restore / fresh install
5. Hub restore: auto-connects to Hub, shows version picker if multiple versions
Local restore: scans all drives for .felhom-infra-backup/ directories (current + history/)
6. User selects backup version → restore: config, settings, passwords, disk layout
7. Controller restarts into normal mode with full config
8. Controller auto-mounts surviving drives by UUID from disk layout
9. Dashboard shows "Visszaállítás" (Restore) page for app-level recovery
10. User confirms → sequential restore: rsync first, restic fallback, DB import
```
**Backup sources (priority order):**
1. **Local infra backup** (`.felhom-infra-backup/` on surviving drives) — fastest, no network needed
2. **Hub recovery endpoint** (`GET /api/v1/recovery/{id}`) — requires retrieval password, supports `?version=ID` for specific versions; Hub retains ~14 versions via GFS pruning (7 daily / 4 weekly / 3 monthly)
3. **Manual config** (wizard form) — enter all details manually as last resort
Whole-guest recovery is the host agent's and the hub's (PBS restore, provisioning a replacement guest); the controller
has no recovery wizard any more (R-554, v0.308.0). On a box that is running, an app is restored on the dashboard's
**Biztonsági mentés → Visszaállítás** page. After a reinstall the earlier off-site backups are reopened on the
**Adatok visszaszerzése** page (`/recovery`) with the household's recovery code. A box that cannot reach the hub at its
first boot waits on the page described in §10.
**Hub verification:** After setup, the controller periodically verifies customer standing via the Hub report push response (`customer_blocked` field). If blocked or Hub unreachable for >7 days, the controller enters limited mode (no new deployments).
@@ -4259,13 +4186,6 @@ controller/
│ │ ├── builder.go # Hub report builder (all subsystems → JSON)
│ │ ├── pusher.go # HTTP POST to hub (retry, Bearer auth, parses customer_blocked)
│ │ └── infra_pull.go # DR: pull recovery/config from Hub (retrieval password auth)
│ ├── setup/ # First-run setup wizard (web-based, replaces docker-setup.sh wizard)
│ │ ├── setup.go # NeedsSetup() detection, state persistence
│ │ ├── handlers.go # HTTP handlers for all wizard steps
│ │ ├── scanner.go # Drive scanner for local infra backups
│ │ ├── csrf.go # Lightweight CSRF (cookie + hidden field)
│ │ ├── network.go # Local IP detection for LAN access URLs
│ │ └── templates/ # 7 wizard HTML templates (Hungarian)
│ ├── recovery/info.go # Recovery info file generator (recovery-info.txt)
│ └── web/
│ ├── server.go # HTTP server, routing, static files, catch-all middleware, executeTemplate wrapper
@@ -4363,7 +4283,7 @@ monitoring:
web:
listen: ":8080"
setup_listen: ":8081" # Plain HTTP for setup wizard LAN access
setup_listen: ":8081" # UNUSED since v0.308.0 (the setup wizard is deleted, R-554)
hub:
enabled: true