package platform import ( "context" "fmt" "os/exec" "strconv" "strings" ) // InstallDisk describes a candidate disk for installation. type InstallDisk struct { Device string // e.g. /dev/sda Model string Size string // human-readable, e.g. "500G" } // ListInstallDisks returns block devices suitable for installation. // Excludes USB drives and the current live boot medium. func (s *System) ListInstallDisks() ([]InstallDisk, error) { out, err := exec.Command("lsblk", "-dn", "-o", "NAME,MODEL,SIZE,TYPE,TRAN").Output() if err != nil { return nil, fmt.Errorf("lsblk: %w", err) } bootDev := findLiveBootDevice() var disks []InstallDisk for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { fields := strings.Fields(line) // NAME MODEL SIZE TYPE TRAN — model may have spaces so we parse from end if len(fields) < 4 { continue } // Last field: TRAN, second-to-last: TYPE, third-to-last: SIZE tran := fields[len(fields)-1] typ := fields[len(fields)-2] size := fields[len(fields)-3] name := fields[0] model := strings.Join(fields[1:len(fields)-3], " ") if typ != "disk" { continue } if strings.EqualFold(tran, "usb") { continue } device := "/dev/" + name if device == bootDev { continue } disks = append(disks, InstallDisk{ Device: device, Model: strings.TrimSpace(model), Size: size, }) } return disks, nil } // findLiveBootDevice returns the block device backing /run/live/medium (if any). func findLiveBootDevice() string { out, err := exec.Command("findmnt", "-n", "-o", "SOURCE", "/run/live/medium").Output() if err != nil { return "" } src := strings.TrimSpace(string(out)) if src == "" { return "" } // Strip partition suffix to get the whole disk device. // e.g. /dev/sdb1 → /dev/sdb, /dev/nvme0n1p1 → /dev/nvme0n1 out2, err := exec.Command("lsblk", "-no", "PKNAME", src).Output() if err != nil || strings.TrimSpace(string(out2)) == "" { return src } return "/dev/" + strings.TrimSpace(string(out2)) } // InstallToDisk runs bee-install and streams output to logFile. // The context can be used to cancel. func (s *System) InstallToDisk(ctx context.Context, device string, logFile string) error { cmd := exec.CommandContext(ctx, "bee-install", device, logFile) return cmd.Run() } // InstallLogPath returns the default install log path for a given device. func InstallLogPath(device string) string { safe := strings.NewReplacer("/", "_", " ", "_").Replace(device) return "/tmp/bee-install" + safe + ".log" } // DiskLabel returns a display label for a disk. func (d InstallDisk) Label() string { model := d.Model if model == "" { model = "Unknown" } sizeBytes, err := strconv.ParseInt(strings.TrimSuffix(d.Size, "B"), 10, 64) _ = sizeBytes _ = err return fmt.Sprintf("%s %s %s", d.Device, d.Size, model) }