diff --git a/audit/internal/platform/install_to_ram.go b/audit/internal/platform/install_to_ram.go index 729c746..de7eabe 100644 --- a/audit/internal/platform/install_to_ram.go +++ b/audit/internal/platform/install_to_ram.go @@ -12,6 +12,7 @@ import ( ) const installToRAMDir = "/dev/shm/bee-live" +const copyProgressLogStep int64 = 100 * 1024 * 1024 func (s *System) IsLiveMediaInRAM() bool { return s.LiveMediaRAMState().InRAM @@ -319,6 +320,7 @@ func copyFileLarge(ctx context.Context, src, dst string, logFunc func(string)) e defer out.Close() total := fi.Size() var copied int64 + var lastLogged int64 buf := make([]byte, 4*1024*1024) for { if err := ctx.Err(); err != nil { @@ -330,7 +332,8 @@ func copyFileLarge(ctx context.Context, src, dst string, logFunc func(string)) e return werr } copied += int64(n) - if logFunc != nil && total > 0 { + if shouldLogCopyProgress(copied, total, lastLogged) { + lastLogged = copied pct := int(float64(copied) / float64(total) * 100) logFunc(fmt.Sprintf(" %s / %s (%d%%)", humanBytes(copied), humanBytes(total), pct)) } @@ -345,6 +348,19 @@ func copyFileLarge(ctx context.Context, src, dst string, logFunc func(string)) e return out.Sync() } +func shouldLogCopyProgress(copied, total, lastLogged int64) bool { + if total <= 0 || copied <= 0 { + return false + } + if copied >= total { + return copied > lastLogged + } + if copied < copyProgressLogStep { + return false + } + return copied-lastLogged >= copyProgressLogStep +} + func cpDir(ctx context.Context, src, dst string, logFunc func(string)) error { return filepath.Walk(src, func(path string, fi os.FileInfo, err error) error { if ctx.Err() != nil { diff --git a/audit/internal/platform/install_to_ram_test.go b/audit/internal/platform/install_to_ram_test.go index 2ad458f..de9c8c7 100644 --- a/audit/internal/platform/install_to_ram_test.go +++ b/audit/internal/platform/install_to_ram_test.go @@ -101,3 +101,26 @@ func TestEvaluateLiveMediaRAMState(t *testing.T) { } }) } + +func TestShouldLogCopyProgress(t *testing.T) { + t.Parallel() + + total := int64(250 * 1024 * 1024) + step := int64(100 * 1024 * 1024) + + if shouldLogCopyProgress(step-1, total, 0) { + t.Fatal("progress logged too early") + } + if !shouldLogCopyProgress(step, total, 0) { + t.Fatal("expected log at first 100MB boundary") + } + if shouldLogCopyProgress(step+16*1024*1024, total, step) { + t.Fatal("progress logged again before next 100MB") + } + if !shouldLogCopyProgress(2*step, total, step) { + t.Fatal("expected log at second 100MB boundary") + } + if !shouldLogCopyProgress(total, total, 2*step) { + t.Fatal("expected final completion log") + } +} diff --git a/audit/internal/webui/server.go b/audit/internal/webui/server.go index af200dd..0e20960 100644 --- a/audit/internal/webui/server.go +++ b/audit/internal/webui/server.go @@ -690,7 +690,7 @@ func chartDataFromSamples(path string, samples []platform.LiveMetricSample) (dat // Use per-PSU stacked chart when PSU SDR data is available. // Collect the union of PSU slots seen across all samples. psuSlots := psuSlotsFromSamples(samples) - if len(psuSlots) > 1 { + if len(psuSlots) > 0 { // Build one dataset per PSU slot. psuDatasets := make([][]float64, len(psuSlots)) psuNames := make([]string, len(psuSlots)) @@ -709,7 +709,7 @@ func chartDataFromSamples(path string, samples []platform.LiveMetricSample) (dat } datasets = psuDatasets names = psuNames - stacked = true + stacked = len(psuDatasets) > 0 yMax = autoMax120(psuStackedTotal(psuDatasets)) } else { power := make([]float64, len(samples)) diff --git a/audit/internal/webui/server_test.go b/audit/internal/webui/server_test.go index 240f43a..d402902 100644 --- a/audit/internal/webui/server_test.go +++ b/audit/internal/webui/server_test.go @@ -420,6 +420,45 @@ func TestHandleMetricsChartSVGRendersCustomSVG(t *testing.T) { } } +func TestChartDataFromSamplesServerPowerUsesPerPSUDatasets(t *testing.T) { + start := time.Date(2026, 4, 5, 12, 0, 0, 0, time.UTC) + samples := []platform.LiveMetricSample{ + { + Timestamp: start, + PSUs: []platform.PSUReading{ + {Slot: 1, PowerW: 120}, + {Slot: 2, PowerW: 130}, + }, + PowerW: 250, + }, + { + Timestamp: start.Add(time.Minute), + PSUs: []platform.PSUReading{ + {Slot: 1, PowerW: 140}, + {Slot: 2, PowerW: 135}, + }, + PowerW: 275, + }, + } + + datasets, names, _, title, _, _, stacked, ok := chartDataFromSamples("server-power", samples) + if !ok { + t.Fatal("expected server-power chart data") + } + if title != "System Power" { + t.Fatalf("title=%q", title) + } + if !stacked { + t.Fatal("expected stacked PSU chart") + } + if len(datasets) != 2 || len(names) != 2 { + t.Fatalf("datasets=%d names=%d want 2/2", len(datasets), len(names)) + } + if names[0] != "PSU 1" || names[1] != "PSU 2" { + t.Fatalf("names=%v", names) + } +} + func TestNormalizeFanSeriesHoldsLastPositive(t *testing.T) { got := normalizeFanSeries([]float64{4200, 0, 0, 4300, 0}) want := []float64{4200, 4200, 4200, 4300, 4300} diff --git a/iso/builder/auto/config b/iso/builder/auto/config index da70595..092462e 100755 --- a/iso/builder/auto/config +++ b/iso/builder/auto/config @@ -32,7 +32,7 @@ lb config noauto \ --memtest memtest86+ \ --iso-volume "EASY_BEE_${BEE_GPU_VENDOR_UPPER:-NVIDIA}" \ --iso-application "EASY-BEE-${BEE_GPU_VENDOR_UPPER:-NVIDIA}" \ - --bootappend-live "boot=live components video=1920x1080 console=tty0 console=ttyS0,115200n8 loglevel=3 systemd.show_status=1 username=bee user-fullname=Bee modprobe.blacklist=nouveau,snd_hda_intel,snd_hda_codec_realtek,snd_hda_codec_generic,soundcore" \ + --bootappend-live "boot=live components video=1920x1080 console=ttyS0,115200n8 console=tty0 loglevel=3 systemd.show_status=1 username=bee user-fullname=Bee modprobe.blacklist=nouveau,snd_hda_intel,snd_hda_codec_realtek,snd_hda_codec_generic,soundcore" \ --debootstrap-options "--include=ca-certificates" \ --apt-recommends false \ --chroot-squashfs-compression-type zstd \ diff --git a/iso/builder/build.sh b/iso/builder/build.sh index 1065317..e1fa137 100755 --- a/iso/builder/build.sh +++ b/iso/builder/build.sh @@ -542,6 +542,186 @@ label memtest EOF } +extract_live_grub_entry() { + cfg="$1" + live_linux="$(awk '/^[[:space:]]*linux[[:space:]]+\/live\// { print; exit }' "$cfg")" + live_initrd="$(awk '/^[[:space:]]*initrd[[:space:]]+\/live\// { print; exit }' "$cfg")" + [ -n "$live_linux" ] || return 1 + [ -n "$live_initrd" ] || return 1 + + grub_kernel="$(printf '%s\n' "$live_linux" | awk '{print $2}')" + grub_append="$(printf '%s\n' "$live_linux" | cut -d' ' -f3-)" + grub_initrd="$(printf '%s\n' "$live_initrd" | awk '{print $2}')" + [ -n "$grub_kernel" ] || return 1 + [ -n "$grub_append" ] || return 1 + [ -n "$grub_initrd" ] || return 1 + return 0 +} + +extract_live_isolinux_entry() { + cfg="$1" + isolinux_linux="$(awk '/^[[:space:]]*linux[[:space:]]+\/live\// { print; exit }' "$cfg")" + isolinux_initrd="$(awk '/^[[:space:]]*initrd[[:space:]]+\/live\// { print; exit }' "$cfg")" + isolinux_append="$(awk '/^[[:space:]]*append[[:space:]]+/ { sub(/^[[:space:]]*append[[:space:]]+/, ""); print; exit }' "$cfg")" + [ -n "$isolinux_linux" ] || return 1 + [ -n "$isolinux_initrd" ] || return 1 + [ -n "$isolinux_append" ] || return 1 + + isolinux_kernel="$(printf '%s\n' "$isolinux_linux" | awk '{print $2}')" + isolinux_initrd_path="$(printf '%s\n' "$isolinux_initrd" | awk '{print $2}')" + [ -n "$isolinux_kernel" ] || return 1 + [ -n "$isolinux_initrd_path" ] || return 1 + return 0 +} + +write_canonical_grub_cfg() { + cfg="$1" + kernel="$2" + append_live="$3" + initrd="$4" + + cat > "$cfg" <" { + menuentry "EASY-BEE — GSP=off" { + linux ${kernel} ${append_live} nomodeset bee.nvidia.mode=gsp-off net.ifnames=0 biosdevname=0 mitigations=off transparent_hugepage=always numa_balancing=disable pcie_aspm=off intel_idle.max_cstate=1 processor.max_cstate=1 nowatchdog nosoftlockup + initrd ${initrd} + } + + menuentry "EASY-BEE — KMS (no nomodeset)" { + linux ${kernel} ${append_live} bee.nvidia.mode=normal net.ifnames=0 biosdevname=0 mitigations=off transparent_hugepage=always numa_balancing=disable pcie_aspm=off intel_idle.max_cstate=1 processor.max_cstate=1 nowatchdog nosoftlockup + initrd ${initrd} + } + + menuentry "EASY-BEE — KMS + GSP=off" { + linux ${kernel} ${append_live} bee.nvidia.mode=gsp-off net.ifnames=0 biosdevname=0 mitigations=off transparent_hugepage=always numa_balancing=disable pcie_aspm=off intel_idle.max_cstate=1 processor.max_cstate=1 nowatchdog nosoftlockup + initrd ${initrd} + } + + menuentry "EASY-BEE — fail-safe" { + linux ${kernel} ${append_live} nomodeset bee.nvidia.mode=gsp-off noapic noapm nodma nomce nolapic nosmp vga=normal net.ifnames=0 biosdevname=0 + initrd ${initrd} + } +} + +if [ "\${grub_platform}" = "efi" ]; then + menuentry "Memory Test (memtest86+)" { + chainloader /boot/memtest86+x64.efi + } +else + menuentry "Memory Test (memtest86+)" { + linux16 /boot/memtest86+x64.bin + } +fi + +if [ "\${grub_platform}" = "efi" ]; then + menuentry "UEFI Firmware Settings" { + fwsetup + } +fi +EOF +} + +write_canonical_isolinux_cfg() { + cfg="$1" + kernel="$2" + initrd="$3" + append_live="$4" + + cat > "$cfg" <&2 + fi + fi + + if [ -f "$isolinux_cfg" ]; then + if extract_live_isolinux_entry "$isolinux_cfg"; then + write_canonical_isolinux_cfg "$isolinux_cfg" "$isolinux_kernel" "$isolinux_initrd_path" "$isolinux_append" + echo "bootloader sync: rewrote binary/isolinux/live.cfg with canonical EASY-BEE menu" + else + echo "bootloader sync: WARNING: could not extract live entry from $isolinux_cfg" >&2 + fi + fi +} + copy_memtest_from_deb() { deb="$1" dst_boot="$2" @@ -1229,6 +1409,11 @@ run_step_sh "live-build clean" "80-lb-clean" "lb clean --all 2>&1 | tail -3" run_step_sh "live-build config" "81-lb-config" "lb config 2>&1 | tail -5" dump_memtest_debug "pre-build" "${LB_DIR}" run_step_sh "live-build build" "90-lb-build" "lb build 2>&1" +echo "=== enforcing canonical bootloader assets ===" +enforce_live_build_bootloader_assets "${LB_DIR}" +run_step_sh "rebuild live-build checksums after bootloader sync" "91b-lb-checksums" "lb binary_checksums 2>&1" +run_step_sh "rebuild ISO after bootloader sync" "91c-lb-binary-iso" "rm -f '${LB_DIR}/live-image-amd64.hybrid.iso' && lb binary_iso 2>&1" +run_step_sh "rebuild zsync after bootloader sync" "91d-lb-zsync" "lb binary_zsync 2>&1" # --- persist deb package cache back to shared location --- # This allows the second variant to reuse all downloaded packages. diff --git a/iso/builder/config/bootloaders/grub-efi/theme.cfg b/iso/builder/config/bootloaders/grub-efi/theme.cfg index da359e1..c5d2b6f 100644 --- a/iso/builder/config/bootloaders/grub-efi/theme.cfg +++ b/iso/builder/config/bootloaders/grub-efi/theme.cfg @@ -1,7 +1,7 @@ set color_normal=light-gray/black set color_highlight=yellow/black -if [ -e /boot/grub/splash.png ]; then +if [ -e /boot/grub/live-theme/theme.txt ]; then set theme=/boot/grub/live-theme/theme.txt else set menu_color_normal=yellow/black