# Build software cartridges and use the emulator This walkthrough runs on a Linux workstation. It builds applications from Void source packages, creates a complete cartridge image, runs it in FDS, and explains how to write it to USB. Commands run from the FDS checkout root unless marked as guest commands. ## Prepare the tools The native tools are `fds-cartridge` and `fds-emulator`. Build them with Rust, a C linker and Make; the repository selects its pinned Rust toolchain: ```sh make workstation mkdir -p out export PATH="$PWD/out/workstation:$PATH" ``` | Prerequisite | Used for | | --- | --- | | Prepared writable Void `void-packages` checkout and native XBPS utilities | Build source packages and install their AArch64 runtime dependencies | | `mkfs.erofs`, `fsck.erofs` | Create and inspect read-only payload filesystems | | `bwrap` | Inspect filesystems and install package trees in a user namespace | | `xz` | Read older software cartridges and package/source archives | | `qemu-system-aarch64`, `qemu-img` | Run the emulator and create writable DATA overlays | Install tools through your Linux distribution's package manager. Enable unprivileged user namespaces for bubblewrap. Software builds run as an ordinary user and execute trusted Void build templates on the workstation. Run source builds sequentially when they share the same Void checkout. Prepare a Void source checkout using its [upstream instructions](https://github.com/void-linux/void-packages/blob/02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1/README.md). Complete `xbps-src binary-bootstrap` for the workstation architecture before using it. Keep this writable checkout outside Git submodules. The workstation itself need not run Void. Select that checkout with `--void-packages /path/to/void-packages`; native `xbps-*` tools must be in PATH, or selected with `--xbps-bin /path/to/bin`. Builds target AArch64/glibc. For the complete FDS build workstation, [bootstrap](getting-started.md) already keeps `vendor/void-packages` as a clean upstream reference and prepares the writable `.host/void-packages` checkout plus `.host/xbps/usr/bin`. The native builder defaults to `.host/void-packages`. Reuse its local tools: ```sh ./tools/prepare-image-tools ./tools/in-void xbps-install -y qemu-img fds-cartridge --image-tool-runner tools/in-image-tools doctor fds-emulator doctor --qemu-runner tools/in-void ``` An optional runner receives a utility name followed by its arguments. Image runners select EROFS utilities; XBPS runners provide the package-installation namespace. Without runners, the tools use native utilities and bubblewrap. ## Build the example cartridge The repository includes hello and system-report Void source packages. Each software recipe names a package, its source-template directory and its public commands. A cartridge recipe groups those software recipes into payloads. With native tools and a prepared checkout: ```sh fds-cartridge --void-packages /path/to/void-packages \ create examples/software/cartridge.toml out/demo-tools.img fds-cartridge inspect out/demo-tools.img ``` With the project-local Void and image tools, use this equivalent command: ```sh fds-cartridge --image-tool-runner tools/in-image-tools \ --xbps-tool-runner tools/in-void --xbps-bin "$PWD/.host/xbps/usr/bin" \ create examples/software/cartridge.toml out/demo-tools.img fds-cartridge --image-tool-runner tools/in-image-tools inspect out/demo-tools.img ``` Choose one route. Output paths must be new. The command builds each package with `xbps-src`, installs the package and runtime dependencies into private staging trees, verifies their architecture and integrity, and writes the complete image. Source build logs go to standard error; the final result is JSON. The example image contains: | Partition | Contents | | --- | --- | | 1: `FDS_METADATA` | Cartridge identity and software catalogue | | 2: `FDS_PAYLOAD02` | `programs/demo.hello/`, including `usr/bin/hello` and libraries | | 3: `FDS_PAYLOAD03` | `programs/demo.report/`, including `usr/bin/report` and dependencies | There are no new xz software bundles. Programs are ready to run directly from EROFS. Inspection validates both GPT tables, filesystem contents and software hashes, then prints partition and command details as JSON. ## Build a reusable installed-software directory To separate package compilation from cartridge assembly: ```sh fds-cartridge --xbps-tool-runner tools/in-void \ --xbps-bin "$PWD/.host/xbps/usr/bin" \ software build examples/software/hello/software.toml out/demo-hello fds-cartridge software inspect out/demo-hello ``` The output contains `software.toml` and `root/`. A cartridge's `sources` list can name this directory instead of a source recipe. For example: ```toml format = 2 id = "my.tools" name = "My tools" version = "1.0" [[payload]] sources = ["../out/demo-hello"] ``` Paths are relative to the cartridge recipe. Put several sources in one list to share a partition, or add `[[payload]]` sections for separate partitions. See [the format reference](software-format.md) for complete source metadata and limits. ## Start FDS Build matching boot inputs using [Build and start FDS](getting-started.md), or copy a kernel, initramfs and SYSTEM image from an FDS build machine. ```sh fds-emulator --session out/my-emulator start --qemu-runner tools/in-void ``` Omit `--qemu-runner` when QEMU is installed directly on the workstation. To use copied boot images: ```sh fds-emulator --session out/my-emulator start \ --kernel /path/to/kernel_2712.img \ --initramfs /path/to/fds-initramfs.img \ --system /path/to/fds-system-cli.img ``` The session directory must be new and private. Keep its absolute path short (under 90 bytes) for Unix sockets. Start waits for the FDS prompt and uses two emulated CPUs and 1024 MiB RAM; `--memory-mib 2048` increases memory. SYSTEM and PROGRAM images are read-only. The session holds logs and any DATA overlays. ## Insert and run software From the workstation: ```sh fds-emulator --session out/my-emulator insert 01 out/demo-tools.img fds-emulator --session out/my-emulator guest -- fds bay 01 fds-emulator --session out/my-emulator guest -- hello 'Hello from the emulator' fds-emulator --session out/my-emulator guest -- fds run 01 -- demo.report:report ``` Discovery is asynchronous. Wait until the bay reports `MOUNTED READ ONLY` before running commands. Direct commands return their output and exit status to the caller. `fds run` starts a background job; read its output with: ```sh fds-emulator --session out/my-emulator guest -- tail -20 /run/log/cartridged/current ``` Open an interactive shell with `fds-emulator --session out/my-emulator console`. At `FDS>`, use `hello`, `report`, `fds bays` or other guest commands. **Ctrl-]** detaches without stopping the VM. Detach before using `guest`, `eject` or ordinary `stop`, since they also need the serial console. All twelve virtual bays accept images. Simultaneously inserted PROGRAM cartridges must have distinct cartridge IDs. Duplicate identities produce a guest error. Command-name collisions are resolved as described in [Using cartridges](cartridges.md). ## Eject and stop ```sh fds-emulator --session out/my-emulator eject 01 fds-emulator --session out/my-emulator status fds-emulator --session out/my-emulator stop ``` Eject asks FDS to stop programs and release storage before removing the virtual USB device. If FDS refuses, the cartridge stays attached. `stop` performs native FDS shutdown. Session logs remain available; create a new session to boot again. `unplug BAY` simulates a physical pull, and `stop --force` cuts virtual power. Use these only for deliberate failure simulation; DATA writes can be lost. ## Save emulator DATA Inserting a DATA image creates a writable `data--.qcow2` overlay in the session directory. Eject retains it. Reinserting the original image creates a fresh overlay. To export the changed contents after eject or shutdown: ```sh qemu-img convert -f qcow2 -O raw /path/to/session/data-02-ID.qcow2 out/saved-data.img ``` Keep its backing image at the recorded path until export completes. Never convert an overlay while a running VM is using it. Personal session directories are preserved by `make clean`; keep them under your own names, such as `my-emulator`. ## Write a cartridge to USB Identify the intended whole USB drive, unmount its partitions, then preview: ```sh fds-cartridge preview out/demo-tools.img /dev/sdX out/usb-preview.json ``` Replace `/dev/sdX` with the actual destination. The JSON identifies the drive, capacity, image hash and exact confirmation phrase. Check those values before writing: this replaces the selected drive's contents. ```sh sudo /absolute/path/to/fds-cartridge write out/usb-preview.json \ --confirm 'COPY THE EXACT confirmation VALUE FROM THE PREVIEW' ``` The writer rechecks identity, writes the entire disk image, flushes it and verifies readback. On a larger drive it moves the backup GPT to the end. A changed image or reinserted drive requires a fresh preview. Use the image-tool runner for preview and inspection if your EROFS utilities are in the local container. To rehearse with a disposable regular file: ```sh truncate -s "$(stat -c %s out/demo-tools.img)" out/disposable-usb.img fds-cartridge preview out/demo-tools.img out/disposable-usb.img out/file-preview.json --file-target fds-cartridge write out/file-preview.json --confirm 'COPY THE EXACT confirmation VALUE' ``` See [troubleshooting](troubleshooting.md) for failed builds, namespace errors, busy consoles, invalid media and blocked ejects.