e15d1d30ea
gates / gates (push) Successful in 47s
R-1124: ForgetRetiredDrives (start-up + the 20 s tick) disables and removes the host mount unit of a DECOMMISSIONED drive whose disk is absent and unmounted; the agent then no longer lists it. A present decommissioned drive keeps its unit (one-click re-enroll). The file goes only through the new felhom-priv-apply verb `unit-forget`, which re-checks marker, disabled, unmounted and absent as root. R-1012: the read-only root helper felhom-thin-check <vg> <pool> (bundle) lists the pool's thin ids (metadata snapshot reserved and released) next to LVM's before and after. BackupRunner.SetThinCheck runs it before a snapshot-mode backup (log) and again after a failure, appending the finding in plain words to the error the controller mails to the operator. Two reads, intersected; fails open; never removes. Red-proofs: felhom.eu audits/agent-allowlist-rotation-2026-10-17/P1/. Code only; the CHANGELOG entry follows the release. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
86 lines
3.5 KiB
Go
86 lines
3.5 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// ForgetRetiredDrives (R-1124) removes the host mount unit of every DECOMMISSIONED drive that is physically gone, so a
|
|
// later host boot no longer waits for it and the agent stops listing it (RegistryKnownTargets and
|
|
// ReassertEnrolledMounts both enumerate the units — no unit, no entry, no „absent by UUID" line every 20 s).
|
|
//
|
|
// Only a drive that is BOTH decommissioned (the operator's or the customer's permanent removal) AND absent (its
|
|
// /dev/disk/by-uuid link does not resolve) AND not mounted is forgotten. A decommissioned drive that is still plugged in
|
|
// keeps its unit and its raw host mount — that is what makes the one-click re-enroll (H3) work, and decommission stays a
|
|
// logical retire (`03` §storage). The intent record stays `decommissioned`: a forgotten drive that comes back is a new
|
|
// candidate, never auto-mounted. The root half (`felhom-priv-apply unit-forget`) re-checks every one of these conditions
|
|
// and refuses otherwise. Called at agent start, on the 20-second storage tick, and right after a decommission. Best
|
|
// effort per unit; returns how many units it removed. Pinned by TestForgetRetiredDrives_*.
|
|
func (h *SudoHostOps) ForgetRetiredDrives(ctx context.Context, intent IntentReader) int {
|
|
if intent == nil {
|
|
return 0 // no intent store → we cannot know a drive was retired; forget nothing
|
|
}
|
|
entries, err := os.ReadDir(h.unitDir)
|
|
if err != nil {
|
|
h.logger.Warn("storage: forget retired drives — cannot read unit dir", "dir", h.unitDir, "err", err)
|
|
return 0
|
|
}
|
|
mounted := map[string]bool{}
|
|
if ms, merr := h.host.Mounts(); merr == nil {
|
|
for _, m := range ms {
|
|
mounted[m.MountPoint] = true
|
|
}
|
|
} else {
|
|
h.logger.Warn("storage: forget retired drives — mount table unreadable; forgetting nothing", "err", merr)
|
|
return 0
|
|
}
|
|
removed := 0
|
|
for _, e := range entries {
|
|
name := e.Name()
|
|
if !strings.HasSuffix(name, ".mount") {
|
|
continue
|
|
}
|
|
data, rerr := os.ReadFile(filepath.Join(h.unitDir, name))
|
|
if rerr != nil {
|
|
continue
|
|
}
|
|
spec, ok := parseFelhomMountUnit(string(data))
|
|
if !ok {
|
|
continue // not one of our by-UUID drive units (network units are refused by the parser)
|
|
}
|
|
durableID := "uuid:" + spec.UUID
|
|
if intent.Get(durableID) != IntentDecommissioned {
|
|
continue
|
|
}
|
|
if mounted[spec.Where] {
|
|
h.logger.Debug("storage: decommissioned drive still mounted — keeping its unit", "where", spec.Where)
|
|
continue
|
|
}
|
|
if _, derr := ResolveStorageDevice(durableID); derr == nil {
|
|
h.logger.Debug("storage: decommissioned drive is present — keeping its unit (re-enroll stays one click)", "where", spec.Where)
|
|
continue
|
|
}
|
|
if h.unitEnabled(name) {
|
|
if err := h.run(ctx, h.bins.Systemctl, "disable", "--", name); err != nil {
|
|
h.logger.Warn("storage: forget retired drive — disable failed", "where", spec.Where, "unit", name, "err", err)
|
|
continue
|
|
}
|
|
}
|
|
if err := h.run(ctx, h.bins.PrivApply, "unit-forget", name); err != nil {
|
|
h.logger.Warn("storage: forget retired drive — unit removal refused or failed", "where", spec.Where, "unit", name, "err", err)
|
|
continue
|
|
}
|
|
removed++
|
|
h.logger.Info("storage: forgot a decommissioned drive that is gone — its mount unit is removed",
|
|
"name", spec.Name, "where", spec.Where, "uuid", spec.UUID, "unit", name)
|
|
}
|
|
if removed > 0 {
|
|
if err := h.run(ctx, h.bins.Systemctl, "daemon-reload"); err != nil {
|
|
h.logger.Warn("storage: daemon-reload after forgetting retired drives failed", "err", err)
|
|
}
|
|
}
|
|
return removed
|
|
}
|