From 9a0e7e168bc1c1c475d7251ec23ed196606abdef Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Wed, 10 Jun 2026 15:02:16 +0200 Subject: [PATCH] REPORT + CLAUDE: slice 8B.2 agent half (snapshotted phase, v0.13.0) Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 2 +- REPORT.md | 72 +++++++++++++++++++++++++++++-------------------------- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ef61146..35f8791 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ - Module `gitea.dooplex.hu/admin/felhom-agent`; binary `felhom-agent` (`cmd/felhom-agent/`). - **Pure Go stdlib + `golang.org/x/crypto` only** — no web frameworks. - `go.mod` directive **go 1.25.0**; dep `golang.org/x/crypto v0.52.0` (declares go 1.25, will NOT build on Go 1.24). The **build server (192.168.0.180) runs go1.26.0** (upstream Go on PATH, backward-compatible). Build/run the agent there for live tests (same LAN as the demo host). -- Version: `version` var in `cmd/felhom-agent/main.go`, overridable via `-ldflags "-X main.version="`; `--version` flag. **Current: v0.12.0** (slice 8C Phase A: the `/disks` endpoints — `internal/localapi/disks.go` + `internal/storage` `mkfs` executor + `InspectDevice`/`DeviceProbe` data-bearing detection. **Security centerpiece**: `POST /disks/format` inspects the device itself; data-bearing → `ClassStorageWipe` → gate → `pending_signature` refused (caller's claim ignored); blank → benign `mkfs`. `FELHOM_FORMAT` sudoers. Controller de-privileging is `felhom-controller` v0.37.0. Plus slice 8B: `internal/localapi` `/backup/due` real **cadence** policy + `/backup/status` **phases** (idle|running|done|failed) + `POST /backup` single-flight job id — the app-consistent backup the controller's quiesce loop drives; `backup.backup_cadence_seconds`. Plus slice 8A: the per-guest **local-API server** `internal/localapi` — the doc 03 §6 authorization gate, 7 self-scoped endpoints, hashed per-guest token store, persisted self-signed leaf w/ stable pin; the **provisioning back-half** `internal/provision` — mint token → render `bootstrap.json` → write 0600 → `chown 100000:100000` → `pct set` bind mount, host-side/F3, no registry cred in guest; `--selftest=provision`; `build-golden.sh` now bakes the controller. Plus v0.9.0's PBS escrow. Runtime dep: `proxmox-backup-client`). Bump on meaningful changes + add a CHANGELOG entry. +- Version: `version` var in `cmd/felhom-agent/main.go`, overridable via `-ldflags "-X main.version="`; `--version` flag. **Current: v0.13.0** (slice 8B.2: `/backup/status` gains a **`snapshotted`** phase — `BackupRunner.BackupWithSnapshotHook` tails the vzdump task log for the `create storage snapshot` marker (snapshot mode only) and fires a hook so the controller resumes its app at snapshot-taken, not backup-done. Plus slice 8C Phase A: the `/disks` endpoints — `internal/localapi/disks.go` + `internal/storage` `mkfs` executor + `InspectDevice`/`DeviceProbe` data-bearing detection. **Security centerpiece**: `POST /disks/format` inspects the device itself; data-bearing → `ClassStorageWipe` → gate → `pending_signature` refused (caller's claim ignored); blank → benign `mkfs`. `FELHOM_FORMAT` sudoers. Controller de-privileging is `felhom-controller` v0.37.0. Plus slice 8B: `internal/localapi` `/backup/due` real **cadence** policy + `/backup/status` **phases** (idle|running|done|failed) + `POST /backup` single-flight job id — the app-consistent backup the controller's quiesce loop drives; `backup.backup_cadence_seconds`. Plus slice 8A: the per-guest **local-API server** `internal/localapi` — the doc 03 §6 authorization gate, 7 self-scoped endpoints, hashed per-guest token store, persisted self-signed leaf w/ stable pin; the **provisioning back-half** `internal/provision` — mint token → render `bootstrap.json` → write 0600 → `chown 100000:100000` → `pct set` bind mount, host-side/F3, no registry cred in guest; `--selftest=provision`; `build-golden.sh` now bakes the controller. Plus v0.9.0's PBS escrow. Runtime dep: `proxmox-backup-client`). Bump on meaningful changes + add a CHANGELOG entry. ## Layout diff --git a/REPORT.md b/REPORT.md index 8517c1f..8209a1b 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,49 +1,53 @@ -# REPORT — slice 8C Phase A (agent half): disk endpoints + data-bearing classifier gate (v0.12.0) (2026-06-10) +# REPORT — slice 8B.2 (agent half): quiesce downtime optimization — `snapshotted` phase (v0.13.0) (2026-06-10) -> Overwrite-latest report. Cumulative history: [CHANGELOG.md](CHANGELOG.md). Implements the -> host-agent half of `TASK — Slice 8C`. The controller de-privileging + disk-subsystem retirement is -> the `felhom-controller` half (v0.37.0). No hub change. +> Overwrite-latest report. Cumulative history: [CHANGELOG.md](CHANGELOG.md). Implements the agent +> half of `TASK — Slice 8B.2`. The controller early-resume is `felhom-controller` v0.38.0. No hub change. ## Outcome -The agent now owns disk execution. It serves the §6 disk-management endpoints the controller's UI -drives, with the **8C security invariant**: the agent decides data-bearing-ness by **inspecting the -actual device** (agent-internal evidence), NEVER from the caller's claim. A compromised controller -asserting "this drive is blank" cannot wipe a data-bearing drive — **proven live** (a data-bearing -device → refused `pending_signature`, `mkfs` never called, the device untouched). +In snapshot mode an LXC vzdump only needs the app-stopped state captured at the **storage-snapshot +moment**; after that it reads from the snapshot and the app can safely resume. The agent now emits a +**`snapshotted`** phase on `GET /backup/status` when the snapshot is taken, so the controller resumes +its app early — app downtime drops from *whole-backup* to *until-snapshot* with no loss of +app-consistency. + +## Phase 0 (validated first, on the demo — PVE 9.2.2) + +- **Snapshot mode is in effect** on local-lvm (lvmthin) — `vzdump --mode snapshot` logs `backup mode: + snapshot`, not downgraded to stop. +- **Marker:** `INFO: create storage snapshot 'vzdump'` — the vzdump-level step after which the backup + reads from the snapshot (generic across snapshot-capable storage; only appears in snapshot mode). +- **Downtime delta:** snapshot created in the first ~1s; archive runs ~23s after. So 8B (resume at + `done`) = ~24s app-down; 8B.2 (resume at `snapshotted`) = ~1s — **~95% cut** for a 934 MB guest; + the delta grows with guest size. ## What landed -- **`internal/storage`** — `SudoHostOps.Format(device, fstype)` (device-pinned `mkfs.ext4 -F` / - `mkfs.xfs -f`, `ValidateBlockDevice`+`ValidateFSType`, narrow `FELHOM_FORMAT` sudoers). - `SudoHostOps.InspectDevice(device)` → `DeviceProbe` (`blkid -p` signatures + `lsblk -J` - partitions/mount). **`DeviceProbe.DataBearing()` is conservative**: any filesystem / partition - table / partition / mount — OR a probe that did not read cleanly — is data-bearing (fail-safe). -- **`internal/localapi` — the §6 disk endpoints**, self-scoped (token→guest; cross-guest 403): - `GET /disks` (drives + data-bearing flag), `POST /disks/assign` (benign `EnsureMount`), - `POST /disks/eject` (benign `Unmount` + dependent guests), `POST /disks/format`. - **`/disks/format` is the centerpiece**: the agent inspects the device; blank → benign → `mkfs`; - **data-bearing → `ClassStorageWipe` → the slice-4 gate → `pending_signature` (refused)**. The - signed completion is slice 10. `storageGateAdapter` bridges to the gate (no new gate/crypto). +- **`BackupRunner.BackupWithSnapshotHook(ctx, vmid, onSnapshot)`** — while the vzdump runs, a watcher + tails the task log (`TaskLogTail`) for the `create storage snapshot` marker and fires `onSnapshot` + **once**. It bails on `backup mode: stop` and the marker never appears in stop mode, so it **never + fires in stop/downgraded mode**. `Backup` keeps its signature (scheduler/selftest); both share one + body. `snapshotWatchInterval` is a package var (prod 1s; tests shrink it). +- **`/backup/status` phase `snapshotted`** (between `running` and `done`): `handleBackup` passes the + hook → `markSnapshotted` flips the running job. `done`/`failed` unchanged; `snapshotted` is additive. ## Tests -`go test ./...` green; `-race` green (build server). localapi security matrix: blank → mkfs called, -gate not consulted; **data-bearing → 403, mkfs NEVER called**, gate consulted; ambiguous/unprobed → -fail-safe destructive; gate-allows still refuses in 8C; assign/eject; cross-guest 403; bad -device/fstype 400; unconfigured 503. storage: validators (whitelist + injection rejection), -`InspectDevice` (blank/fs/parttable/mounted/failed-probe-fail-safe), `Format` invokes the right mkfs. +`go test ./...` green; `-race` green (build server). localapi: snapshot mode → `snapshotted` before +`done`; stop mode → `snapshotted` never emitted. runner: the watcher fires on the marker; stop-mode +log never fires. ## Live validation (demo-felhom) -A provisioned de-privileged controller (v0.37.0) drove the agent: `GET /disks` flagged the mounted -`felhom-usb` (`/dev/sdb1`) **`data_bearing: true`** and the LVM/PBS/dir storages false. `POST -/disks/format` on the mounted-vfat `/dev/sda2` → **403** `"device is data-bearing — format requires -operator authorization (pending_signature)"`; agent log `refusing format of a data-bearing device … -gate=pending_signature`; **`/dev/sda2` untouched**. Deployed to the standing demo service as v0.12.0. +End-to-end on a provisioned guest (controller v0.38.0 + a postgres stack): the agent logged `backup +reached snapshotted (app may resume)` mid-backup, the controller resumed the app at that point, and +the backup proceeded to `done`. **App downtime ~3s** (quiesce→snapshotted) vs **~23s** if it had +waited for `done` (~87% cut). The snapshot backup restored **clean** (`database system was shut down`, +no WAL replay) — the early resume did not compromise app-consistency. Deployed to the demo service as +v0.13.0. -## Deferred (stated, not built) +## Deferred / dependency -The operator-**signed completion** of a data-bearing wipe/format → **slice 10**. No spare blank -`/dev/sd*` on the demo to live-exercise an actual `mkfs` (won't format real disks — covered by unit -tests). No secrets committed. +Snapshot-capable storage (lvm-thin/ZFS) is required for the win; on stop/downgraded storage the +controller falls back to resume-at-`done` (8B). No consistency-contract or crash-safety change. No +secrets committed.