3
Flashing
Felis edited this page 2026-09-27 00:29:40 +08:00
Table of Contents
include_toc
include_toc
true

Flash disk images with fds-flash

Home · Real hardware · Workstation tools · Release verification

fds-flash creates, inspects and installs FDS disk images from a Linux workstation. It has an interactive guide and an unattended mode for scripts. It validates the image, checks the destination, writes it, flushes it, reads the written bytes back, and relocates the backup GPT automatically when the destination is larger. You no longer need the manual dd, cmp and sfdisk installation sequence. Cartridge images and the internal disk use the same flashing command. The workstation image operations previously documented as fds-burn create and fds-burn inspect are now available under fds-flash. The workstation package does not install a separate fds-burn; the Pi retains that service worker and its bay-control interface.

Install the tool

From the OS checkout on Arch Linux:

make workstation
make workstation-install
fds-flash --help

The fds-tools package includes fds-flash, fds-cartridge and fds-emulator. For another Linux distribution, or to use the checkout without installing:

make workstation-binaries
out/workstation/fds-flash --help

Build as an ordinary user. Physical disk writes require root. The examples below use the checkout binary; substitute fds-flash after package installation. Inspection and flashing add no crates or external runtime utilities: they use the existing Clap, serde/JSON, SHA-256 and libc dependencies plus Linux block-device APIs. The Python commands in the scripting example only read saved JSON; Python is not required by the installed flasher.

Image creation uses e2fsprogs for DATA and erofs-utils for ENVIRONMENT/SYSTEM. These optional package dependencies supply the filesystem builders and checkers:

sudo pacman -S --needed e2fsprogs erofs-utils

fds-flash create calls those native utilities in /usr/bin; it does not use fds-cartridge's optional Void tool runners. Creating DATA or ENVIRONMENT images from your own files runs as an ordinary user. A prepared SYSTEM root can require privileges to read protected files and preserve its intended ownership.

Choose the correct image

Image Destination
out/fds-internal.img Whole microSD card through a reader; also usable for legacy NVMe
out/fds-system-cli.img Whole USB SYSTEM cartridge
out/fds-system-development.img Alternative SYSTEM cartridge with development tools
A complete PROGRAM, DATA or ENVIRONMENT .img Its separate cartridge disk

Versioned release filenames are supported too. First verify downloaded releases with a separately trusted key as described in Release verification. The flasher's SHA-256 check detects changed input; it does not authenticate a publisher. PROGRAM images should also pass fds-cartridge inspect to check software metadata and installed program trees before deployment.

Use an uncompressed, complete FDS GPT disk image. The raw fds-boot.img and fds-recovery.img partition payloads, compressed archives, arbitrary operating system images and 4 KiB logical-sector destinations are rejected. A disk must have 512-byte logical sectors and enough space for the complete image. The internal image is about 1.75 GiB and fits on a 32 GB SD card. The remaining capacity stays unallocated. A reader may expose it as /dev/sdX; a native host slot may use /dev/mmcblkN. Select the whole device, never a partition.

Flash a newly rebuilt SYSTEM too when migrating from NVMe: the settings loader lives in SYSTEM as well as recovery. The older loader accepts NVMe only. Use Raspberry Pi Imager, rather than fds-flash, for a separate Raspberry Pi OS maintenance USB.

Create a cartridge image

Creation writes a new regular image file. It does not select or write a disk. Start with a directory containing a valid FDS/CARTRIDGE.TOML manifest and the files to include. For example, create a small DATA image:

mkdir -p out/my-data-tree/FDS
cat >out/my-data-tree/FDS/CARTRIDGE.TOML <<'EOF'
format = 1
[cartridge]
id = "my.data"
name = "My data"
class = "data"
version = "1.0"
[media]
writable = true
EOF
printf 'Hello from DATA\n' >out/my-data-tree/hello.txt
out/workstation/fds-flash create data out/my-data-tree out/my-data.img --size-mib 128
out/workstation/fds-flash inspect out/my-data.img
sudo ./out/workstation/fds-flash --image out/my-data.img

--size-mib sets the DATA filesystem size (default 128 MiB, minimum 32 MiB). The complete image is slightly larger because it includes GPT metadata and alignment. Its filesystem root belongs to FDS UID/GID 1000. The output must be outside the source tree and must not already exist.

For other prepared trees:

