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>
109 lines
2.9 KiB
Go
109 lines
2.9 KiB
Go
package tui
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"bee/audit/internal/platform"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
// handleToolsMenu handles the Tools submenu selection.
|
|
func (m model) handleToolsMenu() (tea.Model, tea.Cmd) {
|
|
switch m.cursor {
|
|
case 0: // Install to disk
|
|
m.busy = true
|
|
m.busyTitle = "Scanning disks"
|
|
return m, func() tea.Msg {
|
|
disks, err := m.app.ListInstallDisks()
|
|
return installDisksMsg{disks: disks, err: err}
|
|
}
|
|
case 1: // Check tools
|
|
m.busy = true
|
|
m.busyTitle = "Check tools"
|
|
return m, func() tea.Msg {
|
|
result := m.app.ToolCheckResult([]string{
|
|
"dmidecode", "smartctl", "nvme", "ipmitool", "lspci",
|
|
"ethtool", "bee", "nvidia-smi", "bee-gpu-stress",
|
|
"memtester", "dhclient", "lsblk", "mount",
|
|
"unsquashfs", "parted", "grub-install", "bee-install",
|
|
"all_reduce_perf", "dcgmi",
|
|
})
|
|
return resultMsg{title: result.Title, body: result.Body, back: screenTools}
|
|
}
|
|
case 2: // Back
|
|
m.screen = screenSettings
|
|
m.cursor = 0
|
|
return m, nil
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// handleInstallDiskPickMenu handles disk selection for installation.
|
|
func (m model) handleInstallDiskPickMenu() (tea.Model, tea.Cmd) {
|
|
if m.cursor >= len(m.installDisks) {
|
|
return m, nil
|
|
}
|
|
m.selectedDisk = m.installDisks[m.cursor].Device
|
|
m.pendingAction = actionInstallToDisk
|
|
m.screen = screenConfirm
|
|
m.cursor = 0
|
|
return m, nil
|
|
}
|
|
|
|
// startInstall launches the bee-install script and polls its log for progress.
|
|
func (m model) startInstall() (tea.Model, tea.Cmd) {
|
|
device := m.selectedDisk
|
|
logFile := DefaultInstallLogFile
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
m.installCancel = cancel
|
|
m.installSince = time.Now()
|
|
m.busy = true
|
|
m.busyTitle = "Install to disk: " + device
|
|
m.progressLines = nil
|
|
|
|
installCmd := func() tea.Msg {
|
|
err := m.app.InstallToDisk(ctx, device, logFile)
|
|
return installDoneMsg{err: err}
|
|
}
|
|
|
|
return m, tea.Batch(installCmd, pollInstallProgress(logFile))
|
|
}
|
|
|
|
func renderInstallDiskPick(m model) string {
|
|
var b strings.Builder
|
|
fmt.Fprintln(&b, "INSTALL TO DISK")
|
|
fmt.Fprintln(&b)
|
|
fmt.Fprintln(&b, " WARNING: the selected disk will be completely WIPED.")
|
|
fmt.Fprintln(&b)
|
|
fmt.Fprintln(&b, " Available disks (USB and boot media excluded):")
|
|
fmt.Fprintln(&b)
|
|
if len(m.installDisks) == 0 {
|
|
fmt.Fprintln(&b, " (no suitable disks found)")
|
|
} else {
|
|
for i, d := range m.installDisks {
|
|
pfx := " "
|
|
if m.cursor == i {
|
|
pfx = "> "
|
|
}
|
|
fmt.Fprintf(&b, "%s%s\n", pfx, diskLabel(d))
|
|
}
|
|
}
|
|
fmt.Fprintln(&b)
|
|
fmt.Fprintln(&b, "─────────────────────────────────────────────────────────────────")
|
|
fmt.Fprint(&b, "[↑/↓] move [enter] select [esc] back [ctrl+c] quit")
|
|
return b.String()
|
|
}
|
|
|
|
func diskLabel(d platform.InstallDisk) string {
|
|
model := d.Model
|
|
if model == "" {
|
|
model = "Unknown"
|
|
}
|
|
return fmt.Sprintf("%-12s %-8s %s", d.Device, d.Size, model)
|
|
}
|