v0.55.0: Phase 3 — auto off-drive Tier 2 (rootfs-headroom guard)
Tier 2 rsync-mirrors each HDD app's recovery unit + appdata to a DIFFERENT physical disk (the only off-drive protection bind-mounted userdata can get; PBS can't reach it). Auto-enabled, auto-target: prefer another registered drive (different physical disk via system.SamePhysicalDevice), else the internal SSD for SMALL units only — with a size-aware headroom guard that REFUSES rather than fill the ~8G guest rootfs, recording an honest "needs 2nd HDD" status. Status persisted via the surviving CrossDriveBackup; "2. mentés" UI card now populated. Daily tier2-backup job + POST /api/backup/tier2. - backup/tier2.go (engine+selection+headroom), tier2_test.go (headroom arithmetic) - system.SamePhysicalDevice (linux Stat_t.Dev + stub) - handlers.go Tier2 UI population + tier2DestLabel; backups.html honest no-target reason - fixed stale TestBackupCopiesOnPath (old felhom-data layout -> in-guest layout) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -233,6 +233,17 @@ func isSameBlockDevice(pathA, pathB string) bool {
|
||||
return statA.Dev == statB.Dev
|
||||
}
|
||||
|
||||
// SamePhysicalDevice reports whether two paths resolve to the same block device. Used by the Tier 2
|
||||
// off-drive guard to refuse copying an app's backup onto the same physical disk as its source (the
|
||||
// whole point of Tier 2 is to survive that disk failing). Returns false if either path can't be
|
||||
// stat'd (fail-open to "different" would be unsafe, so callers must also verify the dest separately —
|
||||
// but in practice an unstattable path fails earlier). NOTE: this is mount/device-granularity; two
|
||||
// partitions on one physical disk look "different" here — the agent's durable-id is the stronger
|
||||
// guarantee for that case, but for the felhom layout (external drive vs system rootfs) this suffices.
|
||||
func SamePhysicalDevice(a, b string) bool {
|
||||
return isSameBlockDevice(a, b)
|
||||
}
|
||||
|
||||
// stripPartition strips the partition suffix from a device name.
|
||||
// e.g., "sda1" → "sda", "nvme0n1p1" → "nvme0n1", "mmcblk0p1" → "mmcblk0".
|
||||
func stripPartition(base string) string {
|
||||
|
||||
@@ -48,6 +48,9 @@ type DiskUsageInfo struct {
|
||||
// GetDiskUsage returns nil on non-Linux.
|
||||
func GetDiskUsage(_ string) *DiskUsageInfo { return nil }
|
||||
|
||||
// SamePhysicalDevice always returns false on non-Linux (dev/testing only — Tier 2 runs on Linux).
|
||||
func SamePhysicalDevice(_, _ string) bool { return false }
|
||||
|
||||
// FSInfo holds filesystem type, device, and disk model info.
|
||||
type FSInfo struct {
|
||||
FSType string
|
||||
|
||||
Reference in New Issue
Block a user