Files
admin 0f22447d15 iso: two build-log lines stated things that were not true
Neither changes an artifact, but both are read by an operator deciding whether a build is sound:

- the closing banner printed 'root-pw : <iso>.rootpw.txt ... the console credential for this build'
  unconditionally. In --release mode no password is minted and no such file is written (verified:
  the release build emits only .iso, .sha256 and .manifest.txt). It now says so.
- the repack's menu-surgery line hardcoded '1 entry, 0 submenus' and printed it after a gate that
  had just accepted TWO. It now reports the counts it actually asserted.
2026-07-31 16:42:39 +02:00

418 lines
26 KiB
Bash
Executable File

#!/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 = <path> (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 <<EOF
menu-entries : 1 ('Felhom telepítés', default, timeout 5s)
menu-removed : Graphical, Terminal UI, serial TUI, Advanced Options submenu (nomodeset x2,
debug x3, Rescue Boot, memtest86+, UEFI Firmware Settings)
kernel-line : $(sed -E 's/^[[:space:]]+//' <<<"$LINUX_LINE")
initrd-line : $(sed -E 's/^[[:space:]]+//' <<<"$INITRD_LINE")
theme : /boot/grub/felhomtheme/theme.txt (stock pvetheme removed)
background : 1024x768 PNG24 from website/assets/og-image_2.png
EOF
say "menu surgery OK — $N_ENTRY entr(y/ies), $N_SUB submenus, banned entries not emitted"
fi
#====================================================================================================
# --- 3. mkimage UEFI loader surgery (slice B; recipe unchanged) ------------------------------------
#====================================================================================================
if [[ "$LOADER" == "mkimage" ]]; then
grub-mkimage --version | head -1 > /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" <<CFG
search --no-floppy --fs-uuid --set=root $ISO_UUID
if [ -z "\$root" ]; then search --no-floppy --file --set=root /boot/grub/grub.cfg; fi
set prefix=(\$root)/boot/grub
configfile (\$root)/boot/grub/grub.cfg
CFG
# --- 5b. build the monolithic BOOTX64.EFI from the ISO's OWN modules (-d $GDIR) ------------------
# shellcheck disable=SC2086
grub-mkimage -O x86_64-efi -d "$GDIR" -p /boot/grub -c "$EMB" -o "$LDR" $MODS
[[ -s "$LDR" ]] || { echo "iso-repack: grub-mkimage produced no image" >&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"