A boot splash that fills any screen, and patching a live squashfs layer to ship it
The fixed-size plymouth two-step engine could not scale a splash to an unknown panel, so the theme was moved to the script engine, the engine object was vendored into the initramfs as a bare file to avoid destroying the merged-/usr symlinks, and the shutdown splash was delivered by swapping one theme inside an existing squashfs layer under byte-identity preconditions rather than rebuilding it.
Status
Deployed and confirmed on hardware, 1 October 2026. Rendered by plymouth's real engine at eight screen sizes, boot and shutdown, plus the fallback path — on the stick's exact plymouth build — then bench-booted on the Dell (1366×768): the operator confirmed the splash now fills the whole screen.
On the stick: station-splash.img (1,387,008 bytes, sha256 aca6a337…) and the
patched layer (163,381,248 bytes, 5e906e33…). The previous, hardware-proven
files are kept as station-splash.img.prev and
minimal.standard.live.station.squashfs.prev; the original Xorkbench layer .bak is
untouched; boot\theme was refreshed so an on-machine layer rebuild keeps this
theme. grub.cfg was not edited.
Supersedes the "two-step only" constraint in 2026-09-30-xorkbench-boot-splash.
The problem
The operator booted the Dell with the Xorkbench splash and it filled only part of the screen: a rectangle of artwork on a flat blue fill. The brief was explicit: the resolution of the machines is unknown, so the splash must fill any screen and keep the picture's quality — and it must not be another round of guessing.
The cause was the engine, not the image. two-step, the only engine in
Ubuntu's initramfs, draws its picture at a fixed pixel size and cannot scale
it. Resizing the image for the Dell would only move the problem to the next
panel size.
What was built
The script engine. Plymouth's script engine reads the screen size at
run time and can scale images. The theme (tools/boot/src/xorkbench.script)
scales one full-screen artwork per phase to cover the screen, never
stretched, cropping the overflow evenly — with one guard: a safe area (X logo
to status line) is never cropped. On an extreme aspect ratio the art shrinks
just enough to keep it whole, and the leftover strip takes the art's own edge
colour. It re-lays itself out if the resolution changes mid-boot (a graphics
driver taking over from the firmware framebuffer).
Artwork prep (tools/boot/prep-fluid-art.py, Pillow; the generator stays
stdlib-only). The frozen "70%" progress bar and its label are painted out, and
a live bar is drawn in exactly the same place, in colours sampled from the
original bar — a glowing segment that keeps running, because nothing at boot
knows a real percentage. Masters are 1920 wide (1:1 on 1080p, clean downscale
to 1366×768 and 1024×768): 1.2 MB boot, 1.5 MB shutdown.
The engine in the initramfs. script.so is not in Ubuntu's initramfs, so
station-splash.img now carries it (tools/boot/vendor/plymouth/, taken
unmodified from plymouth_24.004.60-1ubuntu7 — the build the stick's
initramfs was verified to contain — provenance in SOURCE.txt).
It is added as a bare file with no directory entries: a directory entry for
a path that is a symlink in the initramfs (merged /usr) makes the kernel
replace the symlink with an empty directory, which would take every library
with it. The real root needs nothing — script.so ships in the main plymouth
package — so the shutdown splash is a theme change only.
A three-step fallback. station (fluid, script) → bgrt override (the
previous fixed-size Xorkbench splash, two-step, now also shipped in the boot
archive) → Ubuntu. If script.so does not load, the worst case is the old
look, never a black screen.
How the layer gets patched
The shutdown splash lives in the casper layer
minimal.standard.live.station.squashfs. Rather than rebuilding the layer (network,
package fetches, the known build traps), tools/boot/patch-layer-theme.sh
swaps only the plymouth theme in the existing layer, in Docker, and refuses
to write unless:
- compression,
-Xhcand block size match the original, root isdrwxr-xr-x; - all 1,170 entries outside the theme are identical (mode, owner, time, links, file sizes) and every file outside the theme is byte-identical;
- the theme inside is byte-identical to the staged one, selects
script, and keeps the two-step fallback with every file two-step requires.
On the stick the original is kept as .bak and the new file takes the same
name, so grub's layerfs-path needs no edit. This was first used on
30 September for the red shutdown splash, and the operator's bench photo confirmed
it. One check had to be corrected for this change: a squashfs directory's
size is its index size, which grows when a theme gains files, so directories
are compared on mode, owner and time only.
How it was proved
tools/boot/render-splash.sh runs plymouth in Docker — pinned to the stick's
24.004.60-1ubuntu7 build — with its X11
renderer on a virtual screen, which takes its size from the screen — so each
run is the real engine laying the theme out for that panel. The boot run
unpacks station-splash.img onto / the way the kernel does.
- Sixteen renders — boot and shutdown at 1366×768 (the Dell), 1920×1080, 1280×800, 1024×768, 1280×1024, 2560×1600, 3440×1440, 800×600 — all fill the screen, safe area intact. Only the 21:9 shutdown shows thin edge strips, by design.
- Two frames 0.6 s apart show the live bar moving.
- Zero script-engine errors in any run's log.
- Fallback: with
script.soremoved, plymouth logs the failed load and draws the two-step Xorkbench splash — the exact box of the operator's photo, which also shows the harness reproduces hardware behaviour. - The layer patch dry run passes every check above (1207 → 1214 entries, theme files only).
Follow-up: a diagnostic this broke, and the tests that missed it
CI's tool tests were red after this landed (and already red since the
30 September splash work). Three checks in tools/test-boot-report.py still
described the old fixed-size theme. One of them had found a real regression:
the station's boot report (server.py plymouth_state) decided the bgrt
fallback was "ours" by finding the words Xorkbench audit station in a comment, and
this work reworded that comment — so a stick with our fallback would report it
as Ubuntu's. The splash itself was unaffected (plymouth ignores comments); the
diagnosis was wrong.
Fixed by keying on what actually makes the fallback ours —
ImageDir=/usr/share/plymouth/themes/station, where Ubuntu's draws from
themes/spinner — still accepting the old marker for earlier sticks. The
outdated checks now test the fluid theme, the fallback's per-mode groups, and
that the layer copy carries the shutdown artwork; a new check proves the
fallback the builder writes is recognised as ours by the boot report, so the
two cannot drift apart again. 59/59, and all 31 Python tool tests pass. The
sticks pick the fix up on their next sync (server.py); the layer needs no
change.
Open questions
- Version match — resolved, and it mattered. The stick is Ubuntu 24.04.2.
Its initramfs's
libply.so.5,libply-splash-core.so.5andlibply-splash-graphics.so.5are byte-identical to plymouth 24.004.60-1ubuntu7 and differ from 7.1 and 7.2. Thescript.sofirst vendored came from 7.2 and would most likely have fallen back; it was replaced with the ubuntu7 build before deploying, and the render test was re-run with plymouth pinned to ubuntu7 (all 16 renders clean). The same check confirmedusr/lib/x86_64-linux-gnu/plymouth/is a real directory holdingtwo-step.so,lib -> usr/libis a symlink (so the bare-file rule was the right call), every libraryscript.soneeds is present, anddefault.plymouth -> bgrt/bgrt.plymouth, so the fallback really is next in line. A stick built from a different ISO must be re-checked —vendor/plymouth/SOURCE.txtsays how. - Bench boot — done on the Dell. Worth one look on a 1080p machine when
one is on the bench. To undo from Windows: rename the two
.prevfiles back over the live names. - The paint-out under the bar is smooth rather than textured; it is almost entirely covered by the live bar. Worth a look on a real panel.
A published copy. Commit references and internal identifiers have been removed and the operator is not named; the engineering, the counts and the stated limits are unchanged.