#!/bin/bash #=============================================================================== # iso-repack.sh — the post-prepare-iso repack stage: GRUB branding + single-entry menu surgery # (scripts v1.22.0, R-38) and, when asked, the slice-B mkimage UEFI loader swap. # # WAS mkimage-surgery.sh (slice B, v1.18.0). v1.22.0 generalised it because BOTH jobs need the same # expensive extract -> modify -> re-master cycle, and doing them as two separate repacks would double # the runtime and re-master the image twice for no reason. The mkimage recipe below is UNCHANGED and # still the N100 run's proven-live one — do NOT re-derive it. # # RUNS INSIDE the felhom-iso-assistant container; operates on /work/out.iso (the prepare-iso output) # and writes /work/final.iso. NEVER touches the source ISO or the assistant's answer/first-boot # payload — only the GRUB menu, the theme, and (in mkimage mode) the EFI boot path. # # Env: # FELHOM_LOADER = shim | mkimage (default shim) — mkimage swaps BOOTX64.EFI (F1 firmware fix) # FELHOM_BRAND = 1 | 0 (default 1) — 0 leaves the stock PVE menu completely alone # FELHOM_MENU = single | release (default single) — single: ONE automated entry (appliance ISO, # requires a prepared auto-install ISO). release: TWO INTERACTIVE # entries for the PUBLIC image, which carries no answer.toml. # FELHOM_DEB = (optional) — a .deb to inject into /proxmox/packages/, which # the PVE installer unpacks into the target on EVERY install path # including the interactive one (Install.pm:1343-1372, :1378). # # Expects in /work (placed by build-felhom-iso.sh) when FELHOM_BRAND=1: # brand/grub.cfg.tmpl, brand/felhom-theme.txt, brand/generate-grub-background.sh, brand/card.png # # Writes: /work/final.iso, /work/grub-version.txt (mkimage), /work/brand-report.txt (branding) # # --- the mkimage recipe (unchanged from v1.18.0) --------------------------------------------------- # Build BOOTX64.EFI from the ISO's OWN x86_64-efi GRUB modules (the box's working 2.12-9+pmx2 build), # embedding the module set the ISO's grub.cfg needs + an embedded config that `search --fs-uuid`es the # ISO and `configfile`s its real menu; swap it into the ISO9660 EFI/BOOT tree AND inside efi.img. The # image is UNSIGNED (Secure Boot must be OFF on the target) — that is the documented mkimage contract. #=============================================================================== set -euo pipefail OUT=/work/out.iso FINAL=/work/final.iso EX=/work/ex EMB=/work/embedded.cfg LDR=/work/BOOTX64.EFI BRANDDIR=/work/brand ORIG_CFG=/work/grub.cfg.orig LOADER="${FELHOM_LOADER:-shim}" BRAND="${FELHOM_BRAND:-1}" MENU="${FELHOM_MENU:-single}" DEB="${FELHOM_DEB:-}" [[ "$MENU" == "single" || "$MENU" == "release" ]] \ || { echo "iso-repack: FELHOM_MENU must be single|release (got '$MENU')" >&2; exit 2; } say() { echo "iso-repack: $*"; } # The osirrox extract tree is written by container-root; the host-side build cleanup (a non-root user) # cannot remove it. Remove it here (we ARE root in the container) on every exit path so no /tmp litter # survives the run. cleanup_ex() { if [[ -n "${EX:-}" && -e "$EX" ]]; then rm -rf "$EX" 2>/dev/null || true; fi; } trap cleanup_ex EXIT [[ -f "$OUT" ]] || { echo "iso-repack: /work/out.iso missing" >&2; exit 2; } [[ "$LOADER" == "shim" || "$LOADER" == "mkimage" ]] || { echo "iso-repack: bad FELHOM_LOADER '$LOADER'" >&2; exit 2; } # Nothing to do at all -> pass the prepared ISO through byte-for-byte rather than re-mastering it. if [[ "$BRAND" != "1" && "$LOADER" == "shim" ]]; then say "no branding, shim loader — passing the prepared ISO through unmodified" cp "$OUT" "$FINAL"; exit 0 fi command -v xorriso >/dev/null || { echo "iso-repack: missing tool: xorriso" >&2; exit 2; } if [[ "$LOADER" == "mkimage" ]]; then for t in grub-mkimage mcopy mdir; do command -v "$t" >/dev/null || { echo "iso-repack: missing tool: $t" >&2; exit 2; } done fi if [[ "$BRAND" == "1" ]]; then command -v magick >/dev/null || command -v convert >/dev/null \ || { echo "iso-repack: ImageMagick missing (needed for the GRUB background) — rebuild the assistant image" >&2; exit 2; } for f in grub.cfg.tmpl felhom-theme.txt generate-grub-background.sh card.png; do [[ -f "$BRANDDIR/$f" ]] || { echo "iso-repack: brand asset missing: $BRANDDIR/$f" >&2; exit 2; } done fi # --- 1. extract the full prepared ISO tree (osirrox) — preserves the answer + first-boot payload. # osirrox reproduces the ISO's (read-only) file modes, so make the tree writable afterwards or # the loader swap and the workspace cleanup can't overwrite/remove the files. ------------------ [[ -e "$EX" ]] && { chmod -R u+w "$EX" 2>/dev/null || true; rm -rf "$EX"; } mkdir -p "$EX" xorriso -osirrox on -indev "$OUT" -extract / "$EX" >/dev/null 2>&1 chmod -R u+w "$EX" say "extracted prepared ISO tree" # Locate the ISO's real grub.cfg and snapshot it BEFORE branding rewrites it: the mkimage module list # below is derived from the STOCK cfg's insmods, and branding must not be able to shrink that set. GCFG="" for c in "$EX/boot/grub/grub.cfg" "$EX/boot/grub/x86_64-efi/grub.cfg"; do [[ -f "$c" ]] && { GCFG="$c"; break; } done [[ -n "$GCFG" ]] || { echo "iso-repack: no grub.cfg found in the extracted tree" >&2; exit 3; } cp "$GCFG" "$ORIG_CFG" #==================================================================================================== # --- 2. BRANDING + SINGLE-ENTRY MENU SURGERY (R-38) ------------------------------------------------ #==================================================================================================== if [[ "$BRAND" == "1" ]]; then say "branding GRUB (background + $( [[ "$MENU" == "release" ]] && echo "two-entry INTERACTIVE release menu" || echo "single-entry menu" ))" # 2a. R-155 — the guard, NARROWED rather than removed. # # What it protected: in SINGLE-entry mode the menu shows exactly one item, "Felhom telepítés", # and that item boots the AUTOMATED installer, which reads auto-installer-mode.toml. Without # that file the very same label would drop the user into the manual disk-picker — a button # promising an unattended install that silently does the opposite. That promise is real and # the guard still enforces it, unchanged, for FELHOM_MENU=single. # # Why it must not apply to FELHOM_MENU=release: the public image deliberately offers the # interactive installer (see grub-release.cfg.tmpl for the measurements behind that ruling), # so it carries NO answer.toml and NO auto-installer-mode.toml by design — that absence is a # release-gate criterion (G1), not a defect. Refusing it would be the guard firing on the # shape it was written to describe rather than the shape it was written to prevent. if [[ "$MENU" == "single" ]]; then AIM="$(find "$EX" -maxdepth 2 -iname 'auto-installer-mode.toml' | head -1)" [[ -n "$AIM" ]] || { echo "iso-repack: auto-installer-mode.toml not found in the ISO — this is not a prepared" >&2 echo " auto-install ISO, so the single Felhom entry would boot the MANUAL installer. Refusing." >&2 exit 10 } fi # 2b. Lift the kernel + initrd lines VERBATIM from the stock 'Install Proxmox VE (Automated)' # entry, so a PVE bump that changes the kernel path or append line tracks automatically. LINUX_LINE="$(awk ' /menuentry .Install Proxmox VE \(Automated\)./ { inblk=1; next } inblk && /^[[:space:]]*linux[[:space:]]/ { print; exit } inblk && /^[[:space:]]*}/ { inblk=0 } ' "$ORIG_CFG")" INITRD_LINE="$(awk ' /menuentry .Install Proxmox VE \(Automated\)./ { inblk=1; next } inblk && /^[[:space:]]*initrd[[:space:]]/ { print; exit } inblk && /^[[:space:]]*}/ { inblk=0 } ' "$ORIG_CFG")" # release mode lifts the GRAPHICAL and TERMINAL-UI kernel lines instead of the automated one. LINUX_GFX="$(awk ' /menuentry .Install Proxmox VE \(Graphical\)./ { inblk=1; next } inblk && /^[[:space:]]*linux[[:space:]]/ { print; exit } inblk && /^[[:space:]]*}/ { inblk=0 } ' "$ORIG_CFG")" LINUX_TUI="$(awk ' /menuentry .Install Proxmox VE \(Terminal UI\)./ { inblk=1; next } inblk && /^[[:space:]]*linux[[:space:]]/ { print; exit } inblk && /^[[:space:]]*}/ { inblk=0 } ' "$ORIG_CFG")" if [[ "$MENU" == "release" ]]; then [[ -n "$LINUX_GFX" ]] || { echo "iso-repack: could not lift the Graphical 'linux' line" >&2; exit 11; } [[ -n "$LINUX_TUI" ]] || { echo "iso-repack: could not lift the Terminal-UI 'linux' line" >&2; exit 11; } # The release menu must NOT carry the unattended flag — that is the whole point of the ruling. grep -q 'proxmox-start-auto-installer' <<<"$LINUX_GFX$LINUX_TUI" && { echo "iso-repack: a release menu kernel line carries proxmox-start-auto-installer — refusing" >&2 exit 12 } fi [[ -n "$LINUX_LINE" || "$MENU" == "release" ]] || { echo "iso-repack: could not lift the 'linux' line from the stock automated entry" >&2; exit 11; } [[ -n "$INITRD_LINE" ]] || { echo "iso-repack: could not lift the 'initrd' line from the stock automated entry" >&2; exit 11; } # The append flag that MAKES it unattended. If PVE ever renames it, we must not ship an ISO that # boots a manual installer behind a button labelled "Felhom telepítés". if [[ "$MENU" == "single" ]]; then grep -q 'proxmox-start-auto-installer' <<<"$LINUX_LINE" || { echo "iso-repack: the lifted kernel line has no 'proxmox-start-auto-installer' flag:" >&2 echo " $LINUX_LINE" >&2; exit 12 } fi grep -q '/boot/initrd.img' <<<"$INITRD_LINE" || { echo "iso-repack: the lifted initrd line looks wrong: $INITRD_LINE" >&2; exit 12 } # Normalise indentation only — the command and its arguments are untouched. LINUX_NORM=" $(sed -E 's/^[[:space:]]+//' <<<"$LINUX_LINE")" INITRD_NORM=" $(sed -E 's/^[[:space:]]+//' <<<"$INITRD_LINE")" say "lifted kernel line: $(sed -E 's/^[[:space:]]+//' <<<"$LINUX_LINE")" # 2c. Build the background and install the theme. THEMEDIR="$EX/boot/grub/felhomtheme" mkdir -p "$THEMEDIR" bash "$BRANDDIR/generate-grub-background.sh" "$BRANDDIR/card.png" "$THEMEDIR/background.png" \ | sed 's/^/ /' cp "$BRANDDIR/felhom-theme.txt" "$THEMEDIR/theme.txt" # 2d. Render the new grub.cfg. Use awk (not sed) so the lifted lines are inserted literally — # the append line is full of `/` and `=` that sed would need escaped. if [[ "$MENU" == "release" ]]; then GFX_NORM=" $(sed -E 's/^[[:space:]]+//' <<<"$LINUX_GFX")" TUI_NORM=" $(sed -E 's/^[[:space:]]+//' <<<"$LINUX_TUI")" awk -v gfx="$GFX_NORM" -v tui="$TUI_NORM" -v ird="$INITRD_NORM" ' { gsub(/@@LINUX_GFX@@/, gfx); gsub(/@@LINUX_TUI@@/, tui); gsub(/@@INITRD@@/, ird); print } ' "$BRANDDIR/grub-release.cfg.tmpl" > "$GCFG" else awk -v lx="$LINUX_NORM" -v ird="$INITRD_NORM" ' { gsub(/@@LINUX@@/, lx); gsub(/@@INITRD@@/, ird); print } ' "$BRANDDIR/grub.cfg.tmpl" > "$GCFG" fi grep -q '@@LINUX@@\|@@LINUX_GFX@@\|@@LINUX_TUI@@\|@@INITRD@@' "$GCFG" && { echo "iso-repack: grub.cfg still has unfilled markers" >&2; exit 13; } # 2e. GATES — the safety half is the whole point, so assert it on the rendered file rather than # trusting the template. Exactly one entry, zero submenus, no path back to a manual installer. N_ENTRY="$(grep -c '^[[:space:]]*menuentry ' "$GCFG" || true)" N_SUB="$(grep -c '^[[:space:]]*submenu ' "$GCFG" || true)" WANT_ENTRIES=1; [[ "$MENU" == "release" ]] && WANT_ENTRIES=2 [[ "$N_ENTRY" == "$WANT_ENTRIES" ]] || { echo "iso-repack: rendered grub.cfg has $N_ENTRY menuentries, want exactly $WANT_ENTRIES ($MENU mode)" >&2; exit 14; } [[ "$N_SUB" == "0" ]] || { echo "iso-repack: rendered grub.cfg has $N_SUB submenus, want 0" >&2; exit 14; } # Strip comments first: the template's header EXPLAINS which stock entries were dropped, and # naming them there must not trip the gate. What matters is that no live directive uses them. LIVE="$(grep -v '^[[:space:]]*#' "$GCFG")" # The banned set differs by mode, and the difference is the whole ruling (release-gate G6 amendment): # single — six tokens. The one entry promises an unattended install, so ANY route to the manual # installer breaks that promise. Unchanged. # release — four tokens. The manual installer IS the product here, so `proxtui` (the Terminal-UI # installer, one of the two entries we ship) and `nomodeset` (a graphics fallback for # the same installer) are legitimate. What stays banned is what never installs anything: # a debug shell, a rescue boot of an existing system, memtest and firmware settings. BANNED=(proxtui proxdebug nomodeset 'Rescue Boot' memtest fwsetup) [[ "$MENU" == "release" ]] && BANNED=(proxdebug 'Rescue Boot' memtest fwsetup) for banned in "${BANNED[@]}"; do if grep -q "$banned" <<<"$LIVE"; then echo "iso-repack: rendered grub.cfg still has a live reference to '$banned'" >&2; exit 14 fi done grep -q "set theme=/boot/grub/felhomtheme/theme.txt" "$GCFG" \ || { echo "iso-repack: rendered grub.cfg does not point at the Felhom theme" >&2; exit 14; } [[ -s "$THEMEDIR/background.png" && -s "$THEMEDIR/theme.txt" ]] \ || { echo "iso-repack: theme assets missing after install" >&2; exit 14; } # The stock PVE theme is now unreferenced. Remove it so the ISO carries one theme, not two. rm -rf "$EX/boot/grub/pvetheme" cat > /work/brand-report.txt < /work/grub-version.txt say "grub: $(cat /work/grub-version.txt)" # 3a. GRUB build to assemble the loader from. The N100 fix used the box's OWN INSTALLED 2.12 GRUB # (a DIFFERENT, working build than the ISO's problem one — which is the whole point). The ISO # ships modules but NOT kernel.img, so grub-mkimage cannot use the ISO's module dir directly; # the box used its /usr/lib/grub/x86_64-efi. The container mirrors that: grub 2.12 == the PVE # 9.x ISO's 2.12-9+pmx2 generation. We take the module BINARIES from here and the module LIST # from the ISO's own grub.cfg (so we embed exactly what the ISO menu needs). ------------------- GDIR="" for d in /usr/lib/grub/x86_64-efi /usr/lib/grub/x86_64-efi-signed; do [[ -f "$d/kernel.img" ]] && { GDIR="$d"; break; } done [[ -n "$GDIR" ]] || { echo "iso-repack: no usable GRUB x86_64-efi build (kernel.img) in the container" >&2; exit 3; } say "grub module source: $GDIR" # 3b. module list: the base set the search/configfile-from-USB chain needs, PLUS every module the # STOCK grub.cfg insmod's (read from the pre-branding snapshot — branding must not be able to # shrink the embedded set). bitmap/bitmap_scale/trig are gfxmenu's renderer dependencies: the # Felhom theme needs them and the stock cfg does not insmod them explicitly. BASE="part_gpt part_msdos msdospart fat exfat iso9660 udf search search_fs_uuid search_fs_file search_label \ configfile normal boot linux linuxefi chain loadenv loopback echo test true cat ls help \ all_video efi_gop efi_uga video video_fb font gfxterm gfxterm_background gfxmenu bitmap bitmap_scale trig \ png jpeg terminal serial gzio xzio lzopio minicmd reboot halt probe regexp sleep keystatus read" CFGMODS="$(grep -hoE 'insmod[[:space:]]+[a-zA-Z0-9_]+' "$ORIG_CFG" | awk '{print $2}' | sort -u)" MODS="" for m in $BASE $CFGMODS; do [[ -f "$GDIR/$m.mod" ]] && MODS="$MODS $m" done MODS="$(echo "$MODS" | tr ' ' '\n' | awk 'NF' | sort -u | tr '\n' ' ')" say "embedding $(echo "$MODS" | wc -w) modules from the ISO's own x86_64-efi build" fi # --- 4. pin the volume modification-date so the ISO's GRUB fs-uuid is DETERMINISTIC and KNOWN before # we build the loader (GRUB's iso9660 fs_uuid is derived from the PVD modification timestamp). # Reuse the prepared ISO's own timestamp verbatim -> the embedded search matches the re-mastered # image (we pin the same value on re-master in step 6). ----------------------------------------- MDATE="$(xorriso -indev "$OUT" -report_el_torito as_mkisofs 2>/dev/null \ | grep -oE "modification-date='[0-9]+'" | grep -oE '[0-9]+' | head -1)" [[ -n "$MDATE" && ${#MDATE} -ge 14 ]] || { echo "iso-repack: could not read the ISO modification-date" >&2; exit 4; } ISO_UUID="$(echo "${MDATE:0:16}" | sed -E 's/^(.{4})(.{2})(.{2})(.{2})(.{2})(.{2})(.{2})$/\1-\2-\3-\4-\5-\6-\7/')" say "ISO fs-uuid (from modification-date $MDATE): $ISO_UUID" if [[ "$LOADER" == "mkimage" ]]; then # --- 5a. embedded config: find the ISO by fs-uuid, then chain its real menu (the recorded recipe) -- cat > "$EMB" <&2; exit 5; } say "built BOOTX64.EFI ($(stat -c%s "$LDR") bytes)" # --- 5c. swap the loader into BOTH the ISO9660 EFI/BOOT tree AND inside the efi.img ESP. The ISO # tree uses Rock Ridge (LOWERCASE) names — `/efi/boot/bootx64.efi` — so overwrite the # EXISTING files in place (case-insensitive find), NEVER mkdir a spurious uppercase path. # The efi.img ESP is FAT (case-insensitive), the authoritative loader UEFI firmware actually # runs from USB. ------------------------------------------------------------------------- TREE_HITS=0 while IFS= read -r f; do cp "$LDR" "$f"; TREE_HITS=$((TREE_HITS+1)); done \ < <(find "$EX" -ipath '*/efi/boot/bootx64.efi') while IFS= read -r f; do cp "$LDR" "$f"; done \ < <(find "$EX" -ipath '*/efi/boot/grubx64.efi') [[ "$TREE_HITS" -ge 1 ]] || { echo "iso-repack: no bootx64.efi found in the ISO9660 tree to replace" >&2; exit 6; } EFIIMG="$EX/efi.img" [[ -f "$EFIIMG" ]] || EFIIMG="$(find "$EX" -maxdepth 3 -iname 'efi*.img' | head -1)" [[ -f "$EFIIMG" ]] || { echo "iso-repack: efi.img ESP not found in the ISO tree" >&2; exit 6; } # FAT is case-insensitive: ::/EFI/BOOT/BOOTX64.EFI resolves the real loader regardless of stored case. mcopy -i "$EFIIMG" -o "$LDR" ::/EFI/BOOT/BOOTX64.EFI if mdir -i "$EFIIMG" ::/EFI/BOOT 2>/dev/null | grep -qi grubx64; then mcopy -i "$EFIIMG" -o "$LDR" ::/EFI/BOOT/grubx64.efi fi say "swapped bootx64.efi in the ISO tree ($TREE_HITS) and inside $(basename "$EFIIMG")" fi # --- 5b. INJECT the Felhom package into /proxmox/packages/ (release images). # # Install.pm:1343-1372 unpacks EVERY .deb in that directory into the target, with a fixed # skip-list of known package-name patterns, then :1378 runs `dpkg --configure -a` which executes # the postinsts. This happens on EVERY install path — measured on the interactive one in # SPIKE-universal-iso-4 (package installed, postinst run, unit enabled, unit fired at 7.98 s # uptime, with proxmox-first-boot absent on the same machine). It is the ONLY delivery mechanism # that survives an interactive install; the answer file's [first-boot] hook does not. if [[ -n "$DEB" ]]; then [[ -f "$DEB" ]] || { echo "iso-repack: FELHOM_DEB not found: $DEB" >&2; exit 16; } PKGDIR="$EX/proxmox/packages" [[ -d "$PKGDIR" ]] || { echo "iso-repack: $PKGDIR missing — not a PVE ISO?" >&2; exit 16; } cp "$DEB" "$PKGDIR/$(basename "$DEB")" say "injected $(basename "$DEB") into /proxmox/packages/ ($(ls "$PKGDIR"/*.deb | wc -l) debs total)" # The skip-list at Install.pm:1352-1362 matches known package-name prefixes. A Felhom package must # not collide with one, or it would be silently skipped on some hardware and the whole delivery # would fail invisibly — exactly the inert-payload class this project keeps hitting. case "$(basename "$DEB")" in grub-pc_*|grub-efi-*|proxmox-grub*|proxmox-secure-boot-support_*|proxmox-first-boot*|amd64-microcode_*|intel-microcode_*) echo "iso-repack: package name collides with the installer's skip-list — it would be dropped" >&2 exit 16 ;; esac fi # --- 6. re-master from the (modified) tree, reproducing the source ISO's boot geometry from its OWN # as_mkisofs report so we track PVE minor versions. We drop ONLY the Apple APM/HFS+ boot map # (-hfsplus / -apm-block-size): re-emitting it trips xorriso 1.5.6's "Overlapping MBR partition # entries" on THIS layout, and Mac boot is irrelevant for N100/PC hardware. We KEEP the hybrid # BIOS grub2-mbr + El Torito (BIOS eltorito.img + UEFI /efi.img) + the GPT EFI System Partition # (-efi-boot-part) that USB UEFI firmware boots from — the whole point of this fix. The volume # id + modification-date are pinned explicitly so the embedded fs-uuid stays valid. ------------- RPT="$(xorriso -indev "$OUT" -report_el_torito as_mkisofs 2>/dev/null)" VOLID="$(printf '%s\n' "$RPT" | sed -nE "s/^-V '(.*)'\$/\\1/p" | head -1)"; [[ -n "$VOLID" ]] || VOLID="PVE" # Drop, then re-add explicitly: the volume id + modification-date. Drop entirely: the Apple APM/HFS+ # map (-hfsplus / -apm-block-size) AND the isohybrid GPT-basdat marking (-part_like_isohybrid / # -isohybrid-gpt-basdat) — prepare-iso re-masters with these, and re-emitting them alongside # -efi-boot-part + the protective MBR trips xorriso 1.5.6's "Overlapping MBR partition entries". The # resulting image keeps the protective MBR + grub2-mbr (BIOS) + El Torito (BIOS+UEFI) + the GPT EFI # System Partition (verified). Repoint the grub2-mbr template at the in-container out.iso. FILTERED="$(printf '%s\n' "$RPT" \ | grep -vE "^-V '|^--modification-date=|^-apm-block-size |^-hfsplus\$|^-part_like_isohybrid\$|^-isohybrid-gpt-basdat\$" \ | sed -E "s#(--interval:[^']*:)'[^']*'#\\1'$OUT'#")" rm -f "$FINAL" # shellcheck disable=SC2086 eval xorriso -as mkisofs -V "'$VOLID'" --modification-date="'$MDATE'" \ $FILTERED -o "$FINAL" "$EX" >/work/xorriso.log 2>&1 \ || { echo "iso-repack: xorriso re-master FAILED"; tail -25 /work/xorriso.log >&2; exit 7; } [[ -f "$FINAL" ]] || { echo "iso-repack: no final.iso produced" >&2; exit 7; } # assert both boot images survived (BIOS eltorito.img + UEFI efi.img) — a silent loss would fail-safe # to an unbootable stick, so gate it here. ETIMG="$(xorriso -indev "$FINAL" -report_el_torito plain 2>/dev/null | grep -cE 'El Torito boot img')" [[ "$ETIMG" -ge 2 ]] || { echo "iso-repack: re-master lost a boot image (El Torito entries=$ETIMG, want >=2)" >&2; exit 8; } say "re-mastered final.iso ($(stat -c%s "$FINAL") bytes; El Torito boot images=$ETIMG)" # --- 7. verify the re-mastered image kept the modification-date (so the embedded fs-uuid matches) --- FINAL_MDATE="$(xorriso -indev "$FINAL" -report_el_torito as_mkisofs 2>/dev/null \ | grep -oE "modification-date='[0-9]+'" | grep -oE '[0-9]+' | head -1)" if [[ "${FINAL_MDATE:0:14}" != "${MDATE:0:14}" ]]; then echo "iso-repack: WARN final modification-date ($FINAL_MDATE) != source ($MDATE) — the search fs-uuid may not match; re-check" >&2 else say "fs-uuid preserved ($ISO_UUID)" fi # --- 8. post-re-master proof that the branding actually LANDED in the image we ship (not merely in # the extract tree) — read the menu back out of final.iso. -------------------------------------- if [[ "$BRAND" == "1" ]]; then VER=/work/verify; rm -rf "$VER"; mkdir -p "$VER" xorriso -osirrox on -indev "$FINAL" -extract /boot/grub/grub.cfg "$VER/grub.cfg" >/dev/null 2>&1 xorriso -osirrox on -indev "$FINAL" -extract /boot/grub/felhomtheme "$VER/felhomtheme" >/dev/null 2>&1 N="$(grep -c '^[[:space:]]*menuentry ' "$VER/grub.cfg" 2>/dev/null || echo 0)" [[ "$N" == "$WANT_ENTRIES" ]] || { echo "iso-repack: final.iso menu has $N entries, want $WANT_ENTRIES" >&2; exit 15; } [[ -s "$VER/felhomtheme/background.png" ]] || { echo "iso-repack: final.iso carries no theme background" >&2; exit 15; } grep -q "Felhom telepítés" "$VER/grub.cfg" || { echo "iso-repack: final.iso menu entry is not the Felhom one" >&2; exit 15; } rm -rf "$VER" say "verified in final.iso: $N entr(y/ies) + theme background present" fi # --- 8b. and prove the PACKAGE landed in final.iso too (seam-wiring: assert on what ships, not on the # tree we built it from). -------------------------------------------------------------------- if [[ -n "$DEB" ]]; then B="$(basename "$DEB")" xorriso -indev "$FINAL" -find /proxmox/packages -maxdepth 1 2>/dev/null | grep -q "$B" \ || { echo "iso-repack: $B is NOT in final.iso/proxmox/packages — delivery would be inert" >&2; exit 17; } say "verified in final.iso: $B present in /proxmox/packages/" fi say "done"