Files
bee/iso/builder/test-build-libs.sh
Mikhail ChusavitinandClaude Sonnet 5 b8c45d54c1 feat(iso): split the live medium into semantic SquashFS layers
Booting via BMC virtual CD reads the ~2.8 GB filesystem squashfs
sequentially during the live-boot toram copy; a mid-read drop of the
redirected medium loses the whole copy and fails the boot (v14). Split
the rootfs into self-contained semantic layers so a retry re-reads at
most one ~500-700 MiB layer, not everything. This is a resilience /
reduced-re-read mechanism, not a fix for the virtual-media instability.

NVIDIA variants now ship 7 layers (00-base, 05-firmware, 08-desktop,
10-nvidia-driver, 20-nvidia-platform, 30-nvidia-cuda-libs,
40-nvidia-dcgm-cuda) plus an explicit live/filesystem.module that fixes
their OverlayFS order; amd/nogpu keep a single squashfs.

- lib/squashfs-layers.sh: deterministic classifier (dpkg file ownership
  plus explicit rules for build.sh-injected files, never a path
  substring), per-layer mksquashfs, 800 MiB hard ceiling, unsquashfs -s
  plus strict extraction of every layer, merged-rootfs bootability check.
- build.sh: split the monolith after the full lb build, verify and merge,
  write the module file, delete the monolith only then; abort before ISO
  assembly on any failure. Runs the builder test suites up front.
- fast-path: force a full build for a multi-layer medium;
  fast_path_repack_squashfs hard-refuses (it would drop layers).
- iso-validation.sh: validate_iso_squashfs_layers (module vs layer set
  match, size ceiling, no lone giant squashfs) and
  validate_iso_media_integrity (xorriso -check_media).
- bee-install: honour filesystem.module order, abort on any layer failure.
- 9013-toram-retry: record the real rsync exit code (it printed a false
  rc=0) and correct the "resumes the tail" comment (rsync without
  --partial keeps only fully-copied layers). No unsafe partial resume.
- tests: test-squashfs-layers.sh plus a multi-layer guard in
  test-build-libs.sh; both run at the top of every build.
- docs: bible-local architecture and decision, iso/README, iso-build-rules.

Verified by a full nvidia build: 7 layers 622/199/256/466/37/567/562 MiB,
every validator passes, xorriso -check_media good, merged rootfs bootable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 15:38:14 +03:00

183 lines
7.3 KiB
Bash
Executable File