out/workstation/fds-flash create environment environment-tree environment.img
out/workstation/fds-flash create system prepared-root system.img

The manifest class must match. ENVIRONMENT needs a read-only manifest with an [activation] profile; see the manifest reference. SYSTEM needs a complete prepared FDS root, including native init, cartridge management and Dasung. Normal OS builds should use make rootfs PROFILE=cli and make system-card PROFILE=cli; the creation command does not install packages.

Build new PROGRAM cartridges with fds-cartridge, then inspect or flash the resulting complete image with fds-flash. create program is intentionally unavailable: new software uses installed Void package trees, while existing legacy images remain readable and flashable.

Append --json to any create command for a result with status: "created". inspection contains the image path, geometry and SHA-256. Progress and utility diagnostics go to stderr; creation failures return a nonzero exit status.

Interactive mode

Keep the Pi powered off while preparing its boot SD. Connect only the intended installation media, then launch the guide:

sudo ./out/workstation/fds-flash
  1. Enter the image's path.
  2. Select a disk by its model, serial and capacity. Mounted or otherwise occupied disks are marked BLOCKED. There is no default disk selection.
  3. Review the image type, partitions, SHA-256, destination and target ID.
  4. Type the exact ERASE /dev/... phrase shown. Any other response cancels.
  5. Wait for VERIFIED. Errors or interruption return a nonzero exit status.

You can provide either path in advance. With both supplied, the command still shows the plan and requires terminal confirmation:

sudo ./out/workstation/fds-flash \
  --image out/fds-internal.img \
  --device /dev/disk/by-id/REPLACE_WITH_THE_SD_DISK

Replace the destination with the verified whole-disk path, without a -partN suffix. The confirmation uses the resolved kernel path shown in the review. Repeat for SYSTEM with its image and a different disk:

sudo ./out/workstation/fds-flash \
  --image out/fds-system-cli.img \
  --device /dev/disk/by-id/REPLACE_WITH_THE_SYSTEM_DISK

Every confirmed write replaces the destination's partition table and the image region. Reinstalling an internal disk also replaces its saved machine settings. The tool does not automatically unmount filesystems, disable swap or select an alternative disk. Release the intended disk yourself and rerun if it is blocked.

Inspect without writing

Inspect an image without a connected disk or root privileges:

out/workstation/fds-flash inspect out/fds-internal.img
out/workstation/fds-flash inspect out/my-data.img --json

This checks the same geometry and basic filesystem signatures as flashing and reports the complete image's SHA-256. JSON uses status: "inspected" and the same inspection object as creation. It accepts internal and all cartridge image classes. For PROGRAM software contents, also use fds-cartridge inspect.

List physical disks and reasons they are unavailable:

out/workstation/fds-flash list
out/workstation/fds-flash list --json

To inspect an image and destination together, without opening the disk for writing:

out/workstation/fds-flash \
  --image out/fds-internal.img \
  --device /dev/disk/by-id/REPLACE_WITH_THE_SD_DISK \
  --dry-run --json >out/sd-plan.json

Read the saved plan. plan.target contains model, serial, capacity, logical-sector size, canonical device path, host boot ID and kernel insertion identity. plan.target_id binds those fields together. plan.sha256 identifies the complete source image. After a host reboot, or if the device is reinserted, replaced, resized or otherwise changes identity, obtain and review a new plan.

Unattended mode

Unattended writes require all of --image, --device, --expect-target and --sha256. There is no bare --yes that can silently accept a newly discovered disk. Obtain the expected values from a reviewed dry run or from your controlled provisioning inventory, then pass them explicitly:

sudo -n ./out/workstation/fds-flash \
  --image out/fds-internal.img \
  --device /dev/disk/by-id/REPLACE_WITH_THE_SD_DISK \
  --unattended \
  --expect-target TARGET_ID_FROM_THE_REVIEWED_PLAN \
  --sha256 EXPECTED_IMAGE_SHA256 \
  --json >out/sd-flash.json

Replace both hash placeholders with their full 64 hexadecimal characters. sudo -n refuses to ask for a password, so privileges must already be available. A script already running as root can invoke the binary directly. The flasher never prompts or invokes sudo in unattended mode.

For an already reviewed out/sd-plan.json, this complete shell example loads the bindings without adding a JSON utility dependency:

