v0.29.0: OS/Docker-data storage split — golden + provision (Phase 1)

build-golden.sh bakes a small OS rootfs + a dedicated /var/lib/docker volume
(mp0, backup=1) carrying the baked images, plus Docker log rotation. bringup.go
grows the golden's data volume to the per-customer target (DataVolGrowGB) and
emits backup=1 on data mounts (GuestMount.Backup) — closing the spike-B3 silent
DB-loss trap. CLI gains -rootfs-grow/-datavol-grow/-datavol-mount. New
RUNBOOK-provisioning-storage.md. Phase 2 = felhom-controller v0.58.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-13 15:38:26 +02:00
parent 5ab159521d
commit d7d68fdd83
6 changed files with 275 additions and 11 deletions
+23 -3
View File
@@ -43,7 +43,7 @@ import (
// version is the agent version. Overridable at build time with
// -ldflags "-X main.version=<v>"; defaults to the in-repo CHANGELOG version.
var version = "0.28.0"
var version = "0.29.0"
func main() {
var (
@@ -55,6 +55,9 @@ func main() {
mode string
hostname string
keep bool
rootfsGrow int
dataVolGrow int
dataVolMount string
pbsStorage string
paperkey bool
offline bool
@@ -79,6 +82,9 @@ func main() {
flag.StringVar(&mode, "mode", "provision", "for --selftest=bring-up: `provision` (golden, fresh identity) | `dr` (customer backup, preserve continuity)")
flag.StringVar(&hostname, "hostname", "", "for --selftest=bring-up provision: the hostname to set on the new guest")
flag.BoolVar(&keep, "keep", false, "for --selftest=bring-up: KEEP the guest instead of tearing it down at the end")
flag.IntVar(&rootfsGrow, "rootfs-grow", 0, "for --selftest=bring-up|provision: grow the OS rootfs by this many GiB after restore (0 = keep golden size)")
flag.IntVar(&dataVolGrow, "datavol-grow", 0, "for --selftest=bring-up|provision: grow the golden's Docker-data volume (mp0) by this many GiB (0 = keep golden size)")
flag.StringVar(&dataVolMount, "datavol-mount", "", "for --selftest=bring-up|provision: the mpN slot of the Docker-data volume to grow (default mp0)")
flag.StringVar(&pbsStorage, "storage", "", "for --selftest=escrow-create: the pbs storage whose key to escrow (default: escrow.pbs_storage_id)")
flag.BoolVar(&paperkey, "paperkey", false, "for --selftest=escrow-create: ALSO emit the raw-key paperkey (opt-in (a); single-factor, unrevocable)")
flag.BoolVar(&offline, "offline", false, "for --selftest=escrow-create: ALSO emit the R-wrapped offline copy to print (opt-in (b))")
@@ -133,11 +139,13 @@ func main() {
case "lanresolver":
os.Exit(runSelftestLANResolver(context.Background(), cfg, logger, vmid))
case "bring-up":
os.Exit(runSelftestBringUp(context.Background(), cfg, logger, mode, archive, vmid, hostname, keep))
os.Exit(runSelftestBringUp(context.Background(), cfg, logger, mode, archive, vmid, hostname, keep,
bringUpSizing{RootfsGrowGB: rootfsGrow, DataVolGrowGB: dataVolGrow, DataVolMount: dataVolMount}))
case "provision":
os.Exit(runSelftestProvision(context.Background(), cfg, logger, provisionArgs{
archive: archive, vmid: vmid, hostname: hostname,
customerID: custID, hubPassword: hubPassword,
sizing: bringUpSizing{RootfsGrowGB: rootfsGrow, DataVolGrowGB: dataVolGrow, DataVolMount: dataVolMount},
}))
case "escrow-create":
os.Exit(runSelftestEscrowCreate(context.Background(), cfg, logger, pbsStorage, paperkey, offline, upload, idBundlePath, directivePath))
@@ -950,7 +958,16 @@ func runSelftestRestoreTest(ctx context.Context, cfg config.Config, logger *slog
// (a selftest must not leave a guest running) unless -keep. -mode picks provision (golden, fresh
// identity) or dr (customer backup, preserve continuity). It first Recovers, so a leaked guest
// from a prior crashed bring-up is reaped before this run.
func runSelftestBringUp(ctx context.Context, cfg config.Config, logger *slog.Logger, mode, archive string, vmid int, hostname string, keep bool) int {
// bringUpSizing carries the OS-rootfs / Docker-data sizing knobs from the CLI into a bring-up.
// Defaults (0/"") keep the golden's baked sizes; the provisioning spec sources these per-customer
// (flags now; the slice-10 hub storage manifest later — see bringup.go GuestMount comment).
type bringUpSizing struct {
RootfsGrowGB int
DataVolGrowGB int
DataVolMount string
}
func runSelftestBringUp(ctx context.Context, cfg config.Config, logger *slog.Logger, mode, archive string, vmid int, hostname string, keep bool, sizing bringUpSizing) int {
if err := cfg.Validate(); err != nil {
fmt.Fprintln(os.Stderr, "selftest: proxmox not configured:", err)
return 1
@@ -1006,6 +1023,7 @@ func runSelftestBringUp(ctx context.Context, cfg config.Config, logger *slog.Log
spec := reconcile.BringUpSpec{
Mode: bmode, Archive: archive, VMID: vmid, RestoreStorage: cfg.Backup.RestoreStorage,
Hostname: hostname, KeepMAC: bmode == reconcile.ModeDRGuestLoss,
RootfsGrowGB: sizing.RootfsGrowGB, DataVolGrowGB: sizing.DataVolGrowGB, DataVolMount: sizing.DataVolMount,
}
fmt.Printf(" bringing up %s → vmid %d on %s …\n", archive, vmid, cfg.Backup.RestoreStorage)
res := engine.RunBringUp(ctx, spec)
@@ -1049,6 +1067,7 @@ type provisionArgs struct {
hostname string
customerID string // baked into bootstrap (the hub config-pull target)
hubPassword string // the customer's hub retrieval passphrase (SECRET) — baked into bootstrap
sizing bringUpSizing // OS-rootfs / Docker-data sizing for the bring-up half
}
// sanitizeHostname makes s a DNS-safe LXC hostname (RFC 1123 label-ish): lowercase, any run of
@@ -1156,6 +1175,7 @@ func runSelftestProvision(ctx context.Context, cfg config.Config, logger *slog.L
res := engine.RunBringUp(ctx, reconcile.BringUpSpec{
Mode: reconcile.ModeProvision, Archive: a.archive, VMID: a.vmid,
RestoreStorage: cfg.Backup.RestoreStorage, Hostname: hostname,
RootfsGrowGB: a.sizing.RootfsGrowGB, DataVolGrowGB: a.sizing.DataVolGrowGB, DataVolMount: a.sizing.DataVolMount,
})
if res.Err != nil || !res.Pass {
fmt.Fprintf(os.Stderr, " [FAIL] front-half bring-up (vmid %d): %v\n", a.vmid, res.Err)