#!/bin/sh
set -eu
BUILDER_DIR="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
TEST_ROOT="$(mktemp -d)"
trap 'rm -rf "$TEST_ROOT"' EXIT INT TERM HUP
PROJECT_VERSION_EFFECTIVE="13.0-test"
BEE_ISO_VOLUME="EASY_BEE_TEST"
LOG_DIR="$TEST_ROOT/log"
mkdir -p "$LOG_DIR"
. "$BUILDER_DIR/lib/iso-validation.sh"
. "$BUILDER_DIR/lib/template.sh"
. "$BUILDER_DIR/lib/bootloader.sh"
literal_file="$TEST_ROOT/literal-file"
printf '%s\n' 'before %%VALUE%% after %%VALUE%%' > "$literal_file"
replace_literal_in_file "$literal_file" '%%VALUE%%' 'a&b#c\\d'
if [ "$(cat "$literal_file")" != 'before a&b#c\\d after a&b#c\\d' ]; then
echo "ERROR: generic literal replacement changed replacement characters" >&2
exit 1
fi
if replace_literal_in_file "$literal_file" '' value >/dev/null 2>&1; then
echo "ERROR: generic literal replacement accepted an empty placeholder" >&2
exit 1
fi
if replace_literal_in_file "$literal_file" '%%MISSING%%' value >/dev/null 2>&1; then
echo "ERROR: generic literal replacement accepted a missing placeholder" >&2
exit 1
fi
for valid_version in 13 13.0 v13.0-2-gabcdef0-dirty; do
if ! validate_project_version "$valid_version"; then
echo "ERROR: rejected valid project version: $valid_version" >&2
exit 1
fi
done
for invalid_version in '' '.13' '13.' '13/0' '13_0' '13 0' '13&0'; do
if validate_project_version "$invalid_version"; then
echo "ERROR: accepted unsafe project version: $invalid_version" >&2
exit 1
fi
done
literal_template="$TEST_ROOT/literal-template"
literal_output="$TEST_ROOT/literal-output"
printf '%s\n' '@VERSION@ @KERNEL@ @APPEND_LIVE@ @INITRD@' > "$literal_template"
render_bootloader_template "$literal_template" "$literal_output" \
'13&0#test' '@KERNEL@' '/live/vmlinuz\\literal' 'boot=live marker=a&b#c\\d' '@INITRD@' '/live/initrd.img'
literal_expected='13&0#test /live/vmlinuz\\literal boot=live marker=a&b#c\\d /live/initrd.img'
if [ "$(cat "$literal_output")" != "$literal_expected" ]; then
echo "ERROR: bootloader renderer changed literal replacement characters" >&2
exit 1
fi
if render_bootloader_template "$literal_template" "$literal_output" \
'13.0-test' '@MISSING_KERNEL@' '/live/vmlinuz' 'boot=live' '@INITRD@' '/live/initrd.img' >/dev/null 2>&1; then
echo "ERROR: bootloader renderer accepted a missing required placeholder" >&2
exit 1
fi
if render_bootloader_template "$BUILDER_DIR/config/bootloaders/isolinux/live.cfg.in" "$literal_output" \
'13.0-test' '@LINUX@' '/live/vmlinuz' 'boot=live' '@INITRD@' '/live/initrd.img' >/dev/null 2>&1; then
echo "ERROR: isolinux renderer accepted a missing flavour" >&2
exit 1
fi
live_build_config_root="$TEST_ROOT/live-build-config"
mkdir -p "$live_build_config_root/config"
printf '%s\n' \
'LB_BOOTAPPEND_LIVE="boot=live live-media-label=EASY_BEE_TEST"' > "$live_build_config_root/config/binary"
printf '%s\n' \
'LB_LINUX_FLAVOURS_WITH_ARCH="amd64:amd64 686-pae:i386"' > "$live_build_config_root/config/chroot"
load_live_build_boot_config "$live_build_config_root"
if [ "$live_build_append" != 'boot=live live-media-label=EASY_BEE_TEST' ] || \
[ "$live_build_flavour" != amd64 ]; then
echo "ERROR: failed to load the default live-build boot settings" >&2
exit 1
fi
rendered_grub="$TEST_ROOT/grub.cfg"
rendered_isolinux="$TEST_ROOT/live.cfg"
write_canonical_grub_cfg "$rendered_grub" /live/vmlinuz \
'boot=live live-media-label=EASY_BEE_TEST' /live/initrd.img
write_canonical_isolinux_cfg "$rendered_isolinux" /live/vmlinuz /live/initrd.img \
'boot=live live-media-label=EASY_BEE_TEST' "$live_build_flavour"
if grep -Eq '@[A-Z][A-Z_]*@' "$rendered_grub" "$rendered_isolinux"; then
echo "ERROR: canonical bootloader renderer left an unresolved placeholder" >&2
exit 1
fi
if ! grep -q '^label live-amd64-normal$' "$rendered_isolinux"; then
echo "ERROR: canonical isolinux renderer did not apply the live-build flavour" >&2
exit 1
fi
validate_live_cmdline_params "$rendered_grub" linux GRUB "$BEE_ISO_VOLUME"
validate_live_cmdline_params "$rendered_isolinux" append isolinux "$BEE_ISO_VOLUME"
missing_serialization="$TEST_ROOT/missing-serialization.cfg"
sed 's/ udev\.children_max=1//' "$rendered_grub" > "$missing_serialization"
if validate_live_cmdline_params "$missing_serialization" linux GRUB "$BEE_ISO_VOLUME" >/dev/null 2>&1; then
echo "ERROR: validator accepted a live entry without udev.children_max=1" >&2
exit 1
fi
missing_boot_live="$TEST_ROOT/missing-boot-live.cfg"
awk 'BEGIN { removed=0 } /^ linux / && !removed { sub(/ boot=live/, ""); removed=1 } { print }' \
"$rendered_grub" > "$missing_boot_live"
if validate_live_cmdline_params "$missing_boot_live" linux GRUB "$BEE_ISO_VOLUME" >/dev/null 2>&1; then
echo "ERROR: validator ignored a live entry without boot=live" >&2
exit 1
fi
CACHE_ROOT="$TEST_ROOT/cache"
BUILD_VARIANT="test"
BUILD_WORK_DIR="$TEST_ROOT/work"
OVERLAY_STAGE_DIR="$TEST_ROOT/overlay"
DEBIAN_KERNEL_ABI="6.1.0-test"
SQUASHFS_FILENAME="filesystem-v13.0-test.squashfs"
DIST_DIR="$TEST_ROOT/dist"
mkdir -p "$BUILD_WORK_DIR/binary/live" "$OVERLAY_STAGE_DIR"
touch "$BUILD_WORK_DIR/live-image-amd64.hybrid.iso"
touch "$BUILD_WORK_DIR/binary/live/$SQUASHFS_FILENAME"
. "$BUILDER_DIR/lib/fast-path.sh"
# Isolate the decision test from repository content and GNU find extensions.
hash_heavy_config() { printf '%s\n' test-heavy-hash; }
write_overlay_manifest() { find "$OVERLAY_STAGE_DIR" -mindepth 1 -print | sed "s#^$OVERLAY_STAGE_DIR/##" | sort > "$1"; }
printf '%s\n' test-heavy-hash > "$FULL_BUILD_HASH_FILE"
printf '%s\n' "$DEBIAN_KERNEL_ABI" > "$FULL_BUILD_ABI_FILE"
write_overlay_manifest "$FULL_BUILD_OVERLAY_MANIFEST"
touch "$FULL_BUILD_MARKER"
if needs_full_build; then
echo "ERROR: fast path was rejected for unchanged valid state" >&2
exit 1
fi
touch "$OVERLAY_STAGE_DIR/added-by-fast-path"
if needs_full_build; then
echo "ERROR: an additive overlay change unnecessarily forced a full build" >&2
exit 1
fi
write_overlay_manifest "$FULL_BUILD_OVERLAY_MANIFEST"
rm "$OVERLAY_STAGE_DIR/added-by-fast-path"
if ! needs_full_build >/dev/null; then
echo "ERROR: removal from the latest fast-path overlay was not detected" >&2
exit 1
fi
touch "$OVERLAY_STAGE_DIR/added-by-fast-path"
printf '%s\n' different-abi > "$FULL_BUILD_ABI_FILE"
if ! needs_full_build >/dev/null; then
echo "ERROR: kernel ABI change did not force a full build" >&2
exit 1
fi
printf '%s\n' "$DEBIAN_KERNEL_ABI" > "$FULL_BUILD_ABI_FILE"
# A multi-layer live medium must always force a full build: the fast path only
# repacks one squashfs and would silently drop the other layers.
touch "$BUILD_WORK_DIR/binary/live/filesystem-v13.0-test-10-nvidia-driver.squashfs"
if ! needs_full_build >/dev/null; then
echo "ERROR: a multi-layer squashfs medium did not force a full build" >&2
exit 1
fi
printf '%s\n' \
filesystem-v13.0-test-00-base.squashfs \
filesystem-v13.0-test-10-nvidia-driver.squashfs \
> "$BUILD_WORK_DIR/binary/live/filesystem.module"
rm -f "$BUILD_WORK_DIR/binary/live/filesystem-v13.0-test-10-nvidia-driver.squashfs"
if ! needs_full_build >/dev/null; then
echo "ERROR: filesystem.module alone did not force a full build" >&2
exit 1
fi
if ( fast_path_repack_squashfs ) >/dev/null 2>&1; then
echo "ERROR: fast_path_repack_squashfs ran on a multi-layer medium" >&2
exit 1
fi
rm -f "$BUILD_WORK_DIR/binary/live/filesystem.module"
echo "build library tests: OK"