Building guest images
Boxdesk uses minimal Alpine Linux guest images containing only the components
required to run workloads and the static guest agent. Guest rootfs trees are built without root
privileges, and --verify builds one twice and compares the two trees byte for byte.
A tree to boot right away (cargo xtask init)
Section titled “A tree to boot right away (cargo xtask init)”cargo xtask init puts the pinned Alpine minirootfs and the static guest agent where boxdesk resolves
a root (--root, then $BOXDESK_GUEST_ROOT, then ~/.local/share/boxdesk/rootfs), with the /results
mount point and a resolver beside them. It runs on either platform: the base is a tarball and the
agent is a static musl build, so neither step needs apk.
cargo xtask init # this host's arch, into boxdesk's default rootcargo xtask init --root DIR --force # somewhere else, replacing a tree already therecargo xtask dist writes the same tree for this host’s guest and packs it into the release
artifact (rootfs.tar.gz under Boxdesk.app/Contents/Resources, or under share/boxdesk in the
Linux tarball), which install.sh unpacks to that default root. A release is whole without a
checkout.
It is a fixture, not the image: no runtimes, no locked closure, no reproducibility claim, and the
tree carries the invoking user’s ownership rather than 0:0. --force refuses a directory that
does not already look like a guest tree, so it cannot be pointed at a home directory.
Building rootfs trees (cargo xtask build-rootfs)
Section titled “Building rootfs trees (cargo xtask build-rootfs)”Image builds are orchestrated through cargo xtask build-rootfs.
cargo xtask build-rootfs # minimal guest image (artifacts/rootfs-guest)cargo xtask build-rootfs --desktop # desktop image (artifacts/rootfs-desktop)cargo xtask build-rootfs --arch aarch64 # target another architecturecargo xtask build-rootfs --ml # the ML scaffold (artifacts/rootfs-ml)The builder is Linux, either architecture: what runs during a build is apk.static, a Linux ELF,
with fakeroot beside it. --arch picks the guest’s architecture independently of the
builder’s, because the install runs --no-scripts and nothing from the closure executes.
Unprivileged rootfs assembly
Section titled “Unprivileged rootfs assembly”Guest rootfs trees are constructed on Linux without requiring root or Docker:
apk.static: Alpine’s static package manager fetches and extracts.apkpackages into the staged tree (--no-scripts).fakeroot: Wraps file creation so files inartifacts/rootfs-guestare owned byuid 0(root) in the filesystem metadata rather than the builder’s user ID. That is what lets two builds on different machines produce identical trees and hashes, which--verifychecks by building twice.- Cross-architecture builds: Because package installation extracts static archives without
executing guest scripts, a Linux builder of either architecture (
x86_64oraarch64) can assemble an image for the other architecture.
Image closures
Section titled “Image closures”Minimal guest closure (artifacts/rootfs-guest)
Section titled “Minimal guest closure (artifacts/rootfs-guest)”The default sandbox image includes:
- Alpine Linux base packages (musl libc, busybox, standard POSIX utilities).
- The runtimes
GUEST_PACKAGESnames inxtask/src/rootfs.rs:python3andnodejs. guest-agent: The static musl Rust binary baked at/usr/local/bin/guest-agent.
Desktop guest closure (artifacts/rootfs-desktop)
Section titled “Desktop guest closure (artifacts/rootfs-desktop)”The desktop sandbox image adds graphical and terminal session support for --display runs:
cage: A minimal Wayland kiosk compositor based on wlroots.foot: A fast, lightweight Wayland terminal emulator.seatdandeudev: Seat management and device node creation inside the guest.xkeyboard-configandfont-dejavu: The keymaps xkbcommon reads, and one font. No Mesa driver: the session renders with pixman.boxdesk-session: Not a package but a program the build writes to/usr/local/bin/boxdesk-session, which launchesseatd, startscage, and runsfootin it.
ML guest closure (artifacts/rootfs-ml)
Section titled “ML guest closure (artifacts/rootfs-ml)”--ml names a third closure: llama.cpp, mesa-vulkan-virtio (the Venus ICD), vulkan-tools, and
python3 with numpy for a CPU baseline. It is a scaffold: its package names are unverified, it has
no lockfile, and no host has built it, so it is not in the set cargo xtask vendor mirrors. It
joins that set with its first lockfile.
Reproducibility and lockfiles
Section titled “Reproducibility and lockfiles”To ensure deterministic builds across hosts, package closures are locked:
- Lockfiles:
xtask/rootfs-packages.x86_64.lockrecords the exact package versions and SHA-256 hashes (with per-architecture lockfiles generated on build). - Verification:
cargo xtask build-rootfs --verifybuilds the image twice, asserting that the staged trees match byte-for-byte and that package versions match the lockfile. - Updating pins:
cargo xtask build-rootfs --update-lockre-pins the package closure when Alpine package versions update upstream.
Offline vendoring (cargo xtask vendor)
Section titled “Offline vendoring (cargo xtask vendor)”For offline or air-gapped dev environments, cargo xtask vendor downloads all sha-pinned upstream
archives (Alpine base tarballs, apk.static, and .apk package closures) into a local vendor/
mirror directory.
cargo xtask vendor --verifychecks the local vendor mirror against its hash manifest without making network calls.