diff --git a/audit/internal/platform/install_to_ram.go b/audit/internal/platform/install_to_ram.go index a01ccd5..729c746 100644 --- a/audit/internal/platform/install_to_ram.go +++ b/audit/internal/platform/install_to_ram.go @@ -140,26 +140,56 @@ func (s *System) RunInstallToRAM(ctx context.Context, logFunc func(string)) (ret } squashfsFiles, err := filepath.Glob("/run/live/medium/live/*.squashfs") - if err != nil || len(squashfsFiles) == 0 { - return fmt.Errorf("no squashfs files found in /run/live/medium/live/") - } - - free := freeMemBytes() - var needed int64 - for _, sf := range squashfsFiles { - fi, err2 := os.Stat(sf) - if err2 != nil { - return fmt.Errorf("stat %s: %v", sf, err2) - } - needed += fi.Size() - } - const headroom = 256 * 1024 * 1024 - if free > 0 && needed+headroom > free { - return fmt.Errorf("insufficient RAM: need %s, available %s", - humanBytes(needed+headroom), humanBytes(free)) - } + sourceAvailable := err == nil && len(squashfsFiles) > 0 dstDir := installToRAMDir + + // If the source medium is unavailable, check whether a previous run already + // produced a complete copy in RAM. If so, skip the copy phase and proceed + // directly to the loop-rebind / bind-mount steps. + if !sourceAvailable { + copiedFiles, _ := filepath.Glob(filepath.Join(dstDir, "*.squashfs")) + if len(copiedFiles) > 0 { + log("Source medium not available, but a previous RAM copy was found — resuming from existing copy.") + // Proceed to rebind with the already-copied files. + for _, dst := range copiedFiles { + base := filepath.Base(dst) + // Re-associate the loop device that was originally backed by the + // source file (now gone); find it by the old source path pattern. + srcGuess := "/run/live/medium/live/" + base + loopDev, lerr := findLoopForFile(srcGuess) + if lerr != nil { + log(fmt.Sprintf("Loop device for %s not found (%v) — skipping re-association.", base, lerr)) + continue + } + if rerr := reassociateLoopDevice(loopDev, dst); rerr != nil { + log(fmt.Sprintf("Warning: could not re-associate %s → %s: %v", loopDev, dst, rerr)) + } else { + log(fmt.Sprintf("Loop device %s now backed by RAM copy.", loopDev)) + } + } + goto bindMedium + } + return fmt.Errorf("no squashfs files found in /run/live/medium/live/ and no prior RAM copy in %s — reconnect the installation medium and retry", dstDir) + } + + { + free := freeMemBytes() + var needed int64 + for _, sf := range squashfsFiles { + fi, err2 := os.Stat(sf) + if err2 != nil { + return fmt.Errorf("stat %s: %v", sf, err2) + } + needed += fi.Size() + } + const headroom = 256 * 1024 * 1024 + if free > 0 && needed+headroom > free { + return fmt.Errorf("insufficient RAM: need %s, available %s", + humanBytes(needed+headroom), humanBytes(free)) + } + } + if state.CopyPresent { log("Removing stale partial RAM copy before retry...") } @@ -199,6 +229,7 @@ func (s *System) RunInstallToRAM(ctx context.Context, logFunc func(string)) (ret } } +bindMedium: log("Copying remaining medium files...") if err := cpDir(ctx, "/run/live/medium", dstDir, log); err != nil { log(fmt.Sprintf("Warning: partial copy: %v", err)) diff --git a/audit/internal/webui/tasks.go b/audit/internal/webui/tasks.go index 7880ce4..957f95a 100644 --- a/audit/internal/webui/tasks.go +++ b/audit/internal/webui/tasks.go @@ -613,8 +613,9 @@ func (q *taskQueue) runTask(t *Task, j *jobState, ctx context.Context) { } a := q.opts.App + recovered := len(j.lines) > 0 j.append(fmt.Sprintf("Starting %s...", t.Name)) - if len(j.lines) > 0 { + if recovered { j.append(fmt.Sprintf("Recovered after bee-web restart at %s", time.Now().UTC().Format(time.RFC3339))) } diff --git a/iso/builder/config/bootloaders/grub-pc/grub.cfg b/iso/builder/config/bootloaders/grub-pc/grub.cfg index 1db9d30..396fe80 100644 --- a/iso/builder/config/bootloaders/grub-pc/grub.cfg +++ b/iso/builder/config/bootloaders/grub-pc/grub.cfg @@ -16,6 +16,11 @@ menuentry "EASY-BEE" { } submenu "EASY-BEE (advanced options) -->" { + menuentry "EASY-BEE — load to RAM (toram)" { + linux @KERNEL_LIVE@ @APPEND_LIVE@ toram nomodeset 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_LIVE@ + } + menuentry "EASY-BEE — GSP=off" { linux @KERNEL_LIVE@ @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_LIVE@ diff --git a/iso/builder/config/hooks/normal/9000-bee-setup.hook.chroot b/iso/builder/config/hooks/normal/9000-bee-setup.hook.chroot index 35de676..fe337c7 100755 --- a/iso/builder/config/hooks/normal/9000-bee-setup.hook.chroot +++ b/iso/builder/config/hooks/normal/9000-bee-setup.hook.chroot @@ -63,8 +63,10 @@ chmod +x /usr/local/bin/bee-sshsetup 2>/dev/null || true chmod +x /usr/local/bin/bee-smoketest 2>/dev/null || true chmod +x /usr/local/bin/bee 2>/dev/null || true chmod +x /usr/local/bin/bee-log-run 2>/dev/null || true -chmod +x /usr/local/bin/bee-selfheal 2>/dev/null || true -chmod +x /usr/local/bin/bee-boot-status 2>/dev/null || true +chmod +x /usr/local/bin/bee-selfheal 2>/dev/null || true +chmod +x /usr/local/bin/bee-boot-status 2>/dev/null || true +chmod +x /usr/local/bin/bee-install 2>/dev/null || true +chmod +x /usr/local/bin/bee-remount-medium 2>/dev/null || true if [ "$GPU_VENDOR" = "nvidia" ]; then chmod +x /usr/local/bin/bee-nvidia-load 2>/dev/null || true chmod +x /usr/local/bin/bee-gpu-burn 2>/dev/null || true diff --git a/iso/overlay/usr/local/bin/bee-install b/iso/overlay/usr/local/bin/bee-install index ac30157..e6d432f 100755 --- a/iso/overlay/usr/local/bin/bee-install +++ b/iso/overlay/usr/local/bin/bee-install @@ -65,6 +65,9 @@ done SQUASHFS="/run/live/medium/live/filesystem.squashfs" if [ ! -f "$SQUASHFS" ]; then echo "ERROR: squashfs not found at $SQUASHFS" >&2 + echo " The live medium may have been disconnected." >&2 + echo " Reconnect the disc and run: bee-remount-medium --wait" >&2 + echo " Then re-run bee-install." >&2 exit 1 fi @@ -162,10 +165,59 @@ log " Mounted." log "--- Step 5/7: Unpacking filesystem (this takes 10-20 minutes) ---" log " Source: $SQUASHFS" log " Target: $MOUNT_ROOT" -unsquashfs -f -d "$MOUNT_ROOT" "$SQUASHFS" 2>&1 | \ - grep -E '^\[|^inod|^created|^extract' | \ - while read -r line; do log " $line"; done || true -log " Unpack complete." + +# unsquashfs does not support resume, so retry the entire unpack step if the +# source medium disappears mid-copy (e.g. CD physically disconnected). +UNPACK_ATTEMPTS=0 +UNPACK_MAX=5 +while true; do + UNPACK_ATTEMPTS=$(( UNPACK_ATTEMPTS + 1 )) + if [ "$UNPACK_ATTEMPTS" -gt "$UNPACK_MAX" ]; then + die "Unpack failed $UNPACK_MAX times — giving up. Check the disc and logs." + fi + [ "$UNPACK_ATTEMPTS" -gt 1 ] && log " Retry attempt $UNPACK_ATTEMPTS / $UNPACK_MAX ..." + + # Re-check squashfs is reachable before each attempt + if [ ! -f "$SQUASHFS" ]; then + log " SOURCE LOST: $SQUASHFS not found." + log " Reconnect the disc and run 'bee-remount-medium --wait' in another terminal," + log " then press Enter here to retry." + read -r _ + continue + fi + + # wipe partial unpack so unsquashfs starts clean + if [ "$UNPACK_ATTEMPTS" -gt 1 ]; then + log " Cleaning partial unpack from $MOUNT_ROOT ..." + # keep the mount point itself but remove its contents + find "$MOUNT_ROOT" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true + fi + + UNPACK_OK=0 + unsquashfs -f -d "$MOUNT_ROOT" "$SQUASHFS" 2>&1 | \ + grep -E '^\[|^inod|^created|^extract|^ERROR|failed' | \ + while IFS= read -r line; do log " $line"; done || UNPACK_OK=$? + + # Check squashfs is still reachable (gone = disc pulled during copy) + if [ ! -f "$SQUASHFS" ]; then + log " WARNING: source medium lost during unpack — will retry after remount." + log " Run 'bee-remount-medium --wait' in another terminal, then press Enter." + read -r _ + continue + fi + + # Verify the unpack produced a usable root (presence of /etc is a basic check) + if [ -d "${MOUNT_ROOT}/etc" ]; then + log " Unpack complete." + break + else + log " WARNING: unpack produced no /etc — squashfs may be corrupt or incomplete." + if [ "$UNPACK_ATTEMPTS" -lt "$UNPACK_MAX" ]; then + log " Retrying in 5 s ..." + sleep 5 + fi + fi +done # ------------------------------------------------------------------ log "--- Step 6/7: Configuring installed system ---" diff --git a/iso/overlay/usr/local/bin/bee-remount-medium b/iso/overlay/usr/local/bin/bee-remount-medium new file mode 100644 index 0000000..64e4bae --- /dev/null +++ b/iso/overlay/usr/local/bin/bee-remount-medium @@ -0,0 +1,100 @@ +#!/bin/bash +# bee-remount-medium — find and remount the live ISO medium to /run/live/medium +# +# Run this after reconnecting the ISO source disc (USB/CD) if the live medium +# was lost and /run/live/medium/live/filesystem.squashfs is missing. +# +# Usage: bee-remount-medium [--wait] +# --wait keep retrying every 5 seconds until the medium is found (useful +# while physically reconnecting the device) + +set -euo pipefail + +MEDIUM_DIR="/run/live/medium" +SQUASHFS_REL="live/filesystem.squashfs" +WAIT_MODE=0 + +for arg in "$@"; do + case "$arg" in + --wait|-w) WAIT_MODE=1 ;; + --help|-h) + echo "Usage: bee-remount-medium [--wait]" + echo " Finds and remounts the live ISO medium to $MEDIUM_DIR" + echo " --wait retry every 5 s until a medium with squashfs is found" + exit 0 ;; + esac +done + +log() { echo "[$(date +%H:%M:%S)] $*"; } +die() { log "ERROR: $*" >&2; exit 1; } + +# Return all candidate block devices (optical + removable USB mass storage) +find_candidates() { + # CD/DVD drives + for dev in /dev/sr* /dev/scd*; do + [ -b "$dev" ] && echo "$dev" + done + # USB/removable disks and partitions + for dev in /dev/sd* /dev/vd*; do + [ -b "$dev" ] || continue + # Only whole disks or partitions — skip the same device we are running from + local removable + local base + base=$(basename "$dev") + removable=$(cat "/sys/block/${base%%[0-9]*}/removable" 2>/dev/null || echo 0) + [ "$removable" = "1" ] && echo "$dev" + done +} + +# Try to mount $1 to $MEDIUM_DIR and check for squashfs +try_mount() { + local dev="$1" + local tmpdir + tmpdir=$(mktemp -d /tmp/bee-probe-XXXXXX) + if mount -o ro "$dev" "$tmpdir" 2>/dev/null; then + if [ -f "${tmpdir}/${SQUASHFS_REL}" ]; then + # Unmount probe mount and mount properly onto live path + umount "$tmpdir" 2>/dev/null || true + rmdir "$tmpdir" 2>/dev/null || true + # Unmount whatever is currently on MEDIUM_DIR (may be empty/stale) + umount "$MEDIUM_DIR" 2>/dev/null || true + mkdir -p "$MEDIUM_DIR" + if mount -o ro "$dev" "$MEDIUM_DIR"; then + log "Mounted $dev on $MEDIUM_DIR" + return 0 + else + log "Mount of $dev on $MEDIUM_DIR failed" + return 1 + fi + fi + umount "$tmpdir" 2>/dev/null || true + fi + rmdir "$tmpdir" 2>/dev/null || true + return 1 +} + +attempt() { + log "Scanning for ISO medium..." + for dev in $(find_candidates); do + log " Trying $dev ..." + if try_mount "$dev"; then + local sq="${MEDIUM_DIR}/${SQUASHFS_REL}" + log "SUCCESS: squashfs available at $sq ($(du -sh "$sq" | cut -f1))" + return 0 + fi + done + return 1 +} + +if [ "$WAIT_MODE" = "1" ]; then + log "Waiting for live medium (press Ctrl+C to abort)..." + while true; do + if attempt; then + exit 0 + fi + log " Not found — retrying in 5 s (reconnect the disc now)" + sleep 5 + done +else + attempt || die "No ISO medium with ${SQUASHFS_REL} found. Reconnect the disc and re-run, or use --wait." +fi