package storage import ( "context" "log/slog" "os" "path/filepath" "strings" "gitea.dooplex.hu/admin/felhom-agent/internal/hub" ) // RegistryKnownTargets sources the watchdog's known-drive set from the drive INTENT registry + the // Felhom `.mount` units — NOT from PVE storages (Observe). Impl-2a decoupling: a drive tracked this way // needs NO PVE dir-storage (which is what hid registry-only drives from health/detect, the 3b-fix class). // Real PVE storages (local/local-lvm/pbs) are NOT drives and are handled elsewhere via Observe. // // A unit is included iff its drive's intent is not `new` (enrolled/ejected/decommissioned are all // "known" = health-tracked; the watchdog's IntentReader gate then decides whether to RE-MOUNT — only // `enrolled` is remounted). A `new`-intent unit (should not normally exist) is excluded so it never // auto-mounts. type RegistryKnownTargets struct { unitDir string intent IntentReader logger *slog.Logger } // NewRegistryKnownTargets builds the provider. unitDir is the systemd unit dir the Felhom `.mount` // units live in (e.g. /etc/systemd/system); intent is the durable-id → intent store (may be nil → // every Felhom unit is treated as known, matching the legacy ungated behaviour). func NewRegistryKnownTargets(unitDir string, intent IntentReader, logger *slog.Logger) *RegistryKnownTargets { if logger == nil { logger = slog.Default() } return &RegistryKnownTargets{unitDir: unitDir, intent: intent, logger: logger} } // Known enumerates the Felhom drive units under unitDir and returns one KnownTarget per enrolled/ // tracked drive. It mirrors ReassertEnrolledMounts's enumeration (read dir → parseFelhomMountUnit). func (r *RegistryKnownTargets) Known(ctx context.Context) ([]KnownTarget, error) { entries, err := os.ReadDir(r.unitDir) if err != nil { return nil, err // let the caching layer keep the last good set; the watchdog logs + skips a cycle } var out []KnownTarget for _, e := range entries { if !strings.HasSuffix(e.Name(), ".mount") { continue } data, rerr := os.ReadFile(filepath.Join(r.unitDir, e.Name())) if rerr != nil { continue } spec, ok := parseFelhomMountUnit(string(data)) if !ok { continue // not one of our by-uuid drive units (netmount units are excluded by the parser) } durableID := "uuid:" + spec.UUID // the same scheme enroll records in the intent store if r.intent != nil && r.intent.Get(durableID) == IntentNew { // A unit with no recorded intent — not enrolled/ejected/decommissioned. Do NOT track it // (and the watchdog must not auto-mount it): a genuine drive must be enrolled first. continue } out = append(out, KnownTarget{ Name: spec.Name, Type: hub.StorageTypeUSB, // a Felhom drive; MountBacked is what the watchdog keys on DurableID: durableID, UUID: spec.UUID, MountBacked: true, MountPath: spec.Where, // BackingDevice left "" — HostLiveness resolves the device by UUID (by-uuid symlink). }) } return out, nil } // ReconcileExistingDrives migrates drives enrolled before Impl-2a into the intent-registry model so the // registry-sourced Known() tracks them without depending on their (legacy) PVE dir-storage: for each // Felhom `.mount` unit whose drive is currently mounted, ensure the intent registry records it // `enrolled`. Idempotent (an already-enrolled drive is a no-op) and non-destructive — it creates no PVE // storage and removes nothing. Best-effort per drive. Call once at agent start. func ReconcileExistingDrives(unitDir string, mounts []Mount, intent *IntentStore, logger *slog.Logger) { if intent == nil || logger == nil { return } mounted := make(map[string]bool, len(mounts)) for _, m := range mounts { mounted[m.MountPoint] = true } entries, err := os.ReadDir(unitDir) if err != nil { logger.Warn("migrate: cannot read unit dir — existing-drive reconcile skipped", "dir", unitDir, "err", err) return } for _, e := range entries { if !strings.HasSuffix(e.Name(), ".mount") { continue } data, rerr := os.ReadFile(filepath.Join(unitDir, e.Name())) if rerr != nil { continue } spec, ok := parseFelhomMountUnit(string(data)) if !ok || !mounted[spec.Where] { continue // only migrate currently-active Felhom drives } durableID := "uuid:" + spec.UUID if intent.Get(durableID) != IntentNew { continue // already tracked (enrolled/ejected/decommissioned) — idempotent no-op } if serr := intent.SetEnrolled(durableID); serr != nil { logger.Warn("migrate: could not record enrolled intent", "drive", spec.Name, "err", serr) } else { logger.Info("migrate: recorded existing drive as enrolled", "drive", spec.Name, "durable_id", durableID) } } }