playbook/blancco-preferences.xml is now the vendor 7.20 preferences (schema 4.20, "Non_GEAE_Wired" profile) with only our site overrides: SMB report share on 172.16.9.1 with auto_backup, WiFi INTERNETACCESS for cloud licensing (wired stays up for SMB), manual as the fallback process, bootable report, screensaver 86400. workflow default=true is kept: the cloud workflow erases unattended, as live 7.15 already did. No custom fields, matching both the vendor image and live 7.15. prepare-boot-tools.sh: - 7.20 initramfs is an uncompressed early cpio (NIC firmware, itself .xz files) followed by the XZ archive. The old patch decompressed from byte 0, failed, warned and carried on, so PXE boots would drop network after copytoram. Walk the cpio to its trailer and patch the XZ part. - Patch exactly one copytoram match or fail. The looser fallback pattern matches five hooks in 7.20. - config.img now carries playbook/blancco-preferences.xml, so the BIOS (native kernel) and UEFI (HTTP) paths read the same settings. Refuses to build if the playbook preferences are for another Blancco version. - The old global replace() also set the 802.1x, WiFi and repeater passwords to "blancco". Gone with the above. - Keep preferences.xml size by refilling the NUL gap before the trailing filler comment (7.20), or trimming the comment (7.15). - OUT_DIR can be overridden to stage a build outside boot-tools/.
326 lines
13 KiB
Bash
Executable File
326 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# prepare-boot-tools.sh — Download/extract boot files for PXE boot tools
|
|
#
|
|
# Downloads Clonezilla Live and Memtest86+ for PXE booting,
|
|
# and extracts Blancco Drive Eraser from its ISO.
|
|
#
|
|
# Usage:
|
|
# ./prepare-boot-tools.sh [/path/to/blancco.iso]
|
|
#
|
|
# Output directories:
|
|
# boot-tools/clonezilla/ — vmlinuz, initrd.img, filesystem.squashfs
|
|
# boot-tools/blancco/ — extracted boot files or ISO for memdisk
|
|
# boot-tools/memtest/ — memtest.efi
|
|
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")"/.. && pwd)"
|
|
OUT_DIR="${OUT_DIR:-$REPO_ROOT/boot-tools}"
|
|
BLANCCO_ISO="${1:-}"
|
|
|
|
# Auto-detect Blancco ISO in project directory
|
|
if [ -z "$BLANCCO_ISO" ]; then
|
|
BLANCCO_ISO=$(find "$REPO_ROOT" -maxdepth 1 -name '*DriveEraser*.iso' -o -name '*blancco*.iso' 2>/dev/null | head -1)
|
|
fi
|
|
|
|
mkdir -p "$OUT_DIR"/{clonezilla,blancco,memtest}
|
|
|
|
echo "============================================"
|
|
echo "PXE Boot Tools Preparation"
|
|
echo "============================================"
|
|
|
|
# --- Clonezilla Live ---
|
|
echo ""
|
|
echo "[1/3] Clonezilla Live"
|
|
|
|
CLONEZILLA_VERSION="3.2.1-6"
|
|
CLONEZILLA_FILE="clonezilla-live-${CLONEZILLA_VERSION}-amd64.zip"
|
|
CLONEZILLA_URL="https://sourceforge.net/projects/clonezilla/files/clonezilla_live_stable/${CLONEZILLA_VERSION}/${CLONEZILLA_FILE}/download"
|
|
|
|
if [ -f "$OUT_DIR/clonezilla/vmlinuz" ] && [ -f "$OUT_DIR/clonezilla/filesystem.squashfs" ]; then
|
|
echo " Already prepared, skipping. Delete boot-tools/clonezilla/ to re-download."
|
|
else
|
|
echo " Downloading Clonezilla Live ${CLONEZILLA_VERSION}..."
|
|
TMPDIR=$(mktemp -d)
|
|
|
|
wget -q --show-progress -O "$TMPDIR/$CLONEZILLA_FILE" "$CLONEZILLA_URL" || {
|
|
echo " ERROR: Download failed. Trying alternative URL..."
|
|
# Fallback: try OSDN mirror
|
|
wget -q --show-progress -O "$TMPDIR/$CLONEZILLA_FILE" \
|
|
"https://free.nchc.org.tw/clonezilla-live/stable/${CLONEZILLA_FILE}" || {
|
|
echo " ERROR: Could not download Clonezilla. Download manually and place in boot-tools/clonezilla/"
|
|
echo " Need: vmlinuz, initrd.img, filesystem.squashfs from the live ZIP"
|
|
}
|
|
}
|
|
|
|
if [ -f "$TMPDIR/$CLONEZILLA_FILE" ]; then
|
|
echo " Extracting PXE boot files..."
|
|
unzip -o -j "$TMPDIR/$CLONEZILLA_FILE" "live/vmlinuz" -d "$OUT_DIR/clonezilla/"
|
|
unzip -o -j "$TMPDIR/$CLONEZILLA_FILE" "live/initrd.img" -d "$OUT_DIR/clonezilla/"
|
|
unzip -o -j "$TMPDIR/$CLONEZILLA_FILE" "live/filesystem.squashfs" -d "$OUT_DIR/clonezilla/"
|
|
rm -rf "$TMPDIR"
|
|
echo " Done."
|
|
fi
|
|
fi
|
|
|
|
ls -lh "$OUT_DIR/clonezilla/" 2>/dev/null | grep -E 'vmlinuz|initrd|squashfs' | sed 's/^/ /'
|
|
|
|
# --- Blancco Drive Eraser ---
|
|
echo ""
|
|
echo "[2/3] Blancco Drive Eraser"
|
|
|
|
if [ -n "$BLANCCO_ISO" ] && [ -f "$BLANCCO_ISO" ]; then
|
|
echo " Extracting from: $BLANCCO_ISO"
|
|
echo " Using 7z to extract (no root required)..."
|
|
|
|
# Blancco is Arch Linux-based. We need:
|
|
# arch/boot/x86_64/vmlinuz-bde-linux
|
|
# arch/boot/x86_64/initramfs-bde-linux.img
|
|
# arch/boot/intel-ucode.img
|
|
# arch/boot/amd-ucode.img
|
|
# arch/boot/config.img
|
|
# arch/x86_64/airootfs.sfs
|
|
TMPDIR=$(mktemp -d)
|
|
7z x -o"$TMPDIR" "$BLANCCO_ISO" \
|
|
"arch/boot/x86_64/vmlinuz-bde-linux" \
|
|
"arch/boot/x86_64/initramfs-bde-linux.img" \
|
|
"arch/boot/intel-ucode.img" \
|
|
"arch/boot/amd-ucode.img" \
|
|
"arch/boot/config.img" \
|
|
"arch/x86_64/airootfs.sfs" \
|
|
-r 2>/dev/null || {
|
|
echo " 7z extraction failed. Install p7zip-full: apt install p7zip-full"
|
|
}
|
|
|
|
# Flatten into blancco/ directory for HTTP serving
|
|
if [ -f "$TMPDIR/arch/boot/x86_64/vmlinuz-bde-linux" ]; then
|
|
cp "$TMPDIR/arch/boot/x86_64/vmlinuz-bde-linux" "$OUT_DIR/blancco/"
|
|
cp "$TMPDIR/arch/boot/x86_64/initramfs-bde-linux.img" "$OUT_DIR/blancco/"
|
|
cp "$TMPDIR/arch/boot/intel-ucode.img" "$OUT_DIR/blancco/"
|
|
cp "$TMPDIR/arch/boot/amd-ucode.img" "$OUT_DIR/blancco/"
|
|
cp "$TMPDIR/arch/boot/config.img" "$OUT_DIR/blancco/"
|
|
# airootfs.sfs needs to be in arch/x86_64/ path relative to HTTP root
|
|
mkdir -p "$OUT_DIR/blancco/arch/x86_64"
|
|
cp "$TMPDIR/arch/x86_64/airootfs.sfs" "$OUT_DIR/blancco/arch/x86_64/"
|
|
echo " Extracted Blancco boot files."
|
|
|
|
# --- Patch config.img (size-preserving) ---
|
|
# config.img is a CPIO archive. Its preferences.xml is what the native
|
|
# kernel (BIOS) boot path reads; the UEFI path gets the same file over
|
|
# HTTP. Both now carry playbook/blancco-preferences.xml, so the two
|
|
# paths cannot drift. The vendor file is laid out as
|
|
# <xml> [NUL padding] <!-- filler comment --> and its size is kept:
|
|
# 7.20 pads to 32768 with ~15KB of NULs; 7.15 had none, so growth
|
|
# there comes out of the filler comment.
|
|
if [ -f "$OUT_DIR/blancco/config.img" ]; then
|
|
echo " Writing playbook preferences into config.img..."
|
|
CFGTMP=$(mktemp -d)
|
|
cd "$CFGTMP"
|
|
cpio -id < "$OUT_DIR/blancco/config.img" 2>/dev/null
|
|
|
|
if [ -f "$CFGTMP/preferences.xml" ]; then
|
|
PREFS_SRC="$REPO_ROOT/playbook/blancco-preferences.xml" python3 << 'PYEOF'
|
|
import os, re, sys
|
|
|
|
with open("preferences.xml", "rb") as f:
|
|
data = f.read()
|
|
with open(os.environ["PREFS_SRC"], "rb") as f:
|
|
ours = f.read().strip() + b"\n"
|
|
orig_size = len(data)
|
|
|
|
def version(x):
|
|
m = re.search(rb"<version>\s*<generation>(\d+)</generation>\s*"
|
|
rb"<major>(\d+)</major>", x)
|
|
return m.groups() if m else None
|
|
|
|
# Our file must be a merge onto THIS release's preferences, or settings the
|
|
# new schema added or renamed are silently lost.
|
|
if version(ours) != version(data):
|
|
sys.exit(f" ERROR: playbook preferences are for {version(ours)}, ISO is "
|
|
f"{version(data)}. Merge onto the new vendor preferences first.")
|
|
for need in (b"<hostname>172.16.9.1</hostname>", b"<path>blancco-reports</path>",
|
|
b"<auto_backup>true</auto_backup>"):
|
|
if need not in ours:
|
|
sys.exit(f" ERROR: playbook preferences missing {need.decode()}")
|
|
|
|
# Split off the trailing filler comment and any NUL padding before it.
|
|
end = data.find(b"</root>")
|
|
cstart = data.rfind(b"<!--")
|
|
if end < 0 or cstart < end:
|
|
sys.exit(" ERROR: vendor preferences.xml layout not recognised")
|
|
gap, tail = data[end + 7:cstart], data[cstart:]
|
|
xml = ours
|
|
|
|
room = orig_size - len(xml) - len(tail)
|
|
if room >= 0:
|
|
# Refill the gap with what it held: NULs if it was padding, else newlines.
|
|
gap = (b"\x00" if b"\x00" in gap else b"\n") * room
|
|
else:
|
|
# No room left: take it out of the filler comment body (keep "<!-- " + " -->").
|
|
cut = -room
|
|
if len(tail) - cut < len(b"<!-- -->\n"):
|
|
sys.exit(f" ERROR: preferences need {cut} more bytes than the "
|
|
"padding and filler comment can give")
|
|
tail = tail[:5] + tail[5 + cut:]
|
|
gap = b""
|
|
out = xml + gap + tail
|
|
if len(out) != orig_size:
|
|
sys.exit(f" ERROR: preferences.xml size changed {orig_size} -> {len(out)}")
|
|
with open("preferences.xml", "wb") as f:
|
|
f.write(out)
|
|
print(f" preferences.xml: {len(xml)} bytes of XML, {orig_size} total (preserved)")
|
|
PYEOF
|
|
|
|
# Repack CPIO padded to a 512-byte boundary
|
|
ls -1 "$CFGTMP" | (cd "$CFGTMP" && cpio -o -H newc 2>/dev/null) | \
|
|
dd bs=512 conv=sync 2>/dev/null > "$OUT_DIR/blancco/config.img"
|
|
echo " Reports: SMB blancco@172.16.9.1/blancco-reports (from playbook preferences)"
|
|
fi
|
|
cd "$REPO_ROOT"
|
|
rm -rf "$CFGTMP"
|
|
fi
|
|
|
|
# --- Patch initramfs to keep network interfaces up after copytoram ---
|
|
# Blancco uses copytoram=y which triggers archiso_pxe_common latehook to
|
|
# flush all network interfaces. Binary-patch the check from "y" to "N" so
|
|
# the condition never matches. IMPORTANT: full extract/repack BREAKS booting.
|
|
echo " Patching initramfs to preserve network after copytoram..."
|
|
INITRAMFS="$OUT_DIR/blancco/initramfs-bde-linux.img" python3 << 'PYEOF'
|
|
import lzma, os, sys
|
|
|
|
initramfs = os.environ["INITRAMFS"]
|
|
with open(initramfs, "rb") as f:
|
|
data = f.read()
|
|
|
|
# 7.20 prepends an uncompressed early cpio (NIC firmware) before the XZ
|
|
# archive; 7.15 was XZ from byte 0. Patch only the XZ part, keep the prefix.
|
|
# Walk the cpio headers to its trailer: the firmware files are themselves
|
|
# .xz, so searching for the XZ magic would land inside the prefix.
|
|
off = 0
|
|
while data.startswith(b"070701", off):
|
|
namesize = int(data[off + 94:off + 102], 16)
|
|
filesize = int(data[off + 54:off + 62], 16)
|
|
name = data[off + 110:off + 110 + namesize - 1]
|
|
off = (((off + 110 + namesize + 3) & ~3) + filesize + 3) & ~3
|
|
if name == b"TRAILER!!!":
|
|
while off < len(data) and data[off] == 0:
|
|
off += 1
|
|
if not data.startswith(b"\xfd7zXZ\x00", off):
|
|
sys.exit(f" ERROR: initramfs layout not recognised (no XZ archive at {off})")
|
|
prefix, compressed = data[:off], data[off:]
|
|
|
|
raw = lzma.decompress(compressed)
|
|
|
|
# Binary patch: change the copytoram check from "y" to "N" in
|
|
# archiso_pxe_common's run_latehook. Exact match only: the looser
|
|
# '"${copytoram}" = "y"' appears in several hooks and patching the first
|
|
# one found is not safe.
|
|
old = b'"y" ]; then\n for curif in /sys/class/net'
|
|
new = b'"N" ]; then\n for curif in /sys/class/net'
|
|
count = raw.count(old)
|
|
if count != 1:
|
|
sys.exit(f" ERROR: copytoram network-flush pattern found {count} times, "
|
|
"expected 1. PXE boot would lose network after copytoram.")
|
|
raw = raw.replace(old, new, 1)
|
|
|
|
# Recompress with same XZ settings as archiso
|
|
recompressed = lzma.compress(raw, format=lzma.FORMAT_XZ,
|
|
check=lzma.CHECK_CRC32, preset=6)
|
|
with open(initramfs, "wb") as f:
|
|
f.write(prefix + recompressed)
|
|
print(f" initramfs patched: copytoram network flush disabled "
|
|
f"(early cpio {len(prefix)} bytes kept)")
|
|
PYEOF
|
|
|
|
# --- Build GRUB EFI binary for Blancco chainload ---
|
|
# Broadcom iPXE can't pass initrd to Linux kernels in UEFI mode.
|
|
# Solution: iPXE chains to grubx64.efi, which loads kernel+initrd via TFTP.
|
|
GRUB_CFG="$REPO_ROOT/boot-tools/blancco/grub-blancco.cfg"
|
|
if [ -f "$GRUB_CFG" ]; then
|
|
if command -v grub-mkstandalone &>/dev/null; then
|
|
echo " Building grubx64.efi (GRUB chainload for Blancco)..."
|
|
grub-mkstandalone \
|
|
--format=x86_64-efi \
|
|
--output="$OUT_DIR/blancco/grubx64.efi" \
|
|
--modules="linux normal echo net efinet http tftp chain sleep all_video efi_gop" \
|
|
"boot/grub/grub.cfg=$GRUB_CFG" 2>/dev/null
|
|
echo " Built grubx64.efi ($(du -h "$OUT_DIR/blancco/grubx64.efi" | cut -f1))"
|
|
else
|
|
echo " WARNING: grub-mkstandalone not found. Install grub-efi-amd64-bin:"
|
|
echo " sudo apt install grub-efi-amd64-bin grub-common"
|
|
echo " Then re-run this script to build grubx64.efi"
|
|
fi
|
|
else
|
|
echo " WARNING: grub-blancco.cfg not found at $GRUB_CFG"
|
|
fi
|
|
else
|
|
echo " Could not extract boot files from ISO."
|
|
fi
|
|
rm -rf "$TMPDIR"
|
|
else
|
|
echo " No Blancco ISO found. Provide path as argument or place in project directory."
|
|
echo " Usage: $0 /path/to/DriveEraser.iso"
|
|
fi
|
|
|
|
ls -lh "$OUT_DIR/blancco/" 2>/dev/null | grep -v '^total' | sed 's/^/ /'
|
|
|
|
# --- Memtest86+ ---
|
|
echo ""
|
|
echo "[3/3] Memtest86+"
|
|
|
|
MEMTEST_VERSION="7.20"
|
|
MEMTEST_URL="https://memtest.org/download/${MEMTEST_VERSION}/mt86plus_${MEMTEST_VERSION}.binaries.zip"
|
|
|
|
if [ -f "$OUT_DIR/memtest/memtest.efi" ]; then
|
|
echo " Already prepared, skipping."
|
|
else
|
|
echo " Downloading Memtest86+ v${MEMTEST_VERSION}..."
|
|
TMPDIR=$(mktemp -d)
|
|
|
|
wget -q --show-progress -O "$TMPDIR/memtest.zip" "$MEMTEST_URL" || {
|
|
echo " ERROR: Download failed. Download manually from https://memtest.org"
|
|
TMPDIR=""
|
|
}
|
|
|
|
if [ -n "$TMPDIR" ] && [ -f "$TMPDIR/memtest.zip" ]; then
|
|
echo " Extracting EFI binary..."
|
|
unzip -o -j "$TMPDIR/memtest.zip" "memtest64.efi" -d "$OUT_DIR/memtest/" 2>/dev/null || \
|
|
unzip -o -j "$TMPDIR/memtest.zip" "mt86plus_${MEMTEST_VERSION}.x64.efi" -d "$OUT_DIR/memtest/" 2>/dev/null || \
|
|
unzip -o "$TMPDIR/memtest.zip" -d "$TMPDIR/extract/"
|
|
|
|
# Find the EFI file regardless of exact name
|
|
EFI_FILE=$(find "$TMPDIR" "$OUT_DIR/memtest" -name '*.efi' -name '*64*' 2>/dev/null | head -1)
|
|
if [ -n "$EFI_FILE" ] && [ ! -f "$OUT_DIR/memtest/memtest.efi" ]; then
|
|
cp "$EFI_FILE" "$OUT_DIR/memtest/memtest.efi"
|
|
fi
|
|
rm -rf "$TMPDIR"
|
|
echo " Done."
|
|
fi
|
|
fi
|
|
|
|
ls -lh "$OUT_DIR/memtest/" 2>/dev/null | grep -v '^total' | sed 's/^/ /'
|
|
|
|
# --- Summary ---
|
|
echo ""
|
|
echo "============================================"
|
|
echo "Boot tools prepared in: $OUT_DIR/"
|
|
echo "============================================"
|
|
echo ""
|
|
|
|
for tool in clonezilla blancco memtest; do
|
|
COUNT=$(find "$OUT_DIR/$tool" -type f 2>/dev/null | wc -l)
|
|
SIZE=$(du -sh "$OUT_DIR/$tool" 2>/dev/null | cut -f1)
|
|
printf " %-15s %s (%d files)\n" "$tool" "$SIZE" "$COUNT"
|
|
done
|
|
|
|
echo ""
|
|
echo "These files need to be copied to the PXE server's web root:"
|
|
echo " /var/www/html/clonezilla/"
|
|
echo " /var/www/html/blancco/"
|
|
echo " /var/www/html/memtest/"
|
|
echo ""
|
|
echo "The build-usb.sh script will include them automatically,"
|
|
echo "or copy them manually to the server."
|
|
echo ""
|