bc4f2b9168
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
66 lines
4.9 KiB
Markdown
66 lines
4.9 KiB
Markdown
# REPORT — slice 10: external user-data drive passthrough (P1 spike + P2)
|
|
|
|
**Agent v0.25.0** (+ controller v0.48.0, golden rebaked). P1 spike PASSED (gate); P2 BUILT + validated
|
|
live on guest 9201. P3 (self-heal) + P4 (dual-role) are the next phases.
|
|
|
|
## PHASE 1 — SPIKE (GATE PASSED), four proofs on 9201
|
|
- **1A host→guest bind:** `pct set 9201 -mp0 /mnt/felhom-usb,mp=/mnt/felhom-usb` — **bind form** (host
|
|
path), never `storage:size`. Propagation `shared:49` host↔guest automatic.
|
|
- **1B write — chown, not idmap:** idmap not clean (mixed host ownership 1000+0, container-wide,
|
|
restart, subuid). Decision (refined with Viktor): chown only a fresh **`<drive>/felhom-data`**
|
|
namespace to `100000:100000`; the customer's existing data is never touched. Guest-root r+w confirmed.
|
|
- **1C guest→controller-container:** `-v /mnt:/mnt:rslave` + `/mnt` rshared in the guest → newly-mounted
|
|
drives propagate into the running container (proven). The de-priv `/mnt` gap, reopened scoped.
|
|
- **1D app-container:** busybox bind read+write; bytes land on `/dev/sdb1`.
|
|
|
|
## PHASE 2 — passthrough (Model A: the felhom-data namespace is the in-guest mount)
|
|
- **P2A agent — `POST /disks/guest-attach`** (`internal/localapi`, `GuestBinder`): self-scoped; creates
|
|
`<drive>/felhom-data`, chowns it to the guest base (not -R), and `pct set <vmid> -mpN
|
|
<drive>/felhom-data,mp=/mnt/<name>` (RW bind). Idempotent; lowest free `mpN`; `where` validated. Only
|
|
Felhom's namespace crosses into the guest — the customer's other on-drive data never does. Tests:
|
|
`TestGuestAttach_*` (slot select, idempotency, bad-path, not-configured).
|
|
- **P2B golden — `configs/build-golden.sh`:** controller `docker run` gains `-v /mnt:/mnt:rslave`; the
|
|
bootstrap makes `/mnt` rshared first. Scoped to `/mnt` (only felhom-data-namespace mounts).
|
|
- **P2C controller (v0.48.0):** `agentapi.GuestAttach`; `runStorageInit`/`runStorageAttach`/
|
|
`handleStorageRegister` call `attachIntoGuest` after register (best-effort; P3 heals a miss).
|
|
|
|
## Live validation (9201)
|
|
After `guest-attach` + a guest restart to activate mp0:
|
|
- mp0 = `/mnt/felhom-usb SOURCE /dev/sdb1[/felhom-data]` (Model A; the `[/felhom-data]` suffix the
|
|
controller's mount strip already handles).
|
|
- Controller container mountinfo has `/felhom-data /mnt/felhom-usb … /dev/sdb1`.
|
|
- An app (busybox bind) writes `proof.txt` → present on the **host** `/mnt/felhom-usb/felhom-data/...`,
|
|
`df` device `/dev/sdb1` (NOT the rootfs).
|
|
- Banner cleared: **`[PASS] Storage paths: 1 connected, 0 disconnected`**.
|
|
- `go test ./...` green (both repos).
|
|
|
|
## LIVE-ACTIVATION — investigated, mechanism chosen (evidence-based)
|
|
A drive enrolled into a **running** unprivileged guest cannot be activated live, and the host-side
|
|
bind-inject is **blocked** (proven on 9201): `pct set` doesn't hot-apply a mountpoint to a running
|
|
guest; `/proc/<pid>/root/...` bind → `mount: bad superblock` (unprivileged mount-locking); `nsenter -m`
|
|
into the guest ns loses the host source path. The bind activates at the **next guest boot** (validated:
|
|
reboot → mp0 active `/dev/sdb1[/felhom-data]`, controller sees it, banner clears). Fresh guests from the
|
|
rebaked golden are unaffected (mp activates at first boot, before the controller starts).
|
|
|
|
**Decision (Viktor):** enroll persists via `pct set` with **NO forced reboot**; the UI shows a
|
|
"pending activation" state + a user-triggered **"Újraindítás most (~30s)"** button that batches all
|
|
pending drives; P3 self-heal flags "restart to reconnect" for recovered drives. The staging-mp
|
|
live-propagation alternative is deferred to its own spike. (Build remaining — see below.)
|
|
|
|
## Activation-UX — DONE (agent v0.26.0 + controller v0.49.0)
|
|
- **Agent `POST /guest/reboot`** (self-scoped, detached `pct reboot`, 202). Controller
|
|
`pendingActivationDrives()` flags registered drives the agent shows attached but not live-mounted in
|
|
the container; settings banner + batched **"Újraindítás most (~30 mp)"** button → `/api/storage/
|
|
activate` → `agentapi.GuestReboot`. Live-validated on 9201: activate → guest reboots → drive active,
|
|
app writes to `/dev/sdb1`, banner absent. (Pending-banner display couldn't be forced live — LXC mp
|
|
mounts resist manual unmount + no 2nd drive — but the logic is unit-confident.)
|
|
- **Minor known artifact:** the bootstrap's `mount --rbind /mnt /mnt` duplicates the mp submount, so
|
|
`findmnt /mnt/<name>` shows two (identical) rows — functional (IsMountPoint true, apps write fine);
|
|
inherent to rbind (a non-recursive bind would hide the submounts from the container). Tidy later.
|
|
|
|
## Not done (next build increments)
|
|
- **P3** self-heal watchdog reconcile (4-state new/enrolled/ejected/decommissioned, durable-id-keyed) +
|
|
safety rails (flapping backoff, durable-id-only, in-progress-op respect, PVE coordination,
|
|
re-propagate); flags "restart to reconnect" for recovered drives.
|
|
- **P4** dual-role eligibility + backup-aware wipe warning. Cross-drive backup ENGINE stays out of scope.
|