v0.55.0: raw-device discovery + registry-sourced drive tracking (Impl-2a)
GET /disks/candidates enumerates host disks the Impl-1 unclaimed filter proves free (init/attach split). RegistryKnownTargets sources the watchdog's known-drive set from the intent registry + Felhom .mount units (not Observe/PVE storages) — decouples drive health from PVE storage (closes the registry-only false-detach class); Observe kept for real PVE storages + a deduped /disks union. Idempotent existing-drive migration at start. Tests + red-proof (Observe misses a registry-only drive; registry provider tracks it). go build/vet/test clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,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.54.0"
|
||||
var version = "0.55.0"
|
||||
|
||||
// runGuestHook is the PVE pre-start hook body (`felhom-agent guest-hook <vmid> <phase>`). On the
|
||||
// pre-start phase it creates placeholder dirs for any absent bind-mount source so the guest always boots
|
||||
@@ -429,8 +429,23 @@ func runDaemon(cfg config.Config, logger *slog.Logger) int {
|
||||
logger.Warn("storage: format-job store unavailable — format restart-recovery disabled (F20-BUG3)", "err", fjErr)
|
||||
formatJobStore = nil
|
||||
}
|
||||
// Impl-2a: source the watchdog's known-DRIVE set from the intent registry + Felhom .mount units
|
||||
// (NOT PVE storages via Observe) — a registry-only drive (no PVE dir-storage) is now health-tracked.
|
||||
// Real PVE storages (local/local-lvm/pbs) stay Observe-sourced for /disks + reports (observer below).
|
||||
driveUnitDir := cfg.Privileged.UnitDir
|
||||
if driveUnitDir == "" {
|
||||
driveUnitDir = "/etc/systemd/system"
|
||||
}
|
||||
driveKnown := storage.NewRegistryKnownTargets(driveUnitDir, intentReader, logger)
|
||||
// One-time migration: record pre-Impl-2a drives (mounted, with a Felhom unit) as enrolled so the
|
||||
// registry-sourced Known() tracks them without their legacy PVE dir-storage. Idempotent.
|
||||
if mounts, merr := hostReader.Mounts(); merr == nil {
|
||||
storage.ReconcileExistingDrives(driveUnitDir, mounts, intentStore, logger)
|
||||
} else {
|
||||
logger.Warn("storage: existing-drive migration skipped — mount read failed", "err", merr)
|
||||
}
|
||||
watchdog := storage.NewWatchdog(storage.WatchdogOptions{
|
||||
Targets: storage.NewCachingKnownTargets(observer, cfg.Storage.KnownRefresh()),
|
||||
Targets: storage.NewCachingKnownTargets(driveKnown, cfg.Storage.KnownRefresh()),
|
||||
Liveness: storage.NewHostLiveness(hostReader, 0),
|
||||
Remounter: remounter,
|
||||
Intent: intentReader, // P3: only re-mount enrolled drives
|
||||
@@ -513,7 +528,7 @@ func runDaemon(cfg config.Config, logger *slog.Logger) int {
|
||||
jobsRunner := signedjobs.NewRunner(client, gate, signedjobs.ExecutorChain{wipeExec, decommExec}, cfg.Hub.HostID, logger)
|
||||
loop.SetEnvelopeObserver(hub.MultiObserver(desiredSyncer, jobsRunner))
|
||||
|
||||
localSrv := buildLocalAPIServer(cfg, px, backupStore, observer, hostOps, gate, collector, intentRec, guestBindStore, formatJobStore, logger, &localTokens)
|
||||
localSrv := buildLocalAPIServer(cfg, px, backupStore, observer, driveKnown, hostOps, gate, collector, intentRec, guestBindStore, formatJobStore, logger, &localTokens)
|
||||
if localTokens != nil {
|
||||
defer localTokens.Close()
|
||||
}
|
||||
@@ -706,7 +721,7 @@ func buildRestoreTestScheduler(cfg config.Config, px *proxmox.Client, engine *re
|
||||
// leaf (stable fingerprint). Any failure DISABLES the server (returns nil) WITHOUT crashing the
|
||||
// daemon — the host still reports/reconciles; only the controller channel is unavailable until
|
||||
// fixed. The opened token store is returned via outTokens so the caller can Close it.
|
||||
func buildLocalAPIServer(cfg config.Config, px *proxmox.Client, store *backup.Store, observer *storage.Observer, hostOps *storage.SudoHostOps, gate *reconcile.Gate, collector *hub.Collector, intent localapi.IntentRecorder, guestBinds *localapi.GuestBindStore, formatJobs *localapi.FormatJobStore, logger *slog.Logger, outTokens **localapi.TokenStore) *localapi.Server {
|
||||
func buildLocalAPIServer(cfg config.Config, px *proxmox.Client, store *backup.Store, observer *storage.Observer, driveTargets storage.KnownTargets, hostOps *storage.SudoHostOps, gate *reconcile.Gate, collector *hub.Collector, intent localapi.IntentRecorder, guestBinds *localapi.GuestBindStore, formatJobs *localapi.FormatJobStore, logger *slog.Logger, outTokens **localapi.TokenStore) *localapi.Server {
|
||||
if !cfg.LocalAPI.Enabled() {
|
||||
return nil
|
||||
}
|
||||
@@ -751,6 +766,7 @@ func buildLocalAPIServer(cfg config.Config, px *proxmox.Client, store *backup.St
|
||||
Backups: runner,
|
||||
Store: store,
|
||||
Storage: observer,
|
||||
DriveTargets: driveTargets, // Impl-2a: registry+units drives for the /disks view (union w/ Observe storages)
|
||||
Tokens: tokens,
|
||||
BackupCadence: cfg.Backup.BackupCadence(),
|
||||
// Disk management (slice 8C): the privileged host surface + the data-bearing wipe gate.
|
||||
|
||||
Reference in New Issue
Block a user