From 7b4bcc745a6451a9163da4cc5ae286d6f669addd Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Sun, 3 May 2026 23:15:22 +0300 Subject: [PATCH] Split live rootfs into smaller squashfs layers --- audit/internal/platform/install.go | 30 ++-- iso/builder/build.sh | 138 +++++++++++++----- .../config/bootloaders/grub-efi/config.cfg | 5 +- .../config/bootloaders/grub-efi/grub.cfg | 6 +- .../config/bootloaders/isolinux/live.cfg.in | 6 +- iso/overlay/usr/local/bin/bee-install | 36 +++-- iso/overlay/usr/local/bin/bee-remount-medium | 13 +- 7 files changed, 162 insertions(+), 72 deletions(-) diff --git a/audit/internal/platform/install.go b/audit/internal/platform/install.go index 9b77a35..3b2287d 100644 --- a/audit/internal/platform/install.go +++ b/audit/internal/platform/install.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "strconv" "strings" ) @@ -18,7 +19,7 @@ type InstallDisk struct { MountedParts []string // partition mount points currently active } -const squashfsPath = "/run/live/medium/live/filesystem.squashfs" +const squashfsGlob = "/run/live/medium/live/*.squashfs" // ListInstallDisks returns block devices suitable for installation. // Excludes the current live boot medium but includes USB drives. @@ -176,11 +177,22 @@ func inferLiveBootKind(fsType, source, deviceType, transport string) string { // squashfs size × 1.5 to allow for extracted filesystem and bootloader. // Returns 0 if the squashfs is not available (non-live environment). func MinInstallBytes() int64 { - fi, err := os.Stat(squashfsPath) - if err != nil { + files, err := filepath.Glob(squashfsGlob) + if err != nil || len(files) == 0 { return 0 } - return fi.Size() * 3 / 2 + var total int64 + for _, path := range files { + fi, statErr := os.Stat(path) + if statErr != nil { + continue + } + total += fi.Size() + } + if total == 0 { + return 0 + } + return total * 3 / 2 } // toramActive returns true when the live system was booted with toram. @@ -222,12 +234,10 @@ func DiskWarnings(d InstallDisk) []string { humanBytes(min), humanBytes(d.SizeBytes))) } if toramActive() { - sqFi, err := os.Stat(squashfsPath) - if err == nil { - free := freeMemBytes() - if free > 0 && free < sqFi.Size()*2 { - w = append(w, "toram mode — low RAM, extraction may be slow or fail") - } + free := freeMemBytes() + min := MinInstallBytes() + if free > 0 && min > 0 && free < (min*4/3) { + w = append(w, "toram mode — low RAM, extraction may be slow or fail") } } return w diff --git a/iso/builder/build.sh b/iso/builder/build.sh index dcd23dc..f6b389f 100755 --- a/iso/builder/build.sh +++ b/iso/builder/build.sh @@ -516,12 +516,12 @@ validate_iso_live_boot_entries() { exit 1 fi - grep -q 'menuentry "EASY-BEE"' "$grub_cfg" || { + grep -q 'menuentry "EASY-BEE v' "$grub_cfg" || { echo "ERROR: GRUB default EASY-BEE entry is missing" >&2 rm -f "$grub_cfg" "$isolinux_cfg" exit 1 } - grep -q 'menuentry "EASY-BEE -- load to RAM (toram)"' "$grub_cfg" || { + grep -q 'menuentry "EASY-BEE v.* -- load to RAM (toram)"' "$grub_cfg" || { echo "ERROR: GRUB toram entry is missing" >&2 rm -f "$grub_cfg" "$isolinux_cfg" exit 1 @@ -562,40 +562,38 @@ validate_iso_live_boot_entries() { echo "=== live boot validation OK ===" } -validate_iso_grub_theme_assets() { +validate_iso_grub_assets() { iso_path="$1" - echo "=== validating GRUB theme assets in ISO ===" + echo "=== validating GRUB assets in ISO ===" [ -f "$iso_path" ] || { - echo "ERROR: ISO not found for GRUB theme validation: $iso_path" >&2 + echo "ERROR: ISO not found for GRUB asset validation: $iso_path" >&2 exit 1 } require_iso_reader "$iso_path" >/dev/null 2>&1 || { - echo "ERROR: ISO reader unavailable for GRUB theme validation" >&2 + echo "ERROR: ISO reader unavailable for GRUB asset validation" >&2 exit 1 } iso_files="$(mktemp)" iso_list_files "$iso_path" > "$iso_files" || { - echo "ERROR: failed to list ISO files for GRUB theme validation" >&2 + echo "ERROR: failed to list ISO files for GRUB asset validation" >&2 rm -f "$iso_files" exit 1 } for required in \ boot/grub/config.cfg \ - boot/grub/theme.cfg \ - boot/grub/live-theme/theme.txt \ - boot/grub/live-theme/bee-logo.tga; do + boot/grub/grub.cfg; do grep -q "^${required}$" "$iso_files" || { - echo "ERROR: missing GRUB theme asset in ISO: ${required}" >&2 + echo "ERROR: missing GRUB asset in ISO: ${required}" >&2 rm -f "$iso_files" exit 1 } done rm -f "$iso_files" - echo "=== GRUB theme validation OK ===" + echo "=== GRUB asset validation OK ===" } validate_iso_nvidia_runtime() { @@ -610,29 +608,37 @@ validate_iso_nvidia_runtime() { squashfs_tmp="$(mktemp)" squashfs_list="$(mktemp)" - iso_read_member "$iso_path" live/filesystem.squashfs "$squashfs_tmp" || { - rm -f "$squashfs_tmp" "$squashfs_list" - nvidia_runtime_fail "failed to extract live/filesystem.squashfs from ISO" - } - unsquashfs -ll "$squashfs_tmp" > "$squashfs_list" 2>/dev/null || { - rm -f "$squashfs_tmp" "$squashfs_list" - nvidia_runtime_fail "failed to inspect filesystem.squashfs from ISO" + iso_files="$(mktemp)" + iso_list_files "$iso_path" > "$iso_files" || { + rm -f "$squashfs_tmp" "$squashfs_list" "$iso_files" + nvidia_runtime_fail "failed to list ISO files for NVIDIA runtime validation" } + grep '^live/.*\.squashfs$' "$iso_files" | while IFS= read -r squashfs_member; do + iso_read_member "$iso_path" "$squashfs_member" "$squashfs_tmp" || { + rm -f "$squashfs_tmp" "$squashfs_list" "$iso_files" + nvidia_runtime_fail "failed to extract $squashfs_member from ISO" + } + unsquashfs -ll "$squashfs_tmp" >> "$squashfs_list" 2>/dev/null || { + rm -f "$squashfs_tmp" "$squashfs_list" "$iso_files" + nvidia_runtime_fail "failed to inspect $squashfs_member from ISO" + } + : > "$squashfs_tmp" + done grep -Eq 'usr/bin/dcgmi$' "$squashfs_list" || { - rm -f "$squashfs_tmp" "$squashfs_list" + rm -f "$squashfs_tmp" "$squashfs_list" "$iso_files" nvidia_runtime_fail "dcgmi missing from final NVIDIA ISO" } grep -Eq 'usr/bin/nv-hostengine$' "$squashfs_list" || { - rm -f "$squashfs_tmp" "$squashfs_list" + rm -f "$squashfs_tmp" "$squashfs_list" "$iso_files" nvidia_runtime_fail "nv-hostengine missing from final NVIDIA ISO" } grep -Eq 'usr/bin/dcgmproftester([0-9]+)?$' "$squashfs_list" || { - rm -f "$squashfs_tmp" "$squashfs_list" + rm -f "$squashfs_tmp" "$squashfs_list" "$iso_files" nvidia_runtime_fail "dcgmproftester missing from final NVIDIA ISO" } - rm -f "$squashfs_tmp" "$squashfs_list" + rm -f "$squashfs_tmp" "$squashfs_list" "$iso_files" echo "=== NVIDIA runtime validation OK ===" } @@ -726,21 +732,22 @@ write_canonical_grub_cfg() { kernel="$2" append_live="$3" initrd="$4" + version_label="${PROJECT_VERSION_EFFECTIVE}" cat > "$cfg" < "$cfg" </dev/null | head -1) + if [ -n "$_extra_sq" ]; then + echo "=== full build required: multi-squashfs live image present ===" + return 0 + fi _heavy=$(find \ "${BUILDER_DIR}/VERSIONS" \ @@ -947,6 +957,65 @@ fast_path_rebuild_iso() { echo "=== fast-path: ISO rebuilt ===" } +dir_has_entries() { + _dir="$1" + [ -d "$_dir" ] || return 1 + find "$_dir" -mindepth 1 -print -quit 2>/dev/null | grep -q . +} + +move_tree_to_layer() { + _src_root="$1" + _rel="$2" + _dst_root="$3" + [ -e "${_src_root}/${_rel}" ] || return 0 + mkdir -p "${_dst_root}/$(dirname "$_rel")" + mv "${_src_root}/${_rel}" "${_dst_root}/${_rel}" +} + +split_live_squashfs_layers() { + lb_dir="$1" + live_dir="${lb_dir}/binary/live" + base_sq="${live_dir}/filesystem.squashfs" + usr_sq="${live_dir}/10-usr.squashfs" + fw_sq="${live_dir}/20-firmware.squashfs" + + [ -f "$base_sq" ] || return 0 + command -v unsquashfs >/dev/null 2>&1 || return 0 + command -v mksquashfs >/dev/null 2>&1 || return 0 + + tmp_root="$(mktemp -d)" + tmp_usr="$(mktemp -d)" + tmp_fw="$(mktemp -d)" + trap 'rm -rf "$tmp_root" "$tmp_usr" "$tmp_fw"' RETURN + + echo "=== splitting live squashfs into smaller layers ===" + unsquashfs -d "$tmp_root/root" "$base_sq" >/dev/null + mkdir -p "$tmp_usr/root" "$tmp_fw/root" + + move_tree_to_layer "$tmp_root/root" "usr" "$tmp_usr/root" + move_tree_to_layer "$tmp_root/root" "lib/firmware" "$tmp_fw/root" + move_tree_to_layer "$tmp_root/root" "usr/lib/firmware" "$tmp_fw/root" + move_tree_to_layer "$tmp_root/root" "boot/firmware" "$tmp_fw/root" + + rm -f "$usr_sq" "$fw_sq" + mksquashfs "$tmp_root/root" "${base_sq}.new" -comp zstd -b 1048576 -noappend -no-progress >/dev/null + mv "${base_sq}.new" "$base_sq" + + if dir_has_entries "$tmp_usr/root"; then + mksquashfs "$tmp_usr/root" "${usr_sq}.new" -comp zstd -b 1048576 -noappend -no-progress >/dev/null + mv "${usr_sq}.new" "$usr_sq" + fi + if dir_has_entries "$tmp_fw/root"; then + mksquashfs "$tmp_fw/root" "${fw_sq}.new" -comp zstd -b 1048576 -noappend -no-progress >/dev/null + mv "${fw_sq}.new" "$fw_sq" + fi + + echo "=== live squashfs layers ===" + find "$live_dir" -maxdepth 1 -type f -name '*.squashfs' -exec du -sh {} \; | sort + rm -rf "$tmp_root" "$tmp_usr" "$tmp_fw" + trap - RETURN +} + recover_iso_memtest() { lb_dir="$1" iso_path="$2" @@ -1595,7 +1664,7 @@ if ! needs_full_build; then fast_path_rebuild_iso ISO_RAW="${LB_DIR}/live-image-amd64.hybrid.iso" validate_iso_live_boot_entries "$ISO_RAW" - validate_iso_grub_theme_assets "$ISO_RAW" + validate_iso_grub_assets "$ISO_RAW" validate_iso_nvidia_runtime "$ISO_RAW" cp "$ISO_RAW" "$ISO_OUT" echo "" @@ -1618,6 +1687,7 @@ 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" +split_live_squashfs_layers "${LB_DIR}" echo "=== enforcing canonical bootloader assets ===" enforce_live_build_bootloader_assets "${LB_DIR}" reset_live_build_stage "${LB_DIR}" "binary_checksums" @@ -1651,7 +1721,7 @@ if [ -f "$ISO_RAW" ]; then fi validate_iso_memtest "$ISO_RAW" validate_iso_live_boot_entries "$ISO_RAW" - validate_iso_grub_theme_assets "$ISO_RAW" + validate_iso_grub_assets "$ISO_RAW" validate_iso_nvidia_runtime "$ISO_RAW" cp "$ISO_RAW" "$ISO_OUT" touch "${FULL_BUILD_MARKER}" diff --git a/iso/builder/config/bootloaders/grub-efi/config.cfg b/iso/builder/config/bootloaders/grub-efi/config.cfg index c48dc1c..87812c7 100644 --- a/iso/builder/config/bootloaders/grub-efi/config.cfg +++ b/iso/builder/config/bootloaders/grub-efi/config.cfg @@ -1,5 +1,7 @@ set default=1 set timeout=10 +set color_normal=yellow/black +set color_highlight=white/brown if [ x$feature_default_font_path = xy ] ; then font=unicode @@ -26,6 +28,3 @@ insmod gfxterm terminal_input console serial terminal_output gfxterm serial - -insmod tga -source /boot/grub/theme.cfg diff --git a/iso/builder/config/bootloaders/grub-efi/grub.cfg b/iso/builder/config/bootloaders/grub-efi/grub.cfg index 4436396..2a2f9a0 100644 --- a/iso/builder/config/bootloaders/grub-efi/grub.cfg +++ b/iso/builder/config/bootloaders/grub-efi/grub.cfg @@ -1,16 +1,16 @@ source /boot/grub/config.cfg -menuentry "EASY-BEE" { +menuentry "EASY-BEE v@VERSION@" { linux @KERNEL_LIVE@ @APPEND_LIVE@ nomodeset bee.nvidia.mode=normal pci=realloc 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 -- load to RAM (toram)" { +menuentry "EASY-BEE v@VERSION@ -- load to RAM (toram)" { linux @KERNEL_LIVE@ @APPEND_LIVE@ toram nomodeset bee.nvidia.mode=normal pci=realloc 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 -- no GUI / no X11" { +menuentry "EASY-BEE v@VERSION@ -- no GUI / no X11" { linux @KERNEL_LIVE@ @APPEND_LIVE@ nomodeset bee.gui=off bee.nvidia.mode=gsp-off pci=realloc 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/bootloaders/isolinux/live.cfg.in b/iso/builder/config/bootloaders/isolinux/live.cfg.in index 6eb5074..1805a5d 100644 --- a/iso/builder/config/bootloaders/isolinux/live.cfg.in +++ b/iso/builder/config/bootloaders/isolinux/live.cfg.in @@ -1,18 +1,18 @@ label live-@FLAVOUR@-normal - menu label ^EASY-BEE + menu label ^EASY-BEE v@VERSION@ linux @LINUX@ initrd @INITRD@ append @APPEND_LIVE@ 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 label live-@FLAVOUR@-toram - menu label EASY-BEE (^load to RAM) + menu label EASY-BEE v@VERSION@ (^load to RAM) menu default linux @LINUX@ initrd @INITRD@ append @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 label live-@FLAVOUR@-console - menu label EASY-BEE (^no GUI / no X11) + menu label EASY-BEE v@VERSION@ (^no GUI / no X11) linux @LINUX@ initrd @INITRD@ append @APPEND_LIVE@ nomodeset bee.gui=off 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 diff --git a/iso/overlay/usr/local/bin/bee-install b/iso/overlay/usr/local/bin/bee-install index e6d432f..351dfe3 100755 --- a/iso/overlay/usr/local/bin/bee-install +++ b/iso/overlay/usr/local/bin/bee-install @@ -8,7 +8,7 @@ # Layout (UEFI): GPT, /dev/sdX1=EFI 512MB vfat, /dev/sdX2=root ext4 # Layout (BIOS): MBR, /dev/sdX1=root ext4 # -# Squashfs source: /run/live/medium/live/filesystem.squashfs +# Squashfs sources: /run/live/medium/live/*.squashfs set -euo pipefail @@ -62,9 +62,9 @@ for tool in parted mkfs.vfat mkfs.ext4 unsquashfs grub-install update-grub; do fi done -SQUASHFS="/run/live/medium/live/filesystem.squashfs" -if [ ! -f "$SQUASHFS" ]; then - echo "ERROR: squashfs not found at $SQUASHFS" >&2 +mapfile -t SQUASHFS_FILES < <(find /run/live/medium/live -maxdepth 1 -type f -name '*.squashfs' | sort) +if [ "${#SQUASHFS_FILES[@]}" -eq 0 ]; then + echo "ERROR: no squashfs files found under /run/live/medium/live" >&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 @@ -106,7 +106,10 @@ log "=== BEE DISK INSTALLER ===" log "Target device : $DEVICE" log "Root partition: $PART_ROOT" [ "$UEFI" = "1" ] && log "EFI partition : $PART_EFI" -log "Squashfs : $SQUASHFS ($(du -sh "$SQUASHFS" | cut -f1))" +log "Squashfs : ${#SQUASHFS_FILES[@]} layer(s)" +for sf in "${SQUASHFS_FILES[@]}"; do + log " - $sf ($(du -sh "$sf" | cut -f1))" +done log "Log : $LOGFILE" log "" @@ -163,7 +166,9 @@ log " Mounted." # ------------------------------------------------------------------ log "--- Step 5/7: Unpacking filesystem (this takes 10-20 minutes) ---" -log " Source: $SQUASHFS" +for sf in "${SQUASHFS_FILES[@]}"; do + log " Source: $sf" +done log " Target: $MOUNT_ROOT" # unsquashfs does not support resume, so retry the entire unpack step if the @@ -177,9 +182,9 @@ while true; do 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." + mapfile -t SQUASHFS_FILES < <(find /run/live/medium/live -maxdepth 1 -type f -name '*.squashfs' | sort) + if [ "${#SQUASHFS_FILES[@]}" -eq 0 ]; then + log " SOURCE LOST: no squashfs files found under /run/live/medium/live." log " Reconnect the disc and run 'bee-remount-medium --wait' in another terminal," log " then press Enter here to retry." read -r _ @@ -194,12 +199,17 @@ while true; do 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=$? + for sf in "${SQUASHFS_FILES[@]}"; do + log " Unpacking $(basename "$sf") ..." + unsquashfs -f -d "$MOUNT_ROOT" "$sf" 2>&1 | \ + grep -E '^\[|^inod|^created|^extract|^ERROR|failed' | \ + while IFS= read -r line; do log " $line"; done || UNPACK_OK=$? + [ "$UNPACK_OK" -eq 0 ] || break + done # Check squashfs is still reachable (gone = disc pulled during copy) - if [ ! -f "$SQUASHFS" ]; then + mapfile -t SQUASHFS_FILES < <(find /run/live/medium/live -maxdepth 1 -type f -name '*.squashfs' | sort) + if [ "${#SQUASHFS_FILES[@]}" -eq 0 ]; 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 _ diff --git a/iso/overlay/usr/local/bin/bee-remount-medium b/iso/overlay/usr/local/bin/bee-remount-medium index bf3febc..0ba491d 100644 --- a/iso/overlay/usr/local/bin/bee-remount-medium +++ b/iso/overlay/usr/local/bin/bee-remount-medium @@ -2,7 +2,7 @@ # 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. +# was lost and /run/live/medium/live/*.squashfs are missing. # # Usage: bee-remount-medium [--wait] # --wait keep retrying every 5 seconds until the medium is found (useful @@ -11,7 +11,7 @@ set -euo pipefail MEDIUM_DIR="/run/live/medium" -SQUASHFS_REL="live/filesystem.squashfs" +SQUASHFS_GLOB="live/*.squashfs" WAIT_MODE=0 for arg in "$@"; do @@ -56,7 +56,7 @@ try_mount() { 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 + if find "${tmpdir}/live" -maxdepth 1 -type f -name '*.squashfs' 2>/dev/null | grep -q .; then # Unmount probe mount and mount properly onto live path umount "$tmpdir" 2>/dev/null || true rmdir "$tmpdir" 2>/dev/null || true @@ -82,8 +82,9 @@ attempt() { 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))" + local count + count=$(find "${MEDIUM_DIR}/live" -maxdepth 1 -type f -name '*.squashfs' 2>/dev/null | wc -l | tr -d ' ') + log "SUCCESS: ${count} squashfs layer(s) available under ${MEDIUM_DIR}/live" return 0 fi done @@ -100,5 +101,5 @@ if [ "$WAIT_MODE" = "1" ]; then sleep 5 done else - attempt || die "No ISO medium with ${SQUASHFS_REL} found. Reconnect the disc and re-run, or use --wait." + attempt || die "No ISO medium with ${SQUASHFS_GLOB} found. Reconnect the disc and re-run, or use --wait." fi