Copies the live system to a local disk via unsquashfs — no debootstrap, no network required. Supports UEFI (GPT+EFI) and BIOS (MBR) layouts. ISO: - Add squashfs-tools, parted, grub-pc, grub-efi-amd64 to package list - New overlay script bee-install: partitions, formats, unsquashfs, writes fstab, runs grub-install+update-grub in chroot Go TUI: - Settings → Tools submenu (Install to disk, Check tools) - Disk picker screen: lists non-USB, non-boot disks via lsblk - Confirm screen warns about data loss - Runs with live progress tail of /tmp/bee-install.log - platform/install.go: ListInstallDisks, InstallToDisk, findLiveBootDevice Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
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 <device> <logfile> 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)
|
|
}
|