blancco: fix silent prefs fallback, suspend trap, display blank + add View

End-to-end fixes for Blancco Drive Eraser PXE flow uncovered by chasing
"reports never reach SMB share" across two air-gapped sites:

playbook/blancco-init.sh:
  * Drop silent || true on wget of preferences.xml + config.xml. Fail
    loud with shell-drop if download or marker grep fails. Background:
    airootfs /opt/scripts/validate_preferences.sh restores
    /albus/preferences.save (factory defaults, empty network_share) if
    xmllint fails. wget failure made every report silently land nowhere.
  * Clobber /albus/preferences.save with the same served file so even if
    the validator fallback fires, the SMB target survives.
  * Bind-mount /dev/null over /sys/power/{state,disk,mem_sleep,autosleep}
    before switch_root. Albus's license-retry path writes /sys/power/state
    directly (bypassing systemd targets); this is the last-line block.
  * /dev/null symlinks for sleep/suspend/hibernate systemd targets in the
    airootfs overlay + logind drop-in with IdleAction/Handle*=ignore.
    Three independent layers because cmdline systemd.mask alone is bypassed
    by direct /sys/power/state writes.
  * xinitrc.d/00-no-screen-blank.sh runs xset s off -dpms + setterm
    -blank 0 -powerdown 0 so the Blancco GUI doesn't blank during long
    erasures.
  * Removed the 20-failsafeDriver.conf "modesetting" pin. modesetting
    needs DRM/KMS which we disable on kernel cmdline; "vesa" also failed
    on NVIDIA. With the pin gone Xorg auto-picks fbdev which uses the
    kernel framebuffer from vga=normal - works across Intel, AMD, and
    older NVIDIA without nouveau.

playbook/pxe_server_setup.yml:
  * dnsmasq.conf: explicit empty-value dhcp-option=3 + dhcp-option=6.
    Without them, dnsmasq defaults to sending its own IP as router AND
    DNS. Commenting the configured-value lines did NOT disable the push
    (root cause of "wired keeps picking up 10.9.100.1 as gateway").
  * Split the Blancco config.img extraction and preferences.xml deploy
    into separate tasks. The previous shell-with-creates: gate caused
    playbook re-runs to skip the prefs deploy entirely after first run.
  * Added a validation task that runs python3 xml parse + grep on the
    deployed preferences.xml to fail the playbook at deploy time if the
    SMB markers are missing.
  * Added Environment=TZ=America/New_York to the pxe-webapp systemd
    service so report mtimes and audit log render in Eastern time even
    if the Python process is started before timedatectl converges.

webapp:
  * services/blancco_report.py: parse Blancco's XML report format
    (recursive <entries name="..."> walker) into a friendly dict.
  * templates/report_view.html: Bootstrap "Drive Erasure Certificate"
    layout - hero summary, customer + system cards, per-drive cards with
    step-by-step erasure timeline, document signing footer with
    integrity hash detail.
  * /reports/view/<filename> route + View button on the reports list
    (XML reports only; PDFs still download).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-05-13 07:38:54 -04:00
parent adc8d50e66
commit 974accf98a
6 changed files with 462 additions and 15 deletions

View File

@@ -159,8 +159,14 @@
# the wired NIC much later). Removing these options entirely lets
# Windows route internet via WiFi and same-subnet PXE/SMB traffic
# via wired, no migration script needed.
# dhcp-option=3,10.9.100.1
# dhcp-option=6,8.8.8.8
#
# Important: dnsmasq DEFAULTS to sending its own listening address as
# both router and DNS when these options are unset. Commenting them
# out is NOT the same as disabling - imaged PCs (and Blancco PXE
# clients) end up with 10.9.100.1 as gateway. The empty-value form
# below explicitly suppresses both options.
dhcp-option=3
dhcp-option=6
enable-tftp
tftp-root={{ tftp_dir }}
# Arch-aware NBP: legacy BIOS PXE ROMs (client-arch=0) cannot run
@@ -863,9 +869,8 @@
args:
creates: "{{ web_root }}/blancco/kmod.tar.gz"
- name: "Deploy Blancco config and preferences (null-stripped)"
- name: "Extract config-clean.xml from config.img (strip null bytes)"
shell: |
# Strip null bytes from config.img files and deploy
if [ -f "{{ web_root }}/blancco/config.img" ]; then
WORK=$(mktemp -d)
cd "$WORK"
@@ -873,11 +878,30 @@
tr -d '\000' < config.xml > "{{ web_root }}/blancco/config-clean.xml"
rm -rf "$WORK"
fi
# Deploy preferences from playbook (pre-configured with network share)
cp "{{ usb_root }}/playbook/blancco-preferences.xml" "{{ web_root }}/blancco/preferences.xml"
args:
creates: "{{ web_root }}/blancco/config-clean.xml"
# Idempotent file copy (was bundled into the previous shell task with a
# 'creates:' gate, so playbook re-runs never picked up edits to the
# source XML - stale prefs survived deploys).
- name: "Deploy Blancco preferences.xml from playbook source"
copy:
src: "{{ usb_root }}/playbook/blancco-preferences.xml"
dest: "{{ web_root }}/blancco/preferences.xml"
mode: '0644'
# Hard gate: blancco-init.sh in initramfs drops to shell if the served
# preferences.xml is malformed or missing the SMB target. Fail the
# playbook here too so the problem surfaces at deploy time, not at the
# next erasure attempt.
- name: "Validate deployed preferences.xml is well-formed + targets the right SMB share"
shell: |
set -e
python3 -c 'import xml.etree.ElementTree as ET; ET.parse("{{ web_root }}/blancco/preferences.xml")'
grep -q '<hostname>10.9.100.1</hostname>' "{{ web_root }}/blancco/preferences.xml"
grep -q '<path>blancco-reports</path>' "{{ web_root }}/blancco/preferences.xml"
changed_when: false
- name: "Ensure Samba user for Blancco reports exists (idempotent)"
shell: |
id blancco >/dev/null 2>&1 || useradd -r -s /usr/sbin/nologin blancco
@@ -994,6 +1018,11 @@
Environment=BLANCCO_REPORTS=/srv/samba/blancco-reports
Environment=ENROLLMENT_SHARE=/srv/samba/enrollment
Environment=AUDIT_LOG=/var/log/pxe-webapp-audit.log
# Lock TZ so report/backup mtimes and audit-log timestamps render
# in Eastern time regardless of how the host's /etc/localtime ends
# up. Without this, a Python process started before timedatectl
# finishes can cache UTC for its lifetime.
Environment=TZ=America/New_York
ExecStart=/usr/bin/python3 app.py
Restart=always
RestartSec=5