Files
bee/audit/internal/tui/screen_tools.go
Michael Chus a57b037a91 feat(installer): add 'Install to disk' in Tools submenu
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>
2026-03-26 23:35:01 +03:00

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)
}