blancco: upgrade to Drive Eraser 7.20.0

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/.
This commit is contained in:
cproudlock
2026-10-05 09:32:32 -04:00
parent 737a729548
commit 678d10d5c2
2 changed files with 196 additions and 256 deletions

View File

@@ -16,7 +16,7 @@
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")"/.. && pwd)"
OUT_DIR="$REPO_ROOT/boot-tools"
OUT_DIR="${OUT_DIR:-$REPO_ROOT/boot-tools}"
BLANCCO_ISO="${1:-}"
# Auto-detect Blancco ISO in project directory
@@ -106,91 +106,76 @@ if [ -n "$BLANCCO_ISO" ] && [ -f "$BLANCCO_ISO" ]; then
echo " Extracted Blancco boot files."
# --- Patch config.img (size-preserving) ---
# config.img is a CPIO archive containing preferences.xml (padded to 32768 bytes).
# The CPIO itself must remain exactly 194560 bytes (380 x 512-byte blocks).
# We use Python for byte-level replacement to preserve exact sizes.
# 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 " Patching config.img for network report storage..."
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
ORIG_SIZE=$(stat -c%s "$CFGTMP/preferences.xml")
python3 << 'PYEOF'
import sys
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)
# Set SMB share credentials and path
data = data.replace(
b'<username encrypted="false"></username>',
b'<username encrypted="false">blancco</username>'
)
data = data.replace(
b'<password encrypted="false"></password>',
b'<password encrypted="false">blancco</password>'
)
data = data.replace(
b'<hostname></hostname>',
b'<hostname>172.16.9.1</hostname>'
)
data = data.replace(
b'<path></path>',
b'<path>blancco-reports</path>'
)
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
# Enable auto-backup
data = data.replace(
b'<auto_backup>false</auto_backup>',
b'<auto_backup>true</auto_backup>'
)
# 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()}")
# Enable bootable report
data = data.replace(
b'<bootable_report>\n <enabled>false</enabled>\n </bootable_report>',
b'<bootable_report>\n <enabled>true</enabled>\n </bootable_report>'
)
# Maintain exact file size by trimming trailing padding/whitespace
diff = len(data) - orig_size
if diff > 0:
# The file has trailing whitespace/padding before the final XML closing tags
# Trim from the padding area (spaces before closing comment or end of file)
end_pos = data.rfind(b'<!-- ')
if end_pos > 0:
comment_end = data.find(b' -->', end_pos)
if comment_end > 0:
data = data[:comment_end - diff] + data[comment_end:]
if len(data) > orig_size:
# Fallback: trim trailing whitespace
data = data.rstrip()
data = data + b'\n' * (orig_size - len(data))
elif diff < 0:
# Pad with spaces to maintain size
data = data[:-1] + b' ' * (-diff) + data[-1:]
if len(data) != orig_size:
print(f" WARNING: Size mismatch ({len(data)} vs {orig_size}), padding to match")
if len(data) > orig_size:
data = data[:orig_size]
else:
data = data + b'\x00' * (orig_size - len(data))
# 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(data)
print(f" preferences.xml: {orig_size} bytes (preserved)")
f.write(out)
print(f" preferences.xml: {len(xml)} bytes of XML, {orig_size} total (preserved)")
PYEOF
# Repack CPIO with exact 512-byte block alignment (194560 bytes)
# 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, bootable report enabled"
echo " Reports: SMB blancco@172.16.9.1/blancco-reports (from playbook preferences)"
fi
cd "$REPO_ROOT"
rm -rf "$CFGTMP"
@@ -201,41 +186,51 @@ PYEOF
# 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..."
python3 << PYEOF
import lzma, sys, os
initramfs = "$OUT_DIR/blancco/initramfs-bde-linux.img"
INITRAMFS="$OUT_DIR/blancco/initramfs-bde-linux.img" python3 << 'PYEOF'
import lzma, os, sys
initramfs = os.environ["INITRAMFS"]
with open(initramfs, "rb") as f:
compressed = f.read()
data = f.read()
# Decompress XZ stream
try:
raw = lzma.decompress(compressed)
except lzma.LZMAError:
print(" WARNING: Could not decompress initramfs (not XZ?), skipping patch")
sys.exit(0)
# 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:]
# Binary patch: change the copytoram check from "y" to "N"
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)
if old not in raw:
# Try alternate pattern with different whitespace
old = b'"\${copytoram}" = "y"'
new = b'"\${copytoram}" = "N"'
if old in raw:
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(recompressed)
print(" initramfs patched: copytoram network flush disabled")
else:
print(" WARNING: copytoram pattern not found in initramfs, skipping patch")
# 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 ---