From 825ef6b98ad3d54a0c3a8172b49da81aae206e14 Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Sat, 11 Apr 2026 10:04:04 +0300 Subject: [PATCH] Add USB export drive and LiveCD-in-RAM checks to Runtime Health - schema: add ToRAMStatus and USBExportPath fields to RuntimeHealth - platform/runtime.go: collectToRAMHealth (ok/warning/failed based on IsLiveMediaInRAM + toramActive) and collectUSBExportHealth (scans /proc/mounts + lsblk for writable USB-backed filesystems) - pages.go: add USB Export Drive and LiveCD in RAM rows to the health table Co-Authored-By: Claude Sonnet 4.6 --- audit/internal/platform/runtime.go | 87 ++++++++++++++++++++++++++++++ audit/internal/schema/hardware.go | 4 ++ audit/internal/webui/pages.go | 47 ++++++++++++++++ 3 files changed, 138 insertions(+) diff --git a/audit/internal/platform/runtime.go b/audit/internal/platform/runtime.go index c9fc4f8..546f064 100644 --- a/audit/internal/platform/runtime.go +++ b/audit/internal/platform/runtime.go @@ -1,6 +1,7 @@ package platform import ( + "bufio" "os" "os/exec" "strings" @@ -114,6 +115,8 @@ func (s *System) CollectRuntimeHealth(exportDir string) (schema.RuntimeHealth, e } s.collectGPURuntimeHealth(vendor, &health) + s.collectToRAMHealth(&health) + s.collectUSBExportHealth(&health) if health.Status != "FAILED" && len(health.Issues) > 0 { health.Status = "PARTIAL" @@ -168,6 +171,90 @@ func resolvedToolStatus(display string, candidates ...string) ToolStatus { return ToolStatus{Name: display} } +// collectToRAMHealth checks whether the LiveCD ISO has been copied to RAM. +// Status values: "ok" = in RAM, "warning" = toram not active (no copy attempted), +// "failed" = toram was requested but medium is not in RAM (copy failed or in progress). +func (s *System) collectToRAMHealth(health *schema.RuntimeHealth) { + inRAM := s.IsLiveMediaInRAM() + active := toramActive() + switch { + case inRAM: + health.ToRAMStatus = "ok" + case active: + // toram was requested but medium is not yet/no longer in RAM + health.ToRAMStatus = "failed" + health.Issues = append(health.Issues, schema.RuntimeIssue{ + Code: "toram_copy_failed", + Severity: "warning", + Description: "toram boot parameter is set but the live medium is not mounted from RAM.", + }) + default: + health.ToRAMStatus = "warning" + } +} + +// collectUSBExportHealth scans /proc/mounts for a writable USB-backed filesystem +// suitable for log export. Sets USBExportPath to the first match found. +func (s *System) collectUSBExportHealth(health *schema.RuntimeHealth) { + health.USBExportPath = findUSBExportMount() +} + +// findUSBExportMount returns the mount point of the first writable USB filesystem +// found in /proc/mounts (vfat, exfat, ext2/3/4, ntfs) whose backing block device +// has USB transport. Returns "" if none found. +func findUSBExportMount() string { + f, err := os.Open("/proc/mounts") + if err != nil { + return "" + } + defer f.Close() + + // fs types that are expected on USB export drives + exportFSTypes := map[string]bool{ + "vfat": true, + "exfat": true, + "ext2": true, + "ext3": true, + "ext4": true, + "ntfs": true, + "ntfs3": true, + "fuseblk": true, + } + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + // fields: device mountpoint fstype options dump pass + fields := strings.Fields(scanner.Text()) + if len(fields) < 4 { + continue + } + device, mountPoint, fsType, options := fields[0], fields[1], fields[2], fields[3] + if !exportFSTypes[strings.ToLower(fsType)] { + continue + } + // Skip read-only mounts + opts := strings.Split(options, ",") + readOnly := false + for _, o := range opts { + if strings.TrimSpace(o) == "ro" { + readOnly = true + break + } + } + if readOnly { + continue + } + // Check USB transport via lsblk on the device + if !strings.HasPrefix(device, "/dev/") { + continue + } + if blockDeviceTransport(device) == "usb" { + return mountPoint + } + } + return "" +} + func (s *System) collectGPURuntimeHealth(vendor string, health *schema.RuntimeHealth) { lsmodText := commandText("lsmod") diff --git a/audit/internal/schema/hardware.go b/audit/internal/schema/hardware.go index 2e75732..82d5b15 100644 --- a/audit/internal/schema/hardware.go +++ b/audit/internal/schema/hardware.go @@ -22,6 +22,10 @@ type RuntimeHealth struct { CUDAReady bool `json:"cuda_ready,omitempty"` NvidiaGSPMode string `json:"nvidia_gsp_mode,omitempty"` // "gsp-on", "gsp-off", "gsp-stuck" NetworkStatus string `json:"network_status,omitempty"` + // ToRAMStatus: "ok" (ISO in RAM), "warning" (toram not active), "failed" (toram active but copy failed) + ToRAMStatus string `json:"toram_status,omitempty"` + // USBExportPath: mount point of the first writable USB drive found, empty if none. + USBExportPath string `json:"usb_export_path,omitempty"` Issues []RuntimeIssue `json:"issues,omitempty"` Tools []RuntimeToolStatus `json:"tools,omitempty"` Services []RuntimeServiceStatus `json:"services,omitempty"` diff --git a/audit/internal/webui/pages.go b/audit/internal/webui/pages.go index 64948e0..de6f82f 100644 --- a/audit/internal/webui/pages.go +++ b/audit/internal/webui/pages.go @@ -674,6 +674,8 @@ func renderHealthCard(opts HandlerOptions) string { buildRuntimeAccelerationRow(health), buildRuntimeToolsRow(health), buildRuntimeServicesRow(health), + buildRuntimeUSBExportRow(health), + buildRuntimeToRAMRow(health), } b.WriteString(``) for _, row := range rows { @@ -789,6 +791,51 @@ func buildRuntimeServicesRow(health schema.RuntimeHealth) runtimeHealthRow { return runtimeHealthRow{Title: "Bee Services", Status: status, Source: "ServiceState", Issue: issue} } +func buildRuntimeUSBExportRow(health schema.RuntimeHealth) runtimeHealthRow { + path := strings.TrimSpace(health.USBExportPath) + if path != "" { + return runtimeHealthRow{ + Title: "USB Export Drive", + Status: "OK", + Source: "/proc/mounts + lsblk", + Issue: path, + } + } + return runtimeHealthRow{ + Title: "USB Export Drive", + Status: "WARNING", + Source: "/proc/mounts + lsblk", + Issue: "No writable USB drive mounted. Plug in a USB drive to enable log export.", + } +} + +func buildRuntimeToRAMRow(health schema.RuntimeHealth) runtimeHealthRow { + switch strings.ToLower(strings.TrimSpace(health.ToRAMStatus)) { + case "ok": + return runtimeHealthRow{ + Title: "LiveCD in RAM", + Status: "OK", + Source: "live-boot / /proc/mounts", + Issue: "", + } + case "failed": + return runtimeHealthRow{ + Title: "LiveCD in RAM", + Status: "FAILED", + Source: "live-boot / /proc/mounts", + Issue: "toram boot parameter set but ISO is not mounted from RAM. Copy may have failed.", + } + default: + // toram not active — ISO still on original boot media (USB/CD) + return runtimeHealthRow{ + Title: "LiveCD in RAM", + Status: "WARNING", + Source: "live-boot / /proc/mounts", + Issue: "ISO not copied to RAM. Use \u201cCopy to RAM\u201d to free the boot drive and improve performance.", + } + } +} + func buildHardwareComponentRows(exportDir string) []runtimeHealthRow { path := filepath.Join(exportDir, "component-status.json") db, err := app.OpenComponentStatusDB(path)
CheckStatusSourceIssue