Skip to content
v0.0.1

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.

Terminal window
cargo xtask init # this host's arch, into boxdesk's default root
cargo xtask init --root DIR --force # somewhere else, replacing a tree already there

cargo 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.

Terminal window
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 architecture
cargo 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.

Guest rootfs trees are constructed on Linux without requiring root or Docker:

  • apk.static: Alpine’s static package manager fetches and extracts .apk packages into the staged tree (--no-scripts).
  • fakeroot: Wraps file creation so files in artifacts/rootfs-guest are owned by uid 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 --verify checks by building twice.
  • Cross-architecture builds: Because package installation extracts static archives without executing guest scripts, a Linux builder of either architecture (x86_64 or aarch64) can assemble an image for the other architecture.

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_PACKAGES names in xtask/src/rootfs.rs: python3 and nodejs.
  • 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.
  • seatd and eudev: Seat management and device node creation inside the guest.
  • xkeyboard-config and font-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 launches seatd, starts cage, and runs foot in it.

--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.

To ensure deterministic builds across hosts, package closures are locked:

  • Lockfiles: xtask/rootfs-packages.x86_64.lock records the exact package versions and SHA-256 hashes (with per-architecture lockfiles generated on build).
  • Verification: cargo xtask build-rootfs --verify builds 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-lock re-pins the package closure when Alpine package versions update upstream.

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 --verify checks the local vendor mirror against its hash manifest without making network calls.