fds_target_id=$(python3 -c 'import json; print(json.load(open("out/sd-plan.json"))["plan"]["target_id"])')
fds_sha256=$(python3 -c 'import json; print(json.load(open("out/sd-plan.json"))["plan"]["sha256"])')
sudo -n ./out/workstation/fds-flash \
  --image out/fds-internal.img \
  --device /dev/disk/by-id/REPLACE_WITH_THE_SD_DISK \
  --unattended --expect-target "$fds_target_id" --sha256 "$fds_sha256" \
  --json >out/sd-flash.json

A successful JSON response has status: "verified"; a dry run has status: "dry_run". Progress and diagnostics go to stderr. Exit status 0 means success for the requested operation; status 2 means an error, rejected input or cancelled confirmation. Treat a killed/interrupted process as a failed flash and reflash before using that disk. Check the process exit status before reading an output file; failures may leave that redirected file empty.

What verification covers

Before writing, the tool checks GPT headers and tables, partition bounds, expected FDS labels/types, basic filesystem signatures, image size/hash and target identity. It rejects partitions, virtual/loop/device-mapper destinations, mounted disks or children, swap, kernel holders, read-only media and unsupported sector sizes. The destination is opened exclusively and checked again through block-device ioctls before the first write. A target replacement or changed source aborts.

Readback compares the written image region and relocated backup GPT, after flushing and invalidating the kernel block cache. Partition starts, sizes and filesystem contents stay unchanged. Unallocated capacity is not expanded or securely erased. Success also requires the kernel to reload the partition table.

After an internal installation, check FDS_BOOT, FDS_RECOVERY and FDS_INTERNAL with lsblk -o NAME,PARTLABEL,FSTYPE,MOUNTPOINTS. If udev is still processing partition events, run udevadm settle before checking labels. SYSTEM should show FDS_SYSTEM. Unmount any filesystems the desktop mounted after completion, safely disconnect the media, and continue with first boot.

Disposable tests and hardware acceptance

--file-target is only for existing disposable regular files, as an ordinary user. It is never inferred from a path, and root use of this test mode is rejected. make flash-test requires e2fsprogs and covers DATA creation, independent filesystem readback, inspection without a disk, the public CLI, interactive terminal confirmation, unattended bindings, cancellation, changed inputs, corruption, and exact/larger destinations. make flash-vm-test also exercises block-device writes and failures on isolated virtual NVMe/USB disks. These tests do not write host block devices. make clean recognizes these generated test directories and retains their latest recorded results.

Physical acceptance remains separate: use a spare drive, record its model, serial and logical-sector size, retain the dry-run and verified JSON plus image hash, safely disconnect/reconnect it, and check its GPT and payloads independently. Then perform the real-hardware boot procedure. Drive firmware flush behavior, USB enclosure recovery and physical power-loss behavior remain unverified until measured on that hardware.

Software validation record

The 2026-09-24 run passed make flash-vm-test, including native CLI and transfer unit tests, real pseudo-terminal interaction, disposable-file writes, inspection of the current internal/SYSTEM build images, and isolated ARM NVMe/USB writes. The VM checks covered the full wizard, unattended operation, kernel partition refresh, reinstallation, mounted/partition/read-only/4 KiB rejection, USB replacement, and injected write, flush and readback errors. These are software and virtual-disk results; they do not establish physical durability.

The existing workstation software-image tests, twelve-bay emulator lifecycle checks and make check also passed. Source and binary hashes, the latest test record paths and package-validation evidence are recorded locally in out/manifests/fds-flash-validation.json. Detailed logs are in out/logs/flash-validation-final.log and out/logs/flash-workstation-regression.log. The existing frozen 0.1.0 release was not modified.

The subsequent workstation-command consolidation was checked with the actual native fds-flash: DATA creation, inspection without a target, unattended file flashing, independent ext4 content readback, and rejection of invalid or existing outputs. ENVIRONMENT and prepared SYSTEM creation were checked with real EROFS filesystems and independent extraction, using project-local filesystem utilities inside a private namespace. The small SYSTEM source was an inert test fixture; this check does not establish bootability. The ARM virtual NVMe/USB flashing and failure checks also passed after the shared inspection refactor. Evidence for this revision is recorded in out/manifests/fds-flash-consolidation-validation.json.

Linux interface reference: kernel block-device ABI.