//go:build linux package report import ( "os" "os/exec" "path/filepath" "strconv" "strings" "gitea.dooplex.hu/admin/felhom-controller/internal/backup" ) // collectDiskLayout reads /host-fstab and correlates with blkid/lsblk to build // the disk mount topology. Only includes data partitions (not root, boot, or swap). func collectDiskLayout(systemDataPath string) backup.DiskLayout { layout := backup.DiskLayout{} fstabPath := "/host-fstab" if _, err := os.Stat(fstabPath); err != nil { fstabPath = "/etc/fstab" } data, err := os.ReadFile(fstabPath) if err != nil { return layout } // Parse fstab into UUID-based entries and bind mount entries type fstabEntry struct { source string mountPoint string fsType string options string } var uuidEntries []fstabEntry var bindEntries []fstabEntry systemMounts := map[string]bool{"/": true, "/boot": true, "/boot/efi": true} for _, line := range strings.Split(string(data), "\n") { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "#") { continue } fields := strings.Fields(line) if len(fields) < 4 { continue } source := fields[0] mountPoint := fields[1] fsType := fields[2] options := fields[3] // Skip system mounts and swap if systemMounts[mountPoint] || fsType == "swap" { continue } if strings.HasPrefix(source, "UUID=") { uuidEntries = append(uuidEntries, fstabEntry{ source: strings.TrimPrefix(source, "UUID="), mountPoint: mountPoint, fsType: fsType, options: options, }) } else if fsType == "none" && strings.Contains(options, "bind") { bindEntries = append(bindEntries, fstabEntry{ source: source, mountPoint: mountPoint, options: options, }) } } // Process UUID-based entries for _, e := range uuidEntries { dm := backup.DiskMount{ UUID: e.source, MountPoint: e.mountPoint, FSType: e.fsType, FstabOptions: e.options, } // Get label via blkid if out, err := exec.Command("blkid", "-o", "value", "-s", "LABEL", "-U", e.source).Output(); err == nil { dm.Label = strings.TrimSpace(string(out)) } // Get size via lsblk (resolve UUID to device first) if devPath, err := exec.Command("blkid", "-U", e.source).Output(); err == nil { dev := strings.TrimSpace(string(devPath)) if dev != "" { if out, err := exec.Command("lsblk", "-b", "-n", "-o", "SIZE", dev).Output(); err == nil { if sz, err := strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64); err == nil { dm.SizeBytes = sz } } } } // Determine role if e.mountPoint == systemDataPath { dm.Role = "system_data" } else { dm.Role = "hdd_storage" } // Check for a corresponding bind mount for _, bind := range bindEntries { if strings.HasPrefix(bind.source, e.mountPoint+"/") { subdir := strings.TrimPrefix(bind.source, e.mountPoint+"/") dm.BindSubdir = subdir dm.RawMount = e.mountPoint dm.MountPoint = bind.mountPoint // the final user-facing mount point break } } // Get label from mount point basename as fallback if dm.Label == "" { if dm.RawMount != "" { dm.Label = filepath.Base(dm.RawMount) } else { dm.Label = filepath.Base(dm.MountPoint) } } layout.Mounts = append(layout.Mounts, dm) } return layout }