Skip to content
The record
Report1 October 20266 min read

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.

systemsmethod

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, -Xhc and block size match the original, root is drwxr-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.so removed, 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

  1. Version match — resolved, and it mattered. The stick is Ubuntu 24.04.2. Its initramfs's libply.so.5, libply-splash-core.so.5 and libply-splash-graphics.so.5 are byte-identical to plymouth 24.004.60-1ubuntu7 and differ from 7.1 and 7.2. The script.so first 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 confirmed usr/lib/x86_64-linux-gnu/plymouth/ is a real directory holding two-step.so, lib -> usr/lib is a symlink (so the bare-file rule was the right call), every library script.so needs is present, and default.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.txt says how.
  2. 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 .prev files back over the live names.
  3. 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.