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>
This commit is contained in:
Mikhail Chusavitin
2026-09-04 15:38:14 +03:00
co-authored by Claude Sonnet 5
parent bc57b85d3f
commit b8c45d54c1
13 changed files with 1056 additions and 13 deletions
+104
View File
@@ -645,6 +645,110 @@ rootfs_layout_fail() {
exit 1
}
squashfs_layers_fail() {
echo "ERROR: $1" >&2
exit 1
}
# Validate the semantic squashfs layering in the final ISO:
# - NVIDIA variants must ship live/filesystem.module and >= 2 layers
# - every squashfs in live/ is listed in the module file and vice versa
# - no single layer exceeds the agreed compressed-size ceiling
# - a single giant squashfs is rejected for a multi-layer variant
validate_iso_squashfs_layers() {
iso_path="$1"
echo "=== validating squashfs layers in ISO ==="
[ -f "$iso_path" ] || squashfs_layers_fail "ISO not found for squashfs layer validation: $iso_path"
require_iso_reader "$iso_path" >/dev/null 2>&1 || squashfs_layers_fail "ISO reader unavailable for squashfs layer validation"
_files="$(mktemp)"
_module="$(mktemp)"
iso_list_files "$iso_path" > "$_files" || squashfs_layers_fail "failed to list ISO files"
_sq_in_iso="$(grep -E '^live/filesystem.*\.squashfs$' "$_files" | sed 's#^live/##' | LC_ALL=C sort)"
_sq_count="$(printf '%s\n' "$_sq_in_iso" | grep -c . || true)"
_has_module=0
grep -qx 'live/filesystem.module' "$_files" && _has_module=1
if [ "${BEE_MULTILAYER_SQUASHFS:-0}" != "1" ]; then
echo "=== single-layer variant: $_sq_count squashfs, module file present=$_has_module ==="
[ "$_has_module" = 0 ] || squashfs_layers_fail "single-layer variant unexpectedly ships filesystem.module"
rm -f "$_files" "$_module"
echo "=== squashfs layer validation OK (single layer) ==="
return 0
fi
[ "$_has_module" = 1 ] || squashfs_layers_fail "multi-layer variant is missing live/filesystem.module (monolith slipped through)"
iso_read_member "$iso_path" live/filesystem.module "$_module" || squashfs_layers_fail "failed to read live/filesystem.module from ISO"
_listed="$(grep -vE '^[[:space:]]*(#|$)' "$_module" | LC_ALL=C sort)"
_listed_count="$(printf '%s\n' "$_listed" | grep -c . || true)"
[ "$_listed_count" -ge 2 ] || squashfs_layers_fail "filesystem.module lists only $_listed_count layer(s), expected >= 2"
if [ "$(printf '%s\n' "$_listed")" != "$(printf '%s\n' "$_sq_in_iso")" ]; then
echo " module lists:" >&2; printf ' %s\n' $_listed >&2
echo " ISO carries:" >&2; printf ' %s\n' $_sq_in_iso >&2
squashfs_layers_fail "filesystem.module and the squashfs files in live/ do not match exactly"
fi
_limit_mib="${BEE_LAYER_MAX_MIB:-800}"
for _name in $_sq_in_iso; do
_bytes=""
if command -v xorriso >/dev/null 2>&1; then
_bytes="$(xorriso -indev "$iso_path" -lsl "/live/${_name}" 2>/dev/null \
| awk '$1 ~ /^-/ { print $5; exit }')"
fi
if ! printf '%s' "$_bytes" | grep -Eq '^[0-9]+$'; then
_tmp_sq="$(mktemp)"
iso_read_member "$iso_path" "live/${_name}" "$_tmp_sq" || squashfs_layers_fail "failed to extract live/${_name}"
_bytes="$(wc -c < "$_tmp_sq" | tr -d ' ')"
rm -f "$_tmp_sq"
fi
_mib=$(( _bytes / 1048576 ))
printf ' %-40s %5d MiB\n' "$_name" "$_mib"
[ "$_mib" -le "$_limit_mib" ] || squashfs_layers_fail "layer ${_name} is ${_mib} MiB, over the ${_limit_mib} MiB ceiling"
case "$_name" in
*-00-base.squashfs) : ;;
*) [ "$_mib" -ge 1 ] || squashfs_layers_fail "layer ${_name} is suspiciously empty" ;;
esac
done
if [ "$_sq_count" -eq 1 ]; then
squashfs_layers_fail "multi-layer variant shipped a single squashfs"
fi
rm -f "$_files" "$_module"
echo "=== squashfs layer validation OK ($_sq_count layers) ==="
}
# Read the whole raw ISO back and confirm every sector is readable. Catches a
# truncated or corrupt image before it ships - the split path rewrites the
# squashfs area and reassembles the ISO, so this is worth a full re-read.
validate_iso_media_integrity() {
iso_path="$1"
echo "=== validating ISO media integrity (xorriso -check_media) ==="
[ -f "$iso_path" ] || { echo "ERROR: ISO not found for media check: $iso_path" >&2; exit 1; }
if ! command -v xorriso >/dev/null 2>&1; then
echo "WARNING: xorriso not available, skipping -check_media" >&2
return 0
fi
_out="$(xorriso -indev "$iso_path" -check_media 2>&1)" || {
printf '%s\n' "$_out" >&2
echo "ERROR: xorriso -check_media failed for $iso_path" >&2
exit 1
}
printf '%s\n' "$_out" | grep -E 'Media region|Bad blocks|checked|md5' || true
if printf '%s\n' "$_out" | grep -Eiq 'bad block[^s]|[1-9][0-9]* bad blocks|damaged|not readable'; then
printf '%s\n' "$_out" >&2
echo "ERROR: xorriso -check_media reported unreadable/bad sectors in $iso_path" >&2
exit 1
fi
echo "=== ISO media integrity OK ==="
}
# Guard against a corrupted merged-usr layout in the live rootfs. On Debian
# bookworm /bin, /sbin, /lib and /lib64 are symlinks into /usr; if a build step
# stages a real directory of that name into the overlay, `rsync -a` replaces the