Compare commits

...
6 Commits
Author SHA1 Message Date
felis f4bc28043a Support native SD storage and consolidate fds-flash tooling
Accept native SD cards for internal settings alongside legacy NVMe,
reject USB ancestry and ambiguous disks, and preserve read-only boot
loading with explicit recovery writes. Require built-in MMC drivers,
validate cached kernel configuration, and provide SD-only, SD-first and
USB-first EEPROM profiles.

Expose typed create and inspect commands through fds-flash, reuse the
existing cartridge creation code, and document the optional e2fsprogs
package dependency. Extend storage, EEPROM, flashing and hardware-test
coverage and advance the wiki reference to the published SD guide.

Validation: rootfs checks for CLI/development, boot matrix, internal
storage, EEPROM, flash, workstation, emulator, make check and wiki checks
passed. The full internal suite passed on an unchanged rerun after one
unexplained VM shutdown stall. Standalone init-test was blocked by its
unavailable pinned upstream kernel. Physical Pi checks remain pending.
2026-09-27 00:29:53 +08:00
felis a14ea77145 update fds-flash tool 2026-09-24 14:51:56 +08:00
felis 685b4135df update doc structure 2026-09-24 13:54:28 +08:00
felis 0d42dc5acc update docs 2026-09-24 13:03:52 +08:00
felis f5d9f96409 Package workstation tools, reuse emulator sessions, and derive build versions 2026-09-23 03:45:48 +08:00
felis 6bfcce8070 FDS/OS 1.0 fixes 2026-09-23 03:12:45 +08:00
178 changed files with 3437 additions and 12132 deletions
+3
View File
@@ -2,3 +2,6 @@
path = vendor/void-packages
url = https://github.com/void-linux/void-packages.git
shallow = true
[submodule "fds-os.wiki"]
path = fds-os.wiki
url = gitea@192.168.1.193:felis/fds-os.wiki.git
+11 -6
View File
@@ -1,6 +1,10 @@
# FDS/OS implementation scope
Read `docs/developer/master-plan.md` and `docs/developer/build-host.md` before changing the build.
The authoritative documentation lives in the `fds-os.wiki` submodule. Edit its
flat Gitea Markdown pages directly; do not recreate a parallel manual or export
workflow. Read `fds-os.wiki/Gitea-Wiki.md` for page and link conventions.
Read `fds-os.wiki/Developer-Master-Plan.md` and `fds-os.wiki/Developer-Build-Host.md` before changing the build.
The user has explicitly authorized implementation through M12. Proceed through
the remaining milestones in order, keeping the repository buildable and recording
actual acceptance evidence. Defer tests that require the physical Raspberry Pi
@@ -12,7 +16,7 @@ do not fill them with placeholder implementations that claim to work.
The user explicitly authorized importing the existing Paperlike 13K `dasungd`
into the base system. Its Rust workspace member, static ARM package, base package
selection, and native s6 service are required throughout M0–M12. Keep it in the base boot bundle, independent of
GUI or ENVIRONMENT cartridges. Read `docs/dasung.md` before changing it; do not
GUI or ENVIRONMENT cartridges. Read `fds-os.wiki/Dasung.md` before changing it; do not
claim Pi boot or physical power-cycle recovery is verified.
- Use English for all project-owned documentation, comments, diagnostics, task
@@ -23,7 +27,8 @@ claim Pi boot or physical power-cycle recovery is verified.
commands and options with typed parsers; do not hand-parse argument vectors.
- No systemd, runit, BusyBox, shell PID1, boot-time cache compilation, or sleeps
to hide races in the target system.
- Keep upstream Void files unchanged; pin its submodule and use `packages/` overlays.
- Keep `vendor/void-packages` clean and pinned. Use `.host/void-packages` for
generated build state; keep authoritative FDS overlays in `packages/`.
- Explain every new dependency and keep documentation accurate.
- Run `make bootstrap`, `make smoke-test`, and `make check` for M0 build changes.
Report skips and missing hardware honestly. Do not invent boot benchmarks.
@@ -33,10 +38,10 @@ claim Pi boot or physical power-cycle recovery is verified.
# Workstation software and emulator follow-up
Read `docs/workstation.md`, `docs/software-format.md` and
`docs/developer/workstation-tooling-plan.md` for this extension. Software builds and new
Read `fds-os.wiki/Workstation.md`, `fds-os.wiki/Software-Format.md` and
`fds-os.wiki/Developer-Workstation-Tooling-Plan.md` for this extension. Software builds and new
PROGRAM cartridge creation run on generic Linux workstations. The public native
Clap tools are `fds-cartridge` and `fds-emulator`. Software images have GPT
Clap tools are `fds-cartridge`, `fds-emulator` and `fds-flash`. Software images have GPT
metadata partition 1 plus m EROFS payload partitions containing installed Void
package trees. Build source templates with xbps-src on the workstation; do not
create new xz software bundles or extract programs at guest launch. Keep
Generated
+13 -13
View File
@@ -236,7 +236,7 @@ dependencies = [
[[package]]
name = "dasungd"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"anyhow",
"clap",
@@ -320,7 +320,7 @@ dependencies = [
[[package]]
name = "fds-boottrace"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"clap",
"fds-common",
@@ -330,7 +330,7 @@ dependencies = [
[[package]]
name = "fds-burn"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"clap",
"fds-common",
@@ -342,7 +342,7 @@ dependencies = [
[[package]]
name = "fds-cartridged"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"clap",
"fds-burn",
@@ -355,7 +355,7 @@ dependencies = [
[[package]]
name = "fds-cli"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"clap",
"fds-burn",
@@ -366,7 +366,7 @@ dependencies = [
[[package]]
name = "fds-common"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"libc",
"serde",
@@ -376,7 +376,7 @@ dependencies = [
[[package]]
name = "fds-control"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"clap",
"fds-common",
@@ -387,7 +387,7 @@ dependencies = [
[[package]]
name = "fds-release"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"clap",
"ed25519-dalek",
@@ -401,11 +401,11 @@ dependencies = [
[[package]]
name = "fds-smoketest"
version = "0.1.0"
version = "1.0.0"
[[package]]
name = "fds-software"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"fds-common",
"libc",
@@ -417,7 +417,7 @@ dependencies = [
[[package]]
name = "fds-stage0"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"clap",
"fds-common",
@@ -427,7 +427,7 @@ dependencies = [
[[package]]
name = "fds-test-helpers"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"clap",
"libc",
@@ -435,7 +435,7 @@ dependencies = [
[[package]]
name = "fds-workstation"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"clap",
"fds-burn",
+4
View File
@@ -11,6 +11,10 @@ members = [
"tests/helpers",
]
[workspace.package]
# Cargo requires three numeric components; public build versions come from Git.
version = "1.0.0"
[workspace.dependencies]
clap = { version = "4", features = ["derive"] }
+37 -6
View File
@@ -7,11 +7,16 @@ SHELL := /bin/bash
.PHONY: help bootstrap smoke-test check dasung dasung-test rootfs rootfs-test init-test vm tooling tooling-test kernel initramfs system-card boot-volume boot-test console-test performance-test
help:
@printf '%s\n' 'FDS/OS — static Rust tools and native s6' \
'make wiki-check Check the documentation in fds-os.wiki' \
'make clean-preview Show obsolete build/test directories and Rust output selected for cleanup' \
'make clean Remove that output; keep current images, latest tests, caches and releases' \
'make clean-test Test cleanup protections using small disposable fixtures' \
'make workstation Build native Linux software/cartridge and QEMU command-line tools' \
'make workstation Build the Arch fds-tools package' \
'make workstation-install Install the Arch fds-tools package with pacman' \
'make workstation-binaries Build native binaries on generic Linux' \
'make workstation-test Verify software builds, multi-partition images and file write/readback' \
'make flash-test Verify guided/unattended flashing with disposable files' \
'make flash-vm-test Verify block-device flashing and rejection paths in an isolated ARM VM' \
'make emulator-test Boot FDS and test cartridge hotplug with the public emulator' \
'make bootstrap Prepare Arch x86_64 host and Void build container' \
'make packages Build and export every FDS base package' \
@@ -44,7 +49,7 @@ help:
'make recovery Build the independent recovery rootfs and EROFS payload' \
'make recovery-test Verify SYSTEM-independent recovery and DATA repair in ARM VMs' \
'make internal-image Assemble FDS_BOOT, FDS_RECOVERY and machine settings as one GPT disk' \
'make internal-test Verify virtual NVMe settings, persistence and rejection paths' \
'make internal-test Verify virtual SD/NVMe settings, persistence and rejection paths' \
'make development-test Compile, execute and debug C/C++/Rust in the development SYSTEM' \
'make clock-test Verify that native startup preserves a newer guest clock' \
'make signing Build the host release signer and static ARM verifier' \
@@ -54,6 +59,10 @@ help:
'make init-test Boot an ARM VM and verify s6 PID 1 and service control' \
'make vm Open a temporary root shell in the verified test VM'
.PHONY: wiki-check
wiki-check:
python3 tests/integration/wiki-checks.py
clean-preview:
./tools/clean-builds --dry-run
@@ -90,7 +99,8 @@ smoke-test:
@mkdir -p out/logs
./tools/smoke-test 2>&1 | tee out/logs/smoke-test.log
check:
check: wiki-check
python3 tests/integration/version-checks.py
./tests/integration/m0-checks
cargo fmt --all -- --check
$(MAKE) clean-test
@@ -216,6 +226,7 @@ internal-image:
./image/build-internal $(if $(MACHINE_CONFIG),--machine-config "$(MACHINE_CONFIG)") 2>&1 | tee out/logs/internal-build.log
internal-test:
cargo test --locked --offline --target x86_64-unknown-linux-gnu -p fds-cli
python3 tests/integration/m12-internal.py 2>&1 | tee out/logs/internal-checks.log
.PHONY: development-test clock-test
@@ -241,14 +252,34 @@ release-contract-test:
@mkdir -p out/logs
python3 tests/integration/m12-release-build.py 2>&1 | tee out/logs/release-contract-checks.log
.PHONY: workstation workstation-test emulator-test
.PHONY: workstation workstation-install workstation-binaries workstation-package-test workstation-test emulator-test
workstation:
@mkdir -p out/logs
./tools/build-workstation 2>&1 | tee out/logs/workstation-build.log
workstation-test: workstation
workstation-package-test: workstation
python3 tests/integration/arch-package.py
workstation-install: workstation
./tools/install-workstation
workstation-binaries:
./tools/build-workstation-binaries
workstation-test: workstation-binaries
cargo test --locked --offline --target $$(rustc -vV | sed -n 's/^host: //p') -p fds-common -p fds-burn -p fds-software -p fds-workstation
python3 tests/integration/workstation-flash.py --cli out/workstation/fds-flash
python3 tests/integration/workstation-images.py --cli out/workstation/fds-cartridge --image-tool-runner tools/in-image-tools --xbps-tool-runner tools/in-void --xbps-bin "$(CURDIR)/.host/xbps/usr/bin" 2>&1 | tee out/logs/workstation-images.log
emulator-test: workstation
emulator-test: workstation-binaries
python3 tests/integration/workstation-emulator.py --cli out/workstation/fds-emulator --qemu-runner tools/in-void 2>&1 | tee out/logs/workstation-emulator.log
.PHONY: flash-test flash-vm-test
flash-test: workstation-binaries
cargo test --locked --offline --target $$(rustc -vV | sed -n 's/^host: //p') -p fds-workstation flash::
python3 tests/integration/workstation-flash.py --cli out/workstation/fds-flash
flash-vm-test: flash-test
./tools/cargo-build --locked --offline --release --target aarch64-unknown-linux-musl -p fds-workstation --bin fds-flash
./tools/verify-elf target/aarch64-unknown-linux-musl/release/fds-flash aarch64 static
python3 tests/integration/workstation-flash-vm.py
+19 -68
View File
@@ -1,73 +1,24 @@
# FDS/OS
FDS/OS is a Linux operating system for the Felis FP-85, a Raspberry Pi 5 computer
with twelve USB cartridge bays and a Dasung Paperlike 13K display. It combines
Void Linux packages, native s6 services and static Rust system tools with a
simple cartridge workflow: insert software, run it, and eject it when finished.
FDS/OS is a cartridge-based Linux system for the Felis FP-85: Raspberry Pi 5,
twelve USB cartridge bays and a Dasung Paperlike 13K display. It uses Void Linux
packages, native s6 services and static Rust system tools. The Dasung daemon is
included in the base system; WindowMaker is an optional desktop.
## How the system fits together
The **[Gitea wiki](http://gitea.home.arpa/felis/fds-os/wiki/Home)** is the complete,
authoritative manual:
| Component | Purpose |
| --- | --- |
| Internal NVMe | Holds boot files, recovery and persistent machine settings |
| SYSTEM cartridge | Supplies the read-only operating system; swapping it changes the OS |
| PROGRAM cartridge | Supplies applications and their dependencies, ready to execute |
| DATA cartridge | Stores writable files at `/data` |
| ENVIRONMENT cartridge | Selects an installed environment, such as WindowMaker |
- [Project overview](http://gitea.home.arpa/felis/fds-os/wiki/Overview)
- [Run on real hardware, including HDMI + USB wiring](http://gitea.home.arpa/felis/fds-os/wiki/Hardware-Setup)
- [Flash SD and cartridge disks](http://gitea.home.arpa/felis/fds-os/wiki/Flashing)
- [Build the OS and start the emulator](http://gitea.home.arpa/felis/fds-os/wiki/Getting-Started)
- [Build software cartridges and use workstation tools](http://gitea.home.arpa/felis/fds-os/wiki/Workstation)
- [Use cartridges](http://gitea.home.arpa/felis/fds-os/wiki/Cartridges), [DATA](http://gitea.home.arpa/felis/fds-os/wiki/Data) and [the desktop](http://gitea.home.arpa/felis/fds-os/wiki/Desktop)
- [Developer guide and acceptance records](http://gitea.home.arpa/felis/fds-os/wiki/Developer-Guide)
The default interface is a console. WindowMaker provides an optional grayscale
retro desktop and an FDS Control panel for inspecting bays, launching programs
and ejecting cartridges. The same theme is used on ordinary screens and E-Ink.
The Dasung controller is part of the base system and starts independently of
the desktop or any ENVIRONMENT cartridge.
## Choose a starting point
- **Build and try FDS:** follow [Build and start FDS](docs/getting-started.md).
- **Build an application cartridge:** use the [software and emulator walkthrough](docs/workstation.md).
- **Use the computer:** read [Using cartridges](docs/cartridges.md), [DATA](docs/data.md), and [the desktop guide](docs/desktop.md).
- **Install or maintain a machine:** see [internal storage](docs/internal-storage.md), [release verification](docs/releases.md), and [recovery](docs/recovery.md).
- **Free build space:** run `make clean-preview`, then `make clean`; see [cleanup](docs/cleanup.md).
The [user manual](docs/README.md) is organized around tasks. Build internals,
design decisions, hardware procedures and validation records live separately in
[developer notes](docs/developer/README.md).
## A typical software session
These commands run on FDS after inserting the example PROGRAM cartridge in bay 1:
```sh
fds bays
fds bay 01
hello 'Hello from FDS'
fds run 01 -- demo.report:report
fds eject 01
```
`hello` runs in your current terminal. `fds run` starts a managed background
command. Both forms are tracked for eject. Wait for **SAFE** before removing
media; use `fds poweroff` to shut down the whole computer.
## Build tools
Run these from the repository root:
| Command | Result |
| --- | --- |
| `make workstation` | Native Linux `fds-cartridge` and `fds-emulator` executables |
| `make bootstrap` | Prepares the complete OS build environment on x86_64 Arch Linux |
| `make rootfs PROFILE=cli` | Builds the ARM base filesystem and its packages |
| `make system-card PROFILE=cli` | Creates a whole-disk SYSTEM cartridge image |
| `make initramfs` | Creates early boot userspace |
| `make all` | Builds the complete CLI/development, recovery and internal image set |
| `make clean` | Removes obsolete generated workspaces and Rust build output |
New software cartridges are built from metadata and Void source packages using
`xbps-src` on a Linux workstation. Their EROFS partitions contain installed
programs and dependencies directly. FDS does not extract those programs at launch.
The workstation walkthrough includes prerequisites, complete example commands,
expected outputs, emulator lifecycle and the confirmed USB-writing workflow.
Append `--help` to either native tool for its command reference; run `make help`
for the complete build target list.
The same pages live in the `fds-os.wiki` Git submodule. Initialize it in an existing
checkout with `git submodule update --init fds-os.wiki`, or clone this repository
with `--recurse-submodules`. Edit those pages directly; there is no separate manual
to synchronize or export. See [Editing the wiki](http://gitea.home.arpa/felis/fds-os/wiki/Gitea-Wiki)
for the Git workflow and page conventions. `make wiki-check` checks local links;
`make help` lists build commands.
+2 -2
View File
@@ -1,6 +1,6 @@
# Pi 5 development: NVMe first, SD rescue fallback, diagnostic UART enabled.
# Pi 5 development: SD first, USB maintenance fallback, diagnostic UART enabled.
[all]
BOOT_ORDER=0xf16
BOOT_ORDER=0xf41
BOOT_UART=1
NET_INSTALL_ENABLED=0
NET_INSTALL_AT_POWER_ON=0
+8
View File
@@ -0,0 +1,8 @@
# Pi 5 maintenance: boot a Raspberry Pi OS USB drive before the FDS microSD.
[all]
BOOT_ORDER=0xf14
BOOT_UART=1
NET_INSTALL_ENABLED=0
NET_INSTALL_AT_POWER_ON=0
POWER_OFF_ON_HALT=1
WAIT_FOR_POWER_BUTTON=0
+2 -2
View File
@@ -1,6 +1,6 @@
# Pi 5 production: internal NVMe only, no firmware network-install scanning.
# Pi 5 production: native microSD only, no firmware USB/network-install scanning.
[all]
BOOT_ORDER=0xf6
BOOT_ORDER=0xf1
BOOT_UART=0
NET_INSTALL_ENABLED=0
NET_INSTALL_AT_POWER_ON=0
+1 -1
View File
@@ -1,2 +1,2 @@
# No physical bay wiring has been measured yet. Calibrate with fds topology.
# Add explicit USB 2 and USB 3 aliases following docs/cartridges.md.
# Add explicit USB 2 and USB 3 aliases following fds-os.wiki/Cartridges.md.
-25
View File
@@ -1,25 +0,0 @@
# FDS/OS user manual
FDS/OS runs a cartridge-based Linux computer. Start with the overview, then choose
the guide for building software, trying the emulator, or operating the machine.
Commands in workstation guides run on your Linux computer; commands in the
operating guides run at the FDS console unless stated otherwise.
| Guide | What you will learn |
| --- | --- |
| [Overview](../README.md) | How the computer, internal storage and cartridges fit together |
| [Build and start FDS](getting-started.md) | Prepare the OS build workstation, create images and boot the emulator |
| [Software and emulator walkthrough](workstation.md) | Build Void source packages, assemble cartridges, insert them and write USB media |
| [Using cartridges](cartridges.md) | Inspect bays, run applications, resolve command names and eject safely |
| [DATA and files](data.md) | Use persistent storage and manage active DATA |
| [Desktop and control panel](desktop.md) | Open WindowMaker, manage cartridges graphically and customize the theme |
| [Software format](software-format.md) | Write source recipes and understand the installed-program image layout |
| [Internal storage](internal-storage.md) | Install boot/recovery storage and manage machine settings |
| [Recovery](recovery.md) | Inspect failed media and check or repair DATA |
| [Release verification](releases.md) | Verify downloaded images before use |
| [Dasung display](dasung.md) | Inspect and control the built-in Paperlike daemon |
| [Cleanup](cleanup.md) | Reclaim obsolete build output without losing current images or personal data |
| [Troubleshooting](troubleshooting.md) | Diagnose build, cartridge, desktop and emulator problems |
Implementation plans, dependency rationale, measurements, hardware procedures and
historical test results are maintained separately in [developer notes](developer/README.md).
-98
View File
@@ -1,98 +0,0 @@
# Use cartridges and run programs
A bay number identifies a physical slot, from `01` through `12`. It stays tied
to the slot rather than the order in which Linux discovers USB disks.
| Cartridge | Role |
| --- | --- |
| SYSTEM | Supplies the read-only operating system used at boot |
| PROGRAM | Supplies applications and their runtime dependencies |
| DATA | Stores your writable files at `/data` |
| ENVIRONMENT | Requests an installed desktop profile, such as WindowMaker |
## Inspect a bay
Insert the cartridge, then run:
```sh
fds bays
fds bay 01
```
USB discovery takes place asynchronously. `fds bay` shows its state, identity,
mount location, running process count and software catalogue. `fds --json bay 01`
returns structured details, including command aliases. `fds rescan` refreshes
the inventory. No application starts merely because a PROGRAM cartridge is inserted.
| State | Meaning and next action |
| --- | --- |
| EMPTY | No cartridge is detected |
| MOUNTED READ ONLY | Contents are available; PROGRAM commands can run |
| MOUNTED READ WRITE | DATA is active at `/data` |
| SAFE | Storage is released; remove the cartridge |
| PROTECTED | This cartridge supplies the running SYSTEM; shut down before removal |
| UNCONFIGURED | The physical slot needs a bay mapping |
| ERROR / AMBIGUOUS | Read the detail; correct the media or mapping before use |
## Run a software command
For the included hello/report cartridge:
```sh
hello 'Hello from my cartridge'
report
fds run 01 -- demo.hello:hello 'Hello from my cartridge'
```
Direct commands run in the foreground with your terminal, input/output streams,
arguments, working directory and exit code. Pipes and redirection work normally.
`fds run` starts a managed background program and prints its PID; its output is
written to `/run/log/cartridged/current`. Both forms run as the ordinary FDS user
and are tracked for cartridge removal.
New cartridges run directly from read-only payload partitions. They require no
program extraction or compilation on FDS. The stable `/run/fds/bin` directory is
already in the console and terminal PATH, so existing shells see inserted commands.
If cartridges export the same command name, the lowest numbered bay wins.
Within a bay, the first software selector in lexical order wins. System commands
appear earlier in PATH and retain their usual meanings. To select a specific
cartridge command, use its qualified alias:
```sh
b01:demo.hello:hello 'Explicit bay and software'
fds run 01 -- demo.hello:hello
```
Legacy PROGRAM media uses `b01:COMMAND` for a qualified direct alias. Commands
are removed on eject/unplug; a shared name falls back to the next available bay.
After the final cartridge is removed, a shell may remember the old executable
path; `hash -r` clears Bash's command cache.
## Eject safely
```sh
cd "$HOME"
fds eject 01
fds bay 01
```
Eject stops managed programs, flushes writable storage and releases its mounts.
Remove the cartridge after **SAFE** is reported. If eject fails, close files,
shells or extra mounts using that cartridge and retry. Never treat a timeout as
permission to pull writable media. Use `fds poweroff` to stop the whole computer;
swap the running SYSTEM only after shutdown.
The [control panel](desktop.md) provides the same safe-eject action. For writable
storage behavior, see [DATA and files](data.md).
## Configure physical bay numbering
Run `fds topology` with one identifiable USB device inserted in each slot in
turn. Record the controller/hub topology for both USB 2 and USB 3 connections,
then map those stable identities in the machine's `bays.toml`. Do not map
`/dev/sda` names or enumeration order. The emulator supplies its own known map.
Use [machine settings](internal-storage.md) to validate, export and install the
configuration. The detailed wiring/calibration procedure is in the
[hardware engineering reference](developer/cartridges.md).
-90
View File
@@ -1,90 +0,0 @@
# Reclaim build space
Run these from the repository root after stopping builds and test VMs:
```sh
make clean-preview
make clean
```
The preview lists every selected directory, its combined allocated footprint,
and the generated directories being kept. It does not delete files. `make clean`
recalculates the selection and deletes it without a confirmation prompt. Repeat
it whenever old build/test workspaces accumulate. It needs Python 3, Git and GNU
`du`, already used by the build host; it needs no Rust compilation, downloads,
root privileges or new Python packages.
## What is removed
- Old generated rootfs, kernel, initramfs, SYSTEM, boot, recovery and internal
image workspaces that are no longer referenced by published output links.
- Old generated integration-test directories, including their disk images,
disposable test DATA overlays and local diagnostic logs. The current results
referenced by `*-latest` links or workstation current-pointer files are kept.
- Old generated package staging copies and service compilation directories.
- This checkout's `target/` Rust compilation output. Exported executables in
`out/` and `out/workstation/` remain available. The next Rust build recompiles
as needed using the retained Cargo source cache.
The command recognizes the specific temporary-directory names produced by the
repository's builders and tests. It never treats all of `out/` as disposable.
Unknown names are left alone, including ad hoc diagnostic experiments.
## What is kept
- Current rootfs archives for every profile, kernel, initramfs, boot, recovery,
internal disk and SYSTEM images, through their published symlinks. Links in
`out/manifests/` and links within retained workspaces also retain their targets.
- Latest published test runs, the current workstation image/emulator fixtures,
the current Dasung s6 database, and the newest `m9-images.*` fixture needed by
the media tests. These can still occupy tens of GiB.
- Signed releases such as `out/fds-os-0.1.0/`, input snapshots such as
`out/inputs-m12-v5/`, and independent restored trees such as
`out/rebuild-m12-v5-a/`. These remain available for release reproduction.
- User-created cartridge images, installed software trees and personal emulator sessions
such as `out/my-emulator/`, including their persistent DATA overlays.
- `out/logs/`, `out/manifests/`, packages, downloads, `out/cache/`, `.host/`, the
Void checkout/build container, and Cargo/Rustup caches outside this checkout.
- Git-tracked files and any recognized workspace containing a `.fds-keep` entry.
To retain an older generated workspace for investigation, put a marker in it
before cleaning:
```sh
touch out/m8-vm.YOUR_RUN/.fds-keep
make clean-preview
```
Replace `YOUR_RUN` with the actual directory suffix. Remove that marker when the
workspace is disposable again. Reserve the generated names for the build/test
tools; store personal sessions and images under your own names.
## Build-space management
Image builds and VM suites retain workspaces for diagnosis and preserve the last
successful published image. Repeat `make clean-preview` and `make clean` after
completed build batches. Cleanup is explicit; there is no automatic retention
limit during a build. Full image builds require tens of GiB per independent tree.
## Running safely and rebuilding
Use cleanup sequentially, just like the existing shared-container builds. It
holds the existing rootfs/base-package/image-tool locks, excludes another
cleanup, and refuses mounted candidate paths or observable active build/VM
processes. These checks are safeguards, not permission to start a separate build
while cleanup is running. It never follows a candidate symlink into another
directory, and refuses a symlinked `out/` entirely. Only the checkout containing
the script is eligible; there is no arbitrary deletion-path option.
Existing published images and native tools remain usable after cleanup. To
refresh exported binaries, run `make tooling` or `make workstation`. To rebuild
the smoke-test artifacts and rerun build checks:
```sh
make smoke-test
make check
```
After cleanup, run the smoke test before `make check`, since the checks need its
rebuilt executable. Cleanup implementation and validation details are in the
[developer notes](developer/cleanup.md).
-47
View File
@@ -1,47 +0,0 @@
# Dasung Paperlike display
FDS includes `dasungd` for the configured Dasung Paperlike 13K grayscale monitor.
The controller starts during early boot and remains supervised in the base system,
including console-only use. No GUI or ENVIRONMENT cartridge is required.
The supplied profile uses the dedicated monitor's EDID and USB companion identity,
with raw mode 1 and contrast 4 as startup defaults. It is specific to that monitor;
do not use its identity as a blanket match for other CH340 USB devices.
## Inspect the controller
The controller's socket is restricted to root. From a root maintenance console
on FDS:
```sh
dasungd status
dasungd query
```
`status` reports whether the monitor is connected and responsive, the age of its
last reply and cached parameters. `query` requests current values. To refresh
the display or adjust contrast:
```sh
dasungd refresh
dasungd set contrast 4
dasungd set mode 1 --save
dasungd forget mode
```
A successful set means the command was sent; use query/status to read back the
result. The grayscale model uses raw numeric modes. Saved overrides in
`/run/dasungd/settings.json` survive a daemon restart during the same boot, but
not power loss. Image configuration defaults are in `/etc/dasungd.toml`.
## Diagnose a connection problem
Inspect `/run/log/dasungd/current` and the reported status. Discovery requires
the configured DRM EDID and matching USB companion topology. An ambiguous match
is rejected. Set `usb_path` only after identifying the intended device topology.
FDS uses the daemon for USB control. Its configuration disables the original
workstation-specific display hotplug procedure. Video mode and cabling are part
of the machine's boot/display configuration. The detailed protocol, build
rationale and physical display procedures are in the
[developer reference](developer/dasung.md).
-52
View File
@@ -1,52 +0,0 @@
# DATA and persistent files
FDS keeps SYSTEM read-only. Store documents, source code and other persistent
files on a DATA cartridge. With one healthy DATA cartridge inserted, FDS mounts
it at `/data` and gives the `fds` user access to its files.
```sh
fds bays
fds bay 02
mkdir -p /data/projects
printf 'My first FDS file\n' >/data/projects/hello.txt
cat /data/projects/hello.txt
```
Confirm that the bay reports `MOUNTED READ WRITE` before writing. Only one DATA
cartridge is active at a time. To select DATA explicitly:
```sh
fds data use 02
```
If another DATA is active, eject it first. In recovery, DATA starts read-only and
requires this explicit activation before it can be used for ordinary writes.
## Programs using DATA
Run a system command under the DATA bay's process tracking when it should stop
as part of ejecting that DATA:
```sh
fds run 02 -- /usr/bin/tail -f /data/application.log
```
This background command uses `/data` as its working directory. Its output goes
to the cartridge service log. A directly launched PROGRAM is tracked under its
PROGRAM bay; close it if it keeps files open on a different DATA cartridge.
## Finish a session
```sh
cd "$HOME"
fds eject 02
```
Wait for SAFE, then remove the cartridge. `fds poweroff` performs the storage
shutdown sequence for all active bays. Unexpected power loss or pulling a drive
during a write can damage files; [recovery](recovery.md) explains checking DATA.
In the emulator, writes go to a separate session overlay. Eject preserves that
overlay, and reinserting the original image creates a new overlay. See
[exporting emulator DATA](workstation.md#save-emulator-data) before moving or
deleting a session.
-67
View File
@@ -1,67 +0,0 @@
# WindowMaker and FDS Control
WindowMaker provides the FDS desktop. Its default appearance is grayscale on
all displays: white backgrounds, black borders and selected titles, gray widgets,
Terminus text, and no animations. No E-Ink monitor detection or special cartridge
is needed to select this theme.
## Open the desktop
At the FDS console:
```sh
fds profile activate windowmaker
fds profiles
```
FDS opens a terminal and **FDS Control**. You can reopen the panel from the
WindowMaker root menu's **FDS Control** item, or run `fds-control` in a terminal.
An ENVIRONMENT cartridge requesting the `windowmaker` profile can also start
the desktop. The Dasung controller runs independently of this session.
## Manage cartridges in the panel
![FDS Control with a PROGRAM cartridge and its terminal output](images/fds-control.png)
1. Select a bay in the left column. The right column shows its identity, state,
running process count and available software commands.
2. Select a command, then choose **Run in terminal**. The terminal stays open
after the command exits so you can read its output; close it when finished.
3. Choose **Safe eject** to stop that bay's programs and release its storage.
Wait for **SAFE** before removing the cartridge.
4. Use **Rescan** to refresh discovery. The panel also checks for changes
automatically and updates the display when the visible state changes.
For an inactive DATA cartridge, the primary button becomes **Use DATA**. Activate
it there to make it available at `/data`; eject another active DATA first.
Errors appear in the status area. Actions are unavailable when their prerequisites
are missing, such as running an application from an empty bay or ejecting the
active SYSTEM. The panel uses the same cartridge service as `fds`.
Keyboard controls: **Tab** moves focus between bays, programs and buttons;
**Up/Down** changes the selected bay or program; **Enter** activates the focused
control; **R** rescans; **Esc** closes the panel. Closing the panel does not eject cartridges or close
application terminals; use Safe eject when you want to release a cartridge.
## Return to the console
Choose **Return to console** in the root menu, or run:
```sh
fds profile deactivate
```
This closes the desktop session. It does not shut down FDS. Use `fds poweroff`
when you want to turn the computer off.
## Customize the appearance
System defaults are installed in `/etc/WindowMaker/`. Your files in
`$HOME/GNUstep/Defaults/` override those defaults. The session copies the FDS
settings into a new home once and preserves existing preferences. Use
WindowMaker's settings tools or edit those preference files to change fonts,
colors and behavior. Plain `wmaker` also reads the FDS system defaults.
The root menu includes Ethernet on/off controls. Equivalent console commands
are `fds network on`, `fds network off`, and `fds profiles` to inspect the result.
-80
View File
@@ -1,80 +0,0 @@
# Developer notes
This is the engineering side of FDS: how the pieces are built, why choices were
made, and what we actually measured. The [user manual](../README.md) describes
how to use the current interfaces without the milestone history.
Start with [the active cartridge/desktop revision](current-revision.md). Its
acceptance is separate from the frozen 0.1.0 release. Old test reports describe
the inputs named in those reports; a changed source tree needs fresh checks.
## Design and build references
- [Master plan](master-plan.md)
- [Build host and dependencies](build-host.md)
- [Development workflow](development.md)
- [Workstation tooling plan](workstation-tooling-plan.md)
- [Architecture](architecture.md), [packages](packages.md), [rootfs](rootfs.md), [init](init.md), [services](services.md)
- [Reproducible builds and frozen releases](reproducible-builds.md)
- [Dasung integration and physical monitor procedure](dasung.md)
- [Physical bay calibration and stress procedures](stress-testing.md)
- [Implementation ledger](implementation-status.md) and [roadmap](roadmap.md)
## Acceptance history
- [clap-validation](clap-validation.md)
- [dasung-validation](dasung-validation.md)
- [m0-validation](m0-validation.md)
- [m1-validation](m1-validation.md)
- [m10-validation](m10-validation.md)
- [m11-validation](m11-validation.md)
- [m12-validation](m12-validation.md)
- [m2-validation](m2-validation.md)
- [m3-validation](m3-validation.md)
- [m4-validation](m4-validation.md)
- [m5-validation](m5-validation.md)
- [m6-validation](m6-validation.md)
- [m7-validation](m7-validation.md)
- [m8-validation](m8-validation.md)
- [m9-validation](m9-validation.md)
- [workstation-validation](workstation-validation.md)
## Earlier guides and working notes
These retain useful implementation details and the historical interfaces that
went with them. Use the user manual for current commands, especially software
cartridge creation: new payloads contain installed Void package trees.
- [architecture](architecture.md)
- [boot](boot.md)
- [build-host](build-host.md)
- [cartridges](cartridges.md)
- [cleanup](cleanup.md)
- [dasung](dasung.md)
- [data](data.md)
- [desktop](desktop.md)
- [development](development.md)
- [eeprom](eeprom.md)
- [getting-started](getting-started.md)
- [glossary](glossary.md)
- [implementation-status](implementation-status.md)
- [init](init.md)
- [internal-storage](internal-storage.md)
- [m4-work](m4-work.md)
- [master-plan](master-plan.md)
- [media-tools](media-tools.md)
- [packages](packages.md)
- [performance](performance.md)
- [power](power.md)
- [recovery](recovery.md)
- [releases](releases.md)
- [reproducible-builds](reproducible-builds.md)
- [roadmap](roadmap.md)
- [rootfs](rootfs.md)
- [services](services.md)
- [software-format](software-format.md)
- [stress-testing](stress-testing.md)
- [tooling](tooling.md)
- [troubleshooting](troubleshooting.md)
- [workstation-tooling-plan](workstation-tooling-plan.md)
- [workstation](workstation.md)
-170
View File
@@ -1,170 +0,0 @@
# Understanding FDS/OS
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Roadmap](roadmap.md) · [Glossary](glossary.md)
FDS/OS is designed for a Raspberry Pi 5 portable computer whose operating system,
user data, applications, and optional environments can be carried on removable
cartridges. Its central design question is: what must stay with the machine, and
what should move with the user?
**Status:** M0–M12 software checks cover the cross-build, configured ARM rootfs,
native init, Pi kernel/initramfs/SYSTEM images, ordinary-user console, cartridge
management, writable DATA, desktop/network activation and verified media writing.
Ordered shutdown, twelve-device virtual stress and independent recovery have
passed software acceptance. M12 internal storage, development tools and signature
verification have also passed software checks. The signed local 0.1.0 release
passed two complete offline builds and archive verification. Physical Pi boot
and wiring remain deferred. Use
[Your first build](getting-started.md) and [Boot images](boot.md) to try the VM.
## Three things to keep separate
1. **The build workstation** is an x86_64 Arch Linux computer. It downloads tools,
compiles ARM software, checks artifacts, assembles the rootfs, and boots the ARM VM.
2. **The machine firmware layer** lives on the Pi's internal NVMe. It supplies the
kernel, early boot program, recovery environment, and hardware configuration.
3. **The removable operating environment** comes from cartridges. SYSTEM supplies
the OS root filesystem; DATA supplies persistent user files; other cartridges
supply programs, configuration, or physical USB hardware.
```text
Build workstation
Arch x86_64
├── Void build container → aarch64 glibc packages
└── Rust toolchain → static AArch64 FDS executables
│
└── Pi kernel + initramfs + SYSTEM → ARM VM checks
Pi 5 machine (planned)
Internal NVMe Removable bays
├── FDS_BOOT ───────────────→ SYSTEM: read-only OS
├── FDS_RECOVERY DATA: user files
└── FDS_INTERNAL PROGRAM / ENVIRONMENT / hardware
machine configuration
```
The Void build container on the workstation is **not** the Pi's root filesystem.
It may contain build dependencies that must never appear in the final FDS image.
Likewise, a successful ARM package build does not prove that a Pi can boot it.
## Why cartridges change the operating model
A conventional writable root filesystem combines the installed OS, local changes,
and often user data on one disk. FDS separates their lifetimes:
- A SYSTEM image is built and validated as a unit. Updating means preparing and
swapping to a new SYSTEM cartridge; retaining the previous one enables rollback.
- A DATA cartridge follows the user. With no DATA cartridge, the home is
temporary memory-backed storage, so it does not survive power loss.
- A PROGRAM cartridge supplies a self-contained application image. It does not
install arbitrary files into SYSTEM.
- An ENVIRONMENT cartridge selects a known configuration such as WindowMaker.
In version 1, the desktop software itself is already present in SYSTEM.
This requires explicit safe-eject behavior: removable does not mean safe to pull
while an application is writing. See [Cartridges](cartridges.md).
## Two library strategies
Both software groups target **Linux on AArch64**. They differ in how they obtain
the C runtime and other libraries, not in which processor runs them.
| Software group | Build target | Runtime approach | Reason |
| --- | --- | --- | --- |
| Ordinary Linux packages | Void `aarch64` | glibc, usually shared libraries | Compatibility with the GNU userland, Xorg, WindowMaker, compilers, and third-party packages |
| FDS-owned control tools | Rust `aarch64-unknown-linux-musl` | Static musl by default | Early boot and control programs can run without the installed glibc loader or version |
M0 demonstrates these with GNU hello and `fds-smoketest`, respectively. A static
FDS executable still needs a compatible Linux kernel; static linkage does not
turn it into firmware or a standalone OS. Future dependencies using C/C++ FFI
will need their own musl cross-build assessment.
The project does not switch the whole distribution to musl. XBPS target packages
remain `aarch64`, not `aarch64-musl`. The downloaded **host** XBPS tools happen to
be static musl for x86_64 so they can run on Arch; their libc is independent of
the target package ABI. See [Packages](packages.md).
## Workstation software and emulation
Native Linux `fds-cartridge` builds software from trusted recipes, packages xz
tarballs, and creates a complete GPT image with metadata partition 1 plus payload
partitions. It verifies that image before a separately confirmed whole-USB write.
Native `fds-emulator` boots the FDS kernel/initramfs/SYSTEM in QEMU and exposes
twelve virtual USB bays. Writable DATA uses separate overlays; source images
stay unchanged. These tools are host programs, independent of the target's ABI.
The guest performs bounded verification and temporary extraction, then runs a
selected software command as UID 1000. It does not compile software or run
installation hooks from cartridges. See [the walkthrough](workstation.md) and
[format contract](software-format.md).
## Storage and writable state
M4 builds the actual FAT32 boot partition and GPT/EROFS SYSTEM cartridge. M5
provides the temporary home. M6 adds managed read-only cartridge mounts. The
M12 internal BOOT/RECOVERY/INTERNAL builder and persistent settings are described
in [Internal storage](internal-storage.md).
| Location | Intended content | Write policy |
| --- | --- | --- |
| Internal `FDS_BOOT` (FAT32) | Pi boot files, kernel, DTBs, initramfs | Managed machine boot content |
| Internal `FDS_RECOVERY` (EROFS) | Independent recovery OS | Read-only recovery image |
| Internal `FDS_INTERNAL` (ext4) | Bay map, hardware configuration, diagnostics | Persistent machine state; no ordinary user files |
| Cartridge `FDS_SYSTEM` (EROFS) | OS root filesystem | Read-only; replace image instead of in-place updates |
| Software `FDS_METADATA` + `FDS_PAYLOAD02`… (EROFS) | Catalogue and xz software archives | Read-only; extracted executable caches are temporary |
| Cartridge `FDS_DATA` (ext4) | User home and/or `/data` | Persistent user writes |
| `/run`, `/tmp`, `/var/tmp`, `/var/log` | Runtime state, temporary files, logs | tmpfs; volatile |
| Home without DATA | Temporary user session | tmpfs; volatile |
Writable DATA uses `/data`; `/home/fds` stays temporary.
The table describes the final storage roles. Use the image builders rather than
applying this layout to an existing workstation disk.
## Boot and service responsibilities
The Pi boots its kernel and small initramfs from internal NVMe. A static Rust
`fds-stage0` locates a partition named `FDS_SYSTEM`, mounts its EROFS filesystem,
and transfers control to native s6 in the new root.
The console should become usable as soon as its own prerequisites are ready.
It must not wait for the network, desktop, all 12 USB devices, or a complete
cartridge catalog. `fds-cartridged` handles hotplug, physical bay mapping and
metadata, and activates known desktop profiles from validated ENVIRONMENT media.
See [Boot](boot.md) for the sequence, [Services](services.md) for responsibilities,
and [Performance](performance.md) for targets and how they will be measured.
## Trust and ownership boundaries
Cartridge metadata describes a cartridge; it is not permission to run supplied
scripts as root. Known profiles and hardware mappings belong to the OS. Ordinary
programs should run with ordinary user privileges. An unrecognized device should
not become trusted merely because it appears in a USB bay.
Package customization belongs in project-owned `packages/fds-*` overlays.
Upstream Void tracked files remain unchanged at a recorded source commit.
M0 checks this boundary automatically. Source pins and artifact hashes improve
traceability. Exact selected input snapshots, offline reproduction and local
release signing have passed [M12 acceptance](m12-validation.md). The
independent [recovery environment](recovery.md) is implemented and tested.
## Where implementation starts and stops
Today the active sources also include `rust/dasungd/`, its package overlay, and
`s6/source/` for the base monitor service. `make dasung` builds its static ARM
executable, XBPS package, and a service database for validation. The monitor
service is independent of GUI and cartridge profiles. M2 includes these in a rootfs with native s6 init and a generic ARM VM test.
M3 adds the FDS CLI and shared Rust contracts. M4 adds the Pi kernel, Rust
initramfs, GPT/EROFS SYSTEM and FAT32 boot partition. Its actual stage0 handoff
and media insertion/recovery paths run in a generic ARM VM. Physical Pi
validation remains deferred. M5 adds the ordinary console and boot tracing; M6
adds the cartridge daemon. M7–M11 add writable DATA, desktop/network profiles,
verified media writing, ordered shutdown and virtual USB stress. M12 adds recovery,
internal machine storage and release preparation.
Use [Boot images](boot.md) for the current builders and their acceptance boundary.
See [M1 rootfs](rootfs.md) to use the current filesystem archive. Consult the [roadmap](roadmap.md) for acceptance
boundaries and the [master plan](master-plan.md) for the complete specification.
-181
View File
@@ -1,181 +0,0 @@
# Boot images and stage0
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [First build](getting-started.md) · [Implementation status](implementation-status.md)
FDS keeps the machine's boot files on internal NVMe and its operating system on a
removable SYSTEM cartridge. The Pi firmware loads the kernel and a small initial
filesystem. Its Rust `fds-stage0` program finds the partition named `FDS_SYSTEM`,
mounts it read-only, and hands control to native s6. Device names such as `sda`
are never used as cartridge identities.
The software boot path has passed on an ARM virtual machine using the actual
FDS Pi kernel. That does not verify Pi firmware, NVMe, RP1, display output, USB
power or physical boot speed. Those checks are deferred until the machine is
assembled. M5 now provides an ordinary-user `FDS>` console with a temporary home
and measured boot events. Password login remains locked; this is an intentional
local single-user session. The M2 test VM separately offers a temporary root
development shell. See [native init](init.md) and [performance reports](performance.md).
## Build on the workstation
Use the [Arch host setup](build-host.md) first. In addition to its original tools,
install host `lz4`, `zstd`, `util-linux`, `e2fsprogs` and `libarchive` if missing.
They supply initramfs compression, independent GPT inspection and the initial
VM's filesystem/archive tools; these are build/test dependencies.
Python 3.14+ reads zstd-compressed XBPS archives. Filesystem builders are obtained
as verified Void packages in `.host/image-tools`, separate from the build container.
From the repository root, run sequentially:
```sh
make bootstrap
make rootfs PROFILE=cli
make initramfs
make system-card PROFILE=cli
make boot-volume
make rootfs-test
make init-test
make boot-test
```
The rootfs build includes the FDS CLI, base Dasung daemon, native s6 database, and
the kernel's matching modules. Its first kernel cross-build is substantial;
progress is recorded in `out/logs/xbps-fds-kernel.log`. `make kernel` also builds
that kernel independently. Later calls reuse its package only when recorded
source inputs and every exported payload hash still match. To force a kernel
rebuild, run `./tools/build-kernel --rebuild`. This source/payload cache is not
yet a complete lock of rolling binary build dependencies.
No command here opens or writes a physical target disk.
On a new build host, `make init-test` installs QEMU's system emulator into the
project-local Void environment before `make boot-test` uses it.
| Output | Meaning |
| --- | --- |
| `out/kernel/boot/kernel_2712.img` | Pi 5 kernel with boot-critical drivers built in |
| `out/kernel/usr/lib/modules/` | Matching optional drivers |
| `out/fds-initramfs.img` | Uncompressed default initramfs; a symlink to a versioned build |
| `out/initramfs/initramfs.cpio*` | Uncompressed, gzip, legacy-LZ4 and Zstandard variants |
| `out/rootfs-aarch64.tar` | Configured userspace archive with target ownership and capabilities |
| `out/fds-system-cli.img` | Whole GPT cartridge image, containing an EROFS `FDS_SYSTEM` partition |
| `out/fds-boot.img` | **512 MiB FAT32 partition image**, labeled `FDS_BOOT`; not a whole NVMe disk |
| `out/boot-volume/manifest.json` | Boot image hash, firmware input and every boot file hash |
| `out/m4-vm-latest/` | Logs and exact inputs from the last passing full boot matrix |
`make internal-image` combines BOOT, independent RECOVERY and INTERNAL settings
into `out/fds-internal.img`; see [Internal storage](internal-storage.md) for the
full procedure. The FAT partition image alone is not that complete disk layout.
Use the [media tools](media-tools.md) for confirmed removable-cartridge writes.
## What stage0 does
The initial filesystem contains static-musl stage0 and Dasung executables,
configuration, the monitor's EDID, mountpoint directories and `/dev/console`.
There is no shell, BusyBox, Python, glibc, or udev in it. Stage0 mounts proc, sysfs
and devtmpfs itself and binds its kernel-event socket before scanning devices.
If a single SYSTEM partition is present, stage0 verifies its block-device identity,
mounts EROFS read-only, and checks the FDS identity and native init executables.
It stops and reaps the early Dasung process before the base service starts,
moves the kernel filesystems into the new root, removes the old in-memory root,
and executes the s6 launcher. It refuses this path outside root PID 1 on ramfs/tmpfs.
Without SYSTEM, the console displays a missing-media prompt and keeps processing
device events. Inserting media resumes boot without rebooting or polling sleeps.
When more than one matching partition is visible, stage0 refuses to choose one.
Its waiting console accepts:
```text
list Show the discovered kernel partition identities
rescan Repeat discovery immediately
recovery Select FDS_RECOVERY instead of FDS_SYSTEM
reboot Reboot from early userspace
poweroff Power off from early userspace
```
An invalid filesystem or incomplete FDS root remains at the prompt. `list` is a
diagnostic view and can show technical device paths; physical bay labels arrive
with the cartridge daemon. Version 1 uses explicit recovery rather than an
interactive choice between multiple SYSTEMs. Detection applies to devices visible
at selection time; a cartridge inserted after root handoff cannot replace root.
## Pi display and development output
`image/pi5/config.txt` selects the Pi 5 kernel, initramfs, external PCIe and VC4
KMS, disables HDMI audio, and avoids automatic camera/DSI setup. It does not
overclock the Pi or PCIe link. The pinned Pi kernel retains 16 KiB pages and
disables the CH341 SPI driver required by the Dasung protection policy.
The production command line uses the display console and quiet kernel output.
It loads the Paperlike 13K's recorded EDID from the initramfs before SYSTEM exists.
The unqualified `drm.edid_firmware` setting applies this EDID to connected DRM
outputs: this image is intended for the user's dedicated Dasung display. The
actual adapter, connector, mode acceptance and picture still need Pi validation.
Early USB control and the later base daemon share the same monitor identity.
For serial diagnosis, build:
```sh
make boot-volume BOOT_MODE=development
```
This replaces the convenience output link with a separate development build;
previous versioned directories remain. Development enables kernel/daemon output
and makes the Pi 5 `ttyAMA10` debug UART the primary userspace console. That is
the Pi 5 debug connector, not an assumption about the 40-pin GPIO UART. Rebuild
with `BOOT_MODE=production` to restore the display-console configuration.
`fds.boot=recovery` on the kernel command line selects `FDS_RECOVERY` immediately.
`fds.emulator=1` is reserved for the [workstation emulator](workstation.md):
the SYSTEM loader requires QEMU virt and its fixed xHCI controller before
enabling the twelve virtual bay mappings. Default Pi boot is unchanged.
`fds.debug=1` exposes the early controller's diagnostics. Duplicate or unknown
`fds.*` options fail clearly rather than being silently ignored.
## What the automated test proves
`make boot-test` appends a test-only checker to a copy of the exported rootfs;
the original stage-2 script and compiled service graph still run. It checks:
- All four initramfs formats reach native s6 on read-only EROFS.
- Missing SYSTEM waits; a virtual USB insertion resumes boot.
- Two SYSTEM partitions remain unselected; the console can enter recovery.
- Explicit recovery mode selects RECOVERY even when SYSTEM is also present.
- Invalid filesystems and foreign OS roots are refused; recovery remains usable.
- Kernel page size, root ownership, ping capability, temporary filesystems,
single Dasung ownership, service start/restart/stop and orderly power-off.
- Input disk hashes remain unchanged; the VM has no network or host-device access.
QEMU uses its `max` ARM CPU because the Pi kernel needs 16 KiB page support;
the earlier M2 Cortex-A72 fixture cannot run this kernel. These are compatibility
and behavior checks, not Pi performance measurements. Test RECOVERY is a relabeled
fixture for selection; the independent recovery product is tracked separately.
Reference material: [Pi kernel builds](https://www.raspberrypi.com/documentation/computers/linux_kernel.html),
[boot configuration](https://www.raspberrypi.com/documentation/computers/config_txt.html),
[Pi UARTs](https://www.raspberrypi.com/documentation/computers/configuration.html#configuring-uarts),
and [Linux initramfs](https://docs.kernel.org/filesystems/ramfs-rootfs-initramfs.html).
## Use the ordinary FDS console
After building the CLI rootfs, initramfs and SYSTEM image:
```sh
make console-vm
```
The ARM virtual machine boots the real stage0 and read-only SYSTEM image. At
`FDS>` you are the ordinary `fds` user (UID 1000), with a temporary writable home.
Try `fds info`, `fds inspect /FDS/CARTRIDGE.TOML`, and `fds boot-profile`.
The console deliberately uses local autologin; both account passwords are locked.
Use `fds poweroff` for an orderly shutdown through native s6. Enter `exit` to
restart the console. Press **Ctrl-a, then x** to close QEMU immediately.
Closing QEMU this way ends the test immediately; it is not a graceful-shutdown test.
No host USB devices or physical disks are attached to this VM.
Run `make console-test` for automated console/readiness checks and
`make performance-test` for the software optimization comparison. Read
[M5 validation](m5-validation.md) and [measurement boundaries](performance.md)
before interpreting VM timing as hardware evidence.
-249
View File
@@ -1,249 +0,0 @@
# M0: Arch Linux x86_64 build host
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [First build](getting-started.md) · [Troubleshooting](troubleshooting.md)
This is the technical reference for the build foundation used by later milestones.
The [rootfs guide](rootfs.md) adds assembly, Python and isolated ARM execution requirements. If you are
setting up the repository for the first time, follow [Your first build](getting-started.md)
for an ordered walkthrough with expected output. For editing, rebuilding, and
configuration changes after setup, use [Development](development.md).
## Scope and pinned inputs
M0 validates the cross-build environment. The scoped [Dasung integration](dasung.md)
adds a controller package and service definitions without implementing a full OS.
M1 adds the configured FDS rootfs; M2 adds native init and the generic ARM VM.
M3 adds static FDS tools. M4 cross-builds the Pi kernel and creates initramfs,
SYSTEM GPT/EROFS and a FAT32 boot partition; see [Boot images](boot.md).
M6 adds cartridge management and M7 adds writable DATA. M8 adds the optional
WindowMaker stack and on-demand Ethernet. M12 adds the [complete internal disk](internal-storage.md),
independent recovery and persistent machine settings. The
[signature tools](releases.md) have passed acceptance; the
[frozen-input workflow](reproducible-builds.md) and signed local 0.1.0 release
have passed two complete offline builds and artifact/archive verification. The [desktop guide](desktop.md) explains the
new dependencies and the development image used for virtual display tests.
See [Native init](init.md) for the additional test dependencies. An ARM host
and a connected Pi are not required.
| Input | Pin |
| --- | --- |
| void-packages | Git submodule and `VOID_PACKAGES_COMMIT`: `02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1` |
| Static XBPS host tools | Versioned `0.60.4_1.x86_64-musl` URL and a SHA-256 digest stored in this repository |
| Rust | `rust-toolchain.toml`: 1.98.0, minimal profile, rustfmt |
| Rust target | `aarch64-unknown-linux-musl` |
| Rust crate dependencies | Smoketest has none; all workspace dependencies, including shared Clap CLI parsing, are pinned in `Cargo.lock`, fetched at bootstrap, and built with `--locked --offline` |
| XBPS test package | `srcpkgs/hello/template` in the pinned Void tree: GNU hello 2.12.3_1 |
| Pi kernel | `packages/fds-kernel/template`: 6.12.87, pinned Raspberry Pi source archive and FDS config delta |
| Pi firmware | `config/pi-firmware.conf`: signed 20260715_1 package with a pinned SHA-256 |
The musl ABI of the static host tools does not determine the target ABI. Scripts
explicitly use `XBPS_ARCH=x86_64` for the glibc build container, then run
`./xbps-src -a aarch64 pkg hello` to build the ARM glibc package. Local ARM package
indexing also explicitly sets `XBPS_ARCH=aarch64`.
## Generic Linux workstation tools and desktop additions
`make workstation` builds native `fds-cartridge` and `fds-emulator`. Runtime
requirements are QEMU, erofs-utils, bubblewrap and native XBPS with a prepared
Void source checkout for software creation. `fds-cartridge` invokes xbps-src,
installs runtime dependencies and creates direct EROFS trees. The Rust tar reader
and xz utility remain for legacy cartridges and package/source archives. See
[the current workstation guide](../workstation.md).
The target `fds-control` panel uses x11rb 0.13.2's pure-Rust core X11 connection.
It adds no shared GUI toolkit or background service. Its protocol, hostname and
OS-access dependencies are locked and included in Rust license notices. Both
the panel and foreground launcher are packaged in fds-cli as static ARM binaries.
See [the revision rationale](current-revision.md).
## Host dependencies
Run as an ordinary user on x86_64 Arch Linux with unprivileged user namespaces.
Rootfs construction now requires Linux 6.7+ with namespaced `binfmt_misc` support.
The M5 build uses a private handler and static QEMU for child execution; it does
not register an ARM interpreter in the workstation's global registry.
The checkout path must not contain whitespace, a limitation of upstream
xbps-src. Allow several GB of disk space and provide a network connection.
```sh
sudo pacman -S --needed bash coreutils binutils git curl make file tar xz gzip zstd \
bubblewrap rustup ca-certificates findutils diffutils grep sed gawk util-linux
```
Alternatively, run `./tools/bootstrap-host --install-deps`. Only this explicit
option invokes sudo/pacman. Default `make bootstrap` checks dependencies and
exits nonzero when one is missing, without installing host system packages.
Dependency rationale:
- bash, coreutils, findutils, diffutils, grep, sed, gawk, and util-linux provide
the build scripts and upstream build system with their required shell tools.
- git pins upstream source; curl and CA certificates download HTTPS inputs.
- tar, xz, gzip, and zstd extract archives and XBPS packages.
- file and binutils inspect ELF artifacts; make provides the build entry points.
- bubblewrap runs the build container as an ordinary user.
- rustup installs the pinned Rust toolchain and target. rustfmt checks source
formatting and is not a runtime dependency.
Rust's bundled `rust-lld` and target-provided static musl/CRT are sufficient for
the current pure-Rust smoketest. No Zig, Docker, OpenSSL crate, or additional
musl runtime is required. The imported Dasung controller bundles libusb and uses a Void musl C cross
compiler inside the container; see [its dependency rationale](dasung.md#dependencies-and-source-provenance).
Glibc C libraries cannot simply be reused in static-musl binaries.
## Exact commands
Run sequentially from the repository root:
```sh
make bootstrap
make smoke-test
make check
```
Bootstrap initializes the pinned submodule, verifies and extracts XBPS, checks
the tools' x86_64 static ELF properties, installs the pinned Rust toolchain and
target, fetches the locked workspace crates, copies `config/xbps-src.conf` into the local Void checkout, then runs the
equivalent of:
```sh
export PATH="$PWD/.host/xbps/usr/bin:$PATH"
export XBPS_ARCH=x86_64
cd vendor/void-packages
./xbps-src -A x86_64 binary-bootstrap
```
`binary-bootstrap` installs upstream **base-chroot** as a compilation environment.
It is separate from the FDS `fds-base` target rootfs built in M1. Builds run through
bubblewrap without starting Void services on Arch. Repeated bootstrap reuses the
masterdir and verified downloads. The script checks the container architecture,
installed base-chroot package, and execution of GCC inside the container.
To reproduce the two builds individually from the repository root:
```sh
./tools/cargo-build --locked --offline --release \
--target aarch64-unknown-linux-musl -p fds-smoketest
./tools/build-package hello
```
The latter command sets the local XBPS PATH, checks the Void pin, and executes
this command within the submodule:
```sh
./xbps-src -a aarch64 pkg hello
```
xbps-src installs the aarch64 GNU cross toolchain that runs on x86_64, together
with the target glibc sysroot. No Pi compilation is needed. The pinned template's
checksum verifies the package source archive.
## Validation
```sh
file target/aarch64-unknown-linux-musl/release/fds-smoketest
readelf -hW target/aarch64-unknown-linux-musl/release/fds-smoketest
readelf -lW target/aarch64-unknown-linux-musl/release/fds-smoketest
readelf -dW target/aarch64-unknown-linux-musl/release/fds-smoketest
readelf -VW target/aarch64-unknown-linux-musl/release/fds-smoketest
ldd target/aarch64-unknown-linux-musl/release/fds-smoketest
```
Rust acceptance requires ELF64, little endian, AArch64, executable type EXEC/DYN,
static linkage reported by `file`, no `PT_INTERP`, no `DT_NEEDED`, and no
`GLIBC_*` version requirements. Static PIE is also acceptable. The pinned Rust
target, self-contained linking configuration, and compile-time target checks
establish musl provenance; an ELF's static label alone does not identify its libc.
`ldd` is supplemental diagnostic output. A static executable normally produces
`not a dynamic executable` with exit status 1. **An x86_64 host's ldd alone cannot
prove that an ARM executable is static.**
XBPS validation checks architecture=aarch64 in the local package index, then
extracts `usr/bin/hello` from the generated package. It requires AArch64,
`/lib/ld-linux-aarch64.so.1`, and `libc.so.6`. An architecture suffix in a package
filename is not sufficient evidence.
`make check` verifies rejection of the wrong architecture, dynamic executables
passed as static, a static-musl executable passed as a glibc package, non-ELF
input, missing artifacts, invalid arguments, Void pin drift, and modified
upstream files. It also tests overlay protection and runs rustfmt. Pipelines
enable pipefail so `tee` cannot hide a failed build.
Optionally install `sudo pacman -S --needed qemu-user` to execute the ARM Rust
binary during smoke-test and compare its output. When qemu-aarch64 is absent,
the script explicitly reports SKIP. M0's required acceptance is actual
cross-compilation plus ELF/package inspection. Static inspection is not an ARM
runtime or Pi boot test.
## Inputs, caches, and overlays
Use `make clean-preview` followed by `make clean` when repeated builds and VM
tests accumulate. [Cleanup](cleanup.md) explains the retained current images,
release/input archives and caches. Full image builds and VM suites need tens of
GiB per working tree, beyond the initial M0 smoke test's requirements.
- `.host/xbps/`: project-local host tools, not installed into `/usr`.
- `vendor/void-packages/masterdir-x86_64/`: glibc build container.
- `vendor/void-packages/hostdir/`: download caches, cross packages, and build output.
- `out/packages/`: exported aarch64 test package and its repository index.
- `out/manifests/void-build-packages.txt`: packages remaining installed after the
build. Cross toolchains and temporary dependencies removed by xbps-src remain
traceable through cache digests and build logs.
- `out/manifests/void-package-inputs.sha256`: digests of cached `.xbps` inputs.
- `out/manifests/artifacts.sha256`: validation artifact digests.
- `out/logs/`: actual command logs, including logs from failed attempts.
Place FDS package overlays at `packages/fds-*/template`. `tools/prepare-void`
copies only directories containing a template and refuses to replace tracked
upstream packages. These copies are visible inside the build container. Base,
base-files, init, CLI, kernel and Dasung overlays are active; future directories
without templates remain inactive.
If an overlay changes and preparation reports a stale copy, inspect and preserve
any unique edits before moving the specific generated copy out of `srcpkgs/` and
retrying. Modified upstream tracked files cause immediate failure. Conflicting
local build configuration also causes failure and must be reconciled explicitly.
See [configuration and overlay troubleshooting](troubleshooting.md#configuration-or-overlay-conflict)
for the procedure.
Updating Void requires an explicit change to both the submodule gitlink and
`VOID_PACKAGES_COMMIT`, followed by validation and a commit containing both.
Do not fetch master and silently change the pin. Replacing the static host XBPS
archive also requires updating both its versioned URL and checksum.
## Known limitations
1. Source templates, Rust, and XBPS tools are pinned; **the complete Void binary
repository is not snapshotted**. Initial bootstrap, xbps-src build-container
updates, and missing build dependencies still use signed rolling repositories.
Cached inputs, version inventories, and digests support auditing but cannot
guarantee future upstream retention. M12 preserves the exact selected
packages, sources and build tools through the accepted
[snapshot and offline workflow](reproducible-builds.md). Its two fresh builds
produced 27 matching artifacts. This freezes the release inputs, not the
entire rolling Void repository; the original M0 check alone does not
establish reproducibility.
2. Bootstrap supports x86_64 Arch Linux. Other host distributions and ARM hosts
have not been validated. Unprivileged user namespaces must be available.
3. M4 tests the actual Pi kernel and initramfs-to-EROFS handoff in a generic ARM
VM. This does not emulate Pi firmware, RP1, physical bays or the display.
Media burning and physical boot-performance measurements remain separate work.
4. `opt-level=z` is the plan's initial setting, not a proven fastest setting.
Compare optimization levels later using real programs and hardware.
5. During the initial upstream bootstrap, bubblewrap's mounted `/tmp` produced
`rm: cannot remove '//tmp': Device or resource busy` in the cleanup phase.
Upstream returned success; the subsequent container execution, cross build,
and public bootstrap command passed. The upstream source was left unchanged.
## Upstream references
- [Pinned Void xbps-src usage and foreign host documentation](https://github.com/void-linux/void-packages/blob/02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1/README.md)
- [Pinned aarch64 cross profile](https://github.com/void-linux/void-packages/blob/02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1/common/cross-profiles/aarch64.sh)
- [Void static XBPS](https://docs.voidlinux.org/xbps/troubleshooting/static.html)
- [Official static tool SHA-256 checksums](https://repo-default.voidlinux.org/static/sha256sums.txt)
- [Rust aarch64-unknown-linux-musl](https://doc.rust-lang.org/rustc/platform-support/aarch64-unknown-linux-musl.html)
- [Rust self-contained linking](https://doc.rust-lang.org/rustc/codegen-options/index.html#link-self-contained)
-200
View File
@@ -1,200 +0,0 @@
# Using cartridges and configuring bays
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Boot images](boot.md) · [Services](services.md)
M6 adds the base `fds-cartridged` service, twelve bay states, USB hardware
recognition, strict metadata inspection, read-only storage mounts and eject.
Its [ARM VM acceptance checks passed](m6-validation.md). [Writable DATA integration](data.md) has passed M7 software acceptance;
[desktop/program activation](desktop.md) passed M8 and [media creation tools](media-tools.md)
passed M9 software acceptance. [M10 ordered shutdown](power.md) has passed software acceptance.
## Inspect the current machine
At the FDS console:
```sh
fds bays
fds bay 3
fds cartridge 3
fds --json bays
```
The ordinary `fds` user can use these commands. `bays` shows all twelve bays,
including empty and unconfigured ones. `cartridge` includes validated metadata
and the current mount path. `--json` exposes a versioned machine-readable report.
`fds rescan` explicitly refreshes inventory; normal insert/remove events trigger
refresh automatically. The console does not wait for the inventory scan.
| State | Meaning and next action |
| --- | --- |
| UNCONFIGURED | No measured controller/port mapping exists for this bay |
| EMPTY | Configured bay with no detected USB device |
| HARDWARE | USB device without storage; catalog name or VID/PID is shown |
| UNRECOGNIZED STORAGE | Storage exists but no named FDS partition is available |
| MOUNTED READ ONLY | Manifest validated; filesystem available at the displayed path |
| MOUNTED READ WRITE | Active DATA is writable at `/data`; eject before removal |
| PROTECTED | The active SYSTEM root; eject is refused |
| AMBIGUOUS | Multiple devices or named partitions match; nothing is selected arbitrarily |
| ERROR | Inspection, mounting or metadata validation failed; read the diagnostic |
| SAFE | The requested unmount succeeded; remove the cartridge |
An unrecognized storage state may briefly appear while the kernel discovers its
partitions. It is not permission to remove a device being used elsewhere.
Errors and ambiguous media never produce SAFE. An additional mount of the same filesystem also blocks managed eject until
it is unmounted.
## Calibrate the physical bay map
The shipped `/etc/fds/bays.toml` is deliberately empty: no Pi bay wiring has yet
been measured. This produces UNCONFIGURED states, not invented assignments.
1. On the assembled machine, insert one known USB device into one physical bay.
2. Run `fds topology`. This diagnostic explicitly shows controller/port paths;
ordinary bay commands omit those implementation details.
3. Record that path and repeat for each bay. Test both USB 2 and USB 3 devices,
because their companion root hubs can have different logical port paths.
4. Put the measured map in your machine configuration directory and build the
[internal image](internal-storage.md), or use `fds machine install DIRECTORY`
from recovery and reboot. Machine settings persist independently of SYSTEM.
The packaged `/etc/fds/` files are fallback defaults for missing/invalid internal
storage; `fds machine status` shows which source is active.
5. Verify all twelve devices together, in different insertion orders and after
reboot. That physical acceptance remains deferred.
A **syntax example only**, using identities that must be replaced by observations:
```toml
[front]
hub = "platform/example-controller:usb2/1"
[front.ports]
1 = 1
2 = 2
3 = 3
4 = 4
5 = 5
6 = 6
[front_superspeed]
hub = "platform/example-controller:usb3/1"
[front_superspeed.ports]
1 = 1
2 = 2
3 = 3
4 = 4
5 = 5
6 = 6
```
`hub` names the controller and parent port chain. Its numbered `ports` map the
next downstream port to a bay number from 1 through 12. Additional groups can
map the rear hub. Explicit USB 2/3 aliases may refer to the same bay; duplicate
ports, overlapping parent/child mappings, and duplicate bays within one hub are
rejected. If two devices simultaneously match aliases for one bay, it becomes
AMBIGUOUS. A hub inserted into a cartridge bay with multiple downstream devices
also needs an explicit future composite-device policy; M6 refuses to guess.
The identity excludes the Linux USB bus number and `/dev/sdX` enumeration order.
It includes the controller's sysfs path, USB protocol generation and port chain.
Moving a hub to another controller or changing wiring requires recalibration.
## Storage formats and metadata
| Class | GPT partition name | Filesystem and current behavior |
| --- | --- | --- |
| SYSTEM | FDS_SYSTEM | EROFS; current root is protected, additional media read-only |
| DATA | FDS_DATA | ext4; inspected read-only first, then activated at `/data` under the M7 policy |
| PROGRAM | FDS_METADATA + FDS_PAYLOAD02… | Metadata and xz bundles in `1+m` EROFS partitions; see [software format](software-format.md) |
| Legacy PROGRAM | FDS_PROGRAM | Existing single EROFS application tree; still readable |
| ENVIRONMENT | FDS_ENVIRONMENT | EROFS; declarative selection of a trusted built-in profile |
| UTILITY | FDS_UTILITY | EROFS; inspection only, no automatic actions |
| HARDWARE | None required | VID/PID, optional serial, device/interface class and bay |
Storage contains `/FDS/CARTRIDGE.TOML`. For example:
```toml
format = 1
[cartridge]
id = "fds.windowmaker"
name = "WINDOW SYSTEM"
class = "environment"
version = "0.1"
[media]
writable = false
[activation]
profile = "windowmaker"
```
The manifest class must match the GPT partition name. SYSTEM, PROGRAM and
ENVIRONMENT are read-only; DATA declares writable media during the initial read-only inspection. Unknown keys, root commands, unsafe identifiers, control
characters, symlink metadata and nonregular metadata files are rejected.
The parser accepts at most 64 KiB. No `/FDS/autorun.sh` is run.
Inspect a standalone manifest with `fds inspect /path/to/CARTRIDGE.TOML`.
For mounted storage, `fds cartridge N` reports the daemon's validated copy.
Media is inspected in a root-only staging directory, then published at
`/run/fds/media/NN` after validation, with nodev, nosuid and noexec. Device
major/minor and kernel disk sequence are checked before mounting an opened
block-device descriptor, preventing stale enumeration names from selecting a
replacement disk.
## Eject a mounted cartridge
```sh
fds eject 2
```
Leave any shell working directory inside that cartridge and close files first.
SAFE is emitted only after an ordinary unmount succeeds. Busy mounts return an
error; forced or lazy unmount is never used to declare safe removal. Once SAFE,
the daemon keeps that insertion unmounted until removal and reinsertion, including
across a service restart. Its root-owned volatile marker is tied to the kernel
disk sequence, so a replacement disk is not mistaken for ejected media.
The active SYSTEM cannot be ejected. Swap SYSTEM only after shutting down.
Pulling a mounted cartridge without eject is surprise removal. The daemon clears
its inventory and detaches a vanished read-only mount where needed; that cleanup
is not a successful eject. [M7 DATA handling](data.md) extends ejection to writable DATA, consumer
tracking and syncfs. The temporary `/home/fds` remains usable without DATA and is lost at
power-off.
## Hardware recognition
Edit `packages/fds-cartridged/files/hardware-catalog.toml`, then rebuild SYSTEM.
Entries name devices, never executable actions:
```toml
[[device]]
name = "MY SERIAL ADAPTER"
vendor = "1234"
product = "5678"
# Optional exact restrictions:
serial = "UNIT-1"
class = "02"
```
Replace these example IDs with observed values. Device or interface class may
match `class`. Multiple matching catalog entries produce an error; unknown
hardware remains identified by VID/PID without being mistaken for empty media.
Dasung control remains in the independent base monitor service.
## Implementation and diagnostics
The daemon subscribes to kernel USB/block events before taking its first snapshot.
A receive-buffer overflow causes a fresh snapshot. IPC uses a bounded Unix socket
at `/run/fds/control.sock`, mode 0660 and group `fds`, with peer UID checks for
root and the ordinary FDS user. Slow clients have individual deadlines and do not
block other clients. No asynchronous runtime or new external Rust dependency is
introduced: the existing std, libc, serde, serde_json and toml components suffice.
Root diagnostics: `s6-svstat /run/service/cartridged` and
`cat /run/log/cartridged/current`. Logs are bounded and volatile. Restart through
`s6-rc -l /run/s6-rc -d change cartridged`, then the corresponding `-u` command.
Close users of cartridge mounts first; restart cleanup refuses busy leftovers.
On the build host, `make cartridge-test` runs fixtures and full virtual USB tests.
These exercise actual kernel events and mounts but cannot establish physical
wiring, USB power stability, or Pi port behavior. M11 expands to twelve-device
stress, and physical calibration must be recorded separately.
-131
View File
@@ -1,131 +0,0 @@
# Clap command-line migration validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Command usage](tooling.md) · [M12 evidence](m12-validation.md)
Software acceptance completed on 2026-09-21. Every Rust command-line interface
uses the shared, locked Clap 4.6.7 dependency. The migration adds no third-party
crate versions or shared runtime libraries. Dasung already used Clap; its
workspace dependency now shares the same definition. The dependency-free
smoketest has no argument interface.
## Implemented behavior
- Typed commands cover `fds`, its inspect/eject/power aliases, stage0, boottrace,
cartridge/profile services, media creation/writing, and release signing.
- `fds burn` and `fds-burn` share one grammar. Format options are specific to
the selected class, with duplicate and conflicting options rejected.
- Clap generates command-specific help, versions, required-option errors and
typed bay/size validation. Global `--json` works around nested commands.
- `fds run BAY -- EXECUTABLE ARGS...` preserves every child argument, including
`--help`, `--json`, empty strings, spaces and an additional `--`.
- Private s6 flags and default daemon/PID-1 behavior are preserved. Stage0
enters its emergency path on argument errors as PID 1 instead of exiting.
- VM client and write/stress helpers now build through Cargo and use Clap.
No project-owned Rust source reads or slices a raw process argument vector.
Kernel command-line files and metadata remain separate domain parsers.
A regression test caught that Clap's broad argument/subcommand conflict rule
also rejected `fds-inspect --json image IMAGE`. The final implementation uses
Clap's typed conversion hook to reject only simultaneous target/image forms;
it accepts global options before or after the image subcommand. This behavior
passed host parser tests and execution of the actual ARM binary.
## Build and runtime evidence
The final tooling run passed **48 Rust unit tests**, ELF checks for every ARM
executable/alias, real ARM help/version/invalid-input tests, strict metadata
checks and stage0 diagnostic fixtures. Evidence: `out/logs/clap-tooling-test.log`.
The independent Dasung simulator/service and host/ARM/OpenSSL signing suites
passed in `out/logs/clap-dasung-test.log` and
`out/logs/clap-signing-test.log` (`out/m12-signing.9xgbool5/`).
`make bootstrap`, `make smoke-test`, and `make check` passed. The complete
`make all` build passed in `out/logs/clap-all-final.log`; the earlier
`clap-all.log` attempt was intentionally stopped before fixing the inspection
option-placement regression. The final `make check` also passed in
`out/logs/clap-check-final.log`.
All suites below passed against the rebuilt images. Each has its own
`out/logs/clap-<make-target>.log` and retained VM/image evidence:
| Make target | Evidence directory |
| --- | --- |
| `init-test` | `out/m2-vm.lsptD3/` |
| `boot-test` | `out/m4-vm.4VJHfm/` |
| `console-test` | `out/m5-vm.ktln7za0/` |
| `cartridge-test` | `out/m6-vm.nv3oq41q/` |
| `data-test` | `out/m7-vm.yv53pjkk/` |
| `desktop-test` | `out/m8-vm.q8nwbhgc/` |
| `media-image-test` | `out/m9-images.9ts4w_qb/` |
| `media-test` | `out/m9-vm.06yddrko/`, `out/m9-system.a9n8xxb_/` |
| `power-test` | `out/m10-vm.yk62sod6/` |
| `stress-test` | `out/m11-vm.vg5dicrw/`, `out/m11-faults.pd52oxs7/` |
| `recovery-test` | `out/m12-recovery.0604mqgm/` |
| `internal-test` | `out/m12-internal.uj0rbf4i/` |
| `clock-test` | `out/m12-clock.f54asrop/` |
| `development-test` | `out/m12-development.mu29x4mr/` |
These checks include actual SYSTEM creation/write/readback/boot, confirmed DATA
repair, interrupted-checker quarantine, persistent settings across reboot,
window manager and DHCP control, sustained writes, all twelve overlapping I/O
intervals, and injected write/flush/readback/worker/daemon failures. They use
regular-file images and isolated ARM VMs. No physical media was written.
## Executable size and startup measurements
Clap increases executable sizes. All eight production Rust programs (including
Dasung) remain statically linked and contain no personal build-path prefix.
`out/clap-binary-sizes.json` records the final hashes and these sizes:
| Executable | Before, bytes | After, bytes |
| --- | ---: | ---: |
| `fds` | 900,720 | 1,097,960 |
| `fds-burn` | 821,344 | 990,312 |
| `fds-stage0` | 474,336 | 633,880 |
| `fds-boottrace` | 442,152 | 606,672 |
| `fds-cartridged` | 917,328 | 1,064,832 |
| `fds-profile` | 527,880 | 687,904 |
| `fds-release` | 434,720 | 595,776 |
Five alternating QEMU TCG boot pairs compare the previous complete image set
with the rebuilt images, using the same Pi kernel. The previous images also
precede Rust source-path normalization; this is an image-level comparison,
not an isolated microbenchmark of the parser. Each measurement uses Linux
CLOCK_BOOTTIME from kernel start to the first ordinary-user console.
| Image set | Five samples, ms | Median, ms |
| --- | --- | ---: |
| before | 1958.754852, 2031.634456, 1998.790409, 1951.843302, 1961.052514 | 1961.052514 |
| clap | 1917.447815, 1909.301394, 1923.776234, 1955.579658, 2053.676925 | 1923.776234 |
The median change was **−37.276280 ms**, below the +100 ms regression threshold.
This does not establish a speedup on the Pi. Input hashes, all sample reports
and serial logs are in `out/clap-console-pairs.qyyoppcj/`; the replay script is
`out/clap-boot-comparison.py`.
The updated stage0 was also built at all three optimization levels and booted
three times per level (`out/rust-profiles.8hnnusn8/comparison.json`):
| Optimization | Stage0 bytes | Median kernel-to-console, ms |
| --- | ---: | ---: |
| `z` | 633,880 | 1922.658965 |
| `2` | 730,568 | 1938.769685 |
| `3` | 757,168 | 1950.010662 |
Retain `z`; these VM samples do not establish a physical-hardware winner. The
separate twelve-device test observed a +180.457 ms median versus empty bays.
That comparison adds twelve emulated USB devices and is not the before/after
Clap comparison. Its readiness-gate test still proved that cartridge service
readiness does not block the console. Physical Pi boot and shutdown targets,
USB electrical behavior, display recovery and flash durability remain deferred.
## Release status
The requested CLI migration is complete and included in the signed local
0.1.0 release. Fresh v5 offline builds produced 27 byte-identical artifacts,
which also match the normal images used by every runtime suite above. The old
v4 candidate was stopped before acceptance. See [final M12 evidence](m12-validation.md#final-local-release-acceptance)
and the [67-section implementation ledger](implementation-status.md).
-131
View File
@@ -1,131 +0,0 @@
# Reclaim build space
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
Run these from the repository root after stopping builds and test VMs:
```sh
make clean-preview
make clean
```
The preview lists every selected directory, its combined allocated footprint,
and the generated directories being kept. It does not delete files. `make clean`
recalculates the selection and deletes it without a confirmation prompt. Repeat
it whenever old build/test workspaces accumulate. It needs Python 3, Git and GNU
`du`, already used by the build host; it needs no Rust compilation, downloads,
root privileges or new Python packages.
## What is removed
- Old generated rootfs, kernel, initramfs, SYSTEM, boot, recovery and internal
image workspaces that are no longer referenced by published output links.
- Old generated integration-test directories, including their disk images,
disposable test DATA overlays and local diagnostic logs. The current results
referenced by `*-latest` links or workstation current-pointer files are kept.
- Old generated package staging copies and service compilation directories.
- This checkout's `target/` Rust compilation output. Exported executables in
`out/` and `out/workstation/` remain available. The next Rust build recompiles
as needed using the retained Cargo source cache.
The command recognizes the specific temporary-directory names produced by the
repository's builders and tests. It never treats all of `out/` as disposable.
Unknown names are left alone, including ad hoc diagnostic experiments.
## What is kept
- Current rootfs archives for every profile, kernel, initramfs, boot, recovery,
internal disk and SYSTEM images, through their published symlinks. Links in
`out/manifests/` and links within retained workspaces also retain their targets.
- Latest published test runs, the current workstation image/emulator fixtures,
the current Dasung s6 database, and the newest `m9-images.*` fixture needed by
the media tests. These can still occupy tens of GiB.
- Signed releases such as `out/fds-os-0.1.0/`, input snapshots such as
`out/inputs-m12-v5/`, and independent restored trees such as
`out/rebuild-m12-v5-a/`. These remain available for release reproduction.
- User-created cartridge images, software bundles and personal emulator sessions
such as `out/my-emulator/`, including their persistent DATA overlays.
- `out/logs/`, `out/manifests/`, packages, downloads, `out/cache/`, `.host/`, the
Void checkout/build container, and Cargo/Rustup caches outside this checkout.
- Git-tracked files and any recognized workspace containing a `.fds-keep` entry.
To retain an older generated workspace for investigation, put a marker in it
before cleaning:
```sh
touch out/m8-vm.YOUR_RUN/.fds-keep
make clean-preview
```
Replace `YOUR_RUN` with the actual directory suffix. Remove that marker when the
workspace is disposable again. Reserve the generated names for the build/test
tools; store personal sessions and images under your own names.
Cleanup leaves historical acceptance reports intact. Reports mentioning deleted
older workspaces are historical records; their files cannot be rechecked until
rebuilt, and they never prove that changed source code passed acceptance.
## Why the directory became so large
Image builders and integration tests deliberately retain their working
directories so failed runs can be inspected and a failed build cannot replace a
good published image. Repeated suites therefore accumulate complete rootfs
trees, archives and several disk images per case. Before this target existed,
this checkout's `out/` measured approximately **900 GiB** of allocated file
blocks after repeated M0–M12 and workstation validation runs. That is accumulated
history, not the size of a single OS image.
`make clean` removes superseded workspaces; it does not impose automatic
retention during a build. Use the preview periodically. For complete builds,
allow tens of GiB per independent tree plus space for VM tests and saved inputs;
the small initial smoke test's space requirement does not cover the full suite.
The preview uses allocated blocks, rather than adding the apparent capacities
of sparse VM disks. Hardlinks are counted once within the selection. Reflinks,
filesystem snapshots and hardlinks into retained directories can reduce the
physical space actually recovered. The command also prints the observed change
in filesystem free space; other processes and delayed filesystem accounting can
affect that number.
## Running safely and rebuilding
Use cleanup sequentially, just like the existing shared-container builds. It
holds the existing rootfs/base-package/image-tool locks, excludes another
cleanup, and refuses mounted candidate paths or observable active build/VM
processes. These checks are safeguards, not permission to start a separate build
while cleanup is running. It never follows a candidate symlink into another
directory, and refuses a symlinked `out/` entirely. Only the checkout containing
the script is eligible; there is no arbitrary deletion-path option.
Existing published images and native tools remain usable after cleanup. To
refresh exported binaries, run `make tooling` or `make workstation`. To rebuild
the smoke-test artifacts and rerun build checks:
```sh
make smoke-test
make check
```
`make check` needs the smoke-test binary in `target/`, so run the smoke test first
after cleaning. `make clean-test` independently exercises cleanup selection,
preserved releases/data, repeated runs, symlinks, tracked files, read-only guest
directories, hardlinks, locks, live processes and mount rejection using tiny
temporary repositories. It does not build OS images or boot VMs.
## Recorded cleanup validation
On the development checkout, cleanup removed 576 disposable directories with
a 638.00 GiB allocated footprint. The filesystem reported 605.86 GiB more free
space immediately afterward; `out/` dropped from about 900 GiB to 268 GiB.
The retained space includes independent release rebuilds, input snapshots and
current image/test workspaces. A second preview selected zero directories.
All 52 recorded current image/tool and frozen release file hashes remained
identical, and all 33 published links remained intact. `make bootstrap`,
`make smoke-test`, `make check` and all nine cleanup fixture tests passed.
The smoke test skipped its optional ARM execution because host `qemu-aarch64`
was unavailable; its ELF checks passed. This validates host cleanup and does
not add any physical Pi or monitor acceptance evidence. The local command log
is `out/logs/cleanup.log`, with a summary in
`out/manifests/cleanup-acceptance.json`.
-122
View File
@@ -1,122 +0,0 @@
# Cartridge and desktop revision
This work replaces the custom software bundle workflow and separates the user
manual from development history. The accepted 0.1.0 release stays unchanged.
## Required outcomes
1. Professional user documentation contains installation, usage and reference
material. Milestone history, measurements, test gaps and implementation notes
live separately under `docs/developer/`.
2. `fds-cartridge` builds Void source packages with `xbps-src`, installs their
runtime dependencies on the workstation, and writes installed program trees
directly into EROFS payloads. New cartridges require no guest extraction.
Existing cartridge reading stays available.
3. The grayscale E-Ink WindowMaker appearance is the default desktop theme on
every display, with normal user customization still possible.
4. Validated cartridge commands become available in existing shells through a
stable PATH directory. Direct launches retain terminal behavior, arguments,
exit status, dependency paths and managed eject/unplug cleanup. Command name
collisions have deterministic, documented behavior.
5. A native Rust X11 control panel with a matching retro appearance manages bay
status, cartridge details, program launch and safe eject from WindowMaker.
## Implementation and acceptance
- Replace creation of custom xz bundles with typed Clap source-package options,
installed-tree metadata, deterministic integrity checks and direct mounts.
- Verify real Void builds, dependency installation and resulting image contents;
retain corruption, filesystem and write/readback rejection coverage.
- Add managed PATH launch registration and exercise foreground commands,
collisions, eject, surprise removal and service restart in the ARM guest.
- Package and integrate the X11 panel and defaults. Exercise the actual window,
input actions and rendered appearance in the guest desktop.
- Rebuild CLI and development images. Run workstation/emulator, rootfs/init and
relevant cartridge/DATA/desktop/shutdown regressions against changed sources.
- Rewrite and relocate documentation after the final interfaces are verified.
Keep hardware-specific validation procedures and limits in developer notes.
- Use `make clean-preview` and `make clean` between completed validation batches
to avoid retaining hundreds of GiB of obsolete images again.
Current status: all five changes are implemented and the fresh host/ARM VM
acceptance below has passed. Physical checks remain a separate hardware task.
## Fresh acceptance
The validation logs are retained under `out/logs/cartridge-desktop-revision/`.
`out/manifests/cartridge-desktop-revision.json` records the actual working-tree
source hashes, artifact hashes, fixture identities and log hashes. The source
revision is uncommitted; HEAD alone does not identify these changes.
These checks ran against this revision, independently of the frozen release.
| Check | Evidence and scope |
| --- | --- |
| Build host | `make bootstrap`, `make smoke-test`, `make check`; all passed. The optional host-native `qemu-aarch64` shortcut was skipped; ARM execution uses the prepared Void environment. |
| Root filesystems | `make rootfs PROFILE=cli` and `PROFILE=development`, with `make rootfs-test` for each: passed. Archives `rootfs-build.9f2sNj` and `rootfs-build.PIumlA` contain byte-identical copies of the exported FDS tools and Dasung daemon. |
| Boot / tools | `make init-test`, `out/m2-vm.ATiMG0`: native s6 PID 1, service control and orderly poweroff passed. `make tooling-test` passed shared contracts and real ARM diagnostic execution. Both SYSTEM images and initramfs were rebuilt. |
| Rust | Host tests for common contracts, software, workstation tools, cartridge service, CLI and X11 panel; static-musl ARM compilation passed. |
| Software creation | `make workstation-test`, `out/workstation-images.f6tq1mya`: actual xbps-src builds, installed dependencies, EROFS trees, deterministic image bytes, malformed-content rejection and confirmed file write/readback. |
| Emulator / foreground commands | `out/emu-test.tqxiaglf`: direct PATH and qualified names, literal arguments, pipes, exit status, cwd permissions, interactive input, Ctrl-C, suspend/resume, terminal restoration, collisions, unplug, daemon restart and legacy format-1 archive compatibility passed. |
| Cartridge control | `make cartridge-test`, `out/m6-vm.e5j5n3q5`: active SYSTEM protection, unprivileged IPC, bounded clients, mounts, busy/safe eject, hotplug, removal and restart passed. |
| DATA | `make data-test`, `out/m7-vm.oiam2j2z`: sustained writes, privilege drop, descendant cleanup, sync/unmount, persistence, multiple candidates, interrupted media and surprise removal passed. |
| Desktop | `make desktop-test`, `out/m8-vm.zgk_yiyn`: real ARM Xvfb/WindowMaker, authentication, UID 1000 panel, global grayscale defaults, mouse launch of legacy and Void programs, keyboard rescan, safe eject and session cleanup passed. ENVIRONMENT and isolated DHCP regressions also passed. |
| Shutdown | `make power-test`, `out/m10-vm.rx6kz1zd`: ordered poweroff/reboot, writeback, active preparation, quarantine and refusal on unsafe storage passed. |
| Documentation | User instructions and development history are separated; all 66 Markdown documents had balanced code fences and valid relative file links. The actual panel screenshot was visually reviewed. |
The [desktop screenshot](../images/fds-control.png) comes from the ARM guest,
not a mockup. Additional shell/where commands in its example cartridge belong
to the test fixture; the ordinary example publishes hello and report.
## Validation limits
The host used for acceptance is x86_64 Arch Linux with the prepared Void runner.
The native tool interface supports other Linux workstations with the documented
prerequisites, but this run does not establish coverage of every distribution.
No physical Raspberry Pi boot, USB bay calibration, flash power-loss durability,
Dasung power-cycle recovery, E-Ink refresh quality or hardware latency was tested.
Use the existing hardware procedures for those checks. VM timings are VM
observations only. The independent s6/Dasung base integration remains mandatory.
The saved 0.1.0 release and its frozen inputs were not modified.
## Implementation notes
The X11 panel uses `x11rb` 0.13.2 and its pure-Rust connection, with core X11
requests and no optional extensions or C GUI toolkit. The protocol companion
crate generates requests; `gethostname` and `rustix` support X11 authentication
and OS access. Versions are locked in Cargo.lock, and their licenses are included
by the existing Rust notice generator. This keeps the GUI compatible with the
static-musl target. Reference: https://docs.rs/x11rb/0.13.2/x11rb/.
Foreground commands pass their already-open stdio descriptors to the privileged
service. The service creates the child, joins its root-owned bay cgroup, drops to
UID/GID 1000, and only then resolves the requested working directory. A pidfd and
exit status return to the launcher; interactive sessions use a PTY proxy. No
writable cgroup descriptor or permission to migrate arbitrary PIDs is exposed
to a client. This matters because Linux cgroup writes use open-file credentials.
Reference: Linux 6.12 kernel/cgroup/cgroup.c, cgroup_procs_write_start().
The first ARM compilation exposed musl/glibc differences in msghdr field widths;
portable field casts fixed those. Final acceptance uses rebuilt binaries and
images, rather than the intermediate builds that preceded that correction.
The first emulator run found that Clap's multicall implementation uses
`Path::file_stem()`, truncating dotted aliases such as `b01:demo.report:shell`.
The launcher now lets Clap parse the invocation path as a typed positional and
uses a second typed external-subcommand parser for explicit `fds-program` calls.
Application help/version flags and dotted names have a regression test. This
keeps the no-manual-argument-parser rule while preserving the actual alias.
The initial X11 click test raced the desktop terminal appearing above the panel.
The test now waits for that terminal and raises the panel before each real mouse
click. Actual program output, consumer tracking and SAFE state are still checked;
window existence alone is not treated as successful launch.
The shutdown suite now publishes `out/m10-vm-latest` after successful completion.
That pointer was also installed for the successful run above. Cleanup retains
that evidence using the same published-link rule as the other runtime suites.
After validation, `make clean` removed 52 obsolete workspaces and Rust build
output. The filesystem reported 62.17 GiB of newly available space. Current
images, latest runtime fixtures, caches, personal outputs and frozen release
inputs were retained; the complete cleanup log accompanies this revision.
-72
View File
@@ -1,72 +0,0 @@
# Dasung integration validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Integration and usage](dasung.md)
Validated on 2026-09-20 on the x86_64 Arch development host. This records the FDS
port of the existing local `dasungd`; it is separate from the historical live
monitor validation in [the imported source](../../rust/dasungd/VALIDATION.md).
This is the original integration record. The subsequent rootfs build, emulated
ARM execution, and integration rerun are recorded in [M1 validation](m1-validation.md).
## Commands completed successfully
```sh
make bootstrap
make smoke-test
make check
make dasung
make dasung-test
```
Bootstrap now fetches the locked workspace dependencies. A fresh empty Cargo
cache had exposed that resolving the new workspace member also affects an offline
build of the dependency-free smoketest; the bootstrap fetch addresses that setup
requirement. The original dependency versions were retained; the workspace lock
adds the smoketest member to the imported controller lock.
## Checks performed
- Static AArch64 ELF validation of both the built daemon and the executable
extracted from its `aarch64` XBPS package. No dynamic interpreter, shared-library
requirements, or glibc symbol requirements were present.
- Six imported unit tests: framing/reassembly, malformed streams, parameter and
IPC validation, saved-setting reload, and exact EDID identity/checksum/clock.
- Pseudo-terminal monitor tests: late attachment, keepalive, fragmented replies,
invalid settings, saved-setting replay, unplug/replug, duplicate-instance
exclusion, reply watchdog recovery, restart, and forgetting settings.
- Native s6 execution of the packaged service graph in an isolated container with
a read-only root and no exposed physical USB or display devices. Startup,
logging, socket permissions, restart, and clean stop passed with no monitor.
This used the x86_64 test build of the same source, not ARM emulation.
- Package contents, exact config/profile, mandatory base package selection,
base boot-bundle membership, and compilation of packaged service sources.
- The original M0 ELF/source/overlay protections and Rust formatting checks.
## Artifact identities
```text
7a3daf7e7beadde7d4b0991a08af04952495e1ec155bd4a2e3605fb903dcc782 out/dasungd
9c33dc3808549016f388f409658abc3ba37ff0b8d05973dcf1d5f9f8dbd89e94 out/packages/fds-dasungd-0.1.0_1.aarch64.xbps
```
Package dependencies are coreutils, execline, s6, s6-rc, and eudev. The static
executable does not add a shared musl, libusb, or libudev runtime dependency.
The packet keepalive/pacing and confirmed 304210 kHz EDID are retained.
Local generated evidence: [build log](../../out/logs/dasung-build-final.log),
[test log](../../out/logs/dasung-test-final.log), and
[artifact manifest](../../out/manifests/dasung-artifacts.sha256). These outputs are
ignored by Git and must be regenerated in a fresh checkout.
## Not verified
There is no complete rootfs, native PID 1 deployment, Pi kernel/initramfs, or
bootable image yet. ARM runtime execution, Pi video/cabling, physical image quality,
early-boot display handoff, and corrected cold-power recovery were not tested.
The workstation's live display service and configuration were not changed.
Saved overrides use volatile `/run` until persistent machine storage is integrated.
The service test is not a boot or shutdown benchmark.
-188
View File
@@ -1,188 +0,0 @@
# Dasung Paperlike 13K base-system integration
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [First build](getting-started.md) · [Services](services.md)
FDS includes the Rust **dasungd** controller from the project's earlier
**Fix Dasung monitor black screen** task. It targets the user's **Paperlike 13K
grayscale**, serial `L56051794302`, with the confirmed 3200 × 2400 timing at about
37 Hz. This is core display support: its package is required for the base SYSTEM
image, and its service belongs to the base boot bundle, including console-only use.
It does not require an ENVIRONMENT cartridge, WindowMaker, or a graphical session.
**Implementation boundary:** the daemon, ARM package, and s6 service definitions
are included by the [rootfs assembler](rootfs.md). [M2 native init](init.md) starts
the controller in the base boot graph, including in the ARM VM with no monitor.
The [M4 boot path](boot.md) starts an early copy before SYSTEM exists, then stops
and reaps it before base s6 starts the normal daemon. Software handoff is tested
in a VM; physical Pi display and monitor recovery remain unverified. The final
s6 database is compiled at image build time.
## Build it on the workstation
Complete `make bootstrap` as described in [Your first build](getting-started.md),
then run from the repository root, as your regular user:
```sh
make dasung
make dasung-test
```
The first command prepares the additional C cross compiler inside the existing
Void build container, fetches the crates pinned in `Cargo.lock`, and cross-builds
static-musl `dasungd`. It verifies the ELF, builds the `fds-dasungd` XBPS overlay,
indexes the output, and compiles the actual s6 source into a validation database.
The second runs unit tests and a simulated serial monitor, then inspects the
package and compiles the service definitions extracted from that package. It also
runs that s6 graph with a native test binary, a read-only container root, and no
physical device access to check startup, logging, permissions, restart, and stop.
Python 3 is needed **only on the host for the simulator**. If absent on Arch,
install `python` with pacman. No Python interpreter is shipped for the daemon.
The first additional toolchain download is approximately 243 MB and needs roughly
1 GB of extra container space. Builds still depend on rolling Void binary inputs.
Run these commands sequentially with other FDS builds; they share the container.
| Output | Purpose |
| --- | --- |
| `out/dasungd` | Static AArch64 executable; no target glibc, musl, libusb, or libudev shared library dependency |
| `out/packages/fds-dasungd-0.1.0_1.aarch64.xbps` | Target package with executable, config, exact EDID, s6 source, udev rule, and licenses |
| `out/manifests/dasung-s6-database.txt` | Path to the service database compiled on the host for validation |
| `out/manifests/dasung-artifacts.sha256` | Executable and package hashes |
| `out/manifests/dasung-build-packages.txt` | Installed build-container input versions |
| `out/logs/dasung-build.log`, `dasung-checks.log` | Build and test evidence |
The standalone validation database contains only the Dasung subset. The M2
image assembler merges it with the init package and compiles the full current
graph while building the rootfs. No daemon is started on the workstation by these targets;
the integration test uses its own temporary socket and pseudo-terminal.
## What goes into the base system
`image/base-packages.list` makes `fds-dasungd` a mandatory hardware package.
`packages/fds-dasungd/template` is the active FDS-owned XBPS overlay. The upstream
Void tree remains pinned and unchanged. Source is maintained at `rust/dasungd/`;
the original task directory is not required for future builds.
The package installs:
- `/usr/bin/dasungd` and `/etc/dasungd.toml`.
- `/usr/lib/firmware/edid/dasung-paperlike13k-37hz.bin`.
- `/etc/s6-rc/source/dasungd`, its runtime-directory and log services, and the
`boot/contents.d/dasungd` membership entry.
- `/usr/lib/udev/rules.d/99-dasung-spi.rules`, adapted to eudev syntax.
- Documentation and the controller/libusb licenses.
s6 supervises the foreground daemon. A short oneshot creates private writable
runtime/log directories; it does not wait for the display. The daemon starts with
no monitor attached and retries discovery. Its initial service transition does
not wait for a monitor reply, a desktop, the network, or all cartridge bays.
The log service rotates a small log under `/run/log/dasungd`.
The init package adds a dependency on `runtime-fs` so the controller starts with
its kernel interfaces mounted. The service runs as root because it claims USB interfaces. Its socket
is under a root-owned mode-0750 directory; runtime control is restricted to root.
A future FDS CLI can expose selected operations through an explicit policy.
No world-writable USB rule, systemd unit, or runit service is installed.
## Confirmed profile and Pi-specific choices
| Property | Recorded value |
| --- | --- |
| Monitor | Paperlike 13K grayscale; `L56051794302` |
| Resolution | 3200 × 2400 |
| Pixel clock | **304210 kHz** (304.21 MHz) |
| Horizontal active / sync start / sync end / total | 3200 / 3248 / 3280 / 3360 |
| Vertical active / sync start / sync end / total | 2400 / 2423 / 2427 / 2447 |
| Sync polarities | Positive horizontal and vertical |
| EDID SHA-256 | `b6c1e0a8d315d9cf5c2fb83784a177530bcc085c2d0724dc7478a9c12449e4f4` |
| Control transport | USB UART `1a86:7523`, with companion SPI bridge `1a86:5512` |
| Startup parameters | Raw mode 1 and contrast 4, previously read back on this unit |
| Keepalive | `20 01` every two seconds; minimum 150 ms between command writes |
The video timing was confirmed on the original AMD workstation, **not on a Pi**.
The original USB-C video connection is not a Pi cabling prescription. Pi connector,
cable/adapter, mode acceptance, and picture stability require hardware validation.
Desktop 2× scaling is separate from EDID timing; a console uses its own font size.
FDS configures `display.enabled=false` and `hotplug="none"`: the daemon handles
USB control without applying the source project's AMD debugfs disconnect/reconnect
procedure. The Pi boot/display integration must supply the appropriate KMS/firmware
EDID configuration for its real connector. The Pi boot-volume builder now supplies
an unqualified firmware EDID override for this dedicated monitor; it does not
assume `DP-2` or a numbered HDMI connector. See the [boot guide](boot.md) for
the exact command line and the remaining physical mode test.
The daemon requires the configured monitor's DRM EDID and the matching USB
companion topology before claiming a device. Multiple matching UARTs are rejected;
`usb_path` can disambiguate a verified Pi topology. Do not substitute a blanket
match for all CH340 adapters or copy this unit's identity to a different monitor.
The original monitor's SPI driver interaction caused a dark picture after a power
cycle. `image/kernel/dasung.config` requires `CONFIG_SPI_CH341=n`; the Pi kernel
builder enforces this before boot, including the initramfs environment. The scoped
udev rule is secondary protection: it cannot prevent a built-in or already-loaded
SPI driver from probing. The daemon also reserves the companion interface without
sending SPI data and refuses to take it from an already-bound kernel driver.
## Runtime usage once installed in an FDS image
These are target commands, **not commands to run against the workstation's live
monitor during the build**:
```text
dasungd status
dasungd query
dasungd refresh
dasungd set contrast 4
dasungd set mode 1 --save
dasungd forget mode
```
`status` returns JSON including `connected`, `responsive`, reply age, and cached
parameters. A successful set means a packet was sent; use query/status to check
observed values. It is not proof of physical image quality. Color-model mode names
are not assigned to this grayscale monitor's raw numeric modes.
The default socket is `/run/dasungd/control.sock`. External programs can use its
newline-delimited JSON protocol; the daemon remains the only USB owner:
```json
{"op":"status"}
{"op":"set","parameter":"contrast","value":4,"save":true}
```
In FDS's current payload, saved overrides live in `/run/dasungd/settings.json`.
They survive a daemon restart **within the same boot**, but not power loss.
Mode 1/contrast 4 are persistent image configuration defaults. Once persistent
machine-state mounts are implemented, point `state_file` at that internal writable
storage to retain user overrides across boots. Do not put it on read-only SYSTEM
or make the base display daemon depend on a removable DATA cartridge.
## Dependencies and source provenance
The imported Rust dependencies are retained and pinned by the workspace lockfile:
`rusb` wraps USB access; `libc` supports Linux serial/locking calls; `ctrlc` handles
termination; `clap` provides CLI parsing; `serde`, `serde_json`, and `toml` support
configuration and the local socket; `anyhow` supplies contextual errors.
Transitive versions are recorded in `Cargo.lock`.
The `vendored` feature builds libusb from the pinned `libusb1-sys` crate. The Void
`cross-aarch64-linux-musl` toolchain supplies C headers/compiler for that library;
Rust's bundled musl target alone was enough for the old pure-Rust smoke-test, but
is insufficient for this C dependency. `LIBUSB_NO_PKG_CONFIG` and
`LIBUDEV_NO_PKG_CONFIG` prevent accidental host/shared-library linkage. libusb uses
its Linux netlink backend. No libusb/libudev shared runtime package is needed.
The target package depends on GNU coreutils for directory preparation, execline
for service launch, s6/s6-rc for supervision, and eudev for device rules. These
match the planned base userspace. The container's s6-rc is a build-time compiler,
not a target PID 1 running on Arch.
[Import provenance](../../rust/dasungd/IMPORT.md) records the original source hashes.
[Original validation](../../rust/dasungd/VALIDATION.md) records the live handoff and
remaining physical test. Protocol and reconnect tests use simulated hardware;
Pi boot, image quality, physical disconnect/reconnect, and corrected cold-power
recovery must still be verified on the actual target. No boot benchmark is claimed.
-141
View File
@@ -1,141 +0,0 @@
# Writable DATA and managed programs
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Cartridges](cartridges.md) · [Services](services.md)
M7 implements writable DATA. Its sustained-write, safe-eject and surrounding
image regression checks [passed in the ARM VM](m7-validation.md). A DATA
cartridge uses a GPT partition named `FDS_DATA`, ext4, and a validated
`/FDS/CARTRIDGE.TOML` declaring class `data` and `writable = true`.
Use the [M9 media tools](media-tools.md) to create new DATA media. The virtual
tests construct disposable image files and never format a workstation disk.
## Where your files live
`/home/fds` remains a temporary home, whether DATA is present or absent. Its
contents disappear at power-off. Persistent user files belong under `/data`.
Separating these paths lets you eject DATA without hiding the active shell's
home directory or substituting another cartridge underneath it.
After inserting a DATA cartridge into a configured bay:
```sh
fds bays
fds cartridge 2
ls /data
```
If exactly one valid DATA candidate is available and none is active, the daemon
activates it at `/data`. The filesystem must grant UID/GID 1000 the intended
write permissions; insertion does not recursively change ownership of user files.
M9's DATA formatter supplies that ownership for fresh media.
An active DATA session stays attached to its current cartridge when another is
inserted. If a discovery snapshot contains multiple candidates with none active,
they remain read-only until you select one:
```sh
fds data use 4
```
Eject the existing active DATA before selecting a replacement. The daemon never
changes `/data` underneath an active session. EMPTY, ERROR and SAFE states retain
the meanings in the [cartridge guide](cartridges.md); MOUNTED READ WRITE means
that the displayed DATA filesystem is available for user writes.
## Run a program that participates in eject
Ordinary console commands work as usual. They are not automatically killed when
you request eject: an open file or working directory may make eject fail as busy.
For a background task that should stop when its DATA is ejected, use:
```sh
fds run 2 -- /usr/bin/bash -c 'date > /data/managed-example.txt'
```
This starts a **background** managed program and prints its process ID. It has no
interactive input. Output goes to the bounded, volatile cartridge service log:
```sh
tail /run/log/cartridged/current
fds cartridge 2
```
The executable path must be absolute. The child starts with `/data` as its working
directory, the ordinary FDS UID/GID, no supplementary groups, no effective
capabilities, a small explicit environment, and `no_new_privs`. Programs cannot
gain privileges through setuid executables. The limit is 256 managed processes
per bay. This interface accepts healthy writable DATA; the [desktop guide](desktop.md)
also explains M8's explicit PROGRAM-media launch interface.
A root-owned Linux cgroup tracks the program and its descendants, including
children that outlive their parent. Cgroups are kernel process tracking, not a
new init system or runtime package. The configured kernel already includes the
required cgroup v2 and process-limit support. The daemon sets up its hierarchy
independently of the console. See the [kernel cgroup interface](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html).
## Eject and remove DATA
```sh
cd "$HOME"
fds eject 2
```
Eject checks for additional mounts, stops managed consumers, flushes the DATA
filesystem with `syncfs`, and performs a normal unmount. Only successful completion
produces SAFE. A retained filesystem descriptor observes writeback errors during
the session. M10 additionally makes the filesystem read-only and checks writeback
again before closing that descriptor for unmount, preventing an error-reporting
gap when a busy mount needs a later retry.
Linux's [syncfs interface](https://man7.org/linux/man-pages/man2/syncfs.2.html)
reports filesystem errors that must not be ignored.
Managed programs receive TERM first. The daemon waits on actual cgroup exit
notifications; programs that have not exited after a one-second deadline are
killed as a group, including newly forked descendants. A further bounded exit
check must succeed. These are failure deadlines, not delays imposed on a program
that has already stopped. Requesting eject therefore ends those background jobs;
it does not promise that unfinished application work is completed.
An unmanaged busy shell/file, extra mount, failed flush or failed unmount prevents
SAFE. Close the reported use and retry. A recorded DATA writeback fault requires
investigation or recovery; a later successful call is not used to erase it and
pretend the failed write succeeded. The daemon never uses lazy/forced unmount to
claim successful eject. A successfully ejected insertion remains unmounted across
a daemon restart until removal/reinsertion.
M10 records writable sessions and faults outside the daemon process. If it crashes
before verified unmount, the same insertion stays quarantined and read-only on
restart. A later flush cannot reconstruct the lost error history. See
[Shutdown and recovery records](power.md#data-recovery-records) for the current
implementation and its acceptance status.
Pulling DATA during writes is an error. The daemon stops its managed consumers,
cleans up the vanished mount and logs that writes may have been lost. This path
never produces SAFE and makes no filesystem-cleanliness promise. Use recovery
inspection/repair before trusting media that was removed during I/O.
## Build and verify on the workstation
After changing DATA code or image configuration, build fresh inputs in order:
```sh
make rootfs PROFILE=cli
make initramfs
make system-card
make data-test
```
The test boots disposable virtual media, writes continuously, verifies ordinary
user privileges and descendant shutdown, checks busy and additional mounts,
reopens persisted data, tests multiple candidates and simulates surprise removal.
After safe eject it extracts the ext4 partition into an ordinary file, runs
read-only `e2fsck -fn`, and compares the sustained-write payload byte-for-byte.
No host mount or physical USB device is used.
Run `make cartridge-test`, `make init-test`, `make boot-test` and
`make console-test` for the surrounding regressions. Test logs and VM evidence
are written under `out/`. Real flash-controller caches, battery loss, USB power
and physical Pi eject/shutdown latency remain hardware acceptance work.
-215
View File
@@ -1,215 +0,0 @@
# Desktop, programs, and networking
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Cartridge guide](cartridges.md) · [DATA guide](data.md)
M8 software checks passed in the ARM virtual machine. The [validation report](m8-validation.md)
records desktop, program and networking evidence. Raspberry Pi video output and
Dasung display quality remain deferred physical checks.
## What starts when you boot
FDS opens its ordinary-user console first. Xorg, WindowMaker, and the DHCP client
are installed in SYSTEM but are absent from the boot bundle. Dasung control
remains in the base boot bundle, including when no desktop is running.
The default graphical backend is Xorg on virtual terminal 2, using its built-in
modesetting driver. The physical keyboard and pointer use libinput. Console
access remains on the original terminal. There is no display manager. Physical
VC4 output, monitor resolution, and E-Ink appearance still require Pi testing.
## Start and stop the desktop
At `FDS>`:
```sh
fds profiles
fds profile activate windowmaker
fds profiles
```
Activation is asynchronous. The second status command reports the selected
profile and an activation-to-ready duration once WindowMaker is ready. This
measurement starts at the activation request, so time spent deciding when to
start the desktop is excluded. `fds boot-profile` also records the first desktop
readiness event for the boot. Neither measure includes firmware or power-on.
The desktop opens an FDS terminal. Right-click its white background to open the
menu, launch another terminal, enable/disable Ethernet, or return to the console.
To stop it from either a terminal or the console:
```sh
fds profile deactivate
```
Stopping the desktop closes its terminal and all processes descended from its
session. Save work first. Files in `/home/fds` survive desktop restarts during the
same boot; the home directory is still temporary and disappears at shutdown.
Use [DATA](data.md) for persistent files. Removing an active DATA cartridge also
stops the desktop before flushing DATA, because desktop applications may use it.
`fds-profile status`, `fds-profile activate windowmaker`, and
`fds-profile deactivate` provide the standalone static helper interface.
## ENVIRONMENT cartridges
An ENVIRONMENT cartridge is a read-only EROFS partition named `FDS_ENVIRONMENT`.
Its `FDS/CARTRIDGE.TOML` uses the [documented manifest format](cartridges.md), with:
```toml
[activation]
profile = "windowmaker"
```
When exactly one eligible ENVIRONMENT cartridge is present, the daemon requests
WindowMaker after console readiness. The graphical runtime comes from SYSTEM;
the cartridge provides a declarative request, never a privileged executable.
Unknown profile names do not execute anything. If several eligible cartridges
arrive together, select a profile explicitly instead of relying on bay ordering.
Eject its bay with `fds eject N`. The desktop stops before SAFE is reported.
Surprise removal also stops its associated desktop. Manually deactivating while
the cartridge remains inserted suppresses automatic reactivation until removal
and reinsertion. A manually started desktop is independent of ENVIRONMENT media.
A cartridge-service restart reevaluates mounted media and retires old optional
sessions; this is a maintenance operation, not a way to preserve desktop jobs.
## E-Ink defaults
`fds-eink` installs the WindowMaker policy and Terminus fonts: a white background,
black text and borders, grayscale controls, outline movement/resizing, no
animations, no blinking decorations, no app-icon bounce, no dock, no compositor,
and no periodically updating clock. X11 compositing is disabled. The terminal
uses black on white and a nonblinking cursor.
Defaults are copied into the ordinary user's `~/GNUstep/Defaults` at the first
session. Later sessions preserve edits there. They are temporary unless you
explicitly store a copy on DATA. The authoritative image defaults live in
`/usr/share/fds/eink`; edit their package sources and rebuild SYSTEM to change the
machine-wide defaults.
Font caches and X11 font indexes are generated while building the image.
An active X server's keyboard map is session state, generated only on desktop
activation. That is separate from global boot-time cache generation.
## Launch a software cartridge
Build current software cartridges on a [Linux workstation](workstation.md). They
contain metadata plus xz software bundles in payload partitions. In FDS:
```sh
fds bay 4
fds run 4 -- demo.hello:hello
fds eject 4
```
Use the software id and command listed by `fds bay`. The guest verifies and
extracts the bundle into a read-only temporary cache, then starts it as UID 1000.
There are no guest software build hooks.
### Legacy PROGRAM compatibility
Existing PROGRAM media contains a read-only EROFS partition named `FDS_PROGRAM`, metadata
in `FDS/CARTRIDGE.TOML`, and its self-contained application files:
```text
app/bin/editor
app/lib/
app/share/
```
FDS validates and mounts it at `/run/fds/apps/<cartridge-id>`. Insertion does not
run anything, and the mount initially disallows execution. To launch an executable
named `editor` from bay 4:
```sh
fds cartridge 4
fds run 4 -- editor
```
Use a simple executable name from `app/bin`, followed by that program's arguments.
The daemon enables read-only execution and starts it as UID 1000, with no added
privileges. Executable paths must remain within the cartridge's `app` directory.
Programs receive `FDS_APP`, a PATH including `app/bin`, `LD_LIBRARY_PATH` including
`app/lib`, and `XDG_DATA_DIRS` including `app/share`. Bundled libraries can also use
an executable-relative RPATH. FDS does not resolve dependencies across cartridges.
Programs are background jobs. Graphical programs can use the active authenticated
X display. Standard output/error goes to the bounded, temporary cartridge log:
```sh
tail /run/log/cartridged/current
fds bay 4
fds eject 4
```
The bay status reports managed processes. Eject stops the whole process group,
including forked descendants, before unmounting. The existing DATA form remains
`fds run N -- /absolute/system/command arguments`, using `/data` as its working
directory. DATA itself remains mounted with execution disabled.
## Ethernet on demand
External networking is off until explicitly requested or a mapped USB Ethernet cartridge
produces a real network interface. Interfaces are associated with their USB
ancestors; a descriptive label alone does not start networking. Wi-Fi setup is
not implemented by this Ethernet policy. The local loopback interface is always
up for applications on the same machine; it does not enable external traffic.
```sh
fds network on
fds profiles
ip -brief address
ip route
fds network off
```
Enabling networking starts DHCP without waiting for a lease. An absent server
cannot delay `FDS>`. Explicit activation selects available Ethernet interfaces;
automatic activation selects interfaces under mapped cartridge USB devices.
Stopping networking stops DHCP, brings the managed interfaces down, and clears
temporary DNS settings. This initial profile uses IPv4 DHCP; it disables kernel
IPv6 autoconfiguration on those interfaces. After an explicit stop, an inserted Ethernet
cartridge remains suppressed until it is removed, or you enable networking again.
Leases, DNS settings, and logs live under `/run`; nothing makes SYSTEM writable.
`/etc/resolv.conf` points to `/run/fds/resolv.conf`. Inspect
`/run/log/network/current` when an address is missing.
## Development and troubleshooting
The production console image includes the real desktop stack. The development
image adds Xvfb and X11 diagnostics for a virtual display without a connected GPU:
```sh
make rootfs PROFILE=development
make desktop-test
```
The test creates its own disposable image selecting Xvfb; the normal development
image still selects Xorg. Never mistake an Xvfb pass for Pi graphics verification.
Changing the SYSTEM image profile requires building that rootfs first; the image
builder rejects a profile label that disagrees with its embedded identity.
If desktop activation fails, `fds profiles` reports it. Read
`/run/log/xserver/current` and `/run/log/desktop/current`, then deactivate before
retrying. Xorg's detailed log is `/run/log/xserver/Xorg.0.log`. X access requires
an authority cookie readable only by root and the FDS group; do not use `xhost +`
or disable access control as a workaround.
Dependency rationale: Xorg supplies the display server and modesetting driver;
libinput supplies keyboard/pointer input; WindowMaker supplies window management;
Terminus and fontconfig supply the typography; xterm supplies a terminal; the static FDS helper
observes X11 property events for readiness without polling delays; xset/xsetroot apply the
static session settings. Xvfb, xdotool, xwd, xwininfo, xdpyinfo, xprop, and xauth are
optional development diagnostics. `fds-dhcpcd` builds the pinned upstream DHCP
client with privilege separation and volatile state paths, omitting its runit
service files. All FDS control helpers remain static Rust/musl binaries.
The readiness observer uses the [X11 core protocol](https://xorg.freedesktop.org/archive/X11R7.7/doc/xproto/x11protocol.html)
and establishes its property subscription before launching the window manager.
Font caches are generated with the image's normalized timestamp, following
[fontconfig's reproducible-cache support](https://fontconfig.pages.freedesktop.org/fontconfig/fontconfig-user.html),
so exporting the filesystem does not invalidate their directory timestamps.
-315
View File
@@ -1,315 +0,0 @@
# Working on FDS/OS
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [First build](getting-started.md) · [Troubleshooting](troubleshooting.md)
This guide is for someone who has completed the first build and wants to change
or inspect the working code. Run commands from the repository root, as a normal
user. M0–M12 software acceptance is complete; consult [AGENTS.md](../../AGENTS.md)
and the [roadmap](roadmap.md) before adding components.
## Choose the smallest useful build
| You changed or want to do | Run |
| --- | --- |
| See the available entry points | `make help` |
| Prepare a new checkout or restore missing host tools | `make bootstrap` |
| Edit the Rust smoketest | The Rust build and validation sequence below |
| Build/test the base display controller | `make dasung`, then `make dasung-test`; see [Dasung](dasung.md) |
| Change the CLI, shared contracts or stage0 | `make tooling`, then `make tooling-test`; rebuild initramfs and run `make boot-test` for boot-path changes |
| Change kernel configuration | `make kernel`, rebuild rootfs and boot images, then `make boot-test`; see [Boot images](boot.md) |
| Change USB discovery, mapping or media writing | Build CLI images, then `make stress-test`; see [Stress tests](stress-testing.md) |
| Change shutdown or DATA safety | Rebuild both profiles; run `make power-test` against development, plus `make data-test` and `make init-test`; see [Shutdown](power.md) |
| Change init or boot services | `make rootfs PROFILE=cli`, `make rootfs-test`, then `make init-test`; see [Native init](init.md) |
| Change base packages or rootfs assembly | `make rootfs PROFILE=cli`, `make rootfs-test`, then `make init-test`; see [Rootfs](rootfs.md) |
| Build the native development toolset | `make rootfs PROFILE=development`, `make rootfs-test`, `make system-card PROFILE=development`, then `make development-test` |
| Change internal storage or machine settings | Rebuild CLI and recovery, then `make internal-test`; see [Internal storage](internal-storage.md) |
| Change release signing or verification | `make signing-test`; see [Release signatures](releases.md) |
| Rebuild the test XBPS package | `./tools/build-package hello` |
| Verify an M0 build-system change | `make bootstrap`, then `make smoke-test`, then `make check` |
| Check Rust formatting and validation protections | `make check`, after building the Rust program |
| Read or edit documentation | Check examples, links, and claims against the implementation |
Run builds sequentially. The scripts share one Void masterdir and package cache;
starting bootstrap and package builds concurrently is not a supported workflow.
`make packages` builds every FDS base package. `make all` builds the complete
artifact set; two independent frozen offline builds passed with matching output. See
[Frozen inputs and offline rebuilds](reproducible-builds.md). Use `make clean-preview`
and `make clean` to remove obsolete workspaces and Rust compilation output while
keeping published images, latest test fixtures, saved releases, inputs and
caches. See [Cleanup](cleanup.md) for retention rules and rebuilding afterward.
There is no generic `make test` target.
## Edit and rebuild the Rust program
The active program is [rust/fds-smoketest/src/main.rs](../../rust/fds-smoketest/src/main.rs).
It is deliberately tiny: it establishes that the intended target and static
linking work. `rust/dasungd/`, `rust/fds-common/`, `rust/fds-cli/`, and
`rust/fds-stage0/`, `rust/fds-boottrace/`, and `rust/fds-cartridged/` are also active. Directories absent from the workspace member
list remain reserved. Use the [tooling guide](tooling.md) for the FDS programs,
[Cartridges](cartridges.md) for bay and metadata changes, and [DATA](data.md)
for writable storage and managed jobs.
Use the shared `clap` workspace dependency for every Rust command-line interface.
Declare commands and options with `Parser`, `Subcommand`, `Args`, and typed value
parsers; do not match or consume raw argument vectors. Shared media syntax lives
in `rust/fds-burn/src/cli.rs`; installed FDS aliases use Clap's multicall support
in `rust/fds-cli/src/cli.rs`. Keep authorization and filesystem validation in the
operation layer after parsing. Add parser tests when changing required options,
conflicts, private s6 flags, aliases, or child-argument forwarding.
The disposable Rust VM workloads in `tests/helpers/` are a Cargo workspace
member and use the same locked Clap dependency. They are never installed in
the base packages. Their stress workload uses release optimization level 2;
production executables retain the size-oriented release settings.
After editing the program:
```sh
cargo fmt --all
cargo build --locked --offline --release \
--target aarch64-unknown-linux-musl -p fds-smoketest
./tools/verify-elf target/aarch64-unknown-linux-musl/release/fds-smoketest aarch64 static
make check
```
Cargo writes to `target/aarch64-unknown-linux-musl/release/`. It does **not** refresh
`out/fds-smoketest` or its manifest. Run `make smoke-test` when you need a freshly
exported artifact and matching checksums.
If you installed optional QEMU, execute the freshly built file with:
```sh
qemu-aarch64 target/aarch64-unknown-linux-musl/release/fds-smoketest
```
The smoke-test contract expects exactly `FDS/OS M0: aarch64 static-musl OK`.
Changing that text requires updating the runtime assertion and documentation as
part of the same change. The crate also rejects the wrong architecture, libc,
or missing static CRT at compile time. Do not bypass those checks to make an
x86_64 build appear to pass.
The release profile and target configuration live in [Cargo.toml](../../Cargo.toml)
and [.cargo/config.toml](../../.cargo/config.toml). Rust and target versions are
selected through [rust-toolchain.toml](../../rust-toolchain.toml). This M0 crate has
no external crate dependencies. `--offline` works after bootstrap has installed
the toolchain; it is not a promise that the complete build pipeline is offline.
## Build and inspect a package
```sh
./tools/build-package hello
```
This checks the Void source pin, prepares configuration and overlays, invokes
xbps-src's aarch64 cross build, and copies the resulting main package into
`out/packages/`. Its log is `out/logs/xbps-hello.log`. Follow
[Packages](packages.md) for indexing, querying, and extracting the output.
`hello` is the verified M0 package. The helper accepts other template names, but
its exporter currently looks only for that main package in the top-level
`hostdir/binpkgs/`. Packages emitted into subrepositories and separately named
subpackages need additional handling; the helper is not yet a universal package
export interface.
For diagnosis, this is the underlying command in its correct host environment:
```sh
(
export PATH="$PWD/.host/xbps/usr/bin:$PATH"
export XBPS_ARCH=x86_64
cd vendor/void-packages
./xbps-src -a aarch64 pkg hello
)
```
The subshell keeps the directory and environment changes local. Prefer the FDS
helper for normal work: it also checks the pin and exports the output. The raw
command does neither. Do not run `xbps-install` against the Arch host or install
an ARM package into the x86_64 build container.
## Change build configuration deliberately
The source of truth is [config/xbps-src.conf](../../config/xbps-src.conf).
`XBPS_MAKEJOBS` controls package build parallelism; the current value is 4.
`XBPS_CHROOT_CMD=bwrap` selects the supported container backend.
`tools/prepare-void` copies this file to the ignored
`vendor/void-packages/etc/conf`. If the two differ, it stops instead of silently
overwriting local settings. After intentionally editing the checked-in file,
review the difference:
```sh
diff -u vendor/void-packages/etc/conf config/xbps-src.conf
```
A difference makes `diff` exit 1; that is expected. Once the checked-in version
contains every setting you intend to keep, synchronize and prepare:
```sh
cp config/xbps-src.conf vendor/void-packages/etc/conf
./tools/prepare-void
```
This overwrites the local generated configuration, so reconcile any intentional
local edits before copying. Re-run the three M0 validation commands after a build
configuration change. A mirror change does not lock the packages served by that
mirror; see [input limitations](build-host.md#known-limitations).
## How future package overlays fit
FDS-owned package sources belong in `packages/fds-*/`, outside the upstream
submodule. An overlay becomes active only when its directory contains a
`template`. `fds-base`, `fds-base-files`, `fds-init`, `fds-dasungd`, `fds-cli`, `fds-kernel`,
`fds-cartridged`, `fds-eink`, and `fds-dhcpcd` are active.
For active overlays and future templates, `tools/prepare-void` copies each
active directory into `vendor/void-packages/srcpkgs/`. It allows an identical
repeat copy, rejects a name that collides with a tracked upstream package, and
stops if a previous generated copy differs. Edit the source under `packages/`,
then reconcile the specific stale generated copy. Never put the only copy of an
FDS change inside `vendor/`.
The [troubleshooting guide](troubleshooting.md#configuration-or-overlay-conflict)
explains how to preserve and inspect a conflicting copy. Empty future directories
are intentional; adding a fake template to silence a missing-package error does
not implement that milestone.
## Preserve logs and identify inputs
The normal log filenames are reused. Before reproducing a failure that you need
to retain, save the existing logs:
```sh
log_archive=$(mktemp -d "$PWD/out/saved-logs.XXXXXX")
cp -a out/logs/. "$log_archive/"
printf 'Saved logs to %s\n' "$log_archive"
```
After a successful smoke-test:
```sh
cat VOID_PACKAGES_COMMIT
rustc --version
cargo --version
cat out/manifests/void-build-packages.txt
sha256sum -c out/manifests/artifacts.sha256
```
The package inventory lists what remains installed in the build container after
xbps-src cleans up temporary dependencies. Cached package digests and build logs
provide additional input records. These records do not constitute a complete,
immutable upstream package repository snapshot.
## Source updates and changes for review
Keep upstream tracked files unchanged. Updating Void is a deliberate change to
both the submodule gitlink and `VOID_PACKAGES_COMMIT`, followed by the full M0
checks. Updating host XBPS requires a matching archive URL and checksum. Updating
Rust requires considering the pinned toolchain, target, and generated artifacts
together. Do not use an unreviewed `git pull` inside the submodule as a repair.
For a change report, state what changed, which commands actually passed, and what
was skipped. Include the failing command and relevant log when reporting a bug.
Do not describe ELF inspection as execution, QEMU userspace execution as a Pi
boot, or a target timing as a measured result. Use English for documentation,
comments, diagnostics, and project discussions.
## Desktop verification
Build `make rootfs PROFILE=development`, then run `make desktop-test`. This
executes the actual ARM WindowMaker, terminal, cartridge daemon, and DHCP client
in disposable VMs. Its Xvfb backend and root test console exist only in generated
fixtures. The normal SYSTEM configuration uses Xorg and the ordinary FDS user.
VM helpers place QEMU in a private network namespace. The test's virtual DHCP
server can advertise DNS while remaining unable to reach host interfaces or the
Internet. QEMU diagnostics have separate `*.stderr.log` files, keeping host
emulator messages out of guest serial/JSON responses. Screenshots and measured
activation timestamps are retained under `out/m8-vm.*`.
To return to a console image after desktop development, rebuild
`make rootfs PROFILE=cli` and `make system-card PROFILE=cli`. A SYSTEM image cannot
be relabeled as another profile without rebuilding the matching filesystem.
## Use the development SYSTEM
`profiles/development.list` extends the same immutable base with native aarch64
**glibc** development tools. FDS control programs retain their static-musl build.
Build and verify the profile on the x86_64 workstation:
```sh
make rootfs PROFILE=development
make rootfs-test
make system-card PROFILE=development
make development-test
```
These commands do not install compilers on the Pi's read-only SYSTEM at runtime.
The profile must be built into a new SYSTEM image. Store source code and build
outputs on an active DATA cartridge, or under `/tmp` for disposable experiments.
The ordinary user's home is temporary in this profile too.
At native startup, FDS advances an older wall clock to the image's fixed source
timestamp in `/usr/share/fds/build-epoch`. A newer kernel/RTC clock is retained.
This prevents immutable compiler and CMake inputs from appearing to be in the
future when a machine boots without a usable RTC. It requires no network,
time server or waiting. `/run/fds/clock.json` records which case occurred.
The floor is an approximate lower bound, **not the current date**. Set the date
explicitly from a trusted time source when accurate timestamps or certificate
validity matter. Physical Pi RTC behavior remains to be tested. Boot-duration
measurements continue to use `CLOCK_BOOTTIME`, independent of wall-clock changes.
`make development-test` checks the older-clock case while compiling real projects;
`make clock-test` checks preservation of a newer clock in a disposable VM.
| Selected package(s) | Purpose |
| --- | --- |
| `gcc`, `glibc-devel` | Native C/C++ compilation, libc headers and startup files; GCC selects its matching support libraries and C++ headers |
| `make`, `cmake`, `meson`, `ninja`, `pkg-config` | Common project build systems and dependency discovery; Meson brings its Python runtime |
| `rust`, `cargo` | Native Rust development and package builds; Rust selects its standard library and LLVM runtime dependencies |
| `git` | Local source history and optional remote checkout when networking is explicitly enabled |
| `gdb`, `strace` | Interactive debugging and syscall inspection |
| `vim` | Terminal editing, with no graphical session required |
| Xvfb and the existing X11 diagnostic tools | Virtual desktop integration tests; they remain opt-in |
No development package is added to the boot service bundle. The profile uses
GCC for C/C++ and Rust for Rust; additional compilers and physical performance
profilers can be considered separately when there is a concrete need.
For a disposable first C program at `FDS>`:
```sh
mkdir -p /tmp/hello
cd /tmp/hello
printf '#include <stdio.h>\nint main(void) { puts("Hello from FDS"); }\n' >hello.c
cc -g -o hello hello.c
./hello
gdb ./hello
```
Within GDB, use `break main`, `run`, `next`, and `quit`. For persistent work,
use `/data/projects` after selecting a healthy DATA cartridge. Stop programs and
leave that directory before `fds eject BAY`.
For a dependency-free Rust example:
```sh
cargo new --vcs none /tmp/hello-rust
cd /tmp/hello-rust
cargo run --offline
```
`--offline` avoids contacting a registry. Projects with third-party crates need
those sources prepared beforehand or an explicit network connection. To preserve
Cargo downloads, put `CARGO_HOME` on DATA and stop related builds before ejecting.
An ordinary development program uses the target's glibc environment; rebuilding
the FDS distribution and its static control plane still follows the documented
x86_64 cross-build workflow.
`make development-test` runs native C, C++ and Rust compiler/linker executions,
CMake/Meson/Ninja/Make builds, GDB and strace, a local Git commit, and a headless Vim
edit in a disposable ARM VM. It verifies read-only SYSTEM, absent optional
services and native shutdown. Test results are recorded in
[M12 validation](m12-validation.md); they do not establish Pi compiler throughput.
-152
View File
@@ -1,152 +0,0 @@
# Pi 5 EEPROM configuration
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Boot images](boot.md) · [Recovery](recovery.md) · [Implementation ledger](implementation-status.md)
M12 now provides an offline configuration workflow using the pinned official
Raspberry Pi tool and a real Pi 5 firmware image. The host checks passed;
application to a physical Pi, boot order, PMIC behavior and timing remain deferred.
`tools/configure-pi-eeprom` only creates files. It never reads or writes a hardware
EEPROM, invokes a firmware updater, or reboots a machine.
## Preview a profile on the build workstation
From the repository root:
```sh
./tools/configure-pi-eeprom --profile production
./tools/configure-pi-eeprom --profile development
make eeprom-test
```
The first invocation downloads three checksum-pinned inputs into
`.host/eeprom/`: the official configuration script, the Pi 5 preview firmware and
its upstream license. Later calls verify and reuse them. `FDS_OFFLINE=1` requires
all inputs to exist and refuses a network download.
The command prints its new `out/eeprom-PROFILE.*` directory. It contains:
| File | Purpose |
| --- | --- |
| `base.bin` | Exact input firmware retained unchanged |
| `original.conf` | Configuration to restore |
| `configured.conf` | Reviewed profile merged with the supplied settings |
| `configured.bin` | Real EEPROM image with the new configuration |
| `rollback.bin` | Same base firmware with `original.conf` restored |
| `review.diff` | Human-readable configuration changes |
| `manifest.json` | Input identity, selected settings and output SHA-256 hashes |
| `LICENSE` | Complete upstream tool and firmware notices |
Default inputs are **preview defaults, not a backup of your Pi**. A rollback image
restores the configuration supplied to this command on the supplied base firmware.
It cannot reconstruct a different firmware version or settings that were never
provided. Retain the actual machine's configuration and matching firmware input
before applying anything to that machine.
## Profile behavior
| Setting | Production | Development |
| --- | --- | --- |
| `BOOT_ORDER` | `0xf6`: NVMe, repeat | `0xf16`: NVMe, SD, repeat |
| `BOOT_UART` | `0` | `1` |
| `NET_INSTALL_ENABLED` | `0` | `0` |
| `NET_INSTALL_AT_POWER_ON` | `0` | `0` |
| `POWER_OFF_ON_HALT` | `1` | `1` |
| `WAIT_FOR_POWER_BUTTON` | `0` | `0` |
Boot order is read from the right. Both profiles avoid scanning the twelve USB
cartridges for firmware boot. Development retains an SD rescue path. Both disable
network-install keyboard detection; Raspberry Pi documents that this detection
adds USB initialization and enumeration work. These settings do not establish a
measured FDS boot improvement. [Official bootloader configuration](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#bootloader-configuration).
On Pi 5, the power-off setting requests PMIC standby on halt; the dedicated power
button remains the wake mechanism. The wait setting leaves cold power-on boot
enabled. Whether the assembled computer and attached hardware behave as intended
still needs a physical test. [Official power settings](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#POWER_OFF_ON_HALT).
The source profiles are [production.conf](../../config/eeprom/production.conf) and
[development.conf](../../config/eeprom/development.conf). No GPIO wake option or
unmeasured HDMI tuning is added.
## Preserve the machine's settings
In a Raspberry Pi maintenance environment with the official EEPROM utilities,
collect its configuration and version information before changing firmware:
```sh
rpi-eeprom-config > pi-current.conf
rpi-eeprom-update > pi-eeprom-status.txt
```
Retain the matching original firmware image separately. The status report helps
identify it; it is not a binary backup. Copy the saved inputs to the workstation,
then prepare a new directory:
```sh
./tools/configure-pi-eeprom \
--profile production \
--base-image /absolute/path/to/original-pi5-firmware.bin \
--current-config /absolute/path/to/pi-current.conf \
--output-directory /absolute/path/to/new-eeprom-review
```
The base must be a regular 2 MiB Pi 5 image, not a device node. The output directory
must not already exist. The helper preserves unrelated configuration lines and
conditional sections. It replaces all occurrences of the six managed settings,
including conditional overrides, with the selected profile's final `[all]`
settings. Review that change in `review.diff`; the exact original conditional
configuration remains in `original.conf` and `rollback.bin`.
The upstream parser reads the generated images back before success is reported.
This checks file construction and configuration roundtrip, not hardware
compatibility. No secure-boot key, fuse, customer signature or OTP setting is
modified by this helper.
## Apply and roll back during physical testing
This step is deliberately outside the host/VM acceptance run. Use the Pi's
maintenance environment and the official installed EEPROM utilities. Once the
reviewed files and saved original inputs are available there, the upstream
configuration interface is:
```sh
sudo rpi-eeprom-config --apply ./configured.conf ./base.bin
```
The updater may program EEPROM immediately or schedule an update depending on
its platform and update method. Follow its output and verify the resulting
configuration/version after the required restart. To restore the supplied
configuration on the same supplied firmware:
```sh
sudo rpi-eeprom-config --apply ./original.conf ./base.bin
```
If the firmware version itself changed, use the saved matching original firmware
for the rollback operation. Keep the maintenance SD and an external EEPROM rescue
route available; internal recovery cannot repair an EEPROM that prevents internal
boot. The official [EEPROM update and recovery guide](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-boot-eeprom)
describes that physical workflow. FDS has not yet tested it on your Pi.
## Inputs, dependencies and software evidence
The input lock is [inputs.json](../../config/eeprom/inputs.json), pinned to
`raspberrypi/rpi-eeprom` commit `2fee426f27b6c54d3f5b6f36efd9a2fe1286a45d` and
Pi 5 preview firmware `pieeprom-2026-09-12.bin`. Every download is SHA-256 checked;
a changed cached input is rejected. The
[official source tool](https://github.com/raspberrypi/rpi-eeprom/blob/2fee426f27b6c54d3f5b6f36efd9a2fe1286a45d/rpi-eeprom-config)
is stored unchanged in the cache. Void's tracked source remains unchanged.
The host workflow uses existing Python and curl. It does not add a target daemon,
package or Rust dependency. The upstream parser's optional signing dependencies
are not needed for configuration-only operations.
`make eeprom-test` passed production/development roundtrips, unchanged firmware
payloads, repeatable binary output, refusal to overwrite an existing directory,
preservation of custom settings, exact rollback of conditional settings, and
rejection of malformed images, device nodes and oversized configuration files.
Initial evidence is `out/m12-eeprom.zi144zib/`, with log
`out/logs/m12-eeprom-check.log`. The complete local release acceptance is recorded in [M12 validation](m12-validation.md).
-235
View File
@@ -1,235 +0,0 @@
# Your first build
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Troubleshooting](troubleshooting.md)
This walkthrough takes you from an FDS Git checkout to two verified ARM Linux
artifacts. It explains the output as you go. You do not need a Raspberry Pi.
At the end you will have a small Rust program built with static musl and GNU hello
packaged for aarch64 glibc. This proves that the workstation can build the two
kinds of software FDS/OS needs. It does not produce a bootable OS image.
## 1. Check the machine and checkout
Use an x86_64 Arch Linux workstation as a normal user. Check it with:
```sh
uname -m
cat /etc/os-release
id -u
```
Expect `x86_64`, `ID=arch`, and a user ID other than `0`. The bootstrap script
currently rejects other host platforms and root execution.
Enter your FDS checkout. For the existing development workspace:
```sh
cd /home/felis/source/fds
pwd
git status --short
```
On another workstation, substitute its checkout directory. It must contain
`Makefile`, `Cargo.toml`, `tools/`, `.gitmodules`, and Git metadata. Avoid spaces
in the path. There is no configured public remote in this workspace; obtain a
Git checkout from the project owner rather than using an invented clone URL.
You need HTTPS access to GitHub, Void repositories, Rust distribution servers,
and package source hosts. Initial downloads total hundreds of MB. Plan for
several GB of free disk space; the verified workstation used roughly 1.6 GB for
the Void masterdir and 455 MB for its hostdir cache, before Rustup and other
outputs. These are observations, not fixed requirements or a build-time promise.
```sh
df -h .
```
## 2. Install host prerequisites
On an otherwise maintained Arch installation, install the required packages:
```sh
sudo pacman -S --needed bash coreutils binutils git curl make file tar xz gzip zstd \
bubblewrap rustup ca-certificates findutils diffutils grep sed gawk util-linux
```
This is the host package installation step. Subsequent build commands run as your
normal user. The alternative `./tools/bootstrap-host --install-deps` runs the same
package installation and then continues bootstrap; choose one route.
The [dependency reference](build-host.md#host-dependencies) explains why each
package is needed. This project uses Rustup to select its pinned Rust compiler,
not an arbitrary system Rust version. If pacman reports an existing Rust package
conflict, resolve the host package choice before continuing; do not force file
replacement.
Check that bubblewrap can create a user namespace:
```sh
bwrap --ro-bind / / --unshare-user --uid 0 --gid 0 true
```
Success produces no output and exits normally. If it fails, use
[Troubleshooting](troubleshooting.md#bubblewrap-or-user-namespace-failure).
This is a host capability requirement, not something a Pi can fix.
## 3. Prepare the build environment
From the repository root:
```sh
make bootstrap
```
This performs these steps:
1. Initializes `vendor/void-packages` at the recorded Git commit.
2. Downloads the pinned static XBPS archive, verifies its SHA-256, and extracts
the host tools into `.host/xbps/`.
3. Copies the checked-in Void build configuration into the local upstream checkout.
4. Installs Rust 1.98.0, rustfmt, and `aarch64-unknown-linux-musl` through Rustup.
5. Fetches the locked workspace crates needed for Cargo to resolve the Dasung member,
including when building the dependency-free smoketest offline.
6. Creates the x86_64 glibc Void build container and verifies that GCC runs inside it.
The Void build container supplies compilers and package tools; it is not the
future FDS root filesystem. Rustup changes your user toolchain installation.
XBPS host tools stay inside this repository, and no Pi storage is written.
The final success marker is:
```text
PASS: M0 host bootstrap; run make smoke-test to build both aarch64 artifacts
```
The command saves its output in `out/logs/bootstrap.log`. Re-running bootstrap
reuses the checkout and downloads, but still checks their pins and integrity.
If it exits nonzero, stop here and resolve that failure before running smoke-test.
## 4. Cross-build both artifacts
```sh
make smoke-test
```
First Cargo builds `rust/fds-smoketest` for ARM, using the static musl libraries
and linker bundled with the Rust toolchain. Then xbps-src cross-compiles GNU
hello and packages it for aarch64 glibc. The first package build downloads the
cross compiler, target libraries, and build dependencies such as texinfo.
The script inspects actual ELF files and local package metadata. It checks:
| Artifact | Required result |
| --- | --- |
| Rust smoketest | AArch64, static linkage, no dynamic interpreter or shared library requirement |
| GNU hello in the XBPS package | AArch64, glibc loader and `libc.so.6` dependency |
A successful run contains these messages; other compiler output appears between them:
```text
PASS: aarch64 static ELF
PASS: aarch64 glibc ELF
PASS: XBPS package hello-2.12.3_1 architecture=aarch64 (glibc)
PASS: M0 smoke test complete
```
Without QEMU, it also reports:
```text
SKIP: ARM execution (optional qemu-aarch64 not installed); ELF verification passed
```
That skip means the ARM program was compiled and inspected, but not executed.
It does not hide a failed compiler or ELF check. If QEMU is installed, execution
must pass; an emulator failure is a real smoke-test failure.
## 5. Run the protection and formatting checks
```sh
make check
```
Run this after smoke-test: it expects the built Rust executable to exist. It
checks that bad inputs are rejected, that the Void pin and upstream files are
protected, that overlays cannot replace upstream packages, and that Rust source
is formatted. Several `PASS: rejects ...` lines are expected; they mean the
negative tests worked. The final commands include:
```text
PASS: M0 guardrail checks complete
cargo fmt --all -- --check
```
Rustfmt is normally silent on success. `make check` does not rebuild the ARM
program. After a source change, rebuild first, as described in
[Development](development.md#edit-and-rebuild-the-rust-program).
## 6. Inspect what you built
```sh
ls -lh out/fds-smoketest out/packages/
file out/fds-smoketest
./tools/verify-elf out/fds-smoketest aarch64 static
sha256sum -c out/manifests/artifacts.sha256
```
`file` should include `ARM aarch64` and `statically linked` (or `static-pie linked`).
The checksum command should report `OK` for both artifacts. Hashes verify the
files against this run's manifest; they are not release signatures.
For ELF details, use:
```sh
readelf -hW out/fds-smoketest
readelf -lW out/fds-smoketest
readelf -dW out/fds-smoketest
```
The header names AArch64. There should be no `INTERP` segment or `NEEDED` library.
An x86_64 host's `ldd` may say `not a dynamic executable` and return 1; that message
is expected here, but is insufficient by itself to prove a foreign ELF is static.
The [package guide](packages.md#inspect-the-built-package) walks through the
corresponding inspection of GNU hello.
## 7. Optionally execute the ARM Rust program
The [Arch qemu-user package](https://archlinux.org/packages/extra/x86_64/qemu-user/)
provides user-mode emulation. Install it if you want to execute the ARM program
on the x86_64 workstation:
```sh
sudo pacman -S --needed qemu-user
qemu-aarch64 out/fds-smoketest
```
Expected output:
```text
FDS/OS M0: aarch64 static-musl OK
```
Because this executable is static, this command needs no ARM rootfs or glibc
sysroot. Explicitly invoking `qemu-aarch64` also avoids needing automatic binfmt
registration. Re-running `make smoke-test` records its own QEMU check when the
emulator is on PATH.
This executes one Linux userspace program. It does not emulate the Pi's firmware,
USB bays, display, battery, or operating-system boot. QEMU execution was not
performed during the initial M0 validation; see the [recorded results](m0-validation.md).
## 8. Decide what to do next
- To build the configured OS filesystem and try its ARM programs, follow [Rootfs](rootfs.md): `make rootfs PROFILE=cli`, then `make rootfs-test`.
- To boot native s6 and open a development console, follow [Native init and ARM VM](init.md): `make init-test`, then `make vm`.
- To build the included Dasung monitor package, follow [Dasung](dasung.md).
- To edit the existing Rust program, follow [Development](development.md).
- To understand the `.xbps` artifact, follow [Packages](packages.md).
- To understand the intended removable operating system, read [Architecture](architecture.md) and [Cartridges](cartridges.md).
- To see when bootable images become possible, read [Roadmap](roadmap.md).
Bootstrap, smoke-test, and check complete the M0 foundation. Continue with the
M1 guide for a configured rootfs archive. There is still no flashing step.
-55
View File
@@ -1,55 +0,0 @@
# Glossary
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md)
| Term | Meaning in this repository |
| --- | --- |
| **M0** | Milestone zero: prepare and verify the workstation's cross-build environment. M1 adds the rootfs; M2 adds native init and ARM VM boot. |
| **Host / build workstation** | The x86_64 Arch Linux computer running the build. |
| **Target** | The platform being built for: AArch64 Linux on the Pi. |
| **x86_64** | The processor architecture of the supported build workstation. |
| **AArch64 / aarch64 / arm64** | Names commonly used for 64-bit ARM. Void uses `aarch64` for its glibc package target. |
| **Cross-compilation** | Compiling on one platform to produce code for another. An x86_64 build can produce an ARM executable that does not run directly on its host. |
| **Toolchain** | Compiler, linker, and related tools used to build software. |
| **Sysroot** | Target headers and libraries made available to a cross compiler. It is not necessarily a bootable filesystem. |
| **ABI** | Application binary interface: the machine-level conventions that compiled programs and libraries must agree on. Matching CPU architecture alone is insufficient. |
| **libc** | The C runtime library. FDS uses glibc for conventional packages and musl inside its static Rust tools. |
| **glibc** | GNU's C library, used by the main FDS userspace and Void `aarch64` packages. |
| **musl** | An alternative C library used for static FDS-owned programs. It does not replace the system's glibc package set. |
| **Static linking** | Including needed library code in an executable at build time, so it does not need those shared libraries at startup. It still needs the operating system/kernel. |
| **Dynamic linking / loader** | Loading shared libraries when a program starts. The ARM glibc loader is `/lib/ld-linux-aarch64.so.1`. |
| **ELF** | The executable/library file format inspected by `file` and `readelf`. The validator checks architecture and runtime requirements. |
| **Rust target triple** | A name such as `aarch64-unknown-linux-musl` specifying the compiler's target platform and library environment. |
| **Cargo / Rustup** | Cargo builds Rust projects; Rustup installs/selects Rust toolchains and target components. |
| **QEMU user mode** | Executes a foreign-architecture Linux program on the host. It does not boot or emulate the entire Pi in the M0 example. |
| **XBPS** | Void's package-management tools and package format, reused by FDS. It is separate from Arch's pacman. |
| **xbps-src** | Void's source-package build system. It creates packages using recipes called templates. |
| **Template** | An xbps-src package recipe describing source, dependencies, build actions, and packaging. |
| **Submodule / gitlink** | A separate Git repository embedded at a path; the parent records the exact upstream commit it expects. |
| **Pin** | An explicit version, commit, or digest chosen for an input. A source pin alone does not freeze every downloaded dependency. |
| **Overlay** | An FDS-owned package directory copied into the build tree without modifying tracked upstream packages. |
| **masterdir** | The generated Void build-container filesystem under `vendor/void-packages/masterdir-x86_64/`. This is not the target OS root. |
| **hostdir** | xbps-src's generated cache/output directory under `vendor/void-packages/hostdir/`. |
| **bubblewrap / bwrap** | The host tool used to isolate the build environment with Linux namespaces and mounts. No target init system runs inside this M0 container. |
| **Manifest** | A generated record of package versions or file checksums used to inspect a particular build. |
| **Rootfs** | The root filesystem used by a running OS, including programs, libraries, and configuration. `make rootfs` builds its archive. |
| **Initramfs** | A small early userspace filesystem loaded with the kernel, used before switching to the main root filesystem. |
| **PID 1 / init** | The first userspace process and the foundation of system startup, supervision, and shutdown. FDS uses native s6. |
| **s6 / s6-rc** | FDS's process-supervision and service-dependency tools. The service database is compiled while building the image. |
| **EROFS** | The read-only filesystem format used for SYSTEM and recovery images. |
| **tmpfs** | A memory-backed filesystem for temporary state. Its contents do not persist through power loss. |
| **GPT / PARTNAME** | Disk partition metadata and a partition's name. Stage0 discovers SYSTEM using `FDS_SYSTEM`, rather than a changing `/dev/sdX` name. |
| **NVMe** | The internal solid-state storage interface/device. The FDS internal image holds boot, recovery, and machine configuration. |
| **SYSTEM / DATA** | Cartridge roles: operating-system image and persistent user files, respectively. |
| **PROGRAM / ENVIRONMENT** | Cartridge roles: application payload and activation/configuration of a known environment. |
| **Bay topology** | Mapping a physical USB hub/port path to the user-facing BAY 01–12 labels. |
| **Hotplug** | Inserting or removing a device while the machine is running. Detection does not by itself make removal safe. |
| **sysfs / netlink** | Linux interfaces used by FDS to inspect devices and receive cartridge events. |
| **Profile** | A known OS configuration or service selection activated by a cartridge. It is not an arbitrary root script supplied by media. |
| **Safe eject** | Stopping users of a cartridge, flushing pending writes, unmounting it, then reporting it safe to remove. Use `fds eject BAY`. |
For how these pieces fit together, read [Architecture](architecture.md). For
commands that currently work, follow [Your first build](getting-started.md).
-137
View File
@@ -1,137 +0,0 @@
# Implementation through M12
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
This is the software acceptance ledger for the 67 sections of the
[master plan](master-plan.md). The user authorized implementation through M12
and deferred tests requiring the physical Raspberry Pi or attached hardware.
The current release checkpoint is recorded in [M12 validation](m12-validation.md).
**M0–M12 software acceptance is complete.** The signed local 0.1.0 release
passed two fresh network-isolated builds with all 27 artifacts identical.
Those artifacts also match the complete post-Clap VM acceptance. Signature
verification and actual source/input archive round-trips passed; see
[final release evidence](m12-validation.md#final-local-release-acceptance).
The final command-line migration uses shared, locked Clap for every Rust
command-line interface, including the VM helpers. Its 48 Rust tests, actual ARM
CLI checks and complete runtime acceptance are recorded in
[Clap validation](clap-validation.md). The dependency-free smoketest has no
argument interface. Kernel command-line files and cartridge metadata use their
own strict format parsers; they are not process argument parsers.
## Workstation software and emulator extension
Current sources extend the frozen 0.1.0 checkpoint with native Linux
`fds-cartridge` and `fds-emulator` tools. The software format uses metadata plus
`m` payload partitions containing xz tarballs. Software builds and complete
image creation run on the workstation; the guest verifies, extracts and runs
selected commands as UID 1000. See [usage](workstation.md),
[format](software-format.md), and [current acceptance](workstation-validation.md).
Historical M0–M12 and Clap reports describe their recorded artifacts; they do
not by themselves validate these new sources.
## Requirement coverage
“Verified” below means software, artifact or isolated ARM VM evidence. It does
not mean the assembled Pi has been tested. The detailed milestone reports retain
the commands, artifacts, failures that led to fixes, and measured results.
| Section | Implementation and evidence | Physical or later boundary |
| --- | --- | --- |
| 1. Project objective | Pi 5 images, all cartridge classes, native s6, Dasung base support and 12-device virtual stress; [overview](../../README.md), [stress evidence](m11-validation.md) | Assembled FP-85, battery, display and all physical bays deferred |
| 2. ABI strategy | Every profile uses aarch64 glibc packages; all eight production Rust programs are static ARM musl; rootfs and ELF audits pass | Actual Pi execution deferred |
| 3. Limited musl scope | No target musl runtime package or second general-purpose package repository; glibc compiler/desktop acceptance passes | None in software scope |
| 4. Native init | s6-linux-init hands PID 1 to s6-svscan; s6-rc database compiled during image construction; [native init evidence](m2-validation.md) | Pi startup timing deferred |
| 5. Packages | Nine FDS base packages selected and audited; forbidden init/runtime packages rejected; [packages](packages.md) | None in software scope |
| 6. Build host | x86_64 Arch bootstrap, glibc cross-package and static Rust checks; [build host](build-host.md) | Other host distributions are not validated |
| 7. Repository layout | Complete build/runtime components and public interfaces; location choices documented below | No placeholder component claims |
| 8. Void upstream | Pinned commit, unchanged tracked upstream files, protected overlays, exact frozen package inputs; [offline builds](reproducible-builds.md) | Normal online bootstrap still uses rolling repositories |
| 9. Rust workspace | All required tools and aliases built; release/static settings and shared dependencies; [tooling](tooling.md) | None in software scope |
| 10. Dependencies | Locked synchronous dependencies, Clap policy and all 68 third-party crate notices; z/2/3 builds and repeated boot comparison | Physical optimization winner deferred |
| 11. Boot architecture | Pi kernel → static stage0 → read-only SYSTEM → native s6 and ordinary-user console; [boot evidence](m4-validation.md) | Firmware, RP1 and physical NVMe/USB handoff deferred |
| 12. Internal NVMe | BOOT/RECOVERY/INTERNAL GPT layout; root-private atomic settings and bounded diagnostic snapshots persist across VM reboot; [internal storage](internal-storage.md) | Actual NVMe and power-loss durability deferred |
| 13. SYSTEM | Named GPT partition and EROFS, strict manifest/architecture checks, immutable update by replacement; [media evidence](m9-validation.md) | Physical cartridge write/boot deferred |
| 14. Stage0 | Direct Linux/sysfs/netlink discovery, mount and root handoff; mandatory early Dasung process; nine-case real-kernel VM matrix | Physical display keepalive/recovery deferred |
| 15. Missing SYSTEM | Explicit wait state, event-driven insertion/resume and independent recovery verified | Physical insertion latency deferred |
| 16. Multiple SYSTEMs | Ambiguity enters recovery instead of choosing enumeration order; explicit recovery verified | Interactive selector is a later-version option |
| 17. Kernel | Pinned Pi kernel package, built-in critical driver delta verified after Kconfig resolution, modules/DTBs packaged | Actual RP1, PCIe, graphics and USB support deferred |
| 18. Initramfs | Minimal static early userspace; raw/gzip/lz4/zstd construction, extraction and boot checks | Pi decompression/timing comparison deferred |
| 19. Firmware | Production/development EEPROM previews, exact readback and rollback preparation; [EEPROM](eeprom.md) | No EEPROM was read from or applied to a physical Pi |
| 20. Filesystems | SYSTEM stays read-only; volatile logs, temporary directories and state; root ownership is explicitly root:root 0755 | Physical persistence/durability deferred |
| 21. User data | Persistent DATA at `/data`, temporary `/home/fds`, unprivileged sessions; [DATA evidence](m7-validation.md) | Home persistence is not claimed |
| 22. Service graph | Native boot bundle includes console, devices, cartridge and mandatory Dasung services; optional bundles excluded | Hardware service behavior deferred |
| 23. Fast boot | Console depends on runtime filesystem/hostname, not cartridge scan, DHCP or desktop; blocked-service VM tests pass | Pi timing targets deferred |
| 24. eudev | Native supervision and trigger without settle barriers; console opens with eudev blocked | Physical hotplug races deferred |
| 25. Cartridge daemon | Static event-driven daemon, private metadata probes, bounded IPC, mount lifecycle and restart behavior; [cartridge evidence](m6-validation.md) | Real device-controller behavior deferred |
| 26. Bay topology | Controller/protocol/port identity, strict map validation, 12 virtual bays independent of block/bus enumeration | User must calibrate the physical wiring |
| 27. Metadata | Strict bounded TOML, six class values, architecture and payload validation, unknown fields rejected | None in software scope |
| 28. Hardware cartridges | Filesystem-free VID/PID/class catalog and input/Ethernet recognition; no catalog autorun | Catalog starts empty until physical devices are identified |
| 29. ENVIRONMENT | Validated declarative activation of the trusted WindowMaker profile | GUI runtime on the cartridge itself is a later-version option |
| 30. WindowMaker | SYSTEM includes Xorg, WindowMaker, packaged caches/fonts; authenticated X11 and transitions verified; [desktop evidence](m8-validation.md) | Real graphics/input path deferred |
| 31. E-Ink | White/black/grayscale profile, Terminus and reduced decorations/animations; mandatory Dasung in base and early boot | Picture quality, refresh and power-cycle recovery deferred |
| 32. PROGRAM | Metadata-first GPT software bundles plus legacy reading; xz verification, read-only runtime caches and UID 1000 consumers; see [current evidence](workstation-validation.md) | Physical application cartridge behavior deferred |
| 33. CLI | Complete typed Clap command hierarchy, aliases, nested JSON, help/errors and unchanged child-argument forwarding; [usage](tooling.md) | None in software scope |
| 34. Media writer | Static create/inspect/burn/eject, insertion-bound confirmation, protected disks and full readback; actual virtual write then boot | Physical throughput, cache and power-loss behavior deferred |
| 35. Installation model | Immutable SYSTEM images contain packages; replacement cartridge provides update/rollback | Physical swap/boot deferred |
| 36. Profiles | CLI, development and independent recovery rootfs/images; profile-specific package audits and runtime checks | None in software scope |
| 37. Build interface | Complete `make all`, packages, kernel, initramfs, rootfs, SYSTEM, boot and internal outputs; [offline builds](reproducible-builds.md) | No command silently flashes hardware |
| 38. Cross compilation | Static Rust, glibc XBPS and Pi kernel cross-built on x86_64; generated target files inspected/executed under emulation | Native Pi build performance deferred |
| 39. QEMU | Packaged ARM OS, real kernel, disposable USB/NVMe media and serial evidence across all runtime suites | QEMU does not emulate the Pi firmware/display/electrical system |
| 40. Boot targets | Actual CLOCK_BOOTTIME reports and repeated comparisons; [performance](performance.md) | No physical cold-boot target is claimed |
| 41. Boottrace | T0–T6 event model, first-console event retention, reports and persisted diagnostic exports | Firmware timing and physical display events deferred |
| 42. Regression rule | +100 ms investigation threshold, alternating before/after boots and z/2/3 comparison; [Clap measurements](clap-validation.md) | VM samples are not Pi performance claims |
| 43. Boot blacklist | No systemd, runit, BusyBox, shell PID 1, boot-time cache compilation, settle or sleep-based readiness barriers | Dasung protocol pacing/backoff is not a console readiness barrier |
| 44. Network | Ethernet recognition and on-demand native DHCP bundle; console remains usable without DHCP | Physical adapter/link testing deferred |
| 45. Logging | Runtime logs stay volatile; explicit bounded diagnostics can be stored on internal media or exported; [recovery](recovery.md) | Physical storage durability deferred |
| 46. Shutdown | Ordered desktop/jobs/network/DATA/services stop with native halt/reboot; busy and writeback-error paths remain powered on; [power evidence](m10-validation.md) | Real power removal deferred |
| 47. Shutdown timing | Idle/workload/error-path VM measurements retained without artificial waits | Sub-second Pi/battery/flash targets deferred |
| 48. Eject | Consumers stopped, DATA syncfs/unmount before SAFE, quarantine across failure/restart and insertion identity checks | Physical LEDs are optional later work; electrical/cache durability deferred |
| 49. Recovery | Independent EROFS boots without usable SYSTEM, read-only DATA defaults, root-confirmed repair, media replacement and diagnostics; [M12 evidence](m12-validation.md) | Physical recovery/EEPROM path deferred |
| 50. Security | No cartridge root autorun; unprivileged managed applications, strict metadata/IPC, protected media operations and tested failure paths | Distribution signatures do not implement secure boot |
| 51. Initial UI | Direct ordinary-user FDS console without artificial delays, display manager, splash or automatic desktop | Physical presentation/input latency deferred |
| 52. Development | Native GCC/C++, Rust/Cargo, Make/CMake/Meson/Ninja, Git, GDB, strace, Vim and display tools execute in the development image | Optional example tools such as LLVM are not mandatory; Pi throughput deferred |
| 53. M0 | [Host validation](m0-validation.md), repeated after Clap | Optional host `qemu-aarch64` smoke execution skipped; separate ARM execution suites pass |
| 54. M1 | [Rootfs validation](m1-validation.md), all three current profile audits | Physical boot deferred |
| 55. M2 | [Native s6 validation](m2-validation.md), repeated after Clap | Physical Dasung behavior deferred |
| 56. M3 | [Tooling acceptance](tooling.md), complete Clap migration and static ARM tests | None in software scope |
| 57. M4 | [Boot matrix](m4-validation.md), repeated after Clap | Physical Pi boot deferred |
| 58. M5 | [Console/performance evidence](m5-validation.md), current repeated Clap comparison | Physical speed targets deferred |
| 59. M6 | [Cartridge acceptance](m6-validation.md), repeated after Clap | Real bay calibration deferred |
| 60. M7 | [DATA acceptance](m7-validation.md), repeated after Clap | Physical storage durability deferred |
| 61. M8 | [Desktop/network acceptance](m8-validation.md), repeated after Clap | Physical display/input/link behavior deferred |
| 62. M9 | [Media acceptance](m9-validation.md), repeated after Clap | Physical write/readback deferred |
| 63. M10 | [Shutdown acceptance](m10-validation.md), repeated after Clap | Physical power/timing deferred |
| 64. M11 | [Twelve-device stress and fault injection](m11-validation.md), repeated after Clap | Simultaneous physical devices and electrical tests deferred |
| 65. M12 | [Final release evidence](m12-validation.md), signatures, input lock, independent offline builds, recovery, internal settings and guides | Hardware qualification and publication are separate actions |
| 66. Implementation rules | Buildable milestones, justified dependencies, strict ABI/init policy, stable topology and measured regressions | No fabricated physical benchmarks |
| 67. First task | Original M0 setup/negative checks completed and repeated; complete scope explicitly authorized afterward | Historical M0-only gate was superseded by the user's through-M12 request |
## Interface and policy decisions
| Design sketch | Implemented interface |
| --- | --- |
| `image/build-rootfs` | `tools/build-rootfs`, exposed by `make rootfs` |
| `tools/qemu-test` | `make init-test` for native init and `make boot-test` for the Pi-kernel/stage0 matrix |
| `tools/burn` | Packaged static `fds-burn` and `fds burn` |
| `profiles/cli.toml`, `development.toml`, `recovery.toml` | `profiles/*.list` select packages; immutable profile marker selects recovery policy |
| `profiles/windowmaker.toml` | Built-in `windowmaker` runtime profile with a native s6 desktop bundle |
DATA uses `/data`, one of the two alternatives permitted by section 21.
`/home/fds` remains temporary, avoiding replacement of a live shell's home on
insertion/eject. Internal boot reports and cartridge inventories are explicit
diagnostic snapshots, not authoritative live state or automatic boot writes.
An interactive multi-SYSTEM selector, moving GUI runtime onto ENVIRONMENT media,
and optional eject LEDs remain later-version options in the specification.
## Hardware work intentionally deferred
The next acceptance phase needs the assembled computer: EEPROM backup/application
and rollback, NVMe-to-USB boot, RP1/PCIe/USB behavior, actual bay calibration and
all twelve devices, display picture/refresh/cold-power recovery, removable battery
and power behavior, physical storage durability, cold boot and shutdown timing.
Use [stress testing](stress-testing.md), [Dasung](dasung.md), [EEPROM](eeprom.md),
[internal installation](internal-storage.md) and [performance](performance.md)
to collect that evidence. No physical target disk was flashed, and no Pi EEPROM
or monitor was changed by the software acceptance work.
-190
View File
@@ -1,190 +0,0 @@
# M2: native s6 init and the ARM development VM
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Rootfs build](rootfs.md) · [Services](services.md)
M2 adds the system's real init path. The kernel enters the generated execline
launcher at `/sbin/init`; that immediately executes `s6-linux-init`, which prepares
`/run` and replaces itself with `s6-svscan`. The supervisor remains
PID 1. Bash is used for some stage-2 configuration tasks, as an ordinary child
process. No shell loop, systemd, runit, or BusyBox owns PID 1.
You can boot and inspect this system on the x86_64 workstation using a complete
ARM QEMU machine. This is a generic development VM, not a Raspberry Pi image.
It does not test Pi firmware, a cartridge boot path, or the physical monitor.
## Build and verify
First complete the [build-host setup](getting-started.md) and the
[rootfs prerequisites](rootfs.md#1-prepare-the-workstation). M2 additionally uses
the Arch `e2fsprogs` and `libarchive` packages to create an ext4 VM disk directly
from the tar, without mounting it or requiring root:
```sh
sudo pacman -S --needed e2fsprogs libarchive
make rootfs PROFILE=cli
make rootfs-test
make init-test
```
`make init-test` installs `qemu-system-aarch64` inside the project-local Void build
container when missing. Void packages its emulator with display, audio, and other
libraries, even though this test uses only a serial console. Those dependencies
stay in the build container; they do not become target packages or host services.
The test also downloads a signed generic ARM kernel, pinned by version and SHA-256
in `config/vm-test.conf`. Its AHCI, ext4, serial console and PCI drivers are built
in and devtmpfs is automounted by the kernel, so no test initramfs or replacement stage 0 is needed. The kernel package is
approximately 161 MB; only its boot files are extracted. Its module collection is
not installed into FDS. The separate [M4 boot test](boot.md) uses the actual
FDS Pi kernel and its initramfs-to-EROFS handoff.
The VM uses software CPU emulation, 1 GiB of RAM, two virtual CPUs, no network,
and no host USB, display, filesystem sharing, or block-device passthrough.
Its root filesystem is mounted read-only. QEMU discards any block changes in a
temporary disk overlay, preserving the generated test disk.
Successful output includes:
```text
FDS_M2_PID1: /usr/bin/s6-svscan
FDS_M2_FILESYSTEM: read-only root, writable tmpfs, root ownership and capabilities
FDS_M2_BOOT: getty, eudev and Dasung supervised without a monitor
FDS_M2_SERVICE: s6-rc start, echo, supervised restart, stop and start passed
FDS_M2_PASS
PASS: M2 ARM boot, native s6 PID 1, service control, and orderly power-off
```
The test must finish within 180 seconds. This is a failure deadline, not a boot
performance target. A panic, failed assertion, missing marker, timeout, or missing
power-off message makes the command fail. Retained logs explain the result.
## Open a real booted ARM shell
After `make init-test` succeeds:
```sh
make vm
```
The same guest checks run, then the VM opens a temporary root shell on its serial
console. Try:
```sh
readlink /proc/1/exe
findmnt /
findmnt /run
s6-rc -l /run/s6-rc -a list
s6-svstat /run/service/dasungd
dasungd status
```
The rootfs's ordinary getty still requires authentication and root remains locked.
The VM hook stops getty before opening its shell so they do not compete for the
serial console; `s6-svstat` therefore reports getty down during that shell session.
The password-free shell exists only in the disposable VM test overlay, when
`make vm` explicitly requests it. The checker and shell hook do not ship in the
normal rootfs archive. Type `exit` to request orderly VM power-off. Ctrl+C stops
the emulator immediately if the guest is stuck; it does not shut down your host.
The shell's `/run`, `/tmp`, `/var/tmp`, and `/var/log` writes disappear at power-off.
Changes to `/etc` or `/usr` fail because SYSTEM is read-only. Rebuild the rootfs
and rerun `make init-test` after changing packages or init configuration; `make vm`
otherwise boots the previous successful test disk.
## Control the test service
The `test-echo` service is an actual Unix-socket echo server, supervised by s6.
It is included in the service database but excluded from the normal boot bundle.
In the VM shell:
```sh
s6-rc -l /run/s6-rc -u change test-echo
s6-svstat -o up,ready,pid /run/service/test-echo
printf 'hello from ARM\n' | s6-ipcclient /run/fds-test/echo.sock s6-ioconnect -t 3000
s6-svc -wR -T 5000 -r /run/service/test-echo
s6-rc -l /run/s6-rc -d change test-echo
```
Its socket is root-only, under `/run/fds-test` with mode 0700. The server sends an
actual readiness notification when it is listening. The automated test checks
the reply, confirms the PID changes on restart, stops the service, and starts it
again. No arbitrary boot delay substitutes for readiness.
## What starts at boot
| Component | Role and dependencies |
| --- | --- |
| Native stage 1 | Consume the kernel-provided `/dev`, mount `/run`; start s6 supervision and the catch-all logger |
| `runtime-fs` | Mount proc, sysfs, devpts and volatile temporary/log filesystems |
| `hostname` | Apply the image's `/etc/hostname` |
| `console` | Create volatile login accounting files after runtime mounts |
| `getty` | Offer a login on `/dev/console`, after console and hostname setup |
| `eudevd` | Process device events; readiness requires a successful control request |
| `udev-trigger` | Request initial subsystem and device events after eudev readiness |
| `dasungd-runtime`, `dasungd-log`, `dasungd` | Prepare private state/log paths and supervise the base monitor controller |
Console startup does not depend on eudev enumeration, a monitor connection, the
network, or a cartridge scan. No global `udevadm settle` is used. The eudev package
has no native s6 notification, so `s6-notifyoncheck` performs a bounded check of
its control channel. It signals readiness only after a successful response.
Cartridge discovery, the FDS CLI, desktop activation and on-demand Ethernet were
added after the original M2 milestone. The current image includes their real
implementations; see [Services](services.md), [Cartridges](cartridges.md) and
[Desktop](desktop.md). This page focuses on the native init mechanism and its
generic test fixture. SSH remains outside the default boot bundle.
## Files and build-time work
| Location | Meaning |
| --- | --- |
| `packages/fds-init/` | Init skeleton, runtime mount/accounting helpers, and package template |
| `s6/source/` | Complete current service graph, split between init, Dasung and cartridge/desktop packages |
| `/etc/s6-linux-init/current/` | Generated native init environment and stage scripts in the image |
| `/etc/s6-rc/compiled/` | Compiled service database, produced during image construction |
| `/run/service/`, `/run/s6-rc/` | Writable supervision and live service state |
| `/run/uncaught-logs/` | Catch-all service log, also copied to the console |
| `/run/log/dasungd/` | Rotating monitor-controller log |
| `out/m2-vm-latest/serial.log` | Last successful full ARM boot test |
| `out/m2-vm-latest/rootfs.ext4` | Disposable test disk; not a Pi or SYSTEM cartridge image |
`s6-linux-init-maker` and `s6-rc-compile` run during the rootfs build under ARM
emulation. Boot only reads their output. The VM test preserves that exact compiled
database. Its only init hook runs the original stage-2 script to completion, then
runs the checker as a child process. Normal `out/rootfs-aarch64.tar` contains
neither the checker nor the development-shell hook.
The s6-generated template contains FIFOs. Archive validation permits them only
inside its `run-image` directory; those templates are copied into writable `/run`
at boot. Before entering `/sbin/init`, the kernel or later stage 0 must provide
a mounted devtmpfs at `/dev`. Stage 1 deliberately does not mount it twice.
Services never place their live FIFOs or status files on read-only SYSTEM.
M10 adds cartridge-aware preparation before s6-rc stops the remaining services.
Its native shutdown hook stays alive if DATA cannot be verified and unmounted.
`s6-linux-init-shutdownd` still performs final process cleanup and the kernel
power action. See [Shutdown and reboot](power.md) for usage, blocked-request
recovery, and the current M10 acceptance status.
## Troubleshooting
- **`make vm` asks you to run `make init-test`:** no successful VM disk has been
published yet. Build the rootfs, then run the test.
- **Kernel pin/hash failure:** do not substitute an unverified kernel. The exact
configured package must remain available or be deliberately updated and retested.
- **Disk creation reports missing tar support:** use an `e2fsprogs` build with
libarchive support and install `libarchive`. No loop mount or sudo fallback is used.
- **No guest success marker:** read `out/logs/init-checks.log` and the printed
`out/m2-vm.*` directory's `serial.log`. Failed test workspaces are retained.
- **Root login is denied on the ordinary getty:** this is expected with the locked
base accounts. Use the explicit development VM shell, not a default password.
- **Dasung says disconnected:** the VM intentionally has no physical monitor.
Use [Dasung integration](dasung.md) for its eventual hardware validation.
Upstream references: [s6-linux-init](https://skarnet.org/software/s6-linux-init/overview.html),
[init generation](https://skarnet.org/software/s6-linux-init/s6-linux-init-maker.html),
[s6-rc initialization](https://skarnet.org/software/s6-rc/s6-rc-init.html), and
[ext4 creation](https://man7.org/linux/man-pages/man8/mke2fs.8.html).
-276
View File
@@ -1,276 +0,0 @@
# Internal storage and machine settings
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Boot images](boot.md) · [Recovery](recovery.md)
The internal NVMe supplies the Pi's firmware boot files, independent recovery,
and machine settings. SYSTEM remains a separate removable cartridge. Ordinary
user files belong on DATA, not internal storage. No physical disk is written by
any build command below; Pi/NVMe boot and power-loss tests remain deferred.
## Build the complete disk image
Build the component images first, in this order:
```sh
make kernel
make initramfs
make boot-volume BOOT_MODE=production
make recovery
make internal-image
```
The result is `out/fds-internal.img`, a **complete GPT disk image**. Its versioned
build directory contains `layout.json`, source payload checksums, and readback
verification. This differs from `out/fds-boot.img` (a raw FAT partition) and
`out/fds-recovery.img` (a raw EROFS partition).
| Partition | Format | Default allocation | Purpose |
| --- | --- | --- | --- |
| 1: FDS_BOOT | FAT32, EFI system type | 512 MiB | Pi firmware, kernel, DTBs, configuration and initramfs |
| 2: FDS_RECOVERY | EROFS | 1 GiB | Complete independent maintenance system |
| 3: FDS_INTERNAL | ext4 | 256 MiB | Machine settings and explicitly saved diagnostics |
The builder pads the recovery partition without changing its EROFS contents,
aligns partitions to 1 MiB, writes both GPT copies, verifies each payload, and
checks the table independently with `sfdisk`. Internal ext4 is fully initialized
before deployment. It contains root-owned `config/` and private `diagnostics/`.
Unused capacity beyond this initial layout is not automatically expanded.
For a 2 GiB recovery allocation or larger settings partition:
```sh
./image/build-internal --recovery-mib 2048 --internal-mib 512
```
Output directories supplied with `--output-directory` must already exist and be
empty. The builder accepts ordinary image files, never a host block-device
output. Final hardware provisioning and identity checks belong to the physical
acceptance procedure; do not confuse a partition payload with the complete disk.
## Install the internal disk when hardware is available
This is a **deferred physical procedure**. The image and virtual NVMe workflow
are software-tested; writing and booting the user's actual NVMe still require
the hardware. Installation erases the selected disk, including any existing
machine settings. Retain backups before replacing an existing installation.
Use an NVMe enclosure or another Linux machine that can access the target drive
while the Pi is off. First verify a downloaded release using a separately trusted
public key as described in [Release signatures](releases.md). Its complete disk
is named `fds-internal-0.1.0.img`; a local build uses `out/fds-internal.img`.
Use the complete disk image, not the separate BOOT or RECOVERY payload.
List disks before and after connecting the intended drive:
```sh
lsblk -d -o NAME,PATH,MODEL,SERIAL,SIZE,TRAN,LOG-SEC
ls -l /dev/disk/by-id/
```
Match the physical model, serial and capacity. Choose a persistent **whole-disk**
`/dev/disk/by-id/` path without a `-partN` suffix; never guess a `/dev/sdX` name.
Unmount every target partition and disable any swap on it. Do not select the
workstation's system disk. The current images require 512-byte logical sectors
and a disk at least as large as the image; 4 KiB logical-sector media is not
supported by this layout.
Open Bash (`bash`), replace both paths below, then run the block. It checks the
selected disk again, requires a typed confirmation, writes and reads back the
complete image, and relocates the backup GPT when the disk is larger than the
image. It does not expand any partition or filesystem.
```bash
(
set -euo pipefail
fds_image="$PWD/out/fds-internal.img"
fds_disk=/dev/disk/by-id/REPLACE_WITH_THE_TARGET_DISK
[[ -f "$fds_image" && -s "$fds_image" && -b "$fds_disk" ]]
[[ $(lsblk -dnro TYPE "$fds_disk") == disk ]]
[[ $(sudo blockdev --getss "$fds_disk") == 512 ]]
fds_bytes=$(stat -Lc %s "$fds_image")
(( $(sudo blockdev --getsize64 "$fds_disk") >= fds_bytes ))
lsblk -p -o NAME,TYPE,MODEL,SERIAL,SIZE,MOUNTPOINTS "$fds_disk"
if lsblk -nrpo MOUNTPOINTS "$fds_disk" | grep '[^[:space:]]' >/dev/null; then
echo 'Target has mounted filesystems or swap; stop and release them first.' >&2
exit 1
fi
if lsblk -nrpo TYPE "$fds_disk" | grep -Ev '^(disk|part)$' >/dev/null; then
echo 'Target has device-mapper or other active descendants; stop.' >&2
exit 1
fi
read -r -p "Type ERASE $fds_disk to erase this disk: " fds_confirmation
[[ "$fds_confirmation" == "ERASE $fds_disk" ]]
sudo dd if="$fds_image" of="$fds_disk" bs=4M conv=fsync status=progress
sudo blockdev --flushbufs "$fds_disk"
sudo cmp -n "$fds_bytes" "$fds_image" "$fds_disk"
sudo sfdisk --lock=yes --relocate gpt-bak-std "$fds_disk"
sudo sfdisk --verify "$fds_disk"
sudo blockdev --flushbufs "$fds_disk"
)
```
Every command must succeed. The byte comparison occurs before relocating GPT,
because relocation intentionally changes disk-table headers. GNU `dd`'s `fsync`
flushes output before it returns; `sfdisk`'s `gpt-bak-std` moves the backup header
to the end of the target. See the [GNU dd manual](https://www.gnu.org/s/coreutils/manual/html_node/dd-invocation.html)
and [sfdisk manual](https://man7.org/linux/man-pages/man8/sfdisk.8.html).
These commands use the already documented coreutils, diffutils and util-linux
host tools; they do not add a target daemon.
Confirm that the disk shows `FDS_BOOT`, `FDS_RECOVERY` and `FDS_INTERNAL` in that
order using `lsblk -o NAME,PARTLABEL,FSTYPE,MOUNTPOINTS`. Unmount anything the
desktop automatically mounted, then safely disconnect the enclosure and install
the NVMe in the powered-off Pi. Follow [EEPROM preparation](eeprom.md) for the
separately reviewed NVMe boot settings.
Prepare the **first SYSTEM cartridge on the workstation** using the same guarded
write block, with its two path assignments changed to the chosen SYSTEM image
and a different, empty USB cartridge disk. For a local build select
`out/fds-system-cli.img` or `out/fds-system-development.img`; release files are
`fds-system-cli-0.1.0.img` and `fds-system-development-0.1.0.img`. They are
alternative complete GPT images, each containing one `FDS_SYSTEM` partition.
Readback and backup-GPT relocation apply to this disk too. Check its partition
label, safely disconnect it, and insert exactly one SYSTEM cartridge in the Pi
before normal boot. Subsequent cartridge creation and updates can use FDS's
confirmed [media workflow](media-tools.md) after bay calibration.
On the first physical boot, check `fds info`, `fds machine status`, and
`fds bays`. The supplied bay map is empty until calibration. Follow
[physical acceptance](stress-testing.md) to measure ports, test the Dasung
display, and record actual boot/shutdown behavior before relying on the machine.
## Configure the machine before building
Copy `config/machine/` to your own directory. It contains three files:
- `machine.toml`: `format = 1` and a short human-readable `name`.
- `bays.toml`: the measured controller/port map described in
[Cartridges and bay calibration](cartridges.md).
- `hardware-catalog.toml`: optional USB identification names using the same schema
as the base catalog. Entries are data and cannot run commands.
The supplied bay map is deliberately empty because the physical wiring has not
been measured. Do not invent Pi USB paths. USB 2 and USB 3 companion ports need
explicit aliases for the same bay.
```sh
cp -a config/machine out/my-machine
# Edit the three files in out/my-machine using your editor.
make internal-image MACHINE_CONFIG=out/my-machine
```
The builder compiles a native host copy of `fds` and uses the same strict parser
as the target system. You can also validate or pack settings yourself:
```sh
cargo build --locked --offline --release --target x86_64-unknown-linux-gnu -p fds-cli
./target/x86_64-unknown-linux-gnu/release/fds machine validate out/my-machine
./target/x86_64-unknown-linux-gnu/release/fds machine pack out/my-machine out/my-machine.json
```
The three source files become one atomic `config/machine.json` document on
FDS_INTERNAL. Names, lengths, bay aliases, catalog fields and unknown keys are
validated before use. Do not put passwords or private keys in this configuration:
its active snapshot is readable by the local FDS user.
## What happens at boot
The `machine-config` native s6 oneshot precedes `cartridged`. It does **not**
precede the console or Dasung controller. It accepts exactly one non-removable
NVMe disk with the three named partitions in the order above. USB lookalikes are
ignored; multiple eligible NVMe disks are rejected instead of choosing by name.
The settings partition must be clean ext4 with the expected label. Loading uses
`ro,noload,nosuid,nodev,noexec` in a private mount namespace, validates the entire
settings bundle, copies it into `/run/fds/machine/`, and unmounts. No filesystem
repair, journal replay, cache compilation or persistent write occurs during
normal boot. The cartridge service uses that snapshot throughout this boot,
including after a service restart.
If internal storage is missing, unclean, invalid or ambiguous, the service records
an explanation and uses the immutable image's `/etc/fds/` defaults. The console
still opens. Check the actual source before treating bays as calibrated:
```sh
fds machine status
fds --json machine status
fds machine export /tmp/current-machine
```
Export creates a new directory with the three editable source files. It never
overwrites an existing directory. The status source is `internal_nvme` or
`image_defaults`. A temporarily unavailable NVMe is not adopted later in the same
boot; resolve the issue and reboot to load its settings. This prevents changing
bay identities underneath active cartridge operations.
## Update settings from recovery
Bring the edited source directory on a DATA cartridge. At the local root
`RECOVERY#` console, identify its bay with `fds bays`; healthy DATA is mounted
read-only under `/run/fds/media/NN`. For example, if it is in BAY 02:
```sh
fds machine validate /run/fds/media/02/my-machine
fds machine install /run/fds/media/02/my-machine
fds reboot
```
Installation is restricted to root in the recovery image. It writes the complete
validated bundle atomically, saves the old bytes as `config/previous.json`,
flushes and unmounts the internal filesystem, and reports success only after
those steps. A reboot activates the new settings. The currently running bay map
is unchanged, so active media does not move to a different bay mid-operation.
Keep a copy of your previous source directory on DATA or the build host to
reinstall it if the new calibration is wrong.
An invalid existing JSON document can be replaced this way. Wrong ownership,
symlinks, an unclean filesystem or a damaged directory require offline filesystem
maintenance first. Recovery does not automatically repair internal NVMe; its
`fds recovery repair` command is deliberately limited to DATA cartridges.
## Save and retrieve diagnostics
Logs and boot records stay in RAM by default. Root may explicitly save a file
of up to 16 MiB. Saved names cannot contain paths, and existing names are refused.
These operations mount internal ext4 only for the operation and then unmount it.
For example, from recovery:
```sh
fds --json boot-profile >/tmp/boot.json
fds machine store boot-first.json /tmp/boot.json
fds --json bays >/tmp/cartridge-inventory.json
fds machine store cartridges-first.json /tmp/cartridge-inventory.json
bash /usr/share/fds/capture-hardware /tmp/hardware-capture
tar -C /tmp -czf /tmp/hardware-capture.tar.gz hardware-capture
fds machine store hardware-first.tar.gz /tmp/hardware-capture.tar.gz
fds machine fetch boot-first.json /tmp/retrieved-boot.json
```
Use distinct names for subsequent sessions. A failed flush or unmount is an
error, not a successful save. These are machine diagnostics; do not use this
facility as ordinary user storage. The saved cartridge inventory is a persistent
diagnostic snapshot, including metadata already inspected during this boot.
Retrieve it with `fds machine fetch cartridges-first.json /tmp/saved-inventory.json`.
The daemon's live metadata cache remains volatile and is rebuilt from currently
attached devices; saved snapshots are never used to authorize media actions.
Together, explicit boot reports, inventory snapshots and hardware captures provide
the boot history, cached metadata and diagnostics assigned to FDS_INTERNAL in
master-plan section 12, without adding internal writes to startup or shutdown.
## Dependencies and validation
No new target package or Rust crate is required. The host image-tool prefix adds
`e2fsprogs` for ext4 creation, inspection and validation; it already supplies FAT
and EROFS tools. A private unprivileged user namespace gives created files root
ownership without requiring a root build session.
`make internal-test` exercises the actual packaged runtime in ARM VMs with
virtual NVMe. Acceptance evidence belongs in [M12 validation](m12-validation.md);
a passing VM does not verify the Pi EEPROM, PCIe path or physical flash durability.
The Linux [ext4 mount documentation](https://www.kernel.org/doc/html/latest/admin-guide/ext4.html)
explains why read-only loading also disables journal replay. Filesystem creation
options follow the upstream [mke2fs manual](https://man7.org/linux/man-pages/man8/mke2fs.8.html).
-194
View File
@@ -1,194 +0,0 @@
# M0 validation report
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [First build](getting-started.md) · [Roadmap](roadmap.md)
This is the initial acceptance record, not a setup tutorial. Later documentation
improvements do not change the build results or skips recorded here. Linked logs
and artifacts are generated local files and are not included in a fresh checkout.
Validated on 2026-09-20 in `/home/felis/source/fds` on an x86_64 Arch Linux
Framework host, running Linux 7.2.6-arch2-1.
**M0 is complete:** the host bootstrapped an upstream Void build container,
cross-built an aarch64 glibc XBPS package, and built a static-musl AArch64 Rust
executable. M1 remains a proposal.
All project-owned documentation, source comments, diagnostics, and ongoing
development context use English. The complete master plan retains its 67
numbered sections. The pinned upstream submodule has no tracked modifications.
## Changes
- Added the repository skeleton, milestone instructions, master plan, architecture
contracts, and build documentation.
- Pinned `vendor/void-packages` to
`02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1` using a Git submodule and
`VOID_PACKAGES_COMMIT`.
- Added versioned, checksum-verified static XBPS host tools and a bubblewrap
bootstrap workflow for ordinary Arch users.
- Added FDS overlay preparation with upstream collision and stale-copy checks.
- Added the Rust workspace, Rust 1.98.0 pin, static musl linking configuration,
dependency-free `fds-smoketest`, and release settings from the plan.
- Added `make bootstrap`, `make smoke-test`, `make check`, package export,
ELF inspection, failure-path checks, and build input manifests.
- Reserved future component directories without implementing later milestones.
The initial M0 file inventory appears at the end of this report; guides added
after acceptance are not included in that historical inventory.
## Exact build commands
These public entry points all completed with exit status 0:
```sh
cd /home/felis/source/fds
make bootstrap
make smoke-test
make check
```
The underlying cross-build commands are:
```sh
cargo build --locked --offline --release \
--target aarch64-unknown-linux-musl -p fds-smoketest
export PATH="$PWD/.host/xbps/usr/bin:$PATH"
export XBPS_ARCH=x86_64
cd vendor/void-packages
./xbps-src -A x86_64 binary-bootstrap
./xbps-src -a aarch64 pkg hello
```
The first bootstrap created the build environment on this host. The final
public command runs reused verified downloads and that environment. Large
archives were prefetched from official servers with SHA-256 verification;
XBPS also verified package signatures. This was not a test of a pristine clone
on a second machine.
## Smoke-test output
Actual excerpts from `out/logs/smoke-test.log`:
```text
Void commit: 02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1
rustc 1.98.0 (88d9e12ae 2026-08-18)
cargo 1.98.0 (797e8a9bc 2026-08-05)
PASS: x86_64 static ELF
PASS: aarch64 static ELF
ldd (host diagnostic, exit 1): not a dynamic executable
PASS: aarch64 package exported to out/packages/
PASS: aarch64 glibc ELF
PASS: XBPS package hello-2.12.3_1 architecture=aarch64 (glibc)
SKIP: ARM execution (optional qemu-aarch64 not installed); ELF verification passed
PASS: M0 smoke test complete
```
Rust `file` output identifies an `ELF 64-bit LSB executable, ARM aarch64`,
`statically linked, stripped`. `readelf` confirms no interpreter, shared library
dependency, or glibc symbol version requirement. The extracted GNU hello ELF
uses `/lib/ld-linux-aarch64.so.1` and requires `libc.so.6`.
`make check` passed its ABI rejection, invalid-input, Void pin, dirty-upstream,
overlay collision, stale-overlay, configuration protection, and formatting
checks. An initial package-index architecture error was corrected by explicitly
setting `XBPS_ARCH=aarch64` when indexing the exported package; the full
smoke-test then passed.
Actual logs and inventories:
- [Bootstrap log](../../out/logs/bootstrap.log)
- [Smoke-test log](../../out/logs/smoke-test.log)
- [Package build log](../../out/logs/xbps-hello.log)
- [Guardrail and formatting checks](../../out/logs/check.log)
- [Build package inventory](../../out/manifests/void-build-packages.txt)
- [Cached package input digests](../../out/manifests/void-package-inputs.sha256)
- [Artifact digests](../../out/manifests/artifacts.sha256)
Generated output is ignored by Git. These links refer to this validated checkout;
rerun the commands to recreate the output in another checkout.
## Artifacts
| Artifact | Verified ABI | SHA-256 |
| --- | --- | --- |
| `out/fds-smoketest` | AArch64, static musl | `3c719a386cfef5b67f90488d9c67075c835c1a42d4db0efac0b475267dfc25ee` |
| `out/packages/hello-2.12.3_1.aarch64.xbps` | aarch64 package, glibc executable | `2703e81cff78d3d11b1a36f323c0af25691b258effcd60fe7f6d074ca8cdf1d8` |
## Known limitations
- QEMU execution was skipped because qemu-aarch64 is not installed. No ARM
hardware, Pi boot, bay topology, E-Ink, or boot/shutdown timing was tested.
- Void source, Rust, and host XBPS are pinned. Rolling binary dependencies are
audited through cached digests and version inventories, but a complete binary
repository snapshot and reproducible release are still future work.
- This is a cross-build foundation; there is no bootable FDS image yet.
- Initial upstream bubblewrap cleanup reported a busy `/tmp` mount point while
returning success. Subsequent container execution and cross-build checks
passed. See [the build guide](build-host.md#known-limitations).
## Next milestone proposal
M1 should implement `fds-base` and `fds-base-files`, assemble an aarch64 glibc
rootfs with GNU userspace and XBPS, and generate `out/rootfs-aarch64.tar`.
Acceptance should audit package contents, confirm glibc/coreutils/binutils,
require no musl runtime package, and exclude BusyBox, systemd, and runit.
Begin M1 only after an explicit request.
## Initial M0 changed-file inventory
```text
.cargo/config.toml
.gitignore
.gitmodules
AGENTS.md
Cargo.lock
Cargo.toml
Makefile
README.md
VOID_PACKAGES_COMMIT
config/host-tools.conf
config/xbps-src.conf
docs/architecture.md
docs/boot.md
docs/build-host.md
docs/cartridges.md
docs/m0-validation.md
docs/master-plan.md
docs/packages.md
docs/performance.md
docs/recovery.md
docs/services.md
image/.gitkeep
out/.gitkeep
packages/fds-base-files/.gitkeep
packages/fds-base/.gitkeep
packages/fds-cartridged/.gitkeep
packages/fds-cli/.gitkeep
packages/fds-eink/.gitkeep
packages/fds-kernel/.gitkeep
profiles/.gitkeep
rust-toolchain.toml
rust/fds-boottrace/.gitkeep
rust/fds-burn/.gitkeep
rust/fds-cartridged/.gitkeep
rust/fds-cli/.gitkeep
rust/fds-common/.gitkeep
rust/fds-smoketest/Cargo.toml
rust/fds-smoketest/src/main.rs
rust/fds-stage0/.gitkeep
s6/source/.gitkeep
tests/hardware/.gitkeep
tests/integration/m0-checks
tests/unit/.gitkeep
tools/bootstrap-host
tools/build-package
tools/lib.sh
tools/prepare-void
tools/smoke-test
tools/verify-elf
vendor/void-packages
```
-128
View File
@@ -1,128 +0,0 @@
# M1 root filesystem validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Build and usage](rootfs.md) · [Roadmap](roadmap.md)
Validated on **2026-09-21 (Asia/Shanghai)** on the x86_64 Arch workstation.
The archive build timestamp is `2026-09-20T16:58:35Z`. This is userspace and
archive validation, not a Pi boot or native PID 1 result.
## Artifact produced
| Item | Recorded result |
| --- | --- |
| Output | `out/rootfs-aarch64.tar` |
| Archive size | 346,705,920 bytes |
| Configured packages | 108 |
| ARM ELF paths inspected | 1,167 |
| SHA-256 | `b2f0472d49aeb1565d124bc30f250cb304dc265be4d049f1ac9e8cb533742a34` |
| Void source pin | `02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1` |
| Emulation tools | PRoot 5.2.0_1 and QEMU aarch64 userspace 11.1.1_1 |
The successful build is retained in `out/rootfs-build.Ewfkl2/`. Convenience links
point to its archive, staging root, and manifests. Future rebuilds may replace
the convenience links; this report describes the result above.
Selected package versions:
```text
fds-base-0.1.0_1
fds-base-files-0.1.0_1
fds-dasungd-0.1.0_1
glibc-2.41_1
coreutils-9.4_3
binutils-2.44_2
xbps-0.60.7_1
s6-2.15.0.0_1
s6-rc-0.6.1.1_1
```
Every selected package archive is retained with its SHA-256 in the build
directory. Remote packages were verified against Void's RSA keys; local FDS
packages are unsigned development builds. The package archives total 71,371,822
bytes, excluding signature files. This is input retention, not a complete
upstream repository snapshot or a reproducible-release claim.
## Acceptance completed
```sh
make rootfs PROFILE=cli
make rootfs-test
```
Both commands exited successfully. Checks established:
- `fds-base` selects GNU userspace, glibc, XBPS and the mandatory Dasung package.
Neither the Void `base-system` nor its `base-files` package is installed.
- Every installed package is `aarch64` or `noarch` and configured. All scanned
ELF files are little-endian 64-bit ARM. Dynamic interpreters resolve to glibc.
Dasung is statically linked and has no dynamic interpreter or shared-library
dependency. No BusyBox, systemd, runit, or musl runtime package is present.
- The target's package setup ran under QEMU. English UTF-8, CA certificates,
the udev hardware database, the dynamic linker cache, and the Dasung s6 database
were generated before export. `_uuidd` was created and root remains locked.
- ARM GNU coreutils/binutils, glibc, shell pipelines, locale lookup, XBPS queries,
package integrity checking, Dasung configuration/EDID checking, and s6 database
queries succeeded. Tests do not open a real USB or DRM device.
- Exported ownership and permissions were checked, including locked account
files, sticky temporary directories, the tty group for `wall`/`write`, the
xbuilder group for `xbps-uchroot`, and portable `CAP_NET_RAW` for ping.
- GNU tar restored the exported ping capability successfully inside a private
user namespace, verifying the archive's actual extended-attribute encoding.
- A fresh extraction of the exported archive passed the same userspace checks.
- Failure tests rejected a forbidden package, an unconfigured package, a musl
package ABI, an injected x86_64 executable, a missing Dasung boot entry, and an
unsupported image profile.
The build leaves upstream Void tracked files unchanged. The FDS layout manifest
avoids changing upstream lint rules. The tests use the exported tar rather than
trusting the original staging tree alone.
## Regression checks
The following also exited successfully after the M1 changes:
```sh
make bootstrap
make smoke-test
make check
make dasung-test
```
The Dasung rerun passed all six Rust unit tests, the serial simulator cases,
package inspection, and the s6 boot/logger/socket/restart/stop check with a
read-only root and no monitor. Its physical-hardware checks remain skipped.
The existing M0 smoke script still reports its optional host-PATH QEMU skip:
the emulator is installed only inside the project-local build container. M1's
explicit container-based ARM runtime tests passed independently.
Shell syntax checks were updated to recognize shell files by their interpreter,
so adding Python image tools does not make the older test suites parse Python
as shell. Python syntax, documentation file links, English project context,
and upstream tracked-file cleanliness were also checked.
## Emulation and rootless-build limitations addressed
The packaged PRoot version does not translate every modern syscall reliably.
The helper therefore makes the ARM filesystem the actual root with bubblewrap,
and uses direct QEMU for XBPS integrity checking and capability operations.
PRoot is used for child-process execution during package configuration and shell
sessions, not as evidence of successful ownership or capability changes.
Rootless configuration emits expected group-change failures for `wall`, `write`,
and `xbps-uchroot`. Archive generation restores their intended numeric groups
and the archive tests verify the result. Other package payload ownership comes
from the retained XBPS headers. No host user ID is exported as the target root.
## Remaining boundaries
- M2: native s6 PID 1, complete boot graph, runtime mounts and booted service control.
- M4 and later: Pi firmware/kernel/initramfs, EROFS SYSTEM image, console, and media creation.
- Hardware: monitor picture, cold-power recovery, USB hotplug under Pi boot, and 12-bay behavior.
- Performance: no boot, shutdown, or monitor readiness benchmark was measured.
The rootfs is not flashable, does not provide `fds` or a cartridge daemon, and
does not provision a login password. See [Rootfs](rootfs.md) for the working
emulated-shell workflow and the [roadmap](roadmap.md) for later acceptance gates.
-114
View File
@@ -1,114 +0,0 @@
# M10 shutdown validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Shutdown usage](power.md) · [Implementation ledger](implementation-status.md)
M10 software acceptance passed on 2026-09-21 in isolated ARM virtual machines.
The complete shutdown suite, CLI packaging and earlier-milestone regressions
passed. Physical Pi timing, flash-controller durability,
power rails and battery behavior remain deferred.
## Verified shutdown behavior
The ordinary FDS user can request `fds poweroff` or `fds reboot`. New cartridge
operations freeze before the daemon stops the desktop, managed programs and
networking. The original DATA filesystem descriptor checks writeback, the
filesystem becomes read-only, a second check catches remount writeback, and normal
unmount completes before native s6 stops the remaining services and halts or
reboots. Dasung remains supervised until that final service stop.
`make power-test` passed all six cases in `out/m10-vm.tituqzh7/`:
| Case | Observed result | VM preparation-to-kernel-action time |
| --- | --- | --- |
| Idle | Ordinary-user poweroff; unprivileged internal hook refused | 540.733 ms |
| Active workload | GUI, DHCP and TERM-ignoring descendants stopped; DATA verified; actual reboot | 1835.329 ms |
| Direct native poweroff | Busy DATA blocks the native hook until resolved; cancellation refused | 414.091 ms |
| Active media preview | Shutdown and new confirmation refused; cancellation leaves disk unchanged | 289.099 ms after retry |
| Crashed DATA service | Same insertion stays quarantined across further restarts; no false SAFE | 281.307 ms after removing fixture |
| Attached-device writeback error | Real EIO retained across restarts; SAFE and shutdown refused | 344.777 ms after removing fixture |
These are measurements inside generic QEMU with the Pi kernel, not Pi or
physical power-off benchmarks. They do not establish the sub-second idle or
1.5-second normal-DATA hardware targets. The workload deliberately ignores TERM,
so the manager must wait its bounded termination deadline and confirm actual
exit before proceeding.
Each case records ordered `CLOCK_BOOTTIME` events and captures the final native
shutdown record. The test waits for the kernel's halt/reboot message and QEMU's
successful exit; closing the emulator does not count as a completed shutdown.
The full command log is `out/logs/m10-final-power.log`.
## DATA integrity and failure handling
The workload writes at least 128 MiB through a rotating 64 MiB file. After reboot,
independent host `e2fsck` finds a clean filesystem, SHA-256 matches all 64 MiB, and
an ordinary-user marker retains its exact contents. Open writable descriptors
and a shell working directory both block shutdown. Explicit resume enables new
operations but does not recreate stopped jobs.
Before writable activation, a root-private insertion record is created under
`/run/fds/data-sessions`. A service crash cannot discard the original descriptor's
writeback history and then issue SAFE based on a fresh descriptor. The same
insertion becomes read-only and quarantined; a known error retains its diagnostic.
Records survive service restarts during this boot, not power loss or a different
insertion. The separate DATA regression passed in `out/m7-vm.vmhkmyqk/`.
The writeback test uses QEMU's `blkdebug` to return persistent EIO for writes to a
known allocated file block. GPT, manifest and journal metadata remain outside
that injected sector. The device stays attached; Linux reports a real writeback
error and `syncfs` fails. The daemon refuses eject, keeps the fault after restart,
and blocks shutdown through another restart. No filesystem-cleanliness claim is
made for this deliberately damaged image. Removing the test fixture merely lets
the remaining system shut down; it never turns the failed insertion SAFE.
See [QEMU's fault-injection documentation](https://www.qemu.org/docs/master/devel/testing/blkdebug.html).
A one-shot fault is insufficient evidence by itself because the guest may retry
it successfully; the acceptance case verifies the actual guest-visible EIO.
## Native guard and dependencies
The native shutdown hook remains alive while preparation is blocked, including
when an administrator calls the upstream `poweroff` directly. A nonzero hook
exit would let native shutdown continue, so watcher failure uses a blocked
signal wait. Normal waiting uses inotify and process/cgroup exit events, with no
sleep-based assumptions. Once native shutdown starts it cannot be cancelled.
`fds-power` is a static alias of `fds`. The CLI now directly declares the already
locked `libc` crate for inotify, identity and signal operations. No new external
crate or target package is introduced. `fds-init` explicitly depends on
`fds-cartridged`, since the native hook requires its DATA checks. DHCP and its
privilege-separated children now share a managed cgroup. The final native grace
period is zero after managed exits and DATA verification have completed.
## Regression evidence
The development archive passed rootfs rejection checks, native PID 1/service
control and actual poweroff, desktop/network transitions, tooling/static-ELF
checks and `make check`. Logs are `out/logs/m10-final-*.log`; desktop evidence is
`out/m8-vm.1c20rcpb/`, native init is `out/m2-vm.5ARJNL/`. The final CLI image also passed native init (`out/m2-vm.ysuhZ5/`), all nine
stage0 cases (`out/m4-vm.e3SBsX/`), and the ordinary-user console checks
(`out/m5-vm.4l796uhp/`). Cartridge regression passed in `out/m6-vm.p7gfwnj2/`, DATA regression in
`out/m7-vm.82fqc9wa/`, and confirmed media writes in `out/m9-vm.5a58kou6/`.
The development runtime tested here is `out/rootfs-build.WEtGnr/`, with
191 packages and 1406 AArch64 ELF files. Its rootfs SHA-256 is
`9de03100b998cac2a754fc8e05acbad0bd3f45846dd637ba4c597ea3ba61f5bb`.
Its SYSTEM is `out/system-build.dd3e74_2/system.img`, SHA-256
`bef3b01bc8bc68ca673aa6a0ec73892ddaa2901864eed204745749915ee34b18`.
The subsequent explicit init-to-cartridge package dependency was verified in the
final CLI build; the development archive already includes that daemon through
its base package. Runtime binaries and service behavior are identical.
The CLI archive has 180 packages and 1394 AArch64 ELF files, and is 499527680 bytes.
Kernel-to-console was 1821.039788 ms versus M9's 1854.858231 ms: **−33.818443 ms**.
The actual ARM comparison passed without a regression exception; see
`out/logs/m10-boot-comparison.log`. This is a VM observation, not a Pi target result.
| Artifact | Location | SHA-256 |
| --- | --- | --- |
| CLI rootfs | `out/rootfs-build.0AzReg/rootfs-aarch64.tar` | `ebfc58171aeee6f737c2a93a0f3a318d4f02eb915edb3b5a3cf0f51848863930` |
| CLI SYSTEM | `out/system-build.c7q19iil/system.img` | `81dc8083777b497f02a290a15eeb246465689d0deaff97d761aeb882d140b44b` |
| FAT32 boot partition | `out/boot-build.42abbf_h/boot.fat` | `0972b5973d1fefc377ec4c800043fb8e4aa3013140f95218cdebcf38dcd22a49` |
-110
View File
@@ -1,110 +0,0 @@
# M11 stress validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Stress-test usage and physical procedure](stress-testing.md) · [Implementation ledger](implementation-status.md)
M11 software acceptance passed on 2026-09-21. The expanded twelve-device suite
passed in `out/m11-vm.o1c_tqiv/`, including the explicit readiness gate and
diagnostic collector. The public `make stress-test` passed all six initial media
failure cases in `out/m11-faults.g5uw_jao/`. The strengthened final
`make media-fault-test` passed in `out/m11-faults.hfyqoui3/`, verifying the actual
virtual-device byte counter before each interruption. `make check` then passed. Physical Pi/hub/battery tests remain deferred.
## Twelve-device evidence
The complete runs used the packaged M10 CLI rootfs and actual Pi kernel in
an isolated generic ARM VM. A protected virtual SYSTEM root and twelve USB disks
were present: one DATA and eleven distinct PROGRAM cartridges. Every identity
matched its bay through cold boot, three simultaneous hotplug/removal cycles,
rotated placements and reverse device insertion order.
Eleven direct readers each verified 32 MiB while DATA wrote and flushed 64 MiB.
All twelve measured I/O intervals overlapped. QEMU block counters confirmed the
corresponding reads/writes, so cached file reads alone could not satisfy the test.
A subsequent managed writer and its TERM-ignoring descendant ran during native
reboot. Independent host `e2fsck` passed; the entire 64 MiB payload matched its
expected SHA-256. Another boot with a different device declaration order retained
correct mapping and data, and completed native poweroff. Read-only cartridge
image hashes were unchanged.
Kernel logs from normal stress/reboot contained no detected unexpected USB reset,
controller failure, I/O error or filesystem error. This only describes the virtual
hardware used in the test. It says nothing about physical voltage, hub firmware,
cables, RP1 or a storage controller's cache guarantees.
## Startup observations
Three paired samples used the same SYSTEM and virtual controller, with either
zero or twelve USB disks. The first complete run observed:
| USB population | Kernel-to-console samples | Median |
| --- | --- | --- |
| Empty | 1907.961445, 1761.020483, 1847.460910 ms | 1847.460910 ms |
| Twelve | 1968.638224, 1998.194820, 1927.705171 ms | 1968.638224 ms |
The median device-load difference was **+121.177314 ms**, above the 100 ms review
threshold. Stage0 discovery remained approximately 23–27 ms in both scenarios;
the increased work appears mainly between s6 start and console readiness.
The unchanged getty graph has no cartridge-readiness dependency. Concurrent USB
and cartridge processing competes with startup in this two-vCPU emulator. This
is an explanation of the controlled device-load comparison, not a claim that a
code change has been proven faster or that Pi timing targets passed.
Earlier complete cold-boot pairs from interrupted test attempts observed
+122.544124 ms and +76.064782 ms; their raw reports remain in
`out/m11-vm.jcvwpxel/` and `out/m11-vm.w4oyzwje/`. They are not full acceptance runs.
The spread is retained rather than discarding slower observations. The expanded
suite then passed with medians 1900.735990 ms (empty) and 1957.588750 ms (twelve):
**+56.852760 ms**. All samples and the actual ARM comparison are stored in
`out/m11-vm.o1c_tqiv/boot-comparison.json` and `boot-review.log`. The blocked-daemon
case reached the real UID-1000 prompt with no cartridge control socket, then
completed enumeration and native shutdown after an explicit event released the
daemon. This verifies console independence under the same twelve-device load.
## Test-harness findings
The AArch64 Linux `O_DIRECT` flag differs from the x86_64 value. An initial
fixture incorrectly requested directory-only opens and failed before I/O. The
corrected ARM-only test helper performs aligned direct reads; byte checks and
block counters then passed. No target runtime change was needed.
A burst of asynchronous device-deletion events could arrive while a new QMP
monitor connection was being greeted. The shared VM helper now accepts those
events before the greeting, without treating them as command responses. Checked
serial capture executes a command once and retries only a corrupted transfer.
It preserves kernel diagnostics instead of suppressing error messages.
## Media failure acceptance
Actual-device tests passed for write EIO, flush EIO and readback EIO.
Each leaves a failed operation across daemon restart, with no SAFE marker.
Cancellation, worker death and daemon death during confirmed writing also passed
in `out/m11-faults.g5uw_jao/`. The strengthened run in
`out/m11-faults.hfyqoui3/` additionally requires at least 64 MiB in QEMU's actual
write counter while the transfer remains incomplete, retaining that counter and
job state in an interruption record. All three strengthened interruption cases
passed: each had accepted 128 MiB into the guest write path and delivered 64 MiB
to the virtual disk, with a 270532608-byte source still incomplete. Cancellation
retained its diagnostic, worker death retained a failed-completion error, and
daemon death retained a restart error. Each remained failed after another
restart, with the daemon usable and no SAFE marker.
Final logs are `out/logs/m11-final-stress-retry.log`,
`out/logs/m11-final-faults.log` and `out/logs/m11-final-check.log`. The final failure
run records input hashes and QEMU 11.1.1 identity beside its images. Runtime
packages and boot artifacts remain the verified M10 builds; M11 adds tests and
physical-test tooling rather than changing the target service behavior.
## Physical acceptance
The [physical procedure](stress-testing.md#prepare-the-physical-session) covers
bay calibration, cold boot, rotated placement, concurrent I/O, orderly reboot and
poweroff, power behavior and external timing. `tools/capture-hardware` collects
read-only local diagnostics with per-command exit statuses and checksums. It
reports collection success separately from hardware acceptance. All 17 capture
commands returned success in the ARM VM, and the resulting private directory
and checksums passed inspection. The blank physical session template remains
explicitly `not_run`. None of those
physical tests has been performed in this milestone.
-410
View File
@@ -1,410 +0,0 @@
# M12 production preparation: software evidence
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Implementation ledger](implementation-status.md) · [EEPROM](eeprom.md) · [Recovery](recovery.md)
M12 **software acceptance is complete**, including the signed local FDS/OS
0.1.0 release. Physical Pi tests, EEPROM application, display behavior and
storage power-loss behavior are deferred. Earlier checkpoints below preserve
the development history; the final acceptance identifies the released bytes.
## Final local release acceptance
Completed on 2026-09-21. The local release is
[`out/fds-os-0.1.0/README.md`](../../out/fds-os-0.1.0/README.md).
It contains 38 signed manifest entries, versioned images and packages,
profile inventories, source and complete build-input archives, guides and
reproducibility evidence. It has not been published or installed on hardware.
- Frozen source SHA-256: `8947e57552f5db834fbb6bcce6d79623bf0605072b42b4ccf04d1e4fe7bb9f29`.
- Input-lock SHA-256: `6fa630446f91a7a0a2b85324b7a258ea6351978ff8f8a4cf9970f73f8d24fe2c`.
- Manifest SHA-256: `0c223b4996258efbe33db780ab0d05019c35e36be291499a9548c27371c451a4`.
- Signing public-key fingerprint (decoded 32 bytes): `d5c95e8c1a079ed523b859c0ee78b8e849bbf387b3719dcdae1661ae4f7eb267`.
- Signing public-key text-file SHA-256: `30d832747e95dca2c7ad3f5a117d2069a2d94e6d3eee2dd385caea688159e677`.
Two newly restored trees, `out/rebuild-m12-v5-a` and `out/rebuild-m12-v5-b`,
ran `tools/in-frozen-build make all` sequentially with no network access.
Both started without FDS build outputs. The complete 27-artifact comparison
passed, covering all five installable images, three rootfs tars, four initramfs
formats, kernel/DTB, four EEPROM preview files and all nine FDS packages.
Logs: `out/logs/m12-offline-v5-{a,b}.log`; comparison:
[`out/m12-reproducibility-v5.json`](../../out/m12-reproducibility-v5.json).
Every one of those artifacts also matches the normal build used by the complete
[Clap runtime acceptance](clap-validation.md). The identity proof is
[`out/m12-tested-to-release-v5.json`](../../out/m12-tested-to-release-v5.json),
and `out/m12-clap-acceptance-index.json` records the 21 acceptance logs and hashes.
This establishes exact-byte coverage of the released images without repeating
the same VM suite against identical images. Physical tests remain deferred.
The first restored tree additionally passed `make smoke-test`, `make check`
and `make signing-test` inside the isolated wrapper. A second full comparison
after those checks still passed. Logs are `out/logs/m12-v5-{smoke-test,check,signing-test}.log`;
the comparison is `out/m12-reproducibility-v5-after-checks.json`.
The optional direct host `qemu-aarch64` smoke run is reported as skipped when
absent; the separate ARM signing and OS runtime suites execute real ARM code.
The assembler verified source identity and rootfs build inputs before signing.
Verification against the separately retained public key checked every manifest
entry. OpenSSL independently verified the actual release signature. Both actual
release archives were extracted with mode-preserving flags; the complete input
lock and standalone source identity passed. Evidence:
[`out/m12-release-acceptance-v5.json`](../../out/m12-release-acceptance-v5.json),
with logs in `out/logs/m12-v5-*.log` and extracted files under
`out/m12-release-acceptance.mzkfsvev`.
The private signing key is outside the checkout, snapshot and release output.
Its corresponding trusted public key is
`/home/felis/.local/share/fds/signing-0.1.0-20260921/release-01.pub`.
On this workstation, from the checkout:
```sh
out/rebuild-m12-v5-a/target/x86_64-unknown-linux-gnu/release/fds-release verify \
out/fds-os-0.1.0 \
--key /home/felis/.local/share/fds/signing-0.1.0-20260921/release-01.pub
```
| Image | Bytes | SHA-256 |
| --- | ---: | --- |
| `fds-boot-0.1.0.img` | 536,870,912 | `1466bbac3f782115bce78b48376ab90a7364416379700e944c529280b7253703` |
| `fds-internal-0.1.0.img` | 1,881,145,344 | `5197cd5edbbb9f689f12d233371cf0079ac50b36a4d3ca3cfe4c38cd320fc468` |
| `fds-recovery-0.1.0.img` | 491,593,728 | `4b0c9dad4336535d5b90c35cacd1500528836008be3384bdcd22b078b8a25f5b` |
| `fds-system-cli-0.1.0.img` | 493,879,296 | `03233a1118471cd8550ef2813fa99d371ef00b23edde5a3ade161d371e36c177` |
| `fds-system-development-0.1.0.img` | 1,989,148,672 | `9110f966212b59186e60ae7f88b6500fb0909478e6cbbb17735eebc969092c1e` |
| `kernel_2712-0.1.0.img` | 26,751,488 | `9e811bc55aa8e0b6a7233c6c84978244f2affee7bc808f3913dcd2fab3cd8013` |
The [67-section ledger](implementation-status.md) records implementation,
evidence and physical deferrals for the entire master plan. Main-checkout status
documentation was updated after acceptance; the frozen trees and signed archives
retain the exact checkpoint documentation used for their source identity.
Consequently, their historical “verification in progress” wording predates this
record. Runtime code, configuration and image bytes were not changed by this
final documentation update. Use the preserved inputs to reproduce the exact
release images. The signed `RELEASE-NOTES.md` also corrects an archived guide's
fingerprint terminology: the tool hashes decoded key bytes, not the text file.
The standalone release verification guide includes that correction. The original
assembler output is retained at `out/fds-os-0.1.0-checkpoint/`; final packaging
adds these signed release notes and updated standalone guidance without changing
any image, package, source archive or input archive.
## EEPROM configuration and rollback
`make eeprom-test` passed against the actual pinned 2 MiB Pi 5 EEPROM image.
Evidence is in `out/m12-eeprom.5b97ph36/`; the public run is recorded in
`out/logs/m12-eeprom-final.log`. `make check` also passed afterward.
The tests cover both production and development settings, exact configuration
readback, unchanged firmware payloads, identical repeated outputs, preservation
of unrelated conditional settings, exact rollback configuration, existing-output
refusal, and rejection of invalid firmware/device/config inputs. The tool never
invokes an update/apply command. The default firmware is a preparation input,
not a backup of the user's Pi.
See the [EEPROM guide](eeprom.md) for the source commit, hashes, complete profile
settings, generated files, and the deferred physical application/rollback steps.
## Independent recovery
The recovery implementation has its own rootfs profile, EROFS builder, local
maintenance console, read-only cartridge defaults, and explicit DATA check/repair
commands. It retains native s6 and the base Dasung controller. No additional Rust
crate or target package was needed: repair uses the existing e2fsprogs and kernel
loop support while preserving the physical disk reservation.
`make recovery`, `make rootfs-test` and the complete `make recovery-test` passed.
The final runtime evidence is `out/m12-recovery.rlvmytrz/`, linked from
`out/m12-recovery-latest`. Logs are
`out/logs/m12-recovery-identity-build.log`,
`out/logs/m12-recovery-identity-rootfs.log`, and
`out/logs/m12-recovery-final-acceptance.log`.
| Artifact | Verified result |
| --- | --- |
| Recovery rootfs | `out/rootfs-build.D5TCGs/rootfs-aarch64.tar`, 499,609,600 bytes; 180 packages and 1,394 AArch64 ELF files |
| Rootfs SHA-256 | `0611c0f8f24d04a733401063f9f52c7bc08f6389cb707601df083390fe063ae1` |
| Recovery EROFS | `out/recovery-build.9ukiaknh/recovery.erofs`, 490,598,400 bytes |
| EROFS SHA-256 | `7a219ca803325e4d60c3acf1b4cffe8717e7a7ec4e65377f48c900c131910bcd` |
The tests use the packaged recovery image and Pi kernel. The only rootfs change
for virtual cartridge tests is the observed virtual bay map; no replacement
runtime binaries or simulated success responses are inserted.
Verified cases:
- Two independent EROFS constructions from the same tar produce identical bytes.
- Recovery boots with SYSTEM absent and with an unreadable SYSTEM. Its root is
read-only, PID 1 is native s6, the maintenance console is root, and Dasung is
supervised without needing the physical monitor.
- DATA remains read-only; ENVIRONMENT and Ethernet insertion do not automatically
start the desktop or DHCP. An ordinary user cannot invoke recovery repairs.
- A real ext4 inode reference-count error is detected without changing DATA.
Wrong confirmations and busy mounts are rejected. Confirmed repair fixes the
error, flushes/invalidate caches, and passes another read-only check. The
original test file survives, and a separate host e2fsck also passes.
- Failed checks survive daemon restart as quarantine. Confirmation from a previous
insertion is rejected. Clean checks preserve every byte of the DATA image.
- Recovery writes a complete known-good SYSTEM image to a disposable virtual USB
device, verifies it by readback, inspects it, and excludes it from DATA repair.
A separate VM boots that written image to the ordinary UID 1000 FDS console.
The source SYSTEM is the previously verified M10 CLI image; this proves the
recovery replacement workflow, not a new release-signing claim.
- Killing the daemon during a real checker kills the child and preserves DATA.
An explicit retry succeeds. Twenty partition-table rereads and a subsequent
service restart preserve the unmounted SAFE state; temporary loops are gone.
- The diagnostic collector completes all 17 commands and verifies its output
checksums. Successful cases power off through native s6.
The interruption/rescan case also passed three consecutive focused runs in
`out/m12-recovery-rescan.rlr9u2aq/`, `out/m12-recovery-rescan.cvzye8z0/`, and
`out/m12-recovery-rescan.wj0jlfkq/`. This caught and fixed a real bug: temporary
partition disappearance during a reread previously cleared the eject record.
The daemon now retains that record while the same physical disk insertion is
present; a new insertion still invalidates it.
The normal CLI rebuild and regression checks also passed: `make rootfs PROFILE=cli`,
`make rootfs-test`, `make system-card PROFILE=cli`, `make init-test`,
`make console-test`, `make data-test`, `make tooling-test`, and `make check`.
Evidence is in `out/logs/m12-cli-*.log`, `out/logs/m12-tooling.log`, and
`out/logs/m12-check.log`. The normal-image root check emitted
`FDS_M12_RECOVERY_BOUNDARY`, confirming that recovery repair remains unavailable
outside the recovery profile, even to root.
That CLI rootfs is `out/rootfs-build.HqBtpz/rootfs-aarch64.tar`
(SHA-256 `af132511a6784eced9e52e971f010df6717e3caed751d14017ca9e924196e8b3`),
and its SYSTEM image is `out/system-build.psymtwbr/system.img`
(SHA-256 `5bc69f31d74a4c07596b696a72138cd24327ff4bc467f428717a56ce44c6bd27`).
Native init passed in `out/m2-vm.GyJsNg`, console checks in `out/m5-vm.h5ygfe1j`,
and DATA checks in `out/m7-vm.nt_tsdfs`. Console readiness was 1,853.221941 ms,
32.182153 ms above the earlier M10 CLI baseline of 1,821.039788 ms; this is below
the 100 ms investigation threshold. These artifacts precede the internal-settings
implementation below. No physical timing or media-durability claim follows from
these VM measurements.
## Internal NVMe and machine settings
Implementation and current usage are described in [Internal storage](internal-storage.md).
`make internal-test` passed in `out/m12-internal.vqr74fcs/`, linked from
`out/m12-internal-latest`. The public log is
`out/logs/m12-internal-final-acceptance.log`; rootfs evidence is in
`out/logs/m12-machine-final-rootfs.log`. The test uses packaged CLI and recovery
images, the actual Pi kernel, and disposable virtual NVMe devices. No runtime
binary or configuration file is injected into the booted OS.
Verified cases:
- Two complete 1,881,145,344-byte internal GPT images are byte-identical, with
independent FAT, EROFS, ext4, GPT and payload checks. Their SHA-256 is
`0d464ba18b0988a79a75ea54b6d81c5fa01acfea567b3d0a79f9faf2a428dfc6`.
- `make internal-image` publishes the same hash at `out/internal-build.xxzvw_3l/internal.img`,
linked from `out/fds-internal.img`; its log is `out/logs/m12-internal-published.log`.
- The actual loader selects one correctly laid-out NVMe and leaves every byte
unchanged during read-only loading. The private mount is gone afterward.
- Root recovery installs validated settings atomically, preserves previous bytes,
flushes/unmounts storage, and keeps this boot's active mapping unchanged even
across a cartridge-daemon restart.
- A separate reboot loads the saved machine name, bay map and hardware catalog.
The real virtual keyboard is recognized in its assigned bay with its saved name.
- Boot diagnostics survive reboot and are retrieved byte-for-byte. Non-root
writes, path traversal, duplicates, existing retrieval outputs and oversized
diagnostic files are rejected. Independent post-write e2fsck passes.
- BOOT and RECOVERY payload hashes remain unchanged after settings/diagnostic
writes. Ordinary user data is not placed on internal storage.
- A complete USB lookalike cannot supply settings. Two eligible NVMes boot the
normal UID 1000 console with explicit ambiguity/fallback status.
- Invalid JSON, writable settings, symlinks and unclean ext4 leave the disk bytes
unchanged, record an error and retain a usable recovery console with image
defaults. No automatic filesystem repair or journal replay is performed.
An earlier test used unsupported PCI hotplug on QEMU's root PCI bus; the final
ambiguity fixture instead boots the ordinary SYSTEM with both NVMes present.
Earlier partial runs remain available, but are not counted as acceptance.
The settings trust checks exposed omitted `/` metadata in the rootfs archive.
EROFS had synthesized a host-owned mode-0777 root directory. The archive now
records root:root mode 0755 explicitly, both OS builders reject old archives,
and rootfs/VM tests check the result. `out/m12-root-metadata-repro-audit.json`
compares two otherwise-identical recovery builds; only the new `.` entry differs.
The build-specific auxiliary linker cache has been removed.
The tested recovery rootfs is `out/rootfs-build.P4WWwz/rootfs-aarch64.tar`
(SHA-256 `9d0e54533399da79616853e8233d2abb8efbc6b2d51772bd86f2669134c957b4`),
and its EROFS is `out/recovery-build.1ukd0xgl/recovery.erofs`
(SHA-256 `1c1930c86960f8e208408f4a0c244e4e5ae876263bab234b25307d4154ef3f91`).
The updated CLI rootfs is `out/rootfs-build.BP1Ylv/rootfs-aarch64.tar`
(SHA-256 `58eb153416972dca1c29a87d7b356638b8b1c08df56231470c18ee50fbad2d29`),
and SYSTEM is `out/system-build.o9d6affu/system.img`
(SHA-256 `cb544eb25f18b6572f78fc245818bd2cbfa18423b44d57b5af251069f2e6498e`).
The complete recovery regression also passed against these updated artifacts in
`out/m12-recovery.idrwxslu/` (`out/logs/m12-machine-recovery-regression.log`). It
repeats the missing/damaged SYSTEM, actual DATA repair, interrupted-checker,
partition-reread, full SYSTEM burn/readback, diagnostics and replacement-boot
checks described above. This time the source SYSTEM is the updated CLI image
listed here, including the settings loader and corrected filesystem-root metadata.
The updated normal CLI has already passed rootfs, SYSTEM construction, native
init, console independence and DATA regression checks. Logs are
`out/logs/m12-machine-cli-*.log`; native init evidence is `out/m2-vm.5FHPFv`,
console evidence is `out/m5-vm.ifuo9ei6`, and DATA evidence is
`out/m7-vm.6xcpnj97`. The measured VM console time was 1,865.948488 ms,
44.908700 ms above the earlier M10 CLI baseline, below the 100 ms review threshold.
## Build-host regression
After the host image-tool changes, `make bootstrap`, `make smoke-test`, and
`make check` all passed. Logs are `out/logs/m12-foundation-bootstrap.log`,
`out/logs/m12-foundation-smoke.log`, and `out/logs/m12-foundation-check.log`.
The smoke test cross-built and inspected GNU hello as aarch64 glibc and the Rust
smoketest as static aarch64-musl. Optional direct ARM execution was skipped because
the workstation lacks `qemu-aarch64`; the separate ARM VM/runtime suites above
still executed the actual OS tools. Upstream tracked Void files remain unchanged.
## Development profile
The native GNU C/C++ and Rust toolchains, build systems, Git, GDB, strace and Vim
are selected and documented in [Development](development.md). The expanded image
passed `make development-test` in `out/m12-development.r6mrgm0b/`.
The rootfs is `out/rootfs-build.p8DKvd/rootfs-aarch64.tar` (2,012,047,360 bytes;
245 packages and 1,731 AArch64 ELF files), and its SYSTEM is
`out/system-build.jnubdw1v/system.img`. Logs are
`out/logs/m12-development-clock-{build,rootfs,system,acceptance}.log`.
The ordinary UID 1000 console compiled and ran C/C++ with CMake/Ninja, C with
Meson/Ninja and Make, and a dependency-free Rust project with offline Cargo.
GDB stopped at `main`, reported a backtrace and continued; strace captured the
program's write; a local Git commit and headless Vim edit also succeeded.
SYSTEM remained read-only and unchanged, optional services stayed off, and
native shutdown completed. Desktop/network acceptance then passed in
`out/m8-vm.4c6_jj4h/`, and the complete shutdown/DATA error suite passed in
`out/m10-vm.q_ewzl4g/`; see `out/logs/m12-development-{desktop,power}.log`.
This test found a real startup issue: the VM's wall clock started at Unix second
1 while CMake's immutable inputs had the source timestamp 1,789,909,701.
Ninja consequently tried to regenerate its manifest repeatedly. Native startup
now advances only an older clock to the fixed image timestamp, without a time
server or waiting. The compiler acceptance verifies that path; a separate
`make clock-test` passed in `out/m12-clock.l39g3uhx/`, preserving a guest clock
set to 2030 before the actual startup code runs. Non-root clock changes are
rejected and boot measurements still use `Linux CLOCK_BOOTTIME`.
`out/logs/m12-clock-final.log` records that check. This is not physical RTC
validation and the floor does not supply the accurate current date.
The first corrected-clock run also exposed a test transport mistake: interactive
Bash consumed the literal tab in a generated Makefile. Test files now cross the
serial console as base64, preserving their exact bytes.
## Release signature tool
`make signing-test` passed in `out/m12-signing.md4r2kbo/`, recorded in
`out/logs/m12-signing-public.log`. It builds both x86_64 and static-musl ARM
executables, checks the RFC 8032 Ed25519 vector, and independently signs/verifies
the exact manifest domain with OpenSSL. Host and ARM signatures match OpenSSL's
bytes. Wrong and weak keys, altered signature/manifest/artifact bytes, wrong
sizes, missing files, symlinks, FIFOs, path traversal, duplicates, unknown fields,
oversized manifests, insecure private-key permissions and output overwrites are
rejected. These are disposable test keys, not a signed OS release.
The verifier is included in the base CLI package for all profiles. Its guide and
dependency rationale are in [Release signatures](releases.md), and the build now
collects license notices for all 68 resolved third-party ARM workspace crates,
including the vendored libusb notices. `make bootstrap`, `make smoke-test` and
`make check` passed after adding the signing dependencies, with logs under
`out/logs/m12-signing-{bootstrap,smoke,check}.log`. The later package-notice and
offline-build interface changes are being rebuilt and checked separately.
## Repeated console timing checkpoint
After the clock floor, packaged signer/notices and deterministic package epoch
changes, one console run measured 2,103.545197 ms. This exceeded the 100 ms
investigation threshold against the earlier single M10 sample. Five alternating
pairs then compared the preceding internal-settings CLI image with the updated
CLI image, using the same kernel and initramfs for every run. Evidence and image
hashes are in `out/m12-console-pairs.yuoz7unr/comparison.json`; all ten serial logs
are retained beside it. The execution log is `out/logs/m12-console-pairs.log`.
| Image | Five console-ready samples, ms | Median, ms |
| --- | --- | ---: |
| Before | 1884.195231, 1909.087577, 1960.273690, 2055.400846, 1908.411769 | 1909.087577 |
| Updated | 1938.522887, 1913.188789, 1935.906888, 1958.748315, 1943.363749 | 1938.522887 |
The median increased by 29.435310 ms, below the threshold. The initial updated
sample was outside the repeated updated range. This is workstation/QEMU evidence
including scheduling noise, not physical Pi performance; no artificial delay or
boot barrier was added. This checkpoint precedes Rust source-path normalization.
## Complete build and release interface checkpoint
`make packages` now uses the same complete base-package builder as rootfs
assembly. It passed in `out/logs/m12-packages-public.log`, including a fresh
cross-build of the Pi kernel. The preceding `make bootstrap`, `make smoke-test`
and `make check` passed in `out/logs/m12-offline-foundation-{bootstrap,smoke,check}.log`.
`make all` now sequences the complete image set. Its first complete run passed
in `out/logs/m12-all-first.log`. Subsequent rootfs and foundation checks passed,
as did the complete boot matrix (`out/m4-vm.kn9Hku/`), native development tools
(`out/m12-development.hpk59nul/`) and internal-storage acceptance
(`out/m12-internal.8z6nffpa/`). The corresponding logs are
`out/logs/m12-all-{recovery-rootfs,check,boot,development,internal}.log`.
These results precede source-path normalization in Rust release builds.
The frozen-input, restore, network-isolation, comparison and signed-assembly
interfaces are documented in [Offline rebuilds](reproducible-builds.md).
`make release-contract-test` passed in `out/m12-release-contracts.yg33ry5r/`
(`out/logs/m12-release-contracts.log`). Its small fixtures verify rejection of
changed input bytes/modes/paths/symlinks, invalid lock metadata, differing or
missing artifacts, comparison of one tree with itself, changed input locks,
existing output reports/directories and private-key symlinks. Snapshot creation
also refuses an active base-package build. These contract checks do not stand
in for two actual complete offline builds.
A settings-size regression test also passed: individually valid TOML comments
can expand during JSON escaping. The machine configuration now rejects an
encoded bundle exceeding its loader limit before installation writes any data.
All 15 shared-contract tests passed in `out/logs/m12-machine-encoding-tests.log`.
## Earlier release checkpoints
The user-requested [Clap migration](clap-validation.md) is complete: 48 Rust
tests, ARM CLI/alias execution, the complete image build, all runtime suites,
signing and Dasung checks, and repeated startup measurements passed. All Rust
command-line handling now uses the shared locked Clap dependency. The v4
offline candidate was deliberately stopped during kernel compilation to include
this change; its second tree was not built and it has no accepted comparison.
The accepted v5 pair uses this updated source checkpoint.
`make release-contract-test` also passed with the actual archive writer and the
documented extraction flags in `out/logs/m12-release-contracts-v4.log`: repeated
archives match, and extraction preserves ordinary executable modes, sticky
directories, symlinks and the complete frozen lock. Default tar extraction had
stripped those permissions in a small diagnostic fixture. The v3 candidate was
stopped during kernel compilation before acceptance so that the corrected
archive instructions and the completed user-guide audit could be frozen
together. It is not a failed runtime test or an accepted release build.
The first real snapshot exposed two restore defects, now corrected: shallow
Void bundles need their Git boundary file, and xbps-src's cross repository
configuration retains only custom filenames containing `local`. The dependency
that revealed the second issue was already captured; no network fallback was
added. `out/logs/m12-offline-v1-a.log` and `out/logs/m12-offline-v2-a.log` retain
the failed attempts. The resumed v2 tree is a diagnostic probe, not a clean
release acceptance run.
The fresh offline v2 compilation matched the normal build byte-for-byte for all
seven core FDS binaries, Dasung, the Pi kernel/DTB, and the kernel/Dasung packages.
Records are `out/m12-offline-v2-rust-comparison.json` and
`out/m12-offline-v2-native-comparison.json`. Personal absolute build paths are
absent from all eight release executables after path normalization; see
`out/m12-remap-binaries.json`. Bootstrap, smoke-test and checks passed in
`out/logs/m12-remap-{bootstrap,smoke,check}.log`.
The documented larger-disk GPT relocation was checked on a disposable regular
file in `out/m12-internal-relocation.j29f1rx6/`. After extending the image by
512 MiB and relocating its backup GPT, independent partition-table verification
passed and every partition's start, size and payload SHA-256 remained unchanged.
This verifies the image operation only; the physical write/readback and Pi boot
procedure in [Internal storage](internal-storage.md) remains deferred.
Those checkpoints preceded the final clean v5 pair. The acceptance record at
the top of this page closes the software release work with actual complete
builds, comparisons, signing, archive verification and the master-plan audit.
-145
View File
@@ -1,145 +0,0 @@
# M2 native init validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Boot and usage](init.md) · [Roadmap](roadmap.md)
Validated on **2026-09-21 (Asia/Shanghai)** on the x86_64 Arch workstation.
M2's acceptance criteria passed in a complete ARM virtual machine: native s6
owns PID 1, and `s6-rc change` controls an actual test service. This is a generic
ARM boot result, not a Raspberry Pi boot or performance measurement.
## Recorded environment and outputs
| Item | Result |
| --- | --- |
| Workstation | Arch Linux x86_64, Linux 7.2.6-arch2-1, Python 3.14.7 |
| Void source pin | `02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1` |
| Init packages | s6-linux-init 1.2.0.1_1, s6 2.15.0.0_1, s6-rc 0.6.1.1_1 |
| Emulation | QEMU system/user ARM 11.1.1_1; PRoot 5.2.0_1 for build-time child execution |
| VM | ARM `virt`, Cortex-A72 software emulation, 2 CPUs, 1 GiB RAM, AHCI disk, serial console |
| Generic test kernel | Signed Void `linux6.12-6.12.110_1.aarch64.xbps`; SHA-256 pinned in `config/vm-test.conf` |
| Image creator | mke2fs 1.47.4, libarchive tar import; no privileged mount |
| Final rootfs build | `out/rootfs-build.gSQUvr/`, built `2026-09-20T18:14:46Z` |
| Rootfs archive | 346,808,320 bytes; 109 configured packages; 1,167 ARM ELF paths |
| Successful boot test | `out/m2-vm.wYgNtQ/`; stable link `out/m2-vm-latest/` |
Final rootfs SHA-256:
```text
81113e027b5e65c7885aac9d4b88541de92470aaffb1fefd9508de30b97f0cad
```
VM disk SHA-256, verified unchanged after the automated boot:
```text
00b48913a157de7a1107bf1bfff2c96db83dab48599fbe76be2ab433142d27d6
```
The test directory retains `serial.log`, `command-line`, `test-environment.txt`,
`test-inputs.sha256`, `disk-before.sha256`, and `artifacts.sha256`. The rootfs build
retains its package inventory, exact package archives, build inputs, and hashes.
Convenience links may change after future builds; this report names the retained
successful directories. Rolling Void repositories are not a complete release
snapshot, and local FDS packages remain unsigned development artifacts.
## What the ARM guest proved
The VM booted the exported filesystem with its generated native init launcher.
The test overlay preserves all 54 entries in the actual compiled service database
and the original stage-2 script. Its hook runs the original stage 2 to completion,
then runs the checker as a child. It neither substitutes a shell PID 1 nor compiles
a different service graph. The checker is absent from the normal rootfs archive.
The serial log records:
```text
FDS_M2_PID1: /usr/bin/s6-svscan
FDS_M2_FILESYSTEM: read-only root, writable tmpfs, root ownership and capabilities
FDS_M2_BOOT: getty, eudev and Dasung supervised without a monitor
FDS_M2_SERVICE: s6-rc start, echo, supervised restart, stop and start passed
FDS_M2_PASS
```
The guest verified a read-only ext4 root, rejected a write under `/etc`, found
writable tmpfs at `/run`, `/tmp`, `/var/tmp`, and `/var/log`, and checked hostname,
root ownership, protected shadow permissions, and ping's restored capability.
Eudev answered its control-channel readiness request; getty and Dasung were up.
`test-echo` started down. The checker brought it up through s6-rc, waited for
actual listener readiness, received an echo over its private Unix socket, restarted
it under supervision and observed a new PID, stopped it, started it again, and
stopped it before shutdown. Native s6 then completed power-off. The VM had no
network, host filesystem share, physical USB/display access, or host disk passthrough.
QEMU's disposable overlay preserved the backing disk.
The interactive `make vm` workflow was also exercised: native PID 1, read-only
mounts, active-service listing, Dasung status, manual echo-service control, and
exit-to-power-off. Its transcript is `out/logs/m2-vm-shell.log`. Dasung returned
valid status JSON with `connected: false`, as expected without a monitor. The
explicit VM shell stops getty to take over the serial console; normal base
accounts remain locked.
## Checks run
These commands completed successfully during M2 validation:
```sh
make bootstrap
make smoke-test
make check
make rootfs PROFILE=cli
make rootfs-test
make init-test
make dasung-test
make vm
```
Rootfs acceptance checks cover package integrity, ARM glibc execution, static
Dasung ELF properties, ownership/capability restoration, and rejection of wrong
ABIs, forbidden packages, incomplete configuration, host executables, missing
boot members, a shell init replacement, and unsupported profiles.
Dasung regressions passed all six Rust unit tests, the serial simulator, package
inspection, and its independent s6 logger/socket/restart/stop test on a read-only
root. The final device-trigger guard changed only the init service definition;
the rootfs and full ARM boot checks were repeated afterward.
Shell/Python syntax, local documentation links, English project-owned text,
recorded build/test source hashes, and unchanged upstream Void tracked files
were checked. The source pin was preserved.
## Issues caught and fixed
- The test kernel already mounts devtmpfs. Asking native init to mount `/dev`
again caused a genuine kernel panic. The generated launcher now consumes the
existing mount and leaves `/run` creation to s6-linux-init.
- The generated init template contains FIFOs. Auditing/export now permits them
only within its `run-image` subtree, and avoids reading them as ordinary files.
- The upstream CA installer hides updater failures. One build lacked the bundle
despite package configuration reporting success. Finalization now runs the
updater explicitly, checks its exit status, verifies the resulting cache, and
reports the failing command if any required finalization step fails.
- VM image creation uses a UTF-8 locale for libarchive's Unicode paths. AHCI uses
a QEMU snapshot overlay, with temporary files in the retained test directory.
- Initial device triggering now propagates a failed subsystem trigger instead
of continuing and reporting only the final command's result.
The two essential internal s6-rc helpers remain until native shutdown's final
cleanup; warnings that the ordinary service-stop phase leaves them running are
expected. Known rootless `wall`, `write`, and `xbps-uchroot` ownership adjustments
remain handled and verified by archive export, as documented for M1.
## Explicit skips and limits
The M0 smoke script still skips its optional host-PATH `qemu-aarch64` execution
because QEMU is installed inside the project-local container. The separate ARM
userspace and complete ARM VM tests both passed.
No Pi firmware, NVMe-to-cartridge root transition, production kernel/initramfs,
physical Dasung picture or cold-power recovery, bay mapping, DATA safety,
networking, or graphical profile was tested. There is no installable Pi image.
The VM kernel is only a test fixture. Its log timestamps and 180-second test
deadline are not boot or shutdown benchmarks. M3 and later implementation remain
outside this change.
-43
View File
@@ -1,43 +0,0 @@
# M3 static Rust tooling validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
Validated on 2026-09-21 (Asia/Shanghai), x86_64 Arch Linux with pinned Rust 1.98.0.
This records the M3 diagnostic foundation before M4 adds the real boot handoff.
See [tooling usage](tooling.md) and the [through-M12 ledger](implementation-status.md).
Commands completed successfully:
```sh
make tooling
make tooling-test
make bootstrap
make smoke-test
make check
```
Both exported binaries were AArch64 static ELF executables without a dynamic
interpreter, shared-library requirements or GLIBC symbol requirements. Supplemental
`ldd` printed `not a dynamic executable`. At this milestone:
| Artifact | Bytes | SHA-256 |
| --- | --- | --- |
| `out/fds` | 537424 | `04052554eef3c592230aa912d6dbcaec9bc37c168c0e240b924a4fb215a68815` |
| `out/fds-stage0` | 375824 | `930afd537aaf8e90715e5ec73d70480b44fbe9dfe3c0615014ed78b36239bd7c` |
Six shared-library tests passed. Actual ARM execution then verified tool identity,
JSON output, the master-plan manifest example, strict/bounded manifest rejection,
missing/unique/ambiguous SYSTEM selection, device-path rejection, explicit recovery
selection, and duplicate boot-option rejection. All filesystem fixtures were
temporary; no physical block device or monitor was accessed.
Build/source hashes are in `out/manifests/fds-tools*.sha256`; logs are in
`out/logs/m3-tooling.log`, `m3-tooling-test.log`, `m3-bootstrap.log`,
`m3-smoke-test.log`, and `m3-check.log`. Later milestones will replace these tool
artifacts; the hashes above describe the validated M3 version.
The M0 smoke script's optional host-PATH QEMU execution was skipped, as before;
the M3 integration script explicitly ran both ARM tools using container QEMU.
No physical Pi result or boot-speed claim is made. M3's stage0 refused normal
boot invocation rather than claiming its diagnostic skeleton could switch roots.
-96
View File
@@ -1,96 +0,0 @@
# M4 software boot validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
Validated on the x86_64 Arch workstation on 2026-09-21 (Asia/Shanghai).
Physical Pi testing is deferred by the user. This report establishes the
software boot machinery, not the physical NVMe/RP1/display path or performance
targets. M5 supplies the ordinary-user FDS console and timing instrumentation.
## Verified path
The cross-built Pi 5 kernel loaded the actual Rust-only initramfs, discovered
GPT partition identity, mounted EROFS read-only, transferred proc/sys/dev mounts,
stopped and reaped the early Dasung controller, and executed native s6. Guest
checks confirmed 16 KiB pages, s6-svscan as PID 1, the static packaged FDS CLI,
matching module release, loading/unloading the `dummy` kernel module, and one
base Dasung process. Ownership, ping capabilities, volatile filesystems and
start/restart/stop of the test service also passed.
The final VM matrix, `out/m4-vm.flkV6j/`, passed nine cases:
1. Uncompressed initramfs.
2. Gzip initramfs.
3. Legacy-LZ4 initramfs.
4. Zstandard initramfs.
5. No SYSTEM at startup, followed by virtual USB insertion and automatic boot.
6. Two SYSTEMs refused, followed by explicit console recovery.
7. Recovery selected by kernel option while SYSTEM is also present.
8. Invalid SYSTEM filesystem refused, with recovery still usable.
9. Foreign OS root refused, with recovery still usable.
Each case reached the guest checks and orderly power-off. Input image hashes
were unchanged. The test uses QEMU 11.1.1, `virt`, `max` CPU, TCG, 1 GiB RAM and
two virtual CPUs, without networking or host-device passthrough. Its recovery
disk is a selection fixture, not the independent M12 recovery product.
The FAT32 boot builder produced a 512 MiB `FDS_BOOT` partition, checked it with
`fsck.fat`, extracted it with mtools and compared every file hash. Two consecutive
builds matched byte-for-byte. Two full kernel builds also produced identical
kernel Image, Pi 5 DTB and XBPS package hashes. These narrow reproducibility
results do not establish a complete offline release rebuild.
## Commands and artifacts
The completed sequence included `make kernel`, `make tooling`, `make tooling-test`,
`make rootfs PROFILE=cli`, initramfs construction, and these final regression gates:
```text
make bootstrap
make smoke-test
make check
make rootfs-test
make init-test
make system-card
make boot-volume
make boot-test
make dasung-test
```
Final logs are `out/logs/m4-final-*.log`; rootfs construction is recorded in
`out/logs/m4-rootfs.log`. The configured rootfs is `out/rootfs-build.p0HSQv/`:
111 packages, 1,168 inspected ARM ELF files, and a 407,009,280-byte tar.
| Artifact | SHA-256 |
| --- | --- |
| Rootfs tar | `ba06bdcb8f4fc537c8cb4ce9d31404d3cd1d14f01b41cb597a53ec48f0ba6710` |
| SYSTEM GPT image | `d15af6feccbaeba77920e0b5ee13076dbdc80a076c973068a1f68e86bb599bf1` |
| Pi kernel Image | `78f027ad3124da7b06d4559f49ea7f63c312cd24e653d0f8e29ee2391f520bc4` |
| Kernel XBPS package | `6c361df5bf31ef517b6bad7e44220a8126f7c2fac341379e8c1d6b39fcbe8c20` |
| Uncompressed initramfs | `bc8905c5ca40fe827a44f7dd73f88aa1c99a3b84fa474535ccb36a212682b514` |
| Production FAT boot partition | `940b7d62ba5923a3f92db6939aca1534f0f2061376c1fec7604a50e6d3b3f0b8` |
The kernel is 6.12.87-fds1, built from Raspberry Pi commit
`ae4d75fb36deba0fe1a986d9bfae65755e082dd0` with the FDS configuration delta.
Void remains pinned to `02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1` with no tracked
upstream changes. Firmware package/version/hash are pinned in
`config/pi-firmware.conf`. Detailed package inventories and source inputs remain
beside each versioned build.
## Findings and limits
The original Cortex-A72 VM CPU did not support the retained 16 KiB kernel page
configuration; `max` successfully runs the Pi kernel. A repeated VM test exposed
getty's shutdown hangup discarding pending serial output. The checker now stops
getty after verifying it and reopens the console before final service checks;
no boot delay was added.
The M0 host-PATH QEMU execution remains an optional skip. Actual ARM userspace
and full-system VM execution passed using project-local emulators. Physical
Pi firmware boot, EEPROM changes, NVMe/RP1, monitor EDID acceptance, image quality,
cold-power display recovery, bay wiring/power and real boot/shutdown timings
remain explicitly untested.
Use the [boot guide](boot.md) to reproduce these artifacts and understand the
distinction between a boot partition, SYSTEM cartridge and complete internal disk.
-60
View File
@@ -1,60 +0,0 @@
# M4 boot integration development record
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
The user has authorized the remaining software through M12. This page records
M4 development decisions. Software validation is complete; see [M4 evidence](m4-validation.md). Physical Pi validation remains deferred.
The Pi kernel overlay starts from the exact source commit and archive checksum in
Void's pinned `rpi5-kernel` recipe (6.12.87). It uses `bcm2712_defconfig` plus the
small FDS delta, verifies Kconfig actually applied that delta, and exports the Pi 5
Image, DTBs, overlays, and modules without modifying upstream Void files.
The upstream Pi 5 16 KiB page configuration is retained.
Critical storage, EROFS, keyboard, console and VC4 display support are built in.
VC4 also requires the SND/SND_SOC core to be built in; optional sound devices can
remain modules. CH341 SPI is disabled for the Dasung companion bridge. Generic
ARM VM drivers are built into this same kernel so we can exercise software boot
without claiming to emulate Pi firmware or RP1. Debug/BTF artifacts are omitted;
module authentication is part of immutable image/release authentication rather
than random build-generated module keys.
`make kernel` uses the existing Void build container and cross compiler. Its
additional host dependencies are Perl and Python for kernel generation, bc for
build-time arithmetic, Bison/Flex for Kconfig, kmod for module indexes, OpenSSL
headers for kernel build utilities, and archive tools. OpenSSL is a host build
dependency, not a new Rust runtime dependency. The source and checksum are recorded
in `packages/fds-kernel/template`.
Image builders use a separate project-local prefix at `.host/image-tools` for
signed Void x86_64 `erofs-utils`, `dosfstools` and `mtools`, plus their resolved
libraries. They create filesystem images without privileged loop mounts and do
not mutate the shared kernel build container. Versions and cached inputs are
recorded under `out/manifests/image-tool-*`; full input locking is M12 work.
Stage0 now has a Rust PID-1 implementation: direct filesystem
syscalls, authenticated kernel uevents, explicit normal/recovery partition
selection, early Dasung supervision, EROFS validation, mount transfer, and exec
of native s6. Its normal path refuses to run outside root PID 1 on ramfs/tmpfs.
The implementation reuses the locked `libc` crate for these Linux syscalls.
The full initramfs matrix passed all four formats, missing-media USB insertion,
ambiguous SYSTEM recovery, explicit recovery, invalid filesystems and foreign
root rejection. A refreshed base package/rootfs build is being completed.
QEMU uses its `max` CPU because the retained 16 KiB Pi kernel cannot execute
on the old Cortex-A72 VM model.
The FAT32 boot builder verifies every file after reading it back with mtools.
Directory timestamps are normalized as well as file timestamps; two consecutive
builds from the same inputs produced the same complete image SHA-256. This narrow
result is not a claim of complete offline or release reproducibility.
Two complete kernel builds also produced identical kernel Image, Pi 5 DTB and
XBPS package digests. The helper now reuses a build after checking recorded
source inputs, its package and all exported files. `--rebuild` bypasses reuse.
The [boot guide](boot.md) documents usable build commands, image boundaries,
waiting-console commands, display policy, and remaining physical checks.
References: [Raspberry Pi cross-compilation](https://www.raspberrypi.com/documentation/computers/linux_kernel.html#cross-compile-the-kernel),
[Linux initramfs](https://docs.kernel.org/filesystems/ramfs-rootfs-initramfs.html),
and [root handoff semantics](https://man7.org/linux/man-pages/man8/switch_root.8.html).
-72
View File
@@ -1,72 +0,0 @@
# M5 software validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
M5's ordinary-user console and software timing tools passed on the x86_64 Arch
build host using the actual AArch64 kernel, initramfs and SYSTEM image in QEMU
11.1.1. Physical Pi boot, power-on, display and timing acceptance is deferred.
These runs completed on 2026-09-20 UTC (2026-09-21 in Asia/Shanghai).
## Usable console and event evidence
`make console-test` passed in `out/m5-vm.fuml66z0/`:
- Native s6 starts the local autologin console as `fds`, UID/GID 1000. Commands
execute at `FDS>`, HOME and working directory are `/home/fds`, the temporary
home accepts writes, and SYSTEM rejects writes.
- Reports contain stage0 entry, SYSTEM discovery, root mount, root switch,
s6 stage-2 entry and first-console readiness for the current boot. Desktop
readiness is absent. No missing event is replaced with a zero timestamp.
- Exiting and restarting the console preserves the first readiness event.
- Holding eudev startup behind a test FIFO does not prevent the usable prompt.
The test releases that gate after executing a console command.
- Saved-report comparison rejects an unexplained regression greater than
100 ms, permits an explicit explanation, and rejects incompatible platforms.
The normal run measured 1,669.027 ms kernel-clock-to-console and 664.032 ms
s6-stage-2-to-console. The blocked-eudev run measured 1,659.990 ms and
581.113 ms. These are individual emulator observations, not physical budgets.
Nine further boots compare Rust optimization levels in
`out/rust-profiles.x_wgx1kp/`; see [Performance](performance.md) for the table,
raw report locations, clock boundaries and decision to retain `opt-level=z`.
`make console-vm` was also opened interactively and verified UID 1000.
## Build execution correction
PRoot intermittently aborted during ARM subprocess execution on this host. One
failure also returned a misleading zero status. Runtime checks now require an
explicit final child acknowledgement, and finalization propagates failures.
The image builder now uses a private user-namespace `binfmt_misc` registration
with static QEMU, requiring Linux 6.7 or newer. It leaves the workstation's
registration table unchanged. The existing Void `util-linux` package provides
mount and capability-drop helpers; no target package was added for emulation.
`tests/integration/rootfs-emulation.py` verified nested ARM execution, exact
nonzero exit propagation, isolation from the host registry and removal of
setup capabilities before target execution. Only namespace-local SETFCAP remains
for package configuration. A fresh complete rootfs build passed afterward.
Historical M1/M2 reports retain the older PRoot workflow as historical evidence.
## Final artifacts
| Artifact | SHA-256 |
| --- | --- |
| Rootfs TAR, `out/rootfs-build.9kTEAH/` | `eef5764034d315b5e012679ef7ed165c6961b69aa88f9514c77cf08ef67bf8f7` |
| SYSTEM GPT/EROFS, `out/system-build.o948uzwd/` | `d2e00f56f1ecc689de455a9066259cefa95bd0bbd271e7f2d3d4cd3235dffd91` |
| Uncompressed initramfs, `out/initramfs-build.dzggpetk/` | `2a6009800f4c73707b165a3a3142b5baff688c685b7ea9fcd196e9ab6079a4de` |
| Production FAT32 boot partition, `out/boot-build.bzakcn8s/` | `cd242813ded7c860d291c1dbf984bf69b48cc3b58da93f9bdebf83068139abf1` |
| Kernel Image, unchanged from M4 | `78f027ad3124da7b06d4559f49ea7f63c312cd24e653d0f8e29ee2391f520bc4` |
The rootfs contains 111 packages and 1,169 AArch64 ELF files. Its TAR is
407,480,320 bytes. Per-build package/input inventories accompany the image.
The boot FAT is a partition image, not a complete internal NVMe disk.
The final sequential regression run exited zero: `make bootstrap`,
`make smoke-test`, `make tooling-test`, `make check`, `make rootfs-test`,
`make init-test`, `make system-card`, `make boot-volume`, `make boot-test`,
`make console-test`, and `make performance-test`. Logs are
`out/logs/m5-verified-*.log`. The native-init VM is `out/m2-vm.sYk3Hu/`;
the nine-case stage0 matrix is `out/m4-vm.RHUJX5/`. Shared Rust tests passed.
M0 still reports its optional host-PATH QEMU skip; project-local ARM execution
and full-system emulation actually ran. No physical boot benchmark is claimed.
-77
View File
@@ -1,77 +0,0 @@
# M6 cartridge software validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
Verified on 2026-09-21 on the x86_64 Arch build host. These results use actual
AArch64 binaries, kernel USB events, s6 services and filesystem mounts in QEMU
11.1.1. Physical Pi wiring, bay calibration, simultaneous physical devices and
USB electrical behavior remain deferred. Twelve-device virtual stress follows
in M11; this milestone does not claim that test has already run.
## Behavior verified
`make cartridge-test` passed in `out/m6-vm.u4w9b7pl/`:
- The unconfigured production image reports twelve UNCONFIGURED bays. A probe
discovers the virtual controller's actual topology; the fixture then maps its
USB 2/3 companion ports explicitly. No `/dev/sdX` name selects a bay.
- The SYSTEM image boots from virtual USB and is reported PROTECTED. Eject fails.
- A virtual keyboard is recognized by the hardware catalog without a filesystem.
- The ordinary FDS user can query the daemon. A different UID is rejected even
when the test temporarily relaxes socket file permissions. Slow and oversized
clients do not prevent another control request from completing.
- Inserting ENVIRONMENT media triggers a validated read-only mount. Writes fail.
Metadata is inspected privately before the mount becomes publicly accessible.
- A shell inside the mount and an additional bind mount each prevent SAFE.
After those uses end, eject unmounts successfully and reports SAFE. A service
restart preserves that ejected state until removal and reinsertion.
- Reinsertion into another port follows that bay. Wrong manifest classes,
symlink metadata, and multiple named partitions are rejected without choosing
a first candidate. Surprise removal clears inventory; restart remounts valid,
nonejected read-only media.
- Holding cartridged before its readiness notification does not prevent the
ordinary UID-1000 FDS console from accepting a command.
Every VM disk used read-only remained byte-identical. Tests use disposable image
files and virtual USB devices, never host disks or physical USB passthrough.
VM test helpers and the temporary root test console do not ship in SYSTEM.
Shared unit tests also cover USB bus renumbering, mapping aliases and overlapping
mappings, manifest symlinks/FIFOs, malformed contracts, and absent USB subsystems.
The older native-init VM revealed the absent-USB case; its corrected empty-inventory
behavior passed a new unit test and the complete native-init boot afterward.
## Final artifacts and regression evidence
| Artifact | SHA-256 |
| --- | --- |
| Rootfs TAR, `out/rootfs-build.FpSNiJ/` | `00fc851affe0f1c42d289cc05ae376788beae9a6b0dce39c6a1f3b86687d1baf` |
| SYSTEM, `out/system-build.df52fwqj/` | `908645b47bfe207470eeb82436f8a644d34cf3703156a7bdcbf2a3534e9346fe` |
| Production boot FAT, `out/boot-build.bka9vs1y/` | `fb54582a9bd3bb393299002a65a5b0035e806e6733e81a437369a0b152b297e2` |
The rootfs contains 112 packages and 1,170 AArch64 ELF files; its TAR is
408,248,320 bytes. `fds-cartridged` is statically linked, installed by its own
mandatory base package and supervised by native s6. No external Rust dependency
or locked external version changed; the lockfile change only adds the workspace
member. Existing libc, serde, serde_json and toml provide the required interfaces.
The final pipeline exited zero: fresh rootfs and initramfs, tooling tests,
`make check`, rootfs checks, native-init boot, SYSTEM/boot-volume construction,
the nine-case stage0 matrix, console tests and cartridge tests. Logs are
`out/logs/m6-verified-*.log`. M0 bootstrap and smoke-test also passed in
`out/logs/m6-final-bootstrap.log` and `out/logs/m6-final-smoke-test.log`.
The optional M0 host-PATH emulator skip remains explicit; project-local ARM
execution and full-system tests actually ran.
Native-init evidence is in `out/m2-vm.S16Noo/`; stage0 cases are in
`out/m4-vm.XBTVEW/`; ordinary-console traces are in `out/m5-vm.51n612y3/`.
The normal trace measured 1,753.878 ms from the kernel clock to console, versus
1,669.027 ms in the final M5 normal trace: +84.851 ms, below the 100 ms explanation
threshold. The actual ARM comparison tool passed; see
`out/logs/m6-boot-regression.log`. These are individual emulator observations,
not proof of physical Pi latency or a statistically established timing change.
Writable DATA, consumer shutdown and syncfs follow in M7; desktop/profile
activation follows in M8. Read [Cartridge usage](cartridges.md) for exact commands,
configuration syntax, current states and safe-eject boundaries.
-78
View File
@@ -1,78 +0,0 @@
# M7 writable DATA validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
Software acceptance passed on 2026-09-21 using the x86_64 Arch build workstation,
QEMU 11.1.1, the actual AArch64 Pi kernel and static FDS tools. Physical Pi,
removable-media power loss and real shutdown/eject latency remain deferred.
## DATA acceptance workload
`make data-test` passed in `out/m7-vm.drg2a4ml/`:
- Without DATA, the ordinary UID-1000 console can write its temporary home.
Inserting a valid ext4 DATA cartridge activates `/data`; user writes persist
through eject and reinsertion while the temporary home remains separate.
- An unmanaged shell working directory and an extra bind mount each prevent
SAFE. The additional-mount check runs before stopping managed programs.
- A managed writer and its child run as UID/GID 1000 with no effective
capabilities and `no_new_privs`. At least 128 MiB is written while repeatedly
overwriting a 64 MiB file. Both processes intentionally ignore TERM.
- Eject escalates to kernel cgroup termination after its exit deadline, observes
that the complete group has exited, runs syncfs, closes its writeback handle,
unmounts normally and only then reports SAFE. The managed process count is zero.
- SAFE persists across a daemon restart until removal/reinsertion. Reinsertion
reads the previously saved user file, followed by another successful eject.
- Two DATA candidates in one discovery snapshot remain read-only. Explicit
selection activates the requested bay; another request cannot replace an
active session underneath its users.
- Pulling DATA during dirty writes produces actual kernel storage errors. The
daemon stops the remaining managed descendants and clears the vanished mount.
Eject afterward fails; this path never claims SAFE or a clean filesystem.
After the VM releases its disks, the test extracts each safely ejected ext4
partition into an ordinary file. Host e2fsprogs 1.47.4 runs `e2fsck -fn` on both;
both exit zero. `debugfs` extracts the 64 MiB stress payload, whose size and SHA-256
match the complete expected byte pattern. No host filesystem mount is involved.
See `primary-fsck.log`, `secondary-fsck.log`, `filesystem-tools.log`, and
`surprise-removal-kernel.log` in the evidence directory.
During development, removal exposed partially disappearing USB sysfs entries.
Discovery now skips incomplete individual entries, retains a still-present
mounted disk identity, and does not restart the daemon merely because another
USB device is being removed. Fixture checks and the final hotplug workload passed.
Kernel diagnostics sharing the test serial console are preserved in logs and
excluded only from the CLI JSON parser; errors are not treated as successful I/O.
## Final image and regressions
| Artifact | SHA-256 |
| --- | --- |
| Rootfs TAR, `out/rootfs-build.wFuKoS/` | `cdd7c2c8809bf144234470839cc082014f9757276e1d2f769496511c056fe127` |
| SYSTEM, `out/system-build.y7aiuhbi/` | `1675f9225ccca746045f7f999fed5b9b48d7bdd23869aa65a90f97a4219aafab` |
| Production boot FAT, `out/boot-build.abgq4799/` | `fb54582a9bd3bb393299002a65a5b0035e806e6733e81a437369a0b152b297e2` |
The rootfs has 112 packages, 1,170 AArch64 ELF files and a 408,309,760-byte TAR.
M7 adds no external Rust dependency or target package. Its process tracking uses
already-enabled Linux cgroup v2 and PID-limit support. cgroup setup belongs to
the cartridge daemon and adds no console dependency. Test filesystem tools are
the host e2fsprogs tools already used by the M2 workflow.
The final sequential pipeline exited zero: fresh rootfs and initramfs, tooling
checks, formatting/negative checks, rootfs checks, native-init VM, SYSTEM and boot
volume, all nine stage0 cases, ordinary console tests, cartridge tests and DATA
tests. Logs are `out/logs/m7-verified-*.log`. Native-init evidence is
`out/m2-vm.1sPKEy/`; the cartridge regression is `out/m6-vm._cgfbexe/`.
The temporary root test console and writer binary are confined to disposable
fixtures and do not ship in the production rootfs.
The ordinary-console trace in `out/m5-vm.6_55lhge/` measures 1,769.585 ms from the
kernel clock, versus M6's 1,753.878 ms: +15.707 ms. The ARM regression comparison
passes the 100 ms explanation threshold (`out/logs/m7-boot-regression.log`).
These individual VM observations do not establish Pi performance targets.
Attached-media writeback fault injection, broader stress and complete shutdown
ordering remain part of M10/M11. Physical flash-controller cache and power-loss
behavior require hardware. Read [DATA usage](data.md) for the implemented user
workflow and [the ledger](implementation-status.md) for remaining milestones.
-122
View File
@@ -1,122 +0,0 @@
# M8 desktop and network validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Usage guide](desktop.md) · [Implementation ledger](implementation-status.md)
M8 software acceptance passed on 2026-09-21, including corrected CLI and
development images and the complete M0–M7 regression suite. These results concern
actual ARM software under QEMU, not a physical Raspberry Pi or E-Ink display.
## Functional acceptance
`make rootfs PROFILE=development`, `make desktop-test`, `make rootfs-test`, and
`make system-card PROFILE=development` passed. Evidence is retained in
`out/m8-vm.kf5ohhq8/` and `out/logs/m8-verified-*.log`.
The test verified:
- No X server, WindowMaker, or DHCP client runs by default.
- The ordinary FDS user can request and stop the graphical session.
- X11 requires its authority cookie; a client without the cookie is rejected.
- WindowMaker manages a real xterm, and injected X11 keyboard input executes a
command inside that terminal as the ordinary user.
- The screenshot shows the shipped grayscale controls, Terminus font, white
background, and FDS terminal prompt. It is `desktop.png` in the evidence folder.
- No user font cache is generated when the session starts. Image construction
normalizes directory timestamps before generating the packaged cache.
- Stopping the desktop leaves no Xvfb, WindowMaker, or xterm process; Dasung
remains running. Starting a second session succeeds without a reboot.
- Each activation gets its own request/readiness times. The boot report retains
the first desktop-ready event instead of replacing it on restart.
- A recognized ENVIRONMENT cartridge starts the desktop. Safe eject and surprise
removal stop it. Manual deactivation remains effective while media is inserted;
removal/reinsertion permits automatic activation again. Unknown profiles do not
start a session.
- A PROGRAM cartridge initially disallows execution and never autoruns. Explicit
launch runs as UID 1000 with the application/library paths set. A symlink
escaping `app` and a path traversal name are rejected. Eject stops managed
consumers before unmounting and reporting SAFE.
- A virtual USB Ethernet adapter activates DHCP and receives a real virtual
lease. DHCP-provided DNS reaches the volatile resolver file. Explicit network
off stops the client and leaves the managed interfaces administratively down;
restarting networking obtains an address again.
- With Ethernet present but no DHCP server, the console and service controls
remain usable. DHCP readiness is never a lease wait.
The measured first activation was **1048.946380 ms** from request to WindowMaker
readiness in this software-emulated VM. This is a measurement of this run, not a
Pi performance claim. `activation.json` contains the raw CLOCK_BOOTTIME values.
## Retained development artifacts
| Artifact | Location | SHA-256 |
| --- | --- | --- |
| Development rootfs, 499210240 bytes | `out/rootfs-build.eTfJu2/rootfs-aarch64.tar` | `d3ee3b622c2ce9c34783a72a6b40d6a29b624768ac978b6db840347e32ea46c0` |
| Development SYSTEM GPT/EROFS | `out/system-build.p61o8p_o/system.img` | `e7c5c2e716260969eacbab3cd3b0a6bae16005826de1a90f0c325f3f60125855` |
The rootfs audit counted 191 packages and 1402 AArch64 ELF files. Its package
archives, signatures, versions, scripts, ownership, and hashes are retained
beside the rootfs. The graphical test uses a disposable root-console fixture with
Xvfb; the normal image still selects Xorg and the ordinary-user console.
## Regression acceptance
The corrected CLI rootfs contains 180 packages and 1390 AArch64 ELF files.
`make tooling-test`, `make check`, `make rootfs-test`, `make init-test`,
`make boot-test`, `make console-test`, `make cartridge-test`, and `make data-test`
all passed. Bootstrap and smoke-test also passed after the namespace-wrapper
change; their logs are `out/logs/m8-final-bootstrap.log` and
`out/logs/m8-final-smoke.log`.
| Artifact | Location | SHA-256 |
| --- | --- | --- |
| CLI rootfs, 495953920 bytes | `out/rootfs-build.0ayTj2/rootfs-aarch64.tar` | `1b82905789d00ba78f4474ad8613303c1d5684ba7f5f55a30cdde785a35cae06` |
| CLI SYSTEM | `out/system-build.jjgm8xox/system.img` | `be530d8076fffe1ca0de7951952e1bcec763bf6e811d068e40f75961ca3e46e1` |
| FAT32 boot partition | `out/boot-build.newd2icx/boot.fat` | `518deb1b8bee9f013d60c4cd22bea4380357e001db558a74026e2511da2c50a0` |
The boot matrix is in `out/m4-vm.TPiKHf/`, console checks in
`out/m5-vm.f9612amj/`, cartridge checks in `out/m6-vm.cnvl_fxp/`, and sustained
DATA writes plus independent filesystem checks in `out/m7-vm.rko_14_q/`.
The corrected archive and booted guest both verify the terminal-accounting
helper's `root:utmp` ownership, and the guest verifies loopback is up.
The normal kernel-to-console observation was 1854.724813 ms, compared with
1769.584786 ms in `out/m5-vm.6_55lhge/`: **+85.140027 ms**. The actual static ARM
`fds-boottrace compare` passed without a regression exception; output is in
`out/logs/m8-verified-boot-comparison.log`. This single software-emulated comparison
is below the 100 ms review threshold and does not establish Pi performance.
## Test corrections and build findings
The original `xprop -spy` approach could miss readiness when the EWMH atom was
created during startup. The final static helper speaks the small required part
of X11 directly: it installs its event subscription before starting WindowMaker,
then observes the supporting-window property. It adds no external Rust crate or
Xlib dependency to the FDS executable.
Terminus needed its supplied fontconfig acceptance rule enabled. Font caches
also needed the same normalized timestamp as the exported archive; otherwise
image creation invalidated directory timestamps and caused a user cache rebuild.
The final VM test checks the resulting behavior.
QEMU's restricted user network omitted DNS from its DHCP offer. The test now
runs QEMU in a separate network namespace and uses its normal virtual DHCP/DNS
backend there. That namespace has no host or Internet route. QEMU diagnostics
are saved separately from guest serial output, so emulator messages cannot be
mistaken for a CLI JSON response. No workstation network interface, physical
USB device, or physical block device is passed to the guest.
## Deferred acceptance
Pi DRM/VC4 output, virtual-terminal switching on the physical machine, monitor
resolution and E-Ink legibility/refresh quality, physical keyboard/pointer input,
and real CLI-to-desktop latency remain untested. The base Dasung daemon is still
mandatory and independent of optional services. Its physical recovery behavior
remains subject to the [Dasung hardware procedure](dasung.md).
The development profile currently adds X11 diagnostics. The broader development
toolchain, independent recovery, image signing, complete offline input lock, and
release packaging remain part of M12. Full shutdown ordering and broader fault
injection remain M10/M11 work.
-161
View File
@@ -1,161 +0,0 @@
# M9 media-tool validation
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Media-tool usage](media-tools.md) · [Implementation ledger](implementation-status.md)
M9 software acceptance passed on 2026-09-21. Both packaged profiles, ARM image
construction, confirmed virtual USB writes, SYSTEM creation/write/boot, and the
earlier-milestone regression checks passed. All devices in these tests are
disposable regular files exposed only to isolated ARM virtual machines. Physical
Pi, media-controller and display tests remain deferred.
## Verified behavior
`make media-image-test` created SYSTEM, DATA, PROGRAM and ENVIRONMENT images with
the actual static ARM `fds-burn`. Independent `sfdisk`, Python SHA-256,
`fsck.erofs` and `e2fsck` checked the outputs. Invalid GPT extents, extra partitions,
internal partition labels and inconsistent backups were rejected even when the
fixture recomputed valid CRCs. Existing output files and recursive source/output
layouts were refused. Evidence: `out/m9-images.zqojr8hc/`.
`make media-test` verified these cases through the ordinary FDS user's CLI:
- Inspection reports the bay's capacity, logical sector size, model, serial and
kernel insertion identity. The running SYSTEM and mounted media refuse writes.
- Preview and cancellation leave the target unchanged. A confirmation is bound
to its operation, bay, insertion and image hash; a wrong phrase cannot proceed.
- Changing source bytes after preview is detected before writing. Replacing the
device in the same bay invalidates the old confirmation and leaves the new
insertion untouched.
- A PROGRAM image is written, flushed, read back and marked SAFE. Its backup GPT
moves to the end of a larger device. SAFE survives a daemon restart; a new
insertion mounts normally and supports explicit unprivileged execution.
- PROGRAM, DATA and ENVIRONMENT images are created on the running ARM machine.
DATA permits ordinary-user writes, retains data across removal/reinsertion and
passes independent host `e2fsck` after safe eject.
- Cartridge status remains usable while a separate write worker is active.
Source DATA cannot be ejected while the worker holds its image, including
across the worker's private mount namespace.
- Killing the cartridge daemon during an awaiting-confirmation operation leaves
a failed record on restart. It never becomes SAFE or automatically remounts.
- A valid EROFS/GPT containing an approximately 60 KiB malformed manifest is
rejected before writing. Its diagnostic stays bounded and printable, the
daemon retains the same process ID, and subsequent status requests work.
The final virtual USB evidence is `out/m9-vm.cdlj6_r7/`. The crash case uses native s6
supervision controls; see the [upstream s6-svc interface](https://skarnet.org/software/s6/s6-svc.html).
## SYSTEM creation through boot
`tests/integration/m9-system.py` mounts a complete prepared SYSTEM after boot,
then runs the real ARM filesystem/image builder against that read-only tree.
The ordinary user previews and confirms a write to another virtual cartridge.
After device flush and readback verification, a fresh VM boots the written disk
through the actual Pi kernel, stage0 and native s6 to the ordinary FDS prompt.
The test verifies UID 1000, read-only SYSTEM behavior, Dasung, the preserved
`root:utmp` terminal-helper ownership, and packaged Terminus caches without a
new user cache. Final evidence: `out/m9-system.ej8oz_av/`.
This demonstrates creation and boot of a usable SYSTEM on ARM, not just a valid
partition header. It does not emulate Raspberry Pi firmware, physical NVMe/RP1,
USB power behavior or Dasung video output.
## Implementation and dependencies
`fds-burn` is static-musl Rust. `fds-inspect` and `fds-eject` are static aliases of
`fds`; the daemon starts a bounded worker for each media operation. The worker
opens the exact disk with exclusive access, checks live identity again before
writing, validates the image's filesystem and manifest, and verifies actual
readback after flushing and invalidating the device cache. A root-owned record
prevents interrupted operations from acquiring a false SAFE status after restart.
Protection checks cover mounts, swap, kernel holders and the three reserved
internal partition names. Existing primary and backup GPTs are also inspected
directly, so protection does not depend solely on partition uevent timing. Native
4 KiB logical-sector disks are explicitly unsupported by this writer.
The eight new locked Rust dependency entries are explained in the
[media guide](media-tools.md#dependencies-and-current-evidence). Filesystem tools
and GNU coreutils already belong to the base image; their package dependencies
are now declared explicitly. No init replacement, background network service,
new native library dependency of an FDS executable, or boot-time cache builder
was introduced. Void's tracked source files remain unchanged.
## Findings resolved during validation
A private checker device node initially failed to open because `/run` is mounted
with `nodev`. The final code creates an inaccessible private directory on
`devtmpfs`, opens its read-only device inode, and immediately unlinks it. The
filesystem checker receives that handle and runs as UID 1000; global `/dev/loopN`
permissions are unchanged. Image mounts exist only in the worker's private mount
namespace and disappear when it exits.
The namespace review also identified the need to block eject until the worker
exits: unmounting only the daemon's namespace could otherwise report a source
DATA cartridge SAFE while the worker retained its mount. The VM test now covers
that exact case.
Malformed TOML can include most of its source text in an error message. Worker
diagnostics are now limited to 2048 UTF-8 bytes plus a truncation suffix, with
control characters replaced by spaces. This prevents cartridge contents from
overflowing the daemon's status protocol or injecting terminal controls.
The sustained-write regression initially failed because kernel I/O diagnostics
interrupted CLI JSON in the middle of a serial line during deliberate removal.
The test now executes each command once, saves its stdout, and transfers it with
base64 and a SHA-256 checksum. A damaged transfer is retried without rerunning
the command. Kernel output remains in the serial log and a separate diagnostic
capture. The corrected test passed in `out/m7-vm.t5d5fd_4/`, including independent
ext4 checks and the exact 64 MiB write payload.
## Packaged artifacts and regressions
The rebuilt CLI rootfs contains 180 packages and 1393 AArch64 ELF files. All FDS
executables remain static-musl. Package archives, input hashes, ownership and
capability records are retained beside the rootfs.
| Artifact | Location | SHA-256 |
| --- | --- | --- |
| CLI rootfs, 498616320 bytes | `out/rootfs-build.gs9ZTA/rootfs-aarch64.tar` | `4abbc4e4e1823ef78acde2645d915160aa8e1c0f8b3173e8bc73f8d6b923d502` |
| CLI SYSTEM | `out/system-build.a74aryls/system.img` | `b7a71dad45c259c92ec5a5ac72b87356132f642df5c33d74d20e2e6ca564dee6` |
| FAT32 boot partition | `out/boot-build.oapz3fuf/boot.fat` | `ac3ee864c6293dd30a640fbbc0514b08acb92cda2d25d05128d234229308ac8f` |
The earlier-milestone regression run passed tooling, formatting/build checks,
rootfs rejection checks, native init, the stage0 boot matrix, console and
cartridge tests. Its logs are `out/logs/m9-final-*.log`; the corrected DATA run
is `out/logs/m9-data-retry3.log`. The final diagnostic change was then rebuilt
into the CLI archive and passed the complete media write/boot and archive checks
in `out/logs/m9-hardened-*.log`.
Native init evidence is `out/m2-vm.ijzHZC/`, the boot matrix is
`out/m4-vm.sxNb8D/`, console checks are `out/m5-vm.6suv4qjj/`, and cartridge
checks are `out/m6-vm.o5sjhatc/`. Kernel-to-console was 1854.858231 ms in this
VM run, compared with M8's 1854.724813 ms: **+0.133418 ms**. The actual static ARM
comparison passed without an exception; see `out/logs/m9-boot-comparison.log`.
This is below the 100 ms review threshold and is not a Pi performance result.
The development rootfs contains 191 packages and 1405 AArch64 ELF files.
`make rootfs-test`, `make desktop-test` and development SYSTEM construction passed,
followed by the final tooling and formatting/build checks. Evidence is
`out/m8-vm.498wsxsv/` and `out/logs/m9-hardened-*.log`. The screenshot was inspected
and retains the grayscale Terminus terminal. First desktop activation was
1111.300247 ms in this VM; physical display timing remains unmeasured.
| Artifact | Location | SHA-256 |
| --- | --- | --- |
| Development rootfs, 501882880 bytes | `out/rootfs-build.sQCq5V/rootfs-aarch64.tar` | `87e9a35829432f07e4a4bfa4c69eabd5ba0f30a25eadfed05ea9393bd1fb2afa` |
| Development SYSTEM | `out/system-build.l5e3j1pg/system.img` | `bcb8dfa7ec6e3207080f5c68074cc0c27d1bea63a0b51561c8ec628691e03611` |
The generic rootfs convenience links were restored to the CLI build after the
development checks. `out/rootfs-cli.tar` and `out/rootfs-development.tar` retain
explicit pointers to these two verified archives.
## Deferred and remaining acceptance
Physical media throughput, flash-controller write-cache behavior, USB brownouts,
power-loss durability and Pi boot remain deferred. No throughput or hardware
latency claim is made. Broader I/O failure injection and twelve-device stress
belong to M11; ordered power-off and persistent DATA error handling belong to M10;
signing, independent recovery and complete release inputs belong to M12.
File diff suppressed because it is too large Load Diff
-158
View File
@@ -1,158 +0,0 @@
# Cartridge image tools
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Cartridge usage](cartridges.md)
For new software cartridges, use the [Linux workstation workflow](workstation.md):
`fds-cartridge` builds xz bundles and a complete metadata-first `1+m` GPT image,
then previews and verifies a whole-drive write. Software image creation is not
an on-Pi operation. The commands below cover existing target media management
and legacy compatibility.
Historical M9 software acceptance verified image creation for all four classes,
confirmed virtual USB writes, and boot of a newly written SYSTEM. Physical media
and Pi testing remain deferred; see [validation evidence](m9-validation.md).
Images built before M9 do not contain these commands. Build a new image to use
them:
```sh
make rootfs PROFILE=cli
make rootfs-test
make system-card PROFILE=cli
make media-image-test
make media-test
```
Run these build commands on the x86_64 host from the repository root, after the
[first-build prerequisites](getting-started.md). The integration checks use
disposable virtual disks. The `fds` examples below run at the ARM FDS console;
the static ARM executables do not run directly on an x86_64 shell. For an
interactive console, use `make console-vm`; its initial bay map is deliberately
unconfigured, so use the tests for the complete virtual write workflow.
## Images and cartridge writes
An image is a regular file containing a complete GPT and its filesystems. Legacy
classes use one partition; current software media use metadata plus payload
partitions. Creating
one does not touch a disk. Writing one selects a physical bay and requires a
confirmation tied to that insertion, image hash and operation identifier.
Configure the physical bay map first using the [cartridge guide](cartridges.md).
Unconfigured or ambiguous bays are refused. At the FDS console, inspect the target and eject any existing filesystem first:
```sh
fds inspect BAY04
fds eject BAY04
fds burn system /data/fds-system-cli.img BAY04
```
Do not eject a blank disk: it has no mounted filesystem. The running SYSTEM,
internal BOOT/RECOVERY/INTERNAL partitions, mounts, swap and device-mapper holders
are protected. The tools support 512-byte logical sectors. A larger target keeps
the image's filesystem size; GPT backup metadata moves to the actual disk end.
Unused space is not a secure erase of old data.
All cartridge eject requests are refused while a media operation is active. This
keeps a source DATA filesystem attached while the worker holds its image file.
Finish or cancel the operation first, then eject the source normally.
The preview shows the bay, model, serial when available, kernel insertion number,
capacity, image class and SHA-256. Type the complete displayed phrase to proceed.
There is no generic `--yes` switch. A wrong phrase, changed image or different
insertion is rejected. Completion requires a flush, cache invalidation, readback,
and valid final partition tables. A failed or interrupted write never reports SAFE.
For scripts, `fds --json burn CLASS IMAGE BAY04` stops at the prepared preview.
Use its exact `id` and `confirmation` fields with:
```sh
fds burn confirm OPERATION_ID 'EXACT PHRASE FROM PREVIEW'
fds burn status OPERATION_ID
fds burn wait OPERATION_ID
fds burn cancel OPERATION_ID
```
`wait` waits for a terminal result; confirm or cancel from another console if
the operation is still awaiting confirmation. Cartridge
status remains available while the worker hashes or writes. Confirmation expires
after five minutes. Removing/reinserting media invalidates its insertion identity.
A service restart records an unfinished operation as failed, leaving it unmounted.
## Existing DATA and ENVIRONMENT creation
Use a writable working directory with enough free space for both a filesystem
image and its GPT wrapper. `/home/fds` is temporary RAM storage; `/data` is better
for large images, provided the source DATA is not the cartridge being overwritten.
```sh
cd /home/fds
fds format data BAY04 --label FELIS
fds format environment BAY04 --profile windowmaker --label 'WINDOW SYSTEM'
```
Each command first creates an image, then presents the same explicit confirmation.
Labels are the display names shown by FDS; fixed GPT partition names identify
the cartridge class. DATA uses ext4 and normally fills the target's available whole MiB. Use
`--size-mib 512` to choose a smaller filesystem. Its root belongs to the ordinary
FDS user. Create PROGRAM software bundles on the workstation using the linked
workflow; target PROGRAM creation now reports that requirement.
ENVIRONMENT stores only a declarative profile. See [Desktop usage](desktop.md).
`fds burn data BAY04 --label FELIS` is the same creation workflow. `fds-inspect`
and `fds-eject` are static aliases of the corresponding `fds` commands.
## Build a regular image from a prepared directory
Prepare a directory containing a valid `FDS/CARTRIDGE.TOML`, plus the class's
payload. The manifest format is described in [Cartridges](cartridges.md). Then:
```sh
fds-burn create data data-tree data.img --size-mib 128
fds-burn create environment environment-tree environment.img
fds-burn create system prepared-root system.img
fds inspect image system.img
```
The output must be outside the source directory, and existing output files are
never replaced. SYSTEM requires a complete prepared FDS root, including native
init, cartridge management and Dasung; preserve its intended ownership, file
modes, capabilities and prebuilt caches. On the build host, the normal supported
SYSTEM workflow remains `make rootfs PROFILE=cli` followed by `make system-card`.
`fds format system prepared-root BAY04` combines prepared-root construction with
the confirmed write workflow. It does not install packages or change the running
read-only SYSTEM.
SYSTEM construction must be able to read every source file, including protected
account files, while preserving root ownership and capabilities. Run that
construction step from a privileged maintenance session with a correctly
prepared tree. The normal FDS console is UID 1000 and does not gain access to
protected source files merely by invoking `fds-burn`. The ordinary user's
supported workflow is to burn an already-built SYSTEM image. The VM acceptance
test separately exercises privileged SYSTEM creation and ordinary-user confirmed
writing, then boots the result.
## Dependencies and current evidence
The new static Rust crate uses pinned `sha2` 0.10.9 for streaming image hashes.
Its digest/block-buffer/crypto-common/generic-array/typenum dependencies provide
the hashing primitives; cpufeatures selects CPU implementations and version_check
is a build helper. These eight additions are locked in `Cargo.lock`; no glibc,
OpenSSL, async runtime or network client is added to an FDS executable.
[Upstream SHA-2 API](https://docs.rs/sha2/0.10.9/sha2/).
Filesystem construction uses the already-installed glibc `erofs-utils` and
`e2fsprogs`; the retained legacy preparation code uses GNU `cp` from `coreutils`. These are explicit
`fds-cli` package dependencies, and `fds-cartridged` depends on `fds-cli` for its
worker. They add no packages to the existing base selection. The static tool creates and verifies GPT itself. A read-only loop
inside a private mount namespace lets the worker validate the filesystem and
manifest before confirmation. The filesystem checker runs as UID 1000 through a
private, already-open read-only device handle; cartridge code is never executed.
The [M9 validation report](m9-validation.md) records the actual ARM construction,
virtual USB writes, independent GPT/filesystem checks and SYSTEM boot evidence.
Writer unit tests also cover source mutation, readback corruption, cancellation
and overlapping backup relocation on slightly larger targets. Physical flash
behavior and power-loss testing remain deferred.
-154
View File
@@ -1,154 +0,0 @@
# Building and inspecting packages
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Development](development.md) · [Glossary](glossary.md)
FDS uses Void's XBPS package format and xbps-src build machinery for ordinary
Linux software. M0 verifies this route by cross-compiling GNU hello 2.12.3_1 for
**aarch64 glibc** on the x86_64 Arch workstation.
This guide assumes `make bootstrap` and `make smoke-test` have succeeded. All
commands run from the repository root. They inspect a local package; they do not
install it on Arch or create a Pi root filesystem.
## What each tool does
| Tool or file | Role |
| --- | --- |
| `vendor/void-packages/srcpkgs/hello/template` | Pinned upstream recipe: sources, checksums, dependencies, build/install steps |
| `xbps-src` | Creates the build environment, cross-compiles, and packages files |
| `.host/xbps/usr/bin/xbps-query` | Reads package metadata and dependencies |
| `.host/xbps/usr/bin/xbps-rindex` | Creates or updates a package repository index |
| `.host/xbps/usr/bin/xbps-install` | Installs packages into a selected root; bootstrap uses it for the build container |
| `*.aarch64.xbps` | Package archive with metadata and installed file contents |
| `aarch64-repodata` | Index allowing XBPS to find packages in an output directory |
Arch's pacman manages workstation packages. XBPS manages Void/FDS packages. An
XBPS archive cannot be passed to pacman, and an ARM package cannot be executed as
native x86_64 software just because it was built on this workstation.
## Rebuild the verified package
```sh
./tools/build-package hello
```
The helper runs `xbps-src -a aarch64 pkg hello` and exports the main package to:
```text
out/packages/hello-2.12.3_1.aarch64.xbps
```
It reuses upstream's cache and build state; an already built package may not need
recompiling. The helper's log is `out/logs/xbps-hello.log`. The package version in
this guide comes from the current source pin, not a promise about a future pin.
Standalone `tools/build-package` does not index exported output. To refresh the
local index for this package:
```sh
XBPS_ARCH=aarch64 .host/xbps/usr/bin/xbps-rindex \
-a out/packages/hello-2.12.3_1.aarch64.xbps
```
`make smoke-test` already performs that indexing step. Explicit `XBPS_ARCH`
matters: a host tool's default architecture is not the architecture of the
package you want to inspect.
## Inspect the built package
Query only the local exported repository, avoiding a remote prebuilt package:
```sh
XBPS_ARCH=aarch64 .host/xbps/usr/bin/xbps-query \
-i --repository "$PWD/out/packages" -p pkgver hello
XBPS_ARCH=aarch64 .host/xbps/usr/bin/xbps-query \
-i --repository "$PWD/out/packages" -p architecture hello
XBPS_ARCH=aarch64 .host/xbps/usr/bin/xbps-query \
-i --repository "$PWD/out/packages" -x hello
```
The first two commands should print `hello-2.12.3_1` and `aarch64`. The third lists
runtime dependencies recorded by the package. `-i` ignores configured repositories,
so the explicit output directory supplies the lookup here.
List the archive contents and extract just the executable into a new directory:
```sh
tar -tf out/packages/hello-2.12.3_1.aarch64.xbps
extract_dir=$(mktemp -d "$PWD/out/hello-inspect.XXXXXX")
tar -xf out/packages/hello-2.12.3_1.aarch64.xbps \
-C "$extract_dir" ./usr/bin/hello
./tools/verify-elf "$extract_dir/usr/bin/hello" aarch64 glibc
readelf -lW "$extract_dir/usr/bin/hello"
readelf -dW "$extract_dir/usr/bin/hello"
printf 'Extracted program: %s/usr/bin/hello\n' "$extract_dir"
```
The check should print `PASS: aarch64 glibc ELF`. The program headers name
`/lib/ld-linux-aarch64.so.1`, and dynamic entries include `libc.so.6`. This checks
the program inside the archive, not just its filename or metadata. The scratch
folder remains under `out/` for inspection.
Running this dynamic ARM program also needs ARM glibc and its loader. M0 verifies
its package and ELF structure, not hello execution under QEMU. The static Rust
program can be run with the simpler [QEMU example](getting-started.md#7-optionally-execute-the-arm-rust-program).
## Host, build container, and target architectures
```text
Host utility: x86_64 static-musl XBPS runs on Arch
Build container: x86_64 glibc Void supplies native build tools
Target package: aarch64 glibc GNU hello runs on ARM Linux
FDS Rust program: aarch64 static-musl executable runs on ARM Linux
```
The wrapper selects `XBPS_ARCH=x86_64` for build-container operations and
`-a aarch64` for the target cross build. Package indexing/query examples override
`XBPS_ARCH=aarch64`. Using `aarch64-musl` would select the wrong general userspace
ABI for FDS, even though FDS-owned Rust executables use musl internally.
## FDS packages and overlays
| Package directory | Responsibility and status |
| --- | --- |
| `packages/fds-base/` | Implemented: base system package selection and dependencies |
| `packages/fds-base-files/` | Implemented: FDS identity, filesystem layout, and base configuration |
| `packages/fds-init/` | Implemented: native init skeleton, runtime mounts, console and service graph |
| `packages/fds-kernel/` | Implemented: pinned Pi kernel, DTBs, overlays and matching modules |
| `packages/fds-cartridged/` | Cartridge daemon packaging |
| `packages/fds-cli/` | Implemented: verified static-musl FDS CLI |
| `packages/fds-eink/` | E-Ink configuration and related integration |
All nine FDS base packages, including cartridged, E-Ink configuration and the
bounded DHCP client, are built by `make packages` or `make rootfs PROFILE=cli`.
The separate `packages/fds-dasungd/` overlay is active:
use `make dasung` to prepare its verified
static binary and build the package. It is required by `image/base-packages.list`
and belongs to the base boot bundle; see [Dasung](dasung.md).
The build deliberately ignores
directories without a `template`. [Development](development.md#how-future-package-overlays-fit)
explains how active overlays are copied without changing upstream files.
The builder assembles a configured rootfs tar from this explicit set; see [Rootfs](rootfs.md).
M2 adds native init and verifies it with a full [ARM VM boot](init.md).
`make system-card` now packages it as read-only EROFS inside GPT. Runtime updates
replace SYSTEM media; PROGRAM media uses self-contained images instead of
modifying the root. See [Media tools](media-tools.md) for those implemented
workflows; the small package smoke-test only checks the build foundation.
## What the records prove
`out/manifests/artifacts.sha256` records the output hashes from the most recent
successful smoke-test. `void-package-inputs.sha256` records cached XBPS archive
hashes, and `void-build-packages.txt` records packages left installed after the
build. Logs retain additional build dependency information.
The Void source commit, Rust toolchain, and static XBPS archive are pinned.
These ordinary build records alone do not freeze the rolling Void repository.
M12's separate [frozen-input workflow](reproducible-builds.md) captures selected
packages and build dependencies and compares independent offline builds.
See its acceptance status before making a reproducibility claim.
-180
View File
@@ -1,180 +0,0 @@
# Performance targets and measurement
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Boot](boot.md) · [Services](services.md)
**Physical Pi performance remains unmeasured.** The target budgets below are
requirements. M5 now records actual software boot events and provides full-system
ARM VM measurements; these are kept separate from physical targets. The numerical
example in the master plan remains illustrative, not a benchmark.
## Target budgets
| Transition | Target | Current measurement |
| --- | --- | --- |
| Kernel entry → usable CLI | Under 1.5 s desired; under 2.0 s maximum target | Not measured |
| s6 running → usable CLI | Under 400 ms | Not measured |
| Cold power → usable CLI | Under 3 s stretch goal; under 5 s hard goal | Not measured |
| CLI → usable WindowMaker | Under 2 s | Not measured |
| Idle power-off | Under 1 s | Not measured |
| Normal power-off with writable DATA | Under 1.5 s | Not measured |
“Usable” means accepting input, not merely drawing a banner or starting a process.
A quick screen followed by a blocked console does not meet the console target.
Shutdown must finish required data operations; losing writes is not an optimization.
## Timestamp boundaries
| Marker | Event |
| --- | --- |
| T0 | Power applied |
| T1 | Kernel entered |
| T2 | `fds-stage0` entered |
| T3 | Root transition (`switch_root` or equivalent) |
| T4 | s6 running |
| T5 | CLI accepts input |
| T6 | WindowMaker accepts input |
Kernel-to-console is T5 − T1; s6-to-console is T5 − T4; cold boot is T5 − T0.
For desktop activation after the console is already usable, record a separate
activation-request timestamp and measure through T6. Do not include arbitrary
user delay at the prompt as desktop startup time.
The implemented `fds-boottrace` records stage0 entry, SYSTEM discovery, root mount,
root switch, s6 stage-2 entry and first-console readiness. Desktop readiness is
recorded by the M8 desktop session after its startup checks and remains absent
when no desktop has started. Events use Linux
`CLOCK_BOOTTIME`; its origin is the kernel boot clock, not external power-on.
The s6 marker is the start of stage 2, after native PID 1 exists. Console readiness
is recorded immediately before Bash presents its first prompt; the VM test then
sends real commands and verifies their results. These instrumentation boundaries
are explicit approximations to T1/T4/T5, not external electrical/display measurements.
## Collect and compare a report
On FDS, as the ordinary console user:
```sh
fds boot-profile
fds --json boot-profile > ~/boot-profile.json
```
The report lists measured intervals and missing events. A direct-root M2 VM has
no stage0 events; they remain absent rather than becoming zero-duration samples.
The default home is temporary until DATA integration, so export any report you
want to retain before powering off. Host tests save their reports under `out/`.
To compare two saved reports on FDS:
```sh
fds-boottrace compare baseline.json current.json
```
The command requires the same platform and clock and returns nonzero for a
kernel-to-console regression greater than 100 ms. A justified regression can be
recorded with `--explain "Reason for the measured regression"`; keep the explanation
and raw reports with the change. This does not make unlike scenarios comparable.
On the workstation, run the actual ARM tool through the project emulator:
```sh
./tools/in-void qemu-aarch64 "$PWD/out/fds-boottrace" compare \
"$PWD/baseline.json" "$PWD/current.json"
```
`make console-test` boots the ordinary-user console, verifies temporary home and
read-only SYSTEM, collects a trace, and tests the regression threshold. A second
boot holds eudev behind a FIFO gate: the test uses the prompt successfully before
releasing the gate. There is no sleep or artificial readiness delay in the target.
The gate exists only in a disposable test image.
## Physical measurement procedure
Each benchmark record should contain:
1. Hardware identity: Pi revision, firmware/EEPROM version, NVMe and SYSTEM media,
USB hubs/devices, power supply/battery conditions, and display setup.
2. Software identity: FDS revision, kernel configuration/version, image digest,
package/input records, active profile, and relevant boot configuration.
3. Scenario: cold power-on versus reboot, number and classes of inserted devices,
DATA presence, network state, and whether media or caches were already warm.
4. Instrumentation: where each marker was captured, timestamp resolution, clock
source, and how external power-on timing aligns with kernel timestamps.
5. Repeated raw samples, sample count, a typical value and observed range, with
failed or incomplete runs recorded rather than silently discarded.
Use a monotonic time source for intervals within the running OS. T0 occurs before
the kernel can timestamp anything, so cold-power timing needs external capture
or another explicitly justified measurement method. Do not subtract unrelated
clock readings and present the result as a precise interval.
Boot with one SYSTEM, with DATA, and with all 12 bays populated should be separate
scenarios. Missing SYSTEM followed by insertion is also a separate readiness test.
A single favorable run cannot establish behavior under the hardware stress cases.
## Regression and optimization rules
The master plan requires any boot regression greater than 100 ms to be explained
or fixed. Compare like-for-like hardware, images, scenarios, and measurement
boundaries; keep the raw measurements with the change report.
Optimization should remove work from the console's dependency path or make that
work faster. Prepare service databases and caches during image construction.
Start optional networking and desktop services when needed. Handle device readiness
through events and explicit dependencies. Do not add sleeps for a retro effect,
wait for all USB devices before the console, or hide unfinished work behind READY.
Rust's current `opt-level=z` is a starting configuration, not evidence that it is
the fastest choice for future control programs. Real programs and hardware must
guide later comparisons.
## Recorded VM comparison
The final M5 comparison is stored in `out/rust-profiles.x_wgx1kp/`, with nine
serial logs, nine raw reports, executable digests and `comparison.json`.
It uses the real Pi kernel and SYSTEM EROFS, QEMU 11.1.1 TCG, `virt`, `max` CPU,
1 GiB RAM and two virtual CPUs. Only stage0's Rust optimization level changes;
the kernel and SYSTEM image are held constant. Each level has three measured boots.
| Stage0 optimization | Static executable size | Median kernel-clock → console | Median s6 stage 2 → console |
| --- | ---: | ---: | ---: |
| `z` | 474,128 bytes | 1,788.575 ms | 714.436 ms |
| `2` | 517,688 bytes | 1,692.750 ms | 665.673 ms |
| `3` | 533,224 bytes | 1,714.568 ms | 702.154 ms |
These are **emulator measurements on a development workstation**, including its
scheduling noise. Three samples do not establish a hardware winner. Keep `z`
as the current default and repeat on the assembled Pi before selecting an
optimization level for physical boot speed. In particular, these numbers do not
establish either the physical 2-second console target or the 400 ms s6 target.
Reproduce the comparison after building the current SYSTEM and boot inputs:
```sh
make performance-test
```
The helper creates separate build directories and initramfs files for each level;
it does not overwrite the normal tool or initramfs outputs. Read
`out/rust-profiles-latest/comparison.json` for the measured artifact identities.
The uncompressed initramfs remains the default. All four formats boot in the M4
matrix, but compression speed must still be compared on physical NVMe/Pi hardware.
M10 records shutdown events and VM observations in [its validation report](m10-validation.md).
M11 records repeated empty/twelve-device VM comparisons, including the
reviewed greater-than-100 ms samples, in [its validation report](m11-validation.md).
No physical firmware, cold-power, desktop or shutdown timing is claimed here.
M12's [repeated console checkpoint](m12-validation.md#repeated-console-timing-checkpoint)
compares five alternating pairs with fixed kernel/initramfs inputs after the
startup clock floor and release-tool packaging changes. The median increased
29.435310 ms; the initial greater-than-100 ms single-sample difference was
investigated and its raw result retained.
The subsequent [Clap migration checkpoint](clap-validation.md#executable-size-and-startup-measurements)
passed five alternating before/after boot pairs with a −37.276280 ms median
change and repeated the three-sample `z`/`2`/`3` comparison on the updated
stage0. These are image-level VM observations; the default remains `z` and
physical Pi measurements are still deferred.
-135
View File
@@ -1,135 +0,0 @@
# Shutdown and reboot
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [DATA usage](data.md) · [Media tools](media-tools.md)
M10 software acceptance passed; see the [validation report](m10-validation.md).
Physical timing and durability targets remain unmeasured. Rebuild the rootfs to
obtain these commands; images built before M10 do not contain them.
## Normal use
At the ordinary FDS console:
```sh
fds poweroff
fds reboot
```
Use one command for the action you want. `fds-power poweroff` and `fds-power
reboot` are equivalent static aliases. No root password or setuid executable is
needed: the root cartridge daemon authenticates the local FDS user and performs
the controlled operation.
Preparation freezes new cartridge operations, stops the desktop, stops managed
PROGRAM/DATA jobs and their descendants, then stops networking and all DHCP
children. It flushes DATA through the filesystem descriptor retained since
activation, changes the filesystem to read-only, checks writeback again, and
unmounts it normally. Other cartridges are unmounted next. Only after those
steps succeed does native s6 stop remaining services and request kernel halt or
reboot. Dasung remains part of the base service set until that final service stop.
The read-only transition prevents new writes while the error-tracking descriptor
is closed for unmount. An open writer blocks that transition. A working
directory or unexpected additional mount can block unmount. No lazy unmount is
used to turn a busy or faulty cartridge into a successful shutdown.
## If shutdown is blocked
The machine stays running and the command reports the cause. Inspect it with:
```sh
fds power status
fds --json power status
```
The JSON response includes the current phase, requested action, error, whether
native shutdown has begun, and actual `CLOCK_BOOTTIME` timestamps for preparation
steps. It is a software diagnostic, not a physical power-off measurement.
Common cases:
| Reported problem | What to do |
| --- | --- |
| Active media operation | Let an existing write finish, or use `fds burn cancel OPERATION_ID`. A preview awaiting confirmation must be cancelled. New confirmations are refused while shutdown is frozen. Then retry shutdown. |
| Busy DATA filesystem | Close files and programs that were started outside FDS management. Leave `/data` in every shell with `cd /`. Remove any extra mounts you deliberately created. Then retry shutdown. |
| DATA I/O error or interrupted session | Do not interpret this as SAFE. Inspect the read-only data and recover/check the cartridge in a maintenance environment before writable use. Restarting the daemon does not clear this fault. |
| Service transition failure | Inspect the corresponding volatile service logs under `/run/log`, resolve the failure, and retry. |
If native shutdown has **not** begun, you can abandon preparation:
```sh
fds power resume
```
This enables new cartridge operations again. It does not restart stopped jobs.
Start the desktop or network explicitly when needed, using `fds profile activate
windowmaker` and `fds network on`. DATA faults remain quarantined.
If native shutdown has already begun, it cannot be cancelled. Its guard remains
alive while preparation is blocked. Resolve the cause, leave busy directories,
then retry `fds poweroff` to complete preparation; the original native action
continues. This also protects direct administrative use of the native `poweroff`
or `reboot` commands. The final native hook never treats an unsuccessful
preparation command as permission to kill processes and power down.
Flushing real storage may take time. A client timeout is an error, never proof
that a filesystem was unmounted or a request completed. There is no automatic
force-poweroff path after a timeout.
## DATA recovery records
Before enabling writable DATA, FDS creates a root-owned session record under
`/run/fds/data-sessions`. Successful sync and unmount clear it. A daemon crash
can lose the original descriptor's writeback error history; a remaining record
therefore quarantines the same kernel insertion even if a later flush appears
successful. A known I/O fault is also recorded there.
The record survives daemon restarts during this boot. It is tied to the kernel
insertion identity and does not pretend to survive complete power loss or a new
insertion. Filesystem recovery and physical durability still require separate
acceptance. Shutdown preparation itself is recorded under `/run/fds/power`, so
restarting the daemon does not silently unfreeze a blocked shutdown.
## Build and virtual tests
After the [boot-image prerequisites](boot.md), run on the build host:
```sh
make rootfs PROFILE=development
make rootfs-test
make media-image-test
make power-test
make data-test
make desktop-test
```
The development profile supplies Xvfb for the graphical shutdown case. The
tests use private ARM virtual machines and disposable image files, with no
physical disks or USB devices passed through. They exercise native halt/reboot,
busy refusal, explicit resume, active-write protection, daemon crashes, and
independent filesystem/payload checks, plus an attached-device writeback EIO
that must survive daemon restarts. M11 extends media-writer fault injection and
twelve-device stress. Physical Pi timing, power rails, battery and
flash-controller behavior remain deferred.
## Dependencies and native init behavior
No new package or external Rust crate is introduced. `fds-init` explicitly
depends on `fds-cartridged` because native shutdown requires its DATA checks. The CLI now declares the
already-locked `libc` crate directly for its native shutdown guard's identity,
inotify and signal-wait syscalls. DHCP children use the existing cgroup v2
controller and native FDS helpers. `fds-power` is another static copy of `fds`,
with behavior selected by its executable name.
Native s6 still owns shutdown and PID 1. Its configurable final grace period is
set to zero after FDS has verified DATA and the service manager has waited for
managed service exits. Process groups use real exit events and bounded TERM/KILL
deadlines, rather than sleeping before assuming a process has stopped.
References: [s6 shutdown](https://skarnet.org/software/s6-linux-init/s6-linux-init-shutdown.html),
[native init configuration](https://skarnet.org/software/s6-linux-init/s6-linux-init-maker.html),
[Linux remount behavior](https://man7.org/linux/man-pages/man2/mount.2.html), and
[filesystem error reporting](https://man7.org/linux/man-pages/man2/syncfs.2.html).
-210
View File
@@ -1,210 +0,0 @@
# Recovery and rollback
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Boot images](boot.md) · [Media tools](media-tools.md) · [EEPROM](eeprom.md)
Recovery is a separate, read-only FDS image. It contains native s6, Bash, GNU
utilities, XBPS, filesystem tools, the static FDS tools, and the base Dasung
controller. It does not need files or programs from a SYSTEM cartridge.
The software workflow is covered by `make recovery-test`; physical Pi recovery remains deferred.
The [complete internal disk](internal-storage.md) includes this recovery image;
its guide separates verified image construction from deferred physical
installation. The build commands below create ordinary files and do not write a
host disk.
## Build the recovery image
Use the prepared x86_64 build host described in [Your first build](getting-started.md).
From the repository root, run these commands sequentially:
```sh
make recovery
make rootfs-test
make recovery-test
```
The full VM test also needs the Pi kernel/initramfs and a known-good CLI SYSTEM
image. On a fresh checkout, build those first with `make rootfs PROFILE=cli`,
`make rootfs-test`, `make initramfs`, and `make system-card PROFILE=cli`, then
run the recovery sequence above. Run builds and VM tests sequentially because
they share the same project-local Void environment.
`make recovery` builds the `recovery` rootfs profile independently, configures all
packages and caches at image construction time, then creates EROFS. Its outputs
are:
| File | Meaning |
| --- | --- |
| `out/rootfs-recovery.tar` | Complete recovery rootfs, including package database and compiled s6 configuration |
| `out/fds-recovery.img` | Raw EROFS payload for the internal GPT partition named `FDS_RECOVERY` |
| `out/recovery-build.*/manifest.json` | Rootfs hash, filesystem hash, profile and size |
| `out/manifests/rootfs-latest/` | Exact selected package archives and build input records |
The latest-rootfs pointers also select this recovery build. Profile-specific
rootfs pointers preserve earlier CLI/development builds. A recovery EROFS payload
is not a whole-disk image and is not a SYSTEM cartridge. The SYSTEM builder
rejects a recovery rootfs to prevent confusing those roles.
## Enter recovery
Stage0 can enter recovery in two ways:
1. At its missing-SYSTEM or ambiguous-SYSTEM console, type `recovery` and Enter.
2. For a deliberate maintenance boot, use `fds.boot=recovery` in the boot
partition's single-line `cmdline.txt`, replacing `fds.boot=normal` if present.
Restore normal mode when maintenance is complete.
Stage0 requires exactly one readable `FDS_RECOVERY` partition and mounts it
read-only. It does not silently choose between duplicate partitions. The ARM VM
suite supplies disposable virtual partitions; physical firmware/NVMe/display
behavior must still be checked on the Pi.
The local prompt is:
```text
FDS RECOVERY — LOCAL MAINTENANCE CONSOLE
RECOVERY#
```
This is an explicit **root maintenance console**. It reads startup files from the
immutable recovery image and uses a temporary home under `/run/fds`. The root
password remains locked; no SSH or network login service is started. Normal CLI
and development images continue to use the ordinary `fds` account and `FDS>`.
Recovery inspects cartridges read-only. It does not automatically mount DATA
writable, activate an ENVIRONMENT desktop, or enable Ethernet. Those actions
require explicit commands. The Dasung controller remains in the base boot bundle.
## Inspect a failed system
```sh
fds info
fds bays
fds bay 2
fds inspect BAY02
fds topology
fds boot-profile
lsusb -t
lsblk -o NAME,MAJ:MIN,MODEL,SERIAL,SIZE,RO,TYPE,FSTYPE,PARTLABEL,MOUNTPOINTS
dmesg
```
Use the calibrated bay number and the displayed model/serial to identify a
cartridge. Do not infer a bay from `/dev/sda` or from discovery order. A valid
inactive SYSTEM mounts read-only under `/run/fds/media/NN`; `fds bay N` reports
the actual path. A bad filesystem or manifest produces an error instead of
running anything from the cartridge.
An empty bay configuration produces `UNCONFIGURED`, not guessed bay numbers.
See [Cartridges](cartridges.md) for calibration. Permanent machine configuration
on `FDS_INTERNAL` is part of the remaining M12 work.
## Check and repair DATA
Start with a read-only check. For example, for DATA in bay 2:
```sh
fds recovery check BAY02
```
This command is available only to root in the recovery image. It identifies one
USB disk and one `FDS_DATA` partition, unmounts its read-only view, reserves the
disk exclusively, verifies GPT and the kernel partition identity, and invokes
`e2fsck -f -n`. It does not repair the filesystem. A clean result leaves DATA
unmounted and reports `SAFE TO REMOVE`.
If DATA was explicitly activated writable, eject that session first. Close any
shell whose current directory is on DATA and any other reader before checking;
an ordinary busy-unmount failure is reported, never bypassed with lazy unmount.
If the check reports problems, review its log and preserve a backup where
possible. Preview the repair with:
```sh
fds recovery repair BAY02
```
The preview displays the model, serial, capacity and a confirmation command.
Copy that exact command only after checking the intended cartridge. Its token
binds the bay, kernel insertion number and current boot. A token from another
insertion or boot is rejected. The preview makes no filesystem changes.
Confirmed repair runs `e2fsck -f -p`, which performs conservative automatic
repairs and stops when manual judgement is required. It then flushes the device,
invalidates its block cache, and runs a second `e2fsck -f -n`. Only a successful
verification produces `DATA REPAIRED AND VERIFIED` and `SAFE TO REMOVE`.
The checker uses a temporary kernel loop device backed by the already verified
partition descriptor. This lets `e2fsck` take its own exclusive device claim
while FDS retains the physical whole-disk reservation. The loop is removed
automatically when its last descriptor closes. This uses the existing kernel
loop driver, `libc` crate and base `e2fsprogs`; no new package or Rust dependency
is introduced.
Failed or interrupted checks/repairs retain a quarantine record across cartridge
daemon restarts for the same insertion. They do not inherit an earlier SAFE
status. The checker holds the disk reservation and is killed if its supervising
daemon dies. The command runs synchronously: bay operations and orderly shutdown
wait for it to finish; the local shell remains usable. A client timeout is not
success and does not authorize removing media. Inspect the log and wait for the
service to finish before retrying. Logs are in `/run/fds/recovery/` and are
limited to 16 MiB per invocation.
This narrow recovery command requires a readable GPT and primary ext filesystem
signature. It deliberately does not guess partition boundaries, recreate a
broken GPT, or force answers to destructive `e2fsck` questions. Such cases require
an offline backup and expert use of the included filesystem tools. It cannot
recover data that was never written to storage.
For structured results, prefix the command with `fds --json`. After a successful
check or repair, remove/reinsert DATA to inspect it again. Recovery still keeps
it read-only until `fds data use N` is explicitly requested.
## Prepare replacement SYSTEM media
A known-good SYSTEM image must be supplied on separate source media. Do not use
a damaged image as an update source. For example, source DATA in bay 2 and an
unmounted replacement cartridge in bay 4:
```sh
fds bay 2
fds inspect image /run/fds/media/02/fds-system-cli.img
fds inspect BAY04
fds burn system /run/fds/media/02/fds-system-cli.img BAY04
```
Use the actual source mount shown by `fds bay 2`. If the destination already
contains a mounted cartridge, run `fds eject 4` first. The burn preview identifies
the source checksum, destination capacity/model/serial and confirmation command.
Follow [Media creation and writing](media-tools.md) for confirmation and status commands.
Completion requires device flush, readback and GPT verification. Recovery uses
the same protected writer as the main system: it refuses mounted destinations,
active root storage and disks containing internal FDS partition names.
Keep the previous known-good SYSTEM cartridge. After a successful replacement
write, use `fds poweroff`, swap SYSTEM while powered off, and boot normally.
Rollback means restoring the previous SYSTEM cartridge; it does not undo DATA
file changes, application migrations, internal boot updates or EEPROM changes.
## Capture diagnostics and shut down
```sh
bash /usr/share/fds/capture-hardware /tmp/recovery-capture
cat /tmp/recovery-capture/status.tsv
fds power status
fds poweroff
```
The collector records identities, USB topology, mounts, kernel messages, packages
and boot events with checksums. Its output is temporary unless copied to a
healthy DATA cartridge after explicitly activating that DATA with `fds data use N`.
Do not activate the damaged cartridge merely to save a report. Eject writable
DATA after copying, or let `fds poweroff` perform the normal verified shutdown.
See [Shutdown](power.md) when shutdown reports a blocking DATA error.
Recovery is independent of SYSTEM, but it still depends on working internal
boot storage, the kernel and firmware. An external rescue medium is required
when that layer fails. EEPROM preparation and rollback are described separately
in [EEPROM configuration](eeprom.md); no EEPROM is changed by these recovery tools.
-170
View File
@@ -1,170 +0,0 @@
# Release signatures and verification
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [M12 evidence](m12-validation.md)
`fds-release` signs an artifact manifest and verifies its signature and every
listed file. The static ARM executable is included in the base CLI package, so
CLI, development and recovery images can all verify downloads. The workstation
build supplies an x86_64 signer with the same format.
The signing tool has passed host, static ARM and independent OpenSSL acceptance.
The complete frozen-input, versioned local 0.1.0 release passed acceptance;
see [M12 validation](m12-validation.md#final-local-release-acceptance) for its identity
and [Offline rebuilds](reproducible-builds.md) for the workflow.
The examples below describe the working signing interface; they do not identify
a published or hardware-qualified FDS release.
## Build and test the tools
After [bootstrap](getting-started.md), run:
```sh
make signing
make signing-test
```
The workstation executable is
`target/x86_64-unknown-linux-gnu/release/fds-release`. The ARM executable is
`target/aarch64-unknown-linux-musl/release/fds-release`; `make tooling` also
exports it to `out/fds-release`. An ARM file cannot execute directly on the
x86_64 workstation. `make signing-test` runs that file through the project-local
QEMU interpreter as well as testing the host executable.
OpenSSL is a **host test dependency** used as an independent Ed25519
implementation. It is not linked into the FDS verifier. The test creates private
disposable keys under its `out/m12-signing.*` directory, checks exact signatures,
then exercises wrong keys, altered manifests, damaged or missing artifacts,
symlinks, FIFOs, malformed fields and unsafe paths. Test keys are not release
keys.
## Create and retain a signing key
For a local signing identity, choose an existing private directory outside the
checkout and its build output. This example creates a new directory; choose a
different name if it already exists:
```sh
mkdir -m 700 "$HOME/fds-signing"
target/x86_64-unknown-linux-gnu/release/fds-release keygen \
"$HOME/fds-signing/release-01"
```
The command creates `release-01.key` (32 secret bytes, mode 0600) and
`release-01.pub` (the hexadecimal public key). Existing files are never
overwritten. It prints SHA-256 of the decoded 32-byte public key, never the
secret. This fingerprint differs from `sha256sum release-01.pub`, which hashes
the hexadecimal text and its newline. Retain the private key separately from release downloads and source
archives. Anyone who has that key can sign a release under that identity.
If public-key export failed after private-key creation, retain the private file
and export it to a new path:
```sh
target/x86_64-unknown-linux-gnu/release/fds-release public-key \
"$HOME/fds-signing/release-01.key" "$HOME/fds-signing/recovered.pub"
```
Private keys must be regular files owned by the invoking user with no group or
other permissions. The tool rejects symlinks and invalid key lengths. Key
generation uses Linux `getrandom`; temporary seed storage and the signing key
are zeroized when dropped.
## Sign an assembled directory
An assembled release directory must already contain its artifacts and a
`manifest.json` matching the format below. Signing verifies every artifact's
length and hash before creating `manifest.sig`:
```sh
target/x86_64-unknown-linux-gnu/release/fds-release sign \
/path/to/release-directory --key "$HOME/fds-signing/release-01.key"
```
If a signature already exists, use a new release directory. The tool deliberately
does not replace signatures or silently rewrite the manifest. A failed operation
may leave its new output file for inspection; it never declares it verified.
## Verify before using images
Obtain the signer's public key through a channel you already trust, or compare
its fingerprint against a separately authenticated value. A public key included
beside an untrusted download does not establish its identity by itself.
On FDS, including recovery:
```sh
fds-release verify /data/downloads/fds-release --key /data/keys/fds-release.pub
```
On the workstation, use the host executable instead:
```sh
target/x86_64-unknown-linux-gnu/release/fds-release verify \
/path/to/release-directory --key /path/to/trusted-fds-release.pub
```
A successful command reports `VERIFIED FDS/OS 0.1.0`, the number of checked
artifacts, and `Hardware validation: deferred`. Failure returns exit status 2
and an explanation. Keep downloaded images unchanged between verification and
use. Verification covers only files listed in the manifest; unrelated extra
files are not endorsed. The [media writer](media-tools.md) performs its own
image/layout validation and readback checks when writing a cartridge.
These are distribution signatures. The Pi firmware and stage0 currently do not
enforce them at boot, so this is not a secure-boot implementation or a claim
that the hardware has been tested.
## Manifest and signature format
The JSON document has exactly these fields:
```json
{
"format": 1,
"version": "0.1.0",
"source_epoch": 1789909701,
"source_sha256": "<64 lowercase hexadecimal characters>",
"void_commit": "02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1",
"hardware_validation": "deferred",
"files": [
{
"name": "fds-system-cli-0.1.0.img",
"bytes": 123456,
"sha256": "<64 lowercase hexadecimal characters>"
}
]
}
```
This is a schema illustration, not a usable manifest: the size and hash
placeholders must be replaced with values from the actual artifact. Format 1
accepts the current tool version, a positive source epoch, a 40-character Void
commit, and 1–64 nonempty artifacts. Filenames must be flat safe ASCII names;
paths, duplicates, hidden names, reserved manifest filenames and unknown fields
are rejected. The manifest is bounded to 1 MiB. Files must be regular files,
opened without following symlinks; hashing streams their bytes and checks for
changes during the read.
The signature is Ed25519 over the exact bytes
`FDS/OS release manifest v1` followed by a NUL byte and the raw `manifest.json`
bytes. JSON whitespace is therefore authenticated too. `manifest.sig` contains
128 lowercase hexadecimal characters and a newline. The public-key file contains
64 lowercase hexadecimal characters and a newline. Verification rejects weak
public keys and uses strict signature verification.
The implementation uses locked `ed25519-dalek` 3.0.0 for Ed25519,
`zeroize` 1.9.0 for secret buffers, and the existing SHA-256/JSON crates.
Its locked transitive dependencies provide curve arithmetic, digest and
signature types. Default dalek features are disabled; no asynchronous runtime,
OpenSSL binding or shared target library is added. The dependencies and their
license metadata are recorded in `Cargo.lock` and the cached crate sources.
The build collects their license texts, including build dependencies and
vendored libusb notices, into `/usr/share/licenses/fds-cli/RUST-NOTICES.txt`.
The usage guide is installed at `/usr/share/doc/fds/releases.md` in every profile.
Algorithm interoperability is checked against the
[RFC 8032 test vector](https://www.rfc-editor.org/rfc/rfc8032.html#section-7.1)
and OpenSSL; strict verification follows the
[dalek API](https://docs.rs/ed25519-dalek/3.0.0/ed25519_dalek/struct.VerifyingKey.html).
-183
View File
@@ -1,183 +0,0 @@
# Frozen inputs and offline rebuilds
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Build host](build-host.md) · [Release signatures](releases.md)
M12 adds a local input snapshot, fresh restore, network-isolated build, and
complete artifact comparison. **Two fresh network-isolated builds passed with
all 27 artifacts byte-identical.** The [M12 evidence page](m12-validation.md#final-local-release-acceptance)
records the source/input identities, signed local release and actual archive
round-trip. This result applies to the recorded inputs and documented build
host; it does not claim equivalence across arbitrary host environments.
Normal builds still use Void's signed rolling repositories. A frozen build uses
the selected package files and build environment from one explicit snapshot.
It does not silently consult a newer repository if an input is missing.
## What is frozen
`tools/frozen-inputs create NEW_DIRECTORY` records:
- Project-owned source, documentation, configuration and package overlays, with
a content identity independent of an FDS Git commit. This works before the
repository's first commit too; it does not invent a commit identifier.
- The exact pinned Void checkout in a Git bundle, preserving upstream files and
the shallow-history boundary needed to read its commit timestamp after restore.
- Cargo's locked vendored sources, the selected Rust toolchain and its static
ARM target. Cargo credentials and user configuration are not copied.
- The project-local Void binary build environment, static XBPS host tools and
image-tool prefix. Temporary build trees, personal home directories and
mount points are excluded.
- Cached build dependencies, exact third-party packages selected by each of
the three rootfs profiles, and kernel/DHCP/hello source archives. Local FDS
packages are rebuilt from source instead of seeding the output repository.
- Pinned Pi firmware, the VM kernel fixture, and the official EEPROM preview
inputs. The preview is not a backup of a physical Pi.
`lock.json` records paths, kinds, modes, lengths, hashes and symlink targets.
Added, missing or changed entries fail verification. The snapshot is several
gigabytes. The completed offline diagnostic build occupied about 24 GB and its
snapshot occupied 6.9 GB. Two restored builds need separate space; retained VM
test images add further usage. Check available space before starting and keep
headroom beyond these observations, which are not upper bounds. The workstation still supplies the documented
Arch/Linux prerequisites, including Python, bubblewrap, Git, Make and compression
tools. Their environment is recorded; this is not a replacement for a host OS.
## Prepare the snapshot
Complete the [first-build setup](getting-started.md), then run the complete
artifact build from the repository root:
```sh
make all
```
`make all` runs bootstrap and package construction, builds both normal SYSTEM
profiles and independent recovery, then creates the four initramfs formats,
production Pi boot volume, complete internal disk, signing tools and production
EEPROM preview. It does not write physical media or change EEPROM settings.
Use `make packages` to build only the FDS base packages. Builders perform their
normal format, ABI and rootfs checks; the complete runtime acceptance suite
remains separate.
Runtime test targets select their required profile explicitly: init, boot,
console, cartridge, DATA, media and stress checks use CLI; desktop and power
checks use development; recovery checks use recovery. They no longer depend on
which rootfs happened to be built last. `make rootfs-test` intentionally checks
the most recently exported archive, so use it immediately after a profile build
when validating that specific export.
Fetch all locked crates, including platform-specific sources that Cargo's vendor
operation requires even when they are not built for ARM:
```sh
cargo fetch --locked
./tools/frozen-inputs create "$PWD/out/inputs-0.1.0"
./tools/frozen-inputs verify "$PWD/out/inputs-0.1.0"
```
Use a new output directory. The command will not overwrite a previous snapshot.
The snapshot operation itself is offline; missing selected packages or locked
crate sources fail with an error. The subsequent fresh build establishes whether
the complete dependency set was captured. Keep failed outputs for diagnosis and
retry into a new directory.
Run every build, snapshot and VM workflow sequentially: the normal checkout
shares one Void masterdir. Snapshot creation locks rootfs/base-package assembly,
but a separate standalone kernel or VM command must not be started concurrently.
## Restore into two independent trees
If starting from the signed release's input archive, extract it as the ordinary
build user with `tar --extract --zstd --same-permissions --no-same-owner` and
the desired `--file` and new `--directory` paths. Mode preservation matters:
default extraction can mask writable bits or remove sticky bits and cause the
input lock to reject an otherwise intact archive. Verify the extracted snapshot
before restoring it. The release's README provides the complete commands.
The destination parent directory must already exist, and the destination itself
must not exist. Use paths without whitespace, as required by xbps-src:
```sh
./tools/frozen-inputs restore "$PWD/out/inputs-0.1.0" "$PWD/out/rebuild-a"
./tools/frozen-inputs restore "$PWD/out/inputs-0.1.0" "$PWD/out/rebuild-b"
```
Restoration verifies every locked entry first. Each new tree gets its own Void
masterdir, source/download caches, Cargo directory and Rustup directory. It starts
without an FDS target directory or generated FDS packages/images.
Run the builds sequentially and retain their full logs:
```sh
out/rebuild-a/tools/in-frozen-build make all >out/rebuild-a.log 2>&1
out/rebuild-b/tools/in-frozen-build make all >out/rebuild-b.log 2>&1
./tools/compare-builds out/rebuild-a out/rebuild-b \
--report out/reproducibility-0.1.0.json
```
The wrapper changes to the restored project directory, uses its private Rust
caches, makes the rest of the workstation read-only, and removes network access
for the build and all child processes. Missing input therefore cannot be hidden
by a download. FDS's xbps-src wrapper keeps dependency resolution enabled and
excludes remote repositories; the custom repository filename includes `local`
so the pinned xbps-src also retains it in its cross sysroot. Rootfs assembly uses the frozen per-profile
repository. Setting `FDS_OFFLINE=1` alone selects offline inputs but does not
provide the wrapper's network isolation.
The comparison checks both rootfs tars, recovery rootfs, all installable images,
all four initramfs formats, Pi kernel/DTB, every FDS package and the EEPROM
configuration/rollback outputs. It reports each mismatch and exits nonzero if
anything differs. Matching files alone do not attest how they were built; retain
the isolated build logs alongside the report.
## Assemble a local signed release
After both complete builds match, create or choose a signing key as described in
[Release signatures](releases.md). Use the assembler in the first restored tree:
```sh
out/rebuild-a/tools/assemble-release \
--inputs "$PWD/out/inputs-0.1.0" \
--comparison-build "$PWD/out/rebuild-b" \
--output-directory "$PWD/out/fds-os-0.1.0" \
--key "$HOME/fds-signing/release-01.key"
```
The assembler verifies the snapshot again, checks that the first checkout still
matches its source identity, compares the complete pair, and verifies recorded
rootfs build inputs. It creates versioned image names, source and complete input
archives, package inventories, comparison evidence and usage guides. It signs
the manifest and immediately verifies every listed file. The private key is
never copied into the release, and existing output directories are refused.
The result is a **local** signed release with physical validation marked
`deferred`; it is not published or installed automatically. The supplied key
defines its signing identity. Distribute its fingerprint through an independently
trusted channel before someone relies on the bundled public key.
## Determinism and troubleshooting
FDS uses the pinned Void commit's timestamp for package and image timestamps.
Owned Rust binaries are stripped. `make` uses `tools/cargo-build` to map checkout,
Rustup and Cargo registry source paths to stable `/usr/src/fds` paths; otherwise
panic messages can embed the builder's absolute directory even in stripped
binaries. The helper preserves the target's static linking flags, and its flags
participate in Cargo's build cache. Direct `cargo build` remains useful for local
development, but release construction uses the helper. This uses Rust's
[source-path remapping](https://doc.rust-lang.org/rustc/command-line-arguments.html#--remap-path-prefix-remap-source-names-in-output)
and Cargo's [merged configuration arrays](https://doc.rust-lang.org/cargo/reference/config.html).
The kernel has explicit build user/host/time,
and filesystem UUIDs derive from content/configuration identities. Rootfs export
restores target ownership and capabilities instead of preserving the builder's
UID. It removes only the build-specific auxiliary linker cache and fetched XBPS
repository indexes; the installed package database, keys and runtime linker
cache remain available.
If verification reports changed snapshot entries, restore the original snapshot
or create a new one from the intended inputs. Do not edit `lock.json` to hide a
mismatch. If the two builds differ, use the comparison's named artifacts and
each build's rootfs/package manifests to identify the first differing stage.
Archive compression is not a substitute for comparing the uncompressed images.
Physical performance and durability still require the actual Pi and storage.
-80
View File
@@ -1,80 +0,0 @@
# Roadmap and current status
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Master plan](master-plan.md) · [M0 evidence](m0-validation.md)
**M0–M12 software acceptance is complete. Physical Pi testing is deferred.**
The user has additionally authorized [Dasung base integration](dasung.md): an
imported daemon, ARM package, mandatory base selection, and s6 service sources.
M2 starts it under native s6 in a booted ARM virtual machine.
This is a sequence of acceptance milestones, not a release-date schedule.
The user authorized continued implementation through M12, deferring physical Pi
testing. Work proceeds in order with software/VM evidence recorded in the
[implementation ledger](implementation-status.md).
Current development additionally implements [workstation software cartridges
and QEMU hotplug tooling](workstation.md). Its [acceptance record](workstation-validation.md)
is separate from the frozen 0.1.0 artifacts below.
## Milestones and acceptance boundaries
| Milestone | Status | Deliverable and acceptance |
| --- | --- | --- |
| **M0 — Build host** | **Verified** | On x86_64 Arch, bootstrap the pinned Void build environment, cross-build an aarch64 glibc package, and build/inspect a static-musl Rust executable. See the recorded skips below. |
| **M1 — Base rootfs** | **Implemented** | `fds-base`, `fds-base-files`, and `out/rootfs-aarch64.tar` with glibc, GNU tools, XBPS and Dasung; no BusyBox, systemd, runit or musl runtime package. [Usage](rootfs.md) · [Evidence](m1-validation.md). |
| **M2 — Native s6** | **Verified in ARM VM** | Native s6 PID 1, read-only root, eudev/getty/Dasung supervision, test-service control, and orderly power-off. [Usage](init.md) · [Evidence](m2-validation.md). |
| M3 — FDS tooling foundations | Verified | Add shared Rust code and CLI/stage0 skeletons; verify static-musl AArch64 artifacts without a glibc runtime dependency. |
| M4 — Pi boot | Software verified; hardware deferred | Kernel/initramfs/SYSTEM/boot-volume builds and nine VM boot cases passed. The ordinary-user prompt follows in M5; physical NVMe/RP1/display acceptance is deferred. [Evidence](m4-validation.md). |
| M5 — Fast console | Software verified; physical timing deferred | Ordinary-user console, boot tracing and regression checks passed. [Evidence](m5-validation.md). Physical performance targets remain unmeasured. |
| M6 — Cartridge daemon | Software verified; physical calibration deferred | Handle events, configured 12-bay topology, metadata, mounting, and eject; virtual hotplug checks passed. Twelve-device stress follows in M11 and physical calibration remains deferred. |
| M7 — DATA integration | Software verified; physical media behavior deferred | Integrate data/home mounts and safe eject; leave the filesystem clean after sustained writes and ejection. |
| M8 — WindowMaker | Software verified; physical display deferred | Xorg/WindowMaker, grayscale theme, ENVIRONMENT activation, managed PROGRAMs and Ethernet passed virtual display/network tests. [Usage](desktop.md) · [Evidence](m8-validation.md). |
| M9 — Media tools | Software verified; physical media behavior deferred | ARM image creation, protected and confirmed writes, readback verification, and boot of a newly written SYSTEM passed. [Usage](media-tools.md) · [Evidence](m9-validation.md). |
| M10 — Shutdown | Software verified; physical timing/durability deferred | Ordered native halt/reboot, busy refusal, persistent DATA faults and independent filesystem checks passed. [Usage](power.md) · [Evidence](m10-validation.md). |
| M11 — Hardware stress | Software verified; physical tests deferred | Twelve-device cold/hotplug and I/O stress, native reboot/halt, readiness independence and six media failure cases passed. [Usage](stress-testing.md) · [Evidence](m11-validation.md). |
| M12 — Production hardening | Software verified; hardware deferred | Signed local FDS/OS 0.1.0, exact frozen inputs, two matching offline builds, recovery/internal settings, documentation and reversible EEPROM previews. [Final evidence](m12-validation.md#final-local-release-acceptance). |
Detailed requirements remain in sections 53–65 of the [master plan](master-plan.md).
The ledger distinguishes implemented software, remaining work, and tests that
still require physical hardware.
## What M0 completion means
The initial validation successfully ran `make bootstrap`, `make smoke-test`, and
`make check`. It produced and inspected an actual ARM glibc GNU hello package and
an actual static-musl ARM Rust executable. It also checked failure cases for ELF
validation, source pin drift, modified upstream files, and overlay conflicts.
It did **not** run the ARM program in QEMU, boot a Pi, test a physical bay map, or
measure boot/shutdown performance. QEMU was absent and was explicitly reported
as an optional skip. The later snapshot and offline-build work belongs to M12.
See [M0 validation](m0-validation.md) for the exact environment, outputs, and hashes.
## From native init to cartridge boot
M1 changes the output from isolated test artifacts to a base operating-system
filesystem. Its first result is a rootfs archive with an explicit package set
and exclusions. That archive alone will not establish native init or Pi boot;
those are M2 and M4 acceptance boundaries.
M2 now boots that filesystem with a generic ARM kernel and exercises native
s6 supervision. `make init-test` automates the acceptance test; `make vm` opens
a temporary development shell. Neither supplies a Pi boot image or measures
physical boot performance. M3 adds verified FDS Rust foundations;
[tooling usage](tooling.md) explains its commands. M4's [boot guide](boot.md)
covers the Pi kernel, initramfs, SYSTEM image, boot volume, and full stage0 tests.
## Find the current command interface
The master plan includes a design directory sketch as well as runtime commands.
The checkout now implements the complete build interface, cartridge inspection
and media management, and native power-off/reboot. A few script locations and
configuration formats differ from the original sketch; the
[implementation ledger](implementation-status.md#interface-and-policy-decisions)
maps those entries to the actual interfaces. Use [Development](development.md)
for targeted rebuilds and the project overview for the complete build/VM path.
Use `make help` to inspect the current build entry points. Follow
[Your first build](getting-started.md) for the complete usable workflow today.
-240
View File
@@ -1,240 +0,0 @@
# Building and using the ARM root filesystem
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [First build](getting-started.md) · [Validation](m1-validation.md)
The assembler produces a configured **aarch64 glibc Linux userspace** at
`out/rootfs-aarch64.tar`. It contains GNU tools, XBPS, the s6 tools, the cartridge manager, optional desktop stack, and the Dasung
Paperlike 13K controller. You can inspect it and run its ARM programs on the build
workstation. M2 adds native s6 init, runtime mounts and getty. The tar itself has
no bootable partition table or Pi boot volume; do not flash the tar to a disk.
Follow [Native init and ARM VM](init.md) to boot it and open a development shell.
M12 also adds `make rootfs PROFILE=recovery` and `make recovery`; see
[Recovery](recovery.md) for its distinct local maintenance console and read-only
cartridge defaults. Each build preserves a profile-specific
`out/rootfs-PROFILE.tar` pointer in addition to the latest-rootfs pointer.
## 1. Prepare the workstation
Use the x86_64 Arch setup in [Your first build](getting-started.md). M0 should pass
before you build M1:
```sh
make bootstrap
make smoke-test
make check
```
M1 also needs **Python 3.14 or newer** on Arch. It inspects XBPS metadata and zstd
package archives and produces the tar using only the standard library:
```sh
sudo pacman -S --needed python
python3 --version
```
The rootfs builder installs `qemu-user-aarch64` and `util-linux` into the existing
project-local Void build container. Static QEMU executes ARM programs; util-linux
supplies namespace-local mount and capability setup. Linux 6.7+ provides a private
`binfmt_misc` registry inside the new user namespace, allowing ARM child programs
to execute without PRoot's syscall translation. Mount/setup capabilities are
dropped before package scripts run; only namespace-local file-capability support
remains. These are build tools, not additional target requirements. No host-wide
binfmt registration, sudo chroot, or host boot configuration is needed.
The Dasung musl compiler dependencies remain documented
in [Dasung integration](dasung.md#dependencies-and-source-provenance).
Allow several additional GB for package downloads, staging trees, the archive,
and retained inputs. Build commands share one Void container; run them sequentially.
## 2. Build the CLI base
From the checkout root:
```sh
make rootfs PROFILE=cli
```
The first run downloads signed Void ARM packages and may take several minutes.
Later runs reuse the download cache, rebuild the local FDS packages, and assemble
a fresh root. Success ends with:
```text
PASS: FDS rootfs built and verified: out/rootfs-aarch64.tar
```
`cli` boots to the ordinary-user FDS console and includes optional desktop and
Ethernet services. They start only when requested by the user or eligible media.
`PROFILE=development` adds native C/C++/Rust compilers, build systems, Git,
debuggers and Vim, plus virtual display diagnostics; see
[Development](development.md) and the [desktop guide](desktop.md).
Each archive embeds its profile identity. Unknown
profiles and mismatched SYSTEM image labels fail explicitly.
| Output | Purpose |
| --- | --- |
| `out/rootfs-aarch64.tar` | Exported root filesystem, with target ownership, permissions and file capabilities |
| `out/rootfs-aarch64/` | Staging tree for inspection and emulated commands; owned by your host user |
| `out/manifests/rootfs-latest/` | Exact package list, checksums, metadata, scripts and build inputs |
| `out/manifests/rootfs-latest/packages/` | Copies of every selected XBPS package and available upstream signatures |
| `out/packages/fds-base-0.1.0_1.aarch64.xbps` | Explicit base dependency metapackage |
| `out/packages/fds-base-files-0.1.0_1.aarch64.xbps` | FDS identity, accounts, defaults and layout specification |
| `out/logs/rootfs-build.log` | Package installation, configuration and acceptance output |
The first three paths are links into one retained `out/rootfs-build.*` directory.
A failed build keeps its workspace for diagnosis and does not publish a partial
archive over the last successful one. Copy the tar itself when moving the result
to another machine; copying only the link does not copy the artifact.
## 3. Inspect the result
```sh
tar -tf out/rootfs-aarch64.tar | head -40
cat out/rootfs-aarch64/etc/os-release
cat out/manifests/rootfs-latest/packages.json
(cd out/manifests/rootfs-latest && sha256sum -c rootfs.sha256 packages.sha256)
```
The default hostname is `fds`, the timezone is UTC, and the default language is
English (`en_US.UTF-8`). Account passwords are locked and no SSH server starts.
CLI and development boot to the local `fds` account (UID 1000) through autologin;
recovery has a local root maintenance console. The build shell below runs as
emulated root so it can inspect and configure the staging tree. It is separate
from the normal booted user session.
## 4. Run ARM programs on the workstation
```sh
./tools/in-rootfs out/rootfs-aarch64 /usr/bin/ls --version
./tools/in-rootfs out/rootfs-aarch64 /usr/bin/readelf --version
./tools/in-rootfs out/rootfs-aarch64 /usr/bin/xbps-query -l
./tools/in-rootfs out/rootfs-aarch64 /usr/bin/dasungd check
./tools/in-rootfs out/rootfs-aarch64 /bin/bash --login
```
Inside the shell, try `getconf GNU_LIBC_VERSION`, `locale charmap`, `ls /usr/bin`,
or `xbps-query fds-base`. Type `exit` to return to the workstation.
This runs ARM userspace on the host kernel through QEMU. It does not boot an ARM
kernel or validate Pi drivers. Bubblewrap makes the target the actual process
root, supplies minimal devices, and mounts temporary `/run` and `/tmp`. Native
emulation tools are mounted read-only under `/tmp/fds-build-host`. Physical USB
and DRM are absent, so this helper cannot control your monitor. Use it for trusted
image-build commands. Do not use
this shell to test disk formatting, mounts, power-off, or real device access.
Commands can modify the staging tree. Those changes do **not** change the exported
tar. Rebuild to get a clean staging tree and archive. Built SYSTEM images remain
read-only: change packages at image build time, not by upgrading a live SYSTEM.
For the booted target's on-demand Ethernet and DNS policy, see
[Desktop and Ethernet](desktop.md).
## 5. Run the acceptance checks
```sh
make rootfs-test
```
This verifies the archive and saved package checksums, restores a disposable
tree, and runs ARM glibc, GNU tools, a shell pipeline, locale, XBPS integrity,
Dasung configuration, and s6 database queries under QEMU. It also checks every
ELF file for the target architecture, validates archive permissions and ping
capabilities, and tests rejection of forbidden packages, unfinished configuration,
wrong ABIs, a host executable, missing boot entries, a shell init replacement,
and unsupported profiles.
The image has no BusyBox, systemd, runit, or musl runtime package. Dasung carries
its own statically linked musl and libusb. Its service database is compiled at
build time and includes the base monitor bundle. M2 boots the current graph under native s6 PID 1 in a full ARM VM. Physical
monitor recovery still requires a Pi test.
## What goes into the base
`packages/fds-base/template` owns the explicit dependency set. The assembler also
consumes `image/base-packages.list`, so the required hardware package cannot be
lost when selecting a profile.
| Package group | Reason |
| --- | --- |
| glibc, glibc-locales | GNU C runtime and compiled English UTF-8 locale |
| coreutils, binutils, findutils, diffutils, grep, sed, gawk, bash, tar, gzip, xz | GNU command-line tools, ELF inspection, scripting and archives |
| util-linux, procps-ng, shadow, kbd | Standard system, process, account and console utilities |
| e2fsprogs, dosfstools, erofs-utils | Tools for the planned ext4, FAT and EROFS media |
| eudev, kmod | Device discovery tools and module management |
| iproute2, iputils | Network inspection and configuration tools |
| pciutils, usbutils | Hardware inspection |
| ca-certificates, tzdata, xbps | TLS trust, timezones, and the package database/tools |
| skalibs, execline, s6, s6-linux-init, s6-rc | Native init, process supervision, and service dependency tools |
| fds-base-files, fds-init, fds-dasungd | FDS defaults, native init/service graph, and the mandatory monitor controller |
XBPS resolves shared libraries and other transitive dependencies. The exact
resolved set is in `packages.json`; do not mistake the direct dependency list for
the entire installed set. Planned `fds-cli` and `fds-cartridged` packages are
deliberately absent until their implementation milestones.
## Assembly details
The FDS base-files package supplies `usr/share/fds/layout.json`. The assembler
applies that manifest before XBPS extraction to create merged `/usr` links and
empty mountpoints. Upstream xbps-src reserves these links for Void's own
`base-files`; this approach keeps its tracked files and lint rules unchanged.
Remote packages are signature-checked using Void keys from the pinned checkout.
Local FDS packages are built here and are unsigned development artifacts. A
foreign-architecture XBPS transaction unpacks them; the target's own XBPS then
configures every package under QEMU. Locales, CA certificates, the udev hardware
database, the dynamic linker cache, native init environment, and the complete
s6 service database are ready before export. Certificate generation also runs as
an explicit checked step: the upstream CA package wrapper suppresses updater
errors. There is no first-boot package configuration step.
Unused upstream runit service files are excluded by the shipped XBPS `noextract`
rules. This does not replace upstream package templates or add runit to the image.
The staging directory cannot preserve arbitrary target UID/GID values as a normal
host user. Export restores ownership from the exact XBPS archive headers, applies
the util-linux `wall`/`write` tty-group and XBPS `xbps-uchroot` xbuilder-group adjustments, and gives generated
files root ownership. Namespaced Linux capabilities are converted to the portable
root-owned encoding. The archive, not the host-owned staging tree, is the authority
for installation metadata. When eventually restoring as root, use GNU tar with
`--numeric-owner --same-permissions --xattrs --xattrs-include=security.capability`.
For complete disk images and the deferred physical installation procedure, see
[Internal storage](internal-storage.md). Do not write this rootfs tar to a disk.
The rootless configuration log therefore reports group-change failures for
`wall`, `write`, and `xbps-uchroot`. These three known adjustments are applied
and checked in the exported archive. Other installation errors require investigation.
The builder also reapplies and verifies ping's `CAP_NET_RAW` with direct QEMU
inside a user namespace and checks the actual stored xattr.
Package versions, input hashes and selected archives are retained for inspection.
The Void binary repository is rolling; a fresh build later may resolve different
versions. Those records alone do not establish reproducibility. Use the
separate [frozen-input and offline rebuild workflow](reproducible-builds.md) for
release construction and consult its recorded acceptance status.
## If a build fails
Read the last error in `out/logs/rootfs-build.log`; keep the printed build-workspace
path. A package setup error is a build failure, not a reason to publish an
unconfigured filesystem. Correct the issue and run the build again; downloads
already in `out/cache/rootfs` are reused.
If you changed an overlay and receive `Stale overlay`, compare your edited
`packages/fds-*` directory with the named generated copy under
`vendor/void-packages/srcpkgs/`. Move only that generated copy out of the way and
rerun. Never discard tracked upstream changes to silence the pin check. See
[Troubleshooting](troubleshooting.md) for the existing build-container checks.
References: [Void chroot installation](https://docs.voidlinux.org/installation/guides/chroot.html),
[XBPS configuration](https://man.voidlinux.org/xbps.d.5),
[Linux binary-format handlers](https://docs.kernel.org/admin-guide/binfmt-misc.html), and
[QEMU userspace emulation](https://www.qemu.org/docs/master/user/main.html).
The rootless exporter restores ownership changes that package install scripts
cannot represent in a single-UID user namespace. This includes tty ownership for
`wall`/`write`, the xbuilder group for `xbps-uchroot`, and the utmp group for the
terminal-accounting helper. Archive checks validate these identities explicitly;
the ARM boot check also inspects the installed helper's actual group.
-145
View File
@@ -1,145 +0,0 @@
# Services, readiness, and shutdown
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Native init and ARM VM](init.md) · [Cartridges](cartridges.md)
**Implemented in M2:** native s6 PID 1, a compiled base service graph, console
login, eudev, the Dasung controller, and a controllable test service. The
[init walkthrough](init.md) explains how to boot the ARM VM and try the commands
below. M6 adds the base cartridge service; M8 adds optional desktop/network profiles as described in the [desktop guide](desktop.md).
## Startup and service state
```text
Linux → /sbin/init → s6-linux-init → s6-svscan (PID 1)
└── rc.init → s6-rc-init → s6-rc change boot
```
The generated execline launcher immediately executes native init. Stage 1 prepares
writable `/run`, starts the catch-all logger, and replaces itself with s6-svscan.
Bash stage-2 scripts run as ordinary children. They initialize the live database
and activate the `boot` bundle. No service compiler runs during boot.
| Location in FDS | Purpose |
| --- | --- |
| `/etc/s6-rc/source` | Packaged, readable service definitions |
| `/etc/s6-rc/compiled` | Dependency database compiled under ARM emulation during the build |
| `/run/s6-rc` | Live service state on tmpfs |
| `/run/service` | Supervision directories created at runtime |
| `/run/uncaught-logs` | Native init catch-all log, also copied to the console |
| `/run/log/dasungd` | Rotating monitor-controller log |
These are guest paths, not instructions to start services on the Arch host.
The Void build container never boots the FDS service graph.
## The base boot graph
| Service | Function | Dependencies |
| --- | --- | --- |
| `runtime-fs` | Mount proc, sysfs, devpts and volatile temporary/log paths | Kernel or stage 0 supplies `/dev`; native stage 1 supplies `/run` |
| `hostname` | Set the image's hostname | None |
| `console` | Create volatile login accounting files | `runtime-fs` |
| `getty` | Provide a console login | `console`, `hostname` |
| `eudevd` | Process kernel device events | `runtime-fs`; readiness checked through its control channel |
| `udev-trigger` | Request initial subsystem and device events | Ready `eudevd` |
| `machine-config` | Validate internal NVMe settings and publish a fixed boot snapshot, or image defaults | `runtime-fs`; no console dependency |
| `cartridged-runtime` | Prepare bounded cartridge log storage | `runtime-fs` |
| `cartridged-log`, `cartridged` | USB events, bay inventory, validated mounts and IPC | Runtime paths and `machine-config`; no getty or eudev readiness dependency |
| `dasungd-runtime` | Prepare private controller state and log paths | `runtime-fs` |
| `dasungd-log`, `dasungd` | Supervise the monitor controller and its logger | Runtime paths and s6's logging pipeline |
The bundle selects these services through their dependencies; it does not impose
one serial list. Console startup does not wait for device enumeration, monitor
reconnection, networking, or cartridge discovery. Dasung remains part of the base,
independent of a GUI or an inserted ENVIRONMENT cartridge. See [Dasung](dasung.md).
M6 includes the real static `cartridged` service and its logger. Network, DHCP,
NTP, sshd, D-Bus, Xorg, WindowMaker,
Bluetooth, and Wi-Fi are absent from the current boot bundle.
## Inspect and control services
Inside the shell opened by `make vm`:
```sh
s6-rc -l /run/s6-rc -a list
s6-svstat /run/service/getty
s6-svstat /run/service/eudevd
s6-svstat /run/service/dasungd
cat /run/log/dasungd/current
s6-rc -l /run/s6-rc -u change test-echo
s6-svstat -o up,ready,pid /run/service/test-echo
printf 'hello\n' | s6-ipcclient /run/fds-test/echo.sock s6-ioconnect -t 3000
s6-rc -l /run/s6-rc -d change test-echo
```
`test-echo` is a root-only Unix-socket echo server. Its database entry ships in
this development rootfs, but it is excluded from the normal boot bundle. Its
`test-runtime` dependency creates `/run/fds-test` with mode 0700. The ARM boot
test verifies replies, a supervised restart with a new PID, and stop/start state
transitions. It also verifies that getty and Dasung are running without hardware.
## Readiness and failures
A process existing does not always mean its service is usable. The echo server
notifies s6 after it starts listening. Eudev has no native s6 notification, so
`s6-notifyoncheck` checks its control channel within a bounded deadline. The
initial device trigger does not wait for every unrelated device to settle.
There is no global `udevadm settle` or sleep added to hide a boot race. A missing
monitor leaves Dasung supervised and disconnected; it does not hold up getty.
Service errors go to the runtime logs and console. Investigate the failed
service and its dependencies instead of extending an arbitrary startup delay.
## Shutdown
`poweroff`, `reboot`, `halt`, and `shutdown` are the native s6-linux-init wrappers.
M10's `fds poweroff` and `fds reboot` first freeze new operations, stop optional
sessions and managed jobs, then verify DATA and cartridge unmounts. A native
shutdown hook also enforces this preparation for administrative use of the
wrappers. Only successful preparation proceeds to the remaining s6 service stop
and final kernel action. The two internal
s6-rc helper services are marked essential and remain until native final cleanup;
its diagnostic warnings about not stopping them are expected.
The [shutdown guide](power.md) explains commands, failure recovery and the current
M10 acceptance status. No physical Pi shutdown measurement is claimed. See
[Performance](performance.md) for the hardware targets and measurement boundaries.
M8 ENVIRONMENT profiles select OS-defined service bundles after console readiness.
Profile metadata is declarative; a cartridge cannot inject a root shell
script into startup. See [Cartridges](cartridges.md) for that trust boundary.
## Cartridge service
M6 adds `cartridged` and its bounded volatile logger to the base boot bundle.
It depends on runtime filesystem setup and M12 machine settings loading,
independently of getty and eudev. See [Internal storage](internal-storage.md) for
the read-only loading policy and explicit fallback diagnostics. Its
readiness notification means the control socket exists, not that all cartridges
have finished enumeration. Getty has no cartridge dependency.
The service listens before scanning and reconciles kernel USB/block events with
current sysfs state. See [Cartridges](cartridges.md) for configuration, controls,
restart behavior and diagnostics. Dasung stays a separate mandatory base service.
## Optional desktop and network graph
The `desktop` bundle selects `xserver` and `desktop-session`. Xorg's display-fd
notification establishes server readiness; the static session helper subscribes
to X11 property events before launching WindowMaker, then observes its EWMH
supporting-window property. This avoids missing an event when the atom does not
yet exist. Session processes run as fds in a root-owned cgroup. Their descendants
are stopped before Xorg. Native service supervision handles unexpected exits.
The `network` bundle selects `dhcp`. Its process readiness does not wait for a
lease; DHCP remains asynchronous. Both bundles have bounded volatile loggers,
and neither belongs to `boot`. Their runtime directories are prepared by
separate oneshots. See [Desktop and Ethernet](desktop.md) for user commands.
M10 places the DHCP client and its privilege-separated children in a root-owned
cgroup. Network stop checks actual group exit as well as the supervised main
process, then brings the managed interfaces down and clears volatile resolver
state. It introduces no additional daemon or boot dependency.
-120
View File
@@ -1,120 +0,0 @@
# Software cartridge format 1
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Build and use a cartridge](workstation.md) · [Original cartridge classes](cartridges.md)
A software cartridge is a complete GPT disk image with **1 + m** partitions,
where `m` is 1–32. Partition 1 describes every software package in all payload
partitions. All partitions use read-only EROFS. This format is the current
PROGRAM creation format; existing single-partition PROGRAM media remain readable.
SYSTEM, DATA and ENVIRONMENT layouts retain their existing meanings.
## Disk and filesystem layout
```text
GPT protective MBR + primary GPT
1 FDS_METADATA
FDS/CARTRIDGE.TOML
FDS/SOFTWARE.TOML
2 FDS_PAYLOAD02
bundles/demo.hello.tar.xz
bundles/demo.editor.tar.xz
3 FDS_PAYLOAD03
bundles/demo.report.tar.xz
...
backup GPT
```
Partition entries are consecutive, use Linux filesystem type GUIDs, unique
nonzero partition UUIDs, no GPT attributes, and nonoverlapping MiB-aligned
extents. Partition 1 starts at LBA 2048. Both GPT copies, their headers and CRCs
must agree. A full-drive write to a larger USB device relocates the backup table
and header. The existing on-target image parser and the workstation writer
share these checks.
`FDS/CARTRIDGE.TOML` uses the existing format-1 identity with `class = "program"`
and `media.writable = false`. It contains no startup command. A sample catalogue:
```toml
format = 1
[[software]]
id = "demo.hello"
name = "AArch64 hello"
version = "1.0"
architecture = "aarch64"
partition = 2
archive_bytes = 2308
unpacked_bytes = 70504
entries = 2
sha256 = "REPLACE_WITH_THE_64_CHARACTER_LOWERCASE_SHA256"
[software.commands]
hello = "bin/hello"
```
Those lengths and the digest are illustrative. The builder generates actual
values. IDs and command names use the existing restricted FDS identifier syntax.
IDs are unique per cartridge. Every declared payload partition must contain
exactly the catalogue's `bundles/<id>.tar.xz` archive names, and every payload
partition must be represented. Multiple software entries may share a partition.
The catalogue contains 1–128 software entries and at most 64 KiB of TOML.
## Archive contract
Bundles are deterministic USTAR archives compressed with single-threaded xz.
Entries are sorted, timestamps/UID/GID are zero, and permissions normalize to
0755 for directories/executables or 0644 for ordinary data. Host source symlinks
to regular files inside the root are flattened into regular files. Directory or
escaping symlinks are rejected; the archive itself never contains links.
The reader rejects:
- Absolute, parent-traversing, repeated-separator, non-UTF-8 or control-character paths.
- Duplicate entries, file-as-parent conflicts, links, devices, sockets, FIFOs,
privileged permissions, and GNU/PAX extension entries.
- Incorrect SHA-256, compressed length, unpacked length, entry count, or command
paths that do not name executable regular files.
- ELF files that are not little-endian 64-bit AArch64, or any ELF in a bundle
declared `architecture = "any"`.
- Invalid xz data, nonzero material after the tar end marker, and resource-limit
violations. Each archive is at most 512 MiB compressed, 1 GiB unpacked and
65,536 entries. Xz decompression has a 256 MiB memory limit.
A source path must fit USTAR's path fields. Packaging reports paths that cannot
be represented rather than emitting an unsupported extension header.
## Guest lifecycle
On insertion the daemon validates the complete GPT against kernel partition
geometry and the current USB disk identity. It reads metadata without following
symlinks, mounts every payload read-only/noexec/nosuid/nodev, and checks the
archive inventory and lengths. `fds bay BAY` reports the full catalogue;
`fds bays` stays compact. No archive command runs automatically on insertion.
`fds run BAY -- SOFTWARE-ID:COMMAND [ARGUMENT...]` verifies and extracts that
package on first use. It creates a private temporary filesystem, applies the
archive contract, then makes the finished tree read-only and accessible for
execution. The runtime cache has a stricter **256 MiB per software tree** limit,
including reserved inode overhead; a host-valid larger bundle may therefore
need splitting before running. Caches live under `/run/fds/software/<bay>/` and
vanish after eject, unplug, restart cleanup, or shutdown.
Managed consumers run as UID/GID 1000 with no new privileges in the bay's cgroup.
`FDS_APP`, `PATH`, `LD_LIBRARY_PATH` and `XDG_DATA_DIRS` point to that software
root. DISPLAY/XAUTHORITY retain the established optional desktop integration.
The xz utility is already an explicit dependency of the mandatory `fds-base`
package; runtime extraction adds no target package.
Build recipes, compilers, installation hooks and privileged archive scripts are
never part of this runtime path.
Safe eject stops consumers, rejects unexpected mount aliases, unmounts caches,
payloads and metadata, then reports SAFE. Confirmed surprise removal permits
lazy detachment after stopping consumers. Service restart removes stale mounts
before rescanning. SYSTEM stays immutable throughout.
SHA-256 detects corruption and binds an archive to its catalogue; it does not
establish a publisher's identity. Only run software you trust. Release signature
verification remains the separate [release workflow](releases.md).
-154
View File
@@ -1,154 +0,0 @@
# Twelve-bay stress tests
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Cartridge configuration](cartridges.md) · [Shutdown](power.md)
M11 exercises twelve devices together and failures during media writing. The
software tests run on the build workstation. The separate physical procedure
below is for the assembled Pi; no virtual result certifies its wiring, power
budget, USB hubs, storage controllers or display.
## Run the virtual tests
Complete the [boot-image prerequisites](boot.md), then run sequentially:
```sh
make rootfs PROFILE=cli
make rootfs-test
make system-card PROFILE=cli
make media-image-test
make stress-test
```
`make stress-test` compiles two test-only static ARM workloads, boots the actual
packaged CLI image and Pi kernel, then runs the media failure suite. To repeat
only the latter after those prerequisites:
```sh
make media-fault-test
```
No host disk or physical USB device is passed through. All media are disposable
regular files inside a new `out/m11-vm.*` or `out/m11-faults.*` directory. QEMU runs
inside the existing private network namespace. The SYSTEM root is a protected
read-only virtual disk; the twelve additional USB disks stress all twelve bay
slots. Earlier boot tests cover a removable USB SYSTEM itself.
The virtual controller explicitly has twelve USB 2 and USB 3 ports. Its live
controller identity is discovered before generating the test map. This is not a
map to install on the Pi. Physical hub chains and USB 2/3 aliases must be calibrated
on the actual machine using [the cartridge guide](cartridges.md).
The test records:
- Three cold-start pairs with empty and fully populated bays, using the actual
ordinary-user console prompt and identical image/controller configuration.
- Twelve correct cartridge IDs and serials, followed by three simultaneous
removal/insertion cycles with reversed enumeration and rotated bay positions.
- Eleven direct-I/O readers checking known bytes while DATA writes and flushes
64 MiB. Per-thread intervals must overlap, and QEMU's real block counters must
show reads/writes for every disk.
- Console readiness while the cartridge daemon is deliberately held at an
event gate with all twelve disks present. No sleep releases that gate.
- Native reboot during sustained DATA writes, independent `e2fsck`, exact payload
verification, another boot with a different device declaration order, and halt.
- Kernel logs checked for unexpected I/O failures, USB resets, filesystem errors
and controller failures. Intentional fault tests have separate logs.
Boot samples, shutdown events, bay inventories, I/O counters and kernel logs stay
beside the images. A greater-than-100 ms device-load change must receive the
recorded review explanation; it must not be hidden by selecting a faster sample.
Three emulator samples do not establish a Pi performance claim.
The fault suite injects actual EIO during writing, cache flush and readback. Other
cases interrupt a confirmed transfer after at least 64 MiB by cancellation,
worker termination or daemon termination. Failed operations must remain failed
across restart, the daemon must remain usable, and none may acquire SAFE status.
A test-only QEMU bandwidth limit keeps the large transfer observable; production
code has no corresponding delay. See [QEMU fault injection](https://www.qemu.org/docs/master/devel/testing/blkdebug.html).
## Prepare the physical session
Use twelve labelled test cartridges and a written bay inventory. Include the
intended SYSTEM, a backed-up or disposable DATA cartridge, and the PROGRAM,
ENVIRONMENT and hardware cartridges you plan to use. Keep a known-working SYSTEM
available for rollback. Record the Pi revision, EEPROM version, hub/controller
models, cable arrangement, power source, storage serials and image hashes.
Label physical bays `01` through `12`. Confirm one device at a time with
`fds topology` and `fds bay N`, then create the permanent machine map following
the cartridge guide. Check both USB 2 and USB 3 modes where supported. A serial
identifies the cartridge, while the stable controller/port path identifies its
bay; the `/dev/sdX` name is not a bay label. Do not copy the VM map.
No destructive operation is necessary merely to collect diagnostics. For write
stress, use test media whose contents can be replaced. Any `fds burn` or
`fds format` operation still requires its exact per-operation confirmation.
## Capture evidence on the target
Copy [capture-hardware](../../tools/capture-hardware) to the DATA cartridge. At the
FDS console, run it explicitly through Bash; DATA intentionally does not permit
direct execution:
```sh
bash /data/capture-hardware /data/capture-before
```
The destination must be a new absolute directory. It is created private to the
current user. The script collects FDS status, topology, boot events, kernel logs,
USB/block inventories, mounts, network links and package versions. It writes a
status table and checksums. It does not change services, mounts, firmware, disks
or networking. A command denied to the ordinary user is recorded with its exit
status; use an administrative maintenance session for the missing diagnostic if
needed. A successful capture is not a hardware PASS.
Choose a new directory for every capture. If DATA itself is under investigation,
write to `/tmp/capture-before` instead and copy the directory to trusted storage
before shutdown; `/tmp` is volatile. Review captured device serials and network
identifiers before sharing the files.
## Physical acceptance sequence
Copy the [physical session template](../../tests/hardware/session-template.json)
and fill it with observed values. Null means unknown, not zero.
Record every run, including failures. The following is an initial repeatable
matrix, not evidence that it has already passed:
| Scenario | Procedure and required evidence |
| --- | --- |
| Cold boot baseline | Run five full cold boots with the intended SYSTEM and no optional media. Capture boot reports and external power-on/display timing. |
| Fully populated cold boot | Repeat five times with all twelve bays occupied. Compare prompt timing, every physical bay/serial pair and kernel errors against baseline. |
| Hot insertion | Insert each cartridge individually, then insert several together. Verify all twelve positions and expected activation. Capture before/after inventories. |
| Safe removal | Stop explicit jobs and use `fds eject N` for storage. Remove only after SAFE. Verify that reusing the same bay does not retain the old cartridge's identity or SAFE state. |
| Rotated placement | Move the test cartridges to different bays and repeat. Each bay must follow its physical position regardless of Linux disk names. |
| Simultaneous I/O | Read known files from every readable cartridge while a managed DATA job writes a known payload. Record hashes, throughput and logs; USB hardware without storage needs its own functional workload. |
| Reboot under load | Request `fds reboot`. Verify the ordered shutdown record, boot again, and compare DATA hashes. Run a filesystem check only after that filesystem is unmounted in maintenance/recovery. |
| Poweroff under load | Request `fds poweroff`; record software phases and external power/display behavior. A blocked command is not permission to unplug DATA. |
| Power and USB behavior | Observe the intended supply/battery configuration under simultaneous device load, within each component's ratings. Record undervoltage indications, resets, disconnects and any display recovery failure. |
Use external instrumentation for power applied, visible E-Ink readiness and actual
power-off. Kernel timestamps cannot measure time before the kernel starts or prove
that a flash controller honored a flush. Do not convert a VM measurement into a
physical result. Intentional power-loss or surprise-removal durability tests need
a separate disposable-data session and an explicit test plan; ordinary SAFE
acceptance does not establish power-loss durability.
## Pass, fail and follow-up
A physical run fails if a cartridge appears in the wrong bay, a confirmed write
or DATA eject reports success after an actual I/O error, a busy DATA shutdown
forces poweroff, a kernel/controller error is unexplained, or verified payloads
change. Capture logs before restarting a service or rebooting so its evidence is
not lost. Preserve both the observed failure and any later successful rerun.
Compare the same image, power source and device population when evaluating a code
change. Explain or fix a boot regression over 100 ms. Keep the idle <1 second and
normal DATA <1.5 second shutdown targets separate from measured outcomes. The
physical report should explicitly list untested devices, profiles and power modes.
No new target dependency is introduced by M11. Test workloads use Rust's standard
library; the capture script uses Bash and tools already included in the base
image. QEMU, filesystem tools and Python were existing host test dependencies.
-146
View File
@@ -1,146 +0,0 @@
# FDS Rust tools
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [Implementation ledger](implementation-status.md)
M3 introduces `fds-common`, the `fds` command, and the `fds-stage0` diagnostic
entry point. Both executables cross-build as static-musl AArch64 programs. The
normal OS continues to use glibc; no target musl runtime package is added.
For native Linux software packaging and QEMU hotplug, use `make workstation`
and the [workstation guide](workstation.md). Those Clap tools are host binaries;
`make tooling` below builds the static ARM runtime tools.
## Build and test
After the [workstation setup](getting-started.md):
```sh
make tooling
make tooling-test
```
The outputs are `out/fds`, `out/fds-stage0`, `out/fds-boottrace`, `out/fds-cartridged`, `out/fds-profile`, `out/fds-burn`, `out/fds-inspect`, `out/fds-eject`, `out/fds-power` and `out/fds-release`, with hashes and source-input
records under `out/manifests/fds-tools*.sha256`. The tests run shared Rust unit
tests on x86_64, inspect the ARM ELF files, then execute those actual ARM binaries
with the project-local QEMU userspace emulator. No physical monitor or disk is
opened by this test.
The M3 workspace members reuse the already-locked `serde`, `serde_json`,
and `toml` dependencies: typed configuration, machine-readable output, and the
specified cartridge format. No asynchronous runtime, OpenSSL library, libudev
binding, C compiler dependency, or additional native library is introduced.
All Rust command-line interfaces use **Clap**, with typed commands, generated
help, and argument validation before execution. The workspace shares the Clap
dependency already used by Dasung; `Cargo.lock` pins version 4.6.7 and its derive
support. It adds no shared runtime library. The dependency-free smoketest has no
command-line interface. Test helpers also use Clap and build through Cargo.
The [migration validation](clap-validation.md) records parser, full-image, VM
and startup comparison evidence.
M9 adds media creation and confirmed writes; see [Media tools](media-tools.md)
for its current validation state and the new SHA-256 dependency.
M12 adds the base `fds-release` signature verifier and a workstation signer;
see [Release signatures](releases.md) for key handling, verification and the
Ed25519 dependency rationale.
## Find commands and options
Run these inside FDS (or prefix them with the emulator invocation below on the
workstation):
```sh
fds --help
fds help format
fds format data --help
fds recovery repair --help
fds machine --help
fds-burn create --help
fds-release verify --help
```
Help is specific to the selected command. `-h` and `--help` print help without
performing an operation; `--version` reports the executable version. Missing,
unknown, duplicated, and conflicting arguments produce a usage error with exit
status 2. Operational failures still come from the existing FDS safety checks.
`fds --json bays` and `fds bays --json` are equivalent. The installed aliases
`fds-inspect`, `fds-eject`, and `fds-power` have their own generated help and
accept the same relevant arguments as `fds inspect`, `fds eject`, and `fds power`.
Bay selectors accept `1` through `12`, including the `BAY1` through `BAY12` form.
Use `--` to separate a managed program from FDS options:
```sh
fds run 2 -- editor --help
```
Here `--help` belongs to `editor`. Every argument after the separator is passed
to the managed program unchanged, including `--json` and another `--`.
Media writes still require the exact confirmation associated with the current
cartridge insertion and image; accepting CLI syntax never authorizes a write.
## Inspect identity and cartridge metadata
Run the ARM programs through the existing container:
```sh
./tools/in-void qemu-aarch64 "$PWD/out/fds" info
./tools/in-void qemu-aarch64 "$PWD/out/fds" --json info
./tools/in-void qemu-aarch64 "$PWD/out/fds" inspect \
"$PWD/tests/fixtures/manifests/windowmaker.toml"
```
`info` reports tool version, build target, kernel and the visible PID 1. In
userspace emulation those kernel/process values describe the execution environment,
not a booted FDS machine. Full-system boot remains a separate test.
Manifest inspection reads at most 64 KiB and rejects unknown keys, unsupported
format versions, unsafe identifiers, terminal control characters, writable
SYSTEM/PROGRAM/ENVIRONMENT media, and activation actions outside ENVIRONMENT.
A profile name is declarative; validation does not execute or activate it.
DATA requires writable media. `--json` gives parsed, validated metadata.
Unknown commands and invalid data return nonzero. Bay state and eject now come from the base cartridge daemon; see
[Cartridge usage](cartridges.md) for available operations and remaining milestones.
## Stage-0 diagnostics
```sh
./tools/in-void qemu-aarch64 "$PWD/out/fds-stage0" --help
./tools/in-void qemu-aarch64 "$PWD/out/fds-stage0" --check-cmdline /path/to/cmdline
```
Supported FDS kernel options are `fds.boot=normal|recovery` and `fds.debug=0|1`.
Normal boot selects GPT `PARTNAME=FDS_SYSTEM`; recovery selects `FDS_RECOVERY`.
Duplicate/unknown FDS options are rejected, while unrelated kernel options are
left to the kernel. Read-only `--probe SYSFS_ROOT [FDS_SYSTEM|FDS_RECOVERY]`
reports missing, unique, or ambiguous partitions from kernel-reported identities.
It never chooses the first of multiple SYSTEMs and does not infer identity from
`sdX` order.
With no arguments, stage0 now performs the [M4 boot path](boot.md) as root PID 1
in the initramfs. It refuses that operation in an ordinary shell. The full-system
boot matrix exercises root handoff; diagnostics alone do not establish a boot.
M4 reuses the locked `libc` crate for direct Linux syscalls and adds no shared
runtime dependency. The [M2 VM](init.md) remains a separate direct-root fixture.
## Boot-event reporting
M5 adds `fds boot-profile` (or `fds --json boot-profile`) for the current boot.
`fds-boottrace` adopts stage0 events into volatile runtime storage and records
the first console readiness; a restarted shell does not replace that timestamp.
Missing events remain absent. See [Performance](performance.md) for event
boundaries, saved-report comparison, and the greater-than-100 ms regression rule.
## Desktop, applications, and Ethernet
M8 adds `fds profiles`, `fds profile activate windowmaker`, `fds profile deactivate`,
`fds network on|off`, and PROGRAM launches with `fds run N -- executable-name`.
The standalone `fds-profile` is also a static ARM executable. Its root-only s6
helpers create the authenticated X server and supervise the unprivileged desktop
session; ordinary callers use the existing peer-checked control socket.
See [Desktop and programs](desktop.md) for a complete usage walkthrough.
-246
View File
@@ -1,246 +0,0 @@
# Troubleshooting the build
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
[Documentation index](README.md) · [First build](getting-started.md) · [Build reference](build-host.md)
Find the first failing command or `ERROR:` in the log, fix that problem, then
repeat the same step. Do not continue past a failed bootstrap and interpret later
missing files as independent problems. Run all commands below from the repository
root, as your regular user, unless explicitly shown otherwise.
## Find the right log
| Failing action | Where to look |
| --- | --- |
| `make bootstrap` | `out/logs/bootstrap.log` |
| Void build-container creation | `out/logs/xbps-bootstrap.log` |
| `make smoke-test` | `out/logs/smoke-test.log` |
| GNU hello build | `out/logs/xbps-hello.log` |
| `make check` | Terminal output; this target does not save a log automatically |
For example, after a failed smoke-test:
```sh
tail -n 80 out/logs/smoke-test.log
rg -n 'ERROR:|FAILED|error:|No space left|Permission denied' out/logs/
df -h .
```
A search with no matches exits 1. Read the surrounding log too: upstream tools
may report failures using other wording. Save logs before re-running if you need
the previous output; see [Development](development.md#preserve-logs-and-identify-inputs).
## Unsupported host, root execution, or missing commands
- `M0 requires an x86_64 Linux build host` or `supports Arch Linux`: use the
supported host described in [Your first build](getting-started.md). Changing
the check alone does not port the build system to another platform.
- `Run as a normal user, not root`: leave the root shell and run the build from
your normal account. Only host package installation uses sudo.
- `Missing host command`: install the prerequisites from the first-build guide,
then repeat bootstrap. `readelf` comes from binutils, `bwrap` from bubblewrap,
and `flock` from util-linux.
- `checkout path without whitespace`: move or obtain the checkout at a path
with no spaces, tabs, or other whitespace, then use that directory consistently.
If a previous root-run left files that your account cannot write, inspect their
ownership before correcting it. Do not respond by running all later builds as root.
## Bubblewrap or user namespace failure
Reproduce the same capability check bootstrap uses:
```sh
bwrap --ro-bind / / --unshare-user --uid 0 --gid 0 true
```
If it reports `Operation not permitted` or a namespace error, the host kernel,
container policy, or security configuration prevents the unprivileged container.
Use an Arch host that permits this operation, or have the host administrator
resolve the restriction. Repeated downloads, sudo builds, and a different Pi
will not resolve this preflight failure. Success is a zero exit status with no
output; run `make bootstrap` again once that works.
## Void submodule missing, wrong, or modified
Inspect before changing anything:
```sh
cat VOID_PACKAGES_COMMIT
git submodule status vendor/void-packages
git -C vendor/void-packages status --short
git -C vendor/void-packages diff HEAD --
```
If the submodule is missing, `make bootstrap` initializes it. The directory must
be part of a Git checkout; an unpacked source archive is insufficient. If a clone
was interrupted by a network error, restore connectivity and repeat bootstrap.
`Void checkout mismatch` means the checked-out commit differs from the explicit
pin. A `+` in `git submodule status` means it differs from the parent repository's
gitlink. These are two separate checks. Do not update the pin merely to silence
the error; first determine why the commits differ.
`Tracked Void files were changed` means upstream source has local edits. Preserve
and review the diff. Move intentional FDS changes into the overlay design or a
separate patch for review; restore upstream only after saving work you need.
Generated ignored files such as `etc/conf`, `hostdir`, and the masterdir are
normal. Never use a broad reset or clean command without checking what it removes.
## Configuration or overlay conflict
For `etc/conf differs from config/xbps-src.conf`, compare the two:
```sh
diff -u vendor/void-packages/etc/conf config/xbps-src.conf
```
Put the desired settings in `config/xbps-src.conf`, then follow
[the explicit synchronization procedure](development.md#change-build-configuration-deliberately).
A `diff` exit status of 1 means differences were found, not that the comparison failed.
For `Stale overlay`, compare the named `vendor/void-packages/srcpkgs/fds-*`
directory with its source under `packages/`. Preserve any unique edits in the
source first. Move that specific generated copy into a backup directory under
`out/`, outside `srcpkgs/`, then run `./tools/prepare-void` again. Do not move or
remove unrelated upstream packages. The Dasung package is now an active overlay. After changing it, reconcile that
generated copy by this same procedure before rebuilding.
`Overlay would replace an upstream package` is an intentional protection. Choose
an FDS-owned package name and design an overlay; do not disable the check or edit
the tracked upstream template in place.
## Download failure, checksum mismatch, or apparent stall
Bootstrap and package builds need network access. The first run can spend much
of its time downloading compiler packages. Read the latest log and check disk
space before assuming that a quiet download is a deadlock:
```sh
tail -n 40 out/logs/bootstrap.log
tail -n 40 out/logs/xbps-hello.log
du -sh out/downloads vendor/void-packages/hostdir
df -h .
```
A log or directory may not exist if its step has not started. Bootstrap's static
XBPS download has retries and a timeout; an incomplete transfer uses a `.part`
filename. A retry re-downloads that partial file. Completed cached archives are
checksum-checked on every bootstrap.
For a checksum failure, retain the error and compare the named file against
`config/host-tools.conf`. If the cached completed archive is corrupt, move that
specific archive to a backup location and repeat bootstrap so it downloads again.
Do not change the expected hash to match an unexplained download. If a clean
re-download still disagrees, stop and investigate the source.
A Void mirror timeout is separate from the source pin: the templates are pinned,
but bootstrap and missing build dependencies still use rolling binary repositories.
An unavailable historical dependency may need a reviewed input/mirror solution;
blindly changing the source commit does not establish an equivalent build.
## XBPS command not found or package index seems empty
The XBPS tools are intentionally not installed in the host's `/usr/bin`.
`make bootstrap` creates `.host/xbps/usr/bin/`. FDS build helpers set PATH for you.
For manual queries, use the explicit paths in the [package guide](packages.md).
When querying or indexing ARM output, set `XBPS_ARCH=aarch64`. The host's static
musl executable can otherwise default to its own architecture and ignore the ARM
package. `make smoke-test` uses the correct override. `tools/build-package` exports
packages but does not update `out/packages/aarch64-repodata`; use the indexing
command in the package guide after a standalone package build.
## Rust target, linker, or architecture error
Check the selected toolchain and local configuration:
```sh
rustup show active-toolchain
rustup target list --installed
cat rust-toolchain.toml
cat .cargo/config.toml
printenv RUSTFLAGS CARGO_BUILD_TARGET CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER
```
`printenv` exits nonzero when a listed variable is unset; that is normal here.
Bootstrap installs the required target and rustfmt for the pinned toolchain.
Repeat it if those components are missing.
Build from the repository root with the exact Cargo command in
[Development](development.md#edit-and-rebuild-the-rust-program). Environment
variables, command-line `+toolchain` overrides, or outside Cargo configuration
can change the target, flags, or linker. Reconcile such overrides with the
checked-in `rust-lld` and static CRT configuration. Adding an arbitrary system
linker or glibc library is not a repair for a static-musl build.
## Exec format error, ldd output, and QEMU skips
`./out/fds-smoketest` normally gives `Exec format error` on x86_64: it is an ARM
program. Run it explicitly with optional `qemu-aarch64`, as described in
[the first-build guide](getting-started.md#7-optionally-execute-the-arm-rust-program).
Do not change the target to x86_64 merely to make direct execution work.
`ldd: not a dynamic executable` with exit 1 is expected for the static program.
Use `./tools/verify-elf out/fds-smoketest aarch64 static` for the actual check.
Host `ldd` cannot establish that a foreign ARM program will run.
`SKIP: ARM execution` means QEMU is absent. Compilation and ELF checks still run.
If QEMU is present but fails, the smoke-test must fail; inspect its diagnostic.
The dynamic GNU hello program also requires an ARM glibc loader and libraries,
so the simple static-program QEMU command is not a complete hello runtime setup.
## Missing artifacts or check fails before any build
`make check` expects the Cargo release artifact in `target/`. Run the Rust build
or `make smoke-test` first. A bare Cargo build updates `target/`, while
`make smoke-test` also refreshes `out/fds-smoketest` and the output manifests.
If checksums fail after editing or replacing outputs, inspect what changed and
rebuild through smoke-test; do not edit the manifest to conceal a mismatch.
For a Rust formatting failure, run `cargo fmt --all`, inspect the source diff,
then repeat `make check`.
## Busy /tmp message during Void bootstrap
The initial verified bootstrap emitted
`rm: cannot remove '//tmp': Device or resource busy` during upstream cleanup.
The bubblewrap bind mount explained that instance; upstream exited successfully,
and container GCC, the cross build, and the public M0 commands all passed.
Do not treat every later `/tmp` error as harmless. Confirm a zero command exit
status and the final bootstrap PASS, then run smoke-test. If the command exits
nonzero, report it as a failure with the surrounding log.
## Rootfs, images, and runtime commands
M1 implements `make rootfs PROFILE=cli` and `make rootfs-test`. If the target is
missing, check that you are in the current checkout. Read [M1 rootfs](rootfs.md)
for prerequisites, shell access, logs, and retained failed-build directories.
Python 3.14+ is required for XBPS archive inspection. Supported profiles are
`cli`, `development`, and `recovery`; unknown names fail explicitly. Package
configuration must pass before an archive is published.
`tools/in-rootfs` supports ARM child execution through a private user-namespace
binary-format handler. It requires Linux 6.7+ and namespaced `binfmt_misc` support.
The workstation's global registry is unchanged. For a single ELF executable,
`--direct` invokes QEMU without installing even a private child-execution handler.
For example: `./tools/in-rootfs out/rootfs-aarch64 --direct /usr/bin/xbps-pkgdb -a`.
The tar is configured userspace, not a flashable image. M2 adds `make init-test`
for a full ARM boot and `make vm` for a temporary development shell. Follow
[Native init and ARM VM](init.md) for its dependencies, expected output, and
failure logs. For the full kernel/stage0/SYSTEM path, use `make all`,
`make boot-test`, then `make console-vm`. Run `fds bays` and `fds poweroff`
inside the booted FDS console, not on the Arch host. An exported ARM binary will
not execute directly on x86_64. Physical Pi acceptance remains deferred.
## What to include in a bug report
Include the exact failing command, its exit status if available, the relevant log
excerpt, host distribution and architecture, current Void pin, and whether this
is the first build or a previously working checkout. Mention local configuration
changes and toolchain overrides. Preserve the initial error; a later missing-file
message often only describes its consequence.
@@ -1,24 +0,0 @@
# Workstation tooling plan
The current source of requirements is [the active revision](current-revision.md).
The public tools remain native Linux Clap applications: `fds-cartridge` and
`fds-emulator`. Full OS cross builds keep their separate pinned Void environment.
New software creation uses Void source templates and `xbps-src`. The image builder
installs packages and dependencies on the workstation, verifies installed trees,
and writes them directly into EROFS program partitions with format-2 metadata.
Creation no longer generates xz software bundles. Legacy readers stay available.
The emulator still uses actual FDS ARM kernel/userspace, twelve virtual USB bays,
read-only SYSTEM/PROGRAM disks, private writable DATA overlays, the public serial
console, safe eject, forced removal and native shutdown. Hardware timing and
physical behavior require the procedures in the hardware notes.
Acceptance must cover real source-package builds, image integrity and write/readback,
foreground PATH commands and terminal behavior, background `fds run`, eject/unplug,
service restart and the rendered X11 control panel. Keep the frozen 0.1.0 release
unchanged. Build new matching artifacts and record their hashes and source inputs.
See [user instructions](../workstation.md) and [format reference](../software-format.md)
for the supported interface. Historical xz acceptance remains in
[the earlier workstation record](workstation-validation.md).
-101
View File
@@ -1,101 +0,0 @@
# Workstation extension acceptance
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
**Software acceptance passed on 2026-09-21.** This record covers the new native
Linux software/cartridge builder, whole-image writer, public QEMU emulator and
guest software runtime. It is separate from the immutable local 0.1.0 release.
The host was Arch Linux x86_64, with QEMU 11.1.1 and erofs-utils 1.9.4. The public
workstation tools use ordinary Linux interfaces and distribution tools; other
Linux distributions and ARM workstations have not been exercised in this run.
The guest tests used the actual FDS Pi kernel, current initramfs and current CLI
SYSTEM on QEMU `virt`. The separate native-init test used its documented generic
ARM kernel fixture. Neither environment substitutes for physical Pi testing.
## Workstation and emulator evidence
| Check | Result and evidence |
| --- | --- |
| Typed Rust interfaces and archive contracts | **62 tests passed** across all host-testable workspace crates; `out/logs/workstation-all-rust-tests.log`. The architecture-restricted smoketest is validated separately by the ARM build. |
| Workstation builds and images | `out/workstation-images.5jczgwr7/acceptance.json` and `commands.log`; the recorded CLI SHA-256 matches the delivered `fds-cartridge` binary. |
| Public emulator lifecycle | `out/emu-test.36iqvx0j/acceptance.json`, `inputs.sha256`, `commands.log` and serial logs. The input hash matches the delivered `fds-emulator` binary. |
| Exact documentation examples | `out/logs/workstation-documented-example.log`; the compiled C program and report script were packaged into `out/demo-tools.img`, booted, run as UID 1000, safely ejected and shut down. |
| Incompatible initramfs | `out/emu-old-initramfs-final/console.log`; start promptly reports the complete unsupported boot-option diagnostic and stops the failed VM. |
| Current build inventory | `out/manifests/workstation-acceptance.json`, `workstation-artifacts.sha256` and `workstation-sources.sha256`. These are local evidence records, not a new signed release. |
The host suite executed a real AArch64 build recipe and packaged a portable
script. It built both separate-payload three-partition images and shared-payload
two-partition images; unchanged recipes produced identical bytes. Independent
`sfdisk`, `xz` and `tar` checks passed. Whole-image writes to exact-size and larger
disposable files passed flush/readback and backup-GPT checks.
Negative checks covered wrong confirmations, changed source images, stale target
previews, corrupt GPT, corrupt archives, invalid catalogue mappings, ELF files in
an architecture-independent bundle, escaping source symlinks, unsafe archive
paths/types, privileged modes, incorrect lengths/counts, trailing content and a
file replacing an implied parent directory. Existing writer failure tests remain
passing. The final host suite also uses the relative image-tool runner shown in
the guide, including extraction inside the inspection sandbox.
The emulator suite exercised **all twelve virtual USB bays**, catalogue reporting,
execution from both payload partitions and from shared partitions, read-only
runtime caches, safe eject, forced removal during execution, and reinsertion.
It verified that managed consumers and mounts disappear and that QEMU retains
only the SYSTEM block nodes after removal. It also checked literal guest
arguments, guest exit statuses, interactive console detach and terminal-state
restoration, DATA overlays preserving their original image, and native shutdown
with active software.
Normal-image workflows used the ordinary FDS user. A **separate test-only admin
console image** exercised service interruption/restart cleanup, unexpected
payload mount refusal and on-target PROGRAM creation rejection. That admin
console is not part of the delivered base SYSTEM.
## Regression commands completed
| Command | Evidence log under `out/logs/` |
| --- | --- |
| `make bootstrap` | `workstation-bootstrap.log` |
| `make smoke-test` | `workstation-smoke-test.log` |
| `make check` | `workstation-check.log`; final Rust formatting also passed |
| `make rootfs PROFILE=cli` | `workstation-rootfs-cli-accepted.log` |
| `make system-card PROFILE=cli` | `workstation-system-cli-accepted.log` |
| `make initramfs` | `workstation-initramfs.log` |
| `make rootfs-test` | `workstation-rootfs-test-accepted.log` |
| `make init-test` | `workstation-init-test-accepted.log` |
| `make tooling-test` | `workstation-tooling-test.log` |
| `make boot-test` | `workstation-boot-test.log` — all nine boot cases passed |
| `make cartridge-test` | `workstation-cartridge-test.log` |
| `make data-test` | `workstation-data-test.log` |
| `make media-image-test` | `workstation-media-image-test.log` |
| `make media-test` | `workstation-media-test-accepted.log`, including actual SYSTEM write/readback and boot |
| `make power-test` | `workstation-power-test.log` |
| `make desktop-test` | `workstation-desktop-test.log` |
| `make workstation-test` | `workstation-release-final.log` |
| `make emulator-test` | `workstation-emulator-final.log` |
The legacy media tests now require PROGRAM creation to fail inside FDS while
retaining prepared-image writing and legacy application execution. New software
creation is exercised through the native workstation tool. The ARM CLI test also
checks the explicit emulator boot option alongside strict invalid-option rejection.
The final CLI rootfs is `out/rootfs-build.KfWKvx/rootfs-aarch64.tar`; its SYSTEM
is `out/system-build.u1050ckh/system.img`. The matching emulator initramfs is
`out/initramfs-build.y6x9022y/initramfs.cpio`. Public convenience paths point to
these accepted artifacts. Upstream Void tracked files remain unchanged at
`02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1`.
## Skips and hardware boundary
M0's optional host `qemu-aarch64` execution was skipped because that command is
not installed in the host PATH; ARM static/glibc compilation and ELF validation
passed. The required guest tests ran separately through the project-local QEMU.
These checks do not measure Pi boot/shutdown speed, RP1 USB behavior, USB power
cycling, real drive writeback, physical bay wiring, or Dasung display recovery.
Use the [new software-cartridge hardware procedure](workstation.md#physical-pi-acceptance-procedure-deferred),
[stress procedure](stress-testing.md), and [Dasung guide](dasung.md) for those
checks. No physical USB disk or EEPROM was written. The frozen 0.1.0 release and
its source/build-input archives remain unchanged; it does not include this extension.
-349
View File
@@ -1,349 +0,0 @@
# Build software cartridges and run FDS in QEMU
Development reference and historical context. For current operating instructions, use the [user manual](../README.md). Acceptance applies only to the source and artifacts identified in each record.
This guide uses a **Linux workstation**, not the Raspberry Pi. It takes you from
source software to a cartridge disk image, runs that image in FDS, and explains
how to write the same complete image to USB. All commands run from the checkout
root unless marked as guest commands.
The two native host tools are `fds-cartridge` and `fds-emulator`. They use typed
Clap interfaces; append `--help` to any command or subcommand. The target's `fds`
command remains a static AArch64 executable. The new host tools are never part
of the Pi base image. Dasung support remains in the base boot bundle.
The accepted local `out/fds-os-0.1.0/` release predates this extension. Leave that
release intact and build current images for the emulator. See
[workstation acceptance](workstation-validation.md) for current evidence.
For accumulated build and test images, use `make clean-preview` and `make clean`.
Current images, native tools, latest test fixtures and personal emulator sessions
are kept; obsolete automated test sessions are disposable. See
[Cleanup](cleanup.md) for the exact scope and how to preserve an older test run.
## Install and build the workstation tools
Use a normal Linux account with Rust/Cargo, a C linker, Bash and Make. The
repository's `rust-toolchain.toml` selects the Rust version. Runtime prerequisites
are:
| Tool | Purpose |
| --- | --- |
| `qemu-system-aarch64` | Runs the FDS ARM kernel and userspace using portable TCG |
| `qemu-img` | Creates a separate writable overlay for DATA; source images stay unchanged |
| `mkfs.erofs`, `fsck.erofs` from erofs-utils | Build and verify cartridge partitions; tested with 1.9.4 |
| `xz` | Compress/decompress software tarballs with a decompression memory limit |
| `bwrap` from bubblewrap | Restricts filesystem inspection to a private writable staging directory |
| A software-specific cross compiler | Builds AArch64 applications; unnecessary for portable scripts |
| `sfdisk`, `tar`, `mke2fs`, Python 3 | Independent integration tests; not needed to boot an existing image |
Install these with your distribution's package manager. Unprivileged user
namespaces must work for bubblewrap. Building **these host tools** does not need
Void, XBPS, an Arch host, a Pi, root, or a running FDS guest:
```sh
make workstation
export PATH="$PWD/out/workstation:$PATH"
fds-cartridge doctor
fds-emulator doctor
```
Current acceptance ran on Arch Linux x86_64. The tools use native Linux
interfaces and distribution-provided utilities; other distributions and ARM
workstations have not yet been exercised by this acceptance run.
The outputs are native binaries in `out/workstation/`. The build explicitly
selects the workstation architecture instead of the workspace's default ARM
target. Rust dependencies are pinned in `Cargo.lock`. `tar` 0.4.46 and its
`filetime` dependency provide archive decoding/creation; FDS additionally checks
entry types, paths, counts, sizes, executable architecture and hashes.
For this repository's **existing Arch/Void setup**, reuse its local tools:
```sh
make workstation
./tools/prepare-image-tools
./tools/in-void xbps-install -y qemu-img
out/workstation/fds-cartridge --image-tool-runner tools/in-image-tools doctor
out/workstation/fds-emulator doctor --qemu-runner tools/in-void
```
The install command above changes only the project-local build container. The
optional runners accept a program name followed by its arguments; omitting them
uses programs in the workstation's `PATH`. Set the image runner on every
cartridge create, inspect or preview invocation that needs it. A QEMU runner is
saved in the session and reused for later DATA insertions.
## Build two software bundles
Each software recipe declares an id, display name, version, target architecture,
root directory, and named executable commands. A trusted `[build]` section may
run a workstation compiler. It is executed only by `software build`, never by
`software pack`, inspection, or a cartridge insertion.
The [hello recipe](../../examples/software/hello/software.toml) builds an AArch64
C executable. Install an AArch64 glibc cross compiler as `aarch64-linux-gnu-gcc`,
then run:
```sh
fds-cartridge software build examples/software/hello/software.toml out/demo-hello
fds-cartridge software pack examples/software/report/software.toml out/demo-report
fds-cartridge software inspect out/demo-hello
```
If your cross compiler has a different name, edit the recipe's `build.command`.
With this checkout's existing Void cross toolchain, the equivalent is:
```sh
mkdir -p examples/software/hello/root/bin
./tools/in-void aarch64-linux-gnu-gcc -O2 examples/software/hello/hello.c \
-o examples/software/hello/root/bin/hello
out/workstation/fds-cartridge software pack \
examples/software/hello/software.toml out/demo-hello
out/workstation/fds-cartridge software pack \
examples/software/report/software.toml out/demo-report
```
Use **one** of those hello workflows. Outputs must not already exist: choose
new output names when rebuilding, then update your cartridge recipe. The example
bundles each contain `software.toml` and `<id>.tar.xz`. The archive contains the
contents of the software root, such as `bin/hello`; it has no leading `root/`.
For your own application, copy a recipe and populate its root with `bin/`,
`lib/` and `share/` as needed. C/C++ glibc builds target AArch64; static Rust
programs should use `aarch64-unknown-linux-musl`. Set `architecture = "any"` only
for scripts/data without ELF files. An x86_64 executable is rejected. Shared
libraries must be compatible with the target SYSTEM's glibc, or bundled as
appropriate. There is no cross-cartridge dependency resolver.
## Assemble the cartridge image
The example cartridge recipe groups the two bundles into two payload partitions:
```sh
fds-cartridge create examples/software/cartridge.toml out/demo-tools.img
fds-cartridge inspect out/demo-tools.img
```
For the project-local EROFS tools, use:
```sh
out/workstation/fds-cartridge --image-tool-runner tools/in-image-tools \
create examples/software/cartridge.toml out/demo-tools.img
```
The resulting complete disk image has **three GPT partitions**:
| Partition | GPT name | Contents |
| --- | --- | --- |
| 1 | `FDS_METADATA` | Cartridge identity and the software catalogue |
| 2 | `FDS_PAYLOAD02` | `bundles/demo.hello.tar.xz` |
| 3 | `FDS_PAYLOAD03` | `bundles/demo.report.tar.xz` |
To put both programs in one payload partition, use one `[[payload]]` entry with
`bundles = ["../../out/demo-hello", "../../out/demo-report"]`. This makes a
two-partition image. Each payload group creates exactly one partition. The first
partition identifies every software bundle and its payload partition.
Creation finishes only after verifying both GPT tables, all EROFS partitions,
every archive and the catalogue. Inspection prints JSON, including offsets,
lengths, SHA-256 and available commands. Repeating an unchanged recipe produces
the same image bytes. See [the format](software-format.md) for limits and rules.
## Boot current FDS in the emulator
You need three matching current artifacts: the FDS Pi kernel, its initramfs, and
a SYSTEM image containing the guest software runtime. Build these on the OS
build workstation using [the build guide](getting-started.md). For an already
bootstrapped checkout:
```sh
make rootfs PROFILE=cli
make system-card PROFILE=cli
make initramfs
make workstation
out/workstation/fds-emulator --session out/my-emulator start \
--qemu-runner tools/in-void
```
If QEMU is installed directly on your Linux workstation, omit `--qemu-runner`.
To use images copied from another build machine, supply their paths:
```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
```
Create `out/` first if using a fresh directory. The session directory must be
**new**, private, and short enough for Unix sockets (under 90 bytes including its
absolute parent path). Start waits for the FDS prompt and verifies the emulator
bay configuration. It uses two emulated CPUs and 1024 MiB RAM by default;
`--memory-mib 2048` increases RAM. `--timeout` adjusts the boot deadline.
The session records inputs, virtual devices, serial output and QEMU diagnostics.
QEMU stays running after the command exits. SYSTEM and software images are
read-only. No physical disks, host network interface, or Pi monitor are attached.
## Insert, use and remove cartridges
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 -- fds run 01 -- demo.hello:hello
fds-emulator --session out/my-emulator guest -- fds run 01 -- demo.report:report
fds-emulator --session out/my-emulator guest -- tail -20 /run/log/cartridged/current
fds-emulator --session out/my-emulator eject 01
```
Insertion is asynchronous: `fds bay 01` may briefly show `EMPTY` before USB
storage discovery completes. Retry the status command until it reports
`MOUNTED READ ONLY`, or inspect its error. The catalogue lists exact commands.
`fds run` starts a managed process and prints its PID; its output goes to the
cartridge service log. Packages are verified and extracted into temporary,
read-only RAM filesystems on first run. They are not compiled on the guest.
All twelve bays, numbered `01`–`12`, are available. Simultaneously mounted
PROGRAM cartridges need distinct cartridge IDs; inserting duplicate IDs is
reported as a guest error. `eject` asks FDS to stop
managed consumers, release mounts and declare `SAFE`, then removes the virtual
USB device. If FDS refuses, QEMU leaves the cartridge attached. To deliberately
simulate an accidental pull, use:
```sh
fds-emulator --session out/my-emulator unplug 01
```
A forced unplug can lose DATA writes. The guest must then detect the removal
and clean up its processes and mounts. `status` reports the actual QEMU devices
and block nodes as well as the recorded image paths.
Use the interactive shell if preferred:
```sh
fds-emulator --session out/my-emulator console
```
At the `FDS>` prompt, type `fds bays`, `fds bay 01`, or `fds run 01 --
demo.hello:hello`. **Ctrl-] detaches**; it does not stop the VM. Use a second
workstation terminal for insertion/unplug. Detach the console before `guest`,
`eject`, or ordinary `stop`, because they also need exclusive serial access.
Finish with:
```sh
fds-emulator --session out/my-emulator stop
```
This invokes native FDS shutdown. `stop --force` cuts virtual power without that
sequence. Logs and DATA overlays remain in the session directory. Choose a new
session directory to boot again; stopped sessions are retained for inspection,
not resumed from RAM snapshots.
## Writable DATA images
Insert an existing FDS DATA disk image with the same `insert` command. The
emulator creates a unique `data-<bay>-<id>.qcow2` overlay in the session directory;
all guest writes go there. The original image remains unchanged. Safe eject
retains the overlay, while inserting the original image again starts a fresh
one. Software cartridges remain read-only.
After eject or shutdown, export an overlay if you need the changed DATA contents:
```sh
qemu-img convert -f qcow2 -O raw /path/to/session/data-02-IDENTIFIER.qcow2 out/saved-data.img
```
Keep the original backing image at its recorded path until conversion completes.
Never convert or edit an image while it is attached to a running VM.
## Write the completed image to USB
The burn flow always starts with the complete image created above. It does not
construct partitions directly on a drive. Identify the intended **whole USB
drive**, unmount it, then make a preview as your normal user:
```sh
fds-cartridge preview out/demo-tools.img /dev/sdX out/usb-preview.json
```
Replace `/dev/sdX` with the actual whole USB drive. The JSON records its model,
size, insertion identity, exact image hash and an exact `confirmation` string.
Review these before copying the full phrase into the write command:
```sh
sudo /absolute/path/to/fds-cartridge write out/usb-preview.json \
--confirm 'COPY THE EXACT confirmation VALUE FROM THE PREVIEW'
```
The writer rechecks source and target identity, requires a USB whole disk,
rejects mounted/protected storage, writes the **full image**, flushes and verifies
readback. On larger drives it relocates the backup GPT to the end. A replaced
USB drive or changed image requires a fresh preview. This destroys the selected
drive's existing contents. No physical drive has been written by the automated
acceptance tests.
To rehearse on a disposable file without root or USB hardware:
```sh
truncate -s 64M 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'
fds-cartridge inspect out/disposable-usb.img
```
The file must be at least as large as the source image. Use `--image-tool-runner`
for preview/inspection if EROFS tools are provided by the local wrapper.
## Troubleshooting
| Symptom | What to do |
| --- | --- |
| Required executable not found | Run the corresponding `doctor`; install the named host utility or select an explicit runner |
| Bubblewrap namespace failure | Enable unprivileged user namespaces according to your workstation policy; do not run creation/inspection as root |
| Output already exists | Use a new bundle/image/preview/session name; creation does not overwrite outputs |
| Unknown `fds.emulator` option or missing emulator settings | Rebuild both the current initramfs and SYSTEM; the frozen 0.1.0 images predate this feature |
| Console in use | Detach with Ctrl-] before guest commands, safe eject, or shutdown |
| Guest command timed out | Inspect `console.log`; the command may still be running, so do not blindly repeat a write |
| Incomplete insertion | Run `unplug BAY` to reconcile the recorded intent with actual QEMU devices, then reinsert |
| Software digest/path/architecture error | Rebuild the bundle and cartridge on the workstation; the guest will not run an invalid archive |
| Cache limit exceeded | Reduce the software bundle; one runtime tree including inode overhead is limited to 256 MiB |
| Safe eject blocked | Close unmanaged processes or extra mounts using the media, then retry; `unplug` is only for deliberate failure simulation |
| VM will not boot | Read session `console.log` and `qemu.log`; verify all three supplied boot artifacts belong to the current build |
QEMU exercises the actual Linux/FDS software path. It does not emulate Pi
firmware, RP1, USB power sequencing, the physical twelve-bay wiring, or Dasung
power recovery. Those acceptance checks remain hardware procedures.
## Physical Pi acceptance procedure (deferred)
After assembling and calibrating the physical bay map, use a disposable USB
cartridge and the current matching SYSTEM/initramfs. Perform these checks on the
Pi; the VM results do not replace them:
1. Build the hello/report cartridge on the workstation, record its image hash,
and use preview/confirmation to write that entire image to the chosen USB drive.
2. Insert it in a calibrated bay. Record `fds --json bay BAY`; confirm both
software entries and their declared payload partitions appear.
3. Run `demo.hello:hello` and `demo.report:report`. Save their output from the
cartridge log and confirm UID 1000. Check `/proc/self/mountinfo` for read-only
payload and software-cache mounts.
4. Run `fds eject BAY`, verify SAFE, then remove and reinsert the cartridge in
another calibrated bay. Repeat both commands. Record any USB enumeration or
I/O errors.
5. With a disposable cartridge and a deliberately long-running test program,
test surprise removal separately. Verify its managed processes terminate and
all of that bay's software mounts disappear. Do not use valuable DATA for
this failure test.
6. Exercise native shutdown with active software, cold boot, and a real USB
power cycle. Save actual observations and kernel/service logs. Follow the
separate [Dasung hardware procedure](dasung.md) for monitor recovery.
Record the Pi, hub, USB drive and kernel versions with the result. Hardware
latency, electrical behavior and data durability remain unverified until these
measurements are performed on the assembled machine.
-121
View File
@@ -1,121 +0,0 @@
# Pi 5 EEPROM configuration
[Boot images](developer/boot.md) · [Recovery](recovery.md) · [User manual](README.md)
The profile tool creates reviewable firmware/configuration files on the
workstation. Applying them to a Pi is a separate maintenance operation. Preserve
the machine's original firmware and configuration before changing them.
## Preview a profile on the build workstation
From the repository root:
```sh
./tools/configure-pi-eeprom --profile production
./tools/configure-pi-eeprom --profile development
```
The first invocation downloads three checksum-pinned inputs into
`.host/eeprom/`: the official configuration script, the Pi 5 preview firmware and
its upstream license. Later calls verify and reuse them. `FDS_OFFLINE=1` requires
all inputs to exist and refuses a network download.
The command prints its new `out/eeprom-PROFILE.*` directory. It contains:
| File | Purpose |
| --- | --- |
| `base.bin` | Exact input firmware retained unchanged |
| `original.conf` | Configuration to restore |
| `configured.conf` | Reviewed profile merged with the supplied settings |
| `configured.bin` | Real EEPROM image with the new configuration |
| `rollback.bin` | Same base firmware with `original.conf` restored |
| `review.diff` | Human-readable configuration changes |
| `manifest.json` | Input identity, selected settings and output SHA-256 hashes |
| `LICENSE` | Complete upstream tool and firmware notices |
Default inputs are **preview defaults, not a backup of your Pi**. A rollback image
restores the configuration supplied to this command on the supplied base firmware.
It cannot reconstruct a different firmware version or settings that were never
provided. Retain the actual machine's configuration and matching firmware input
before applying anything to that machine.
## Profile behavior
| Setting | Production | Development |
| --- | --- | --- |
| `BOOT_ORDER` | `0xf6`: NVMe, repeat | `0xf16`: NVMe, SD, repeat |
| `BOOT_UART` | `0` | `1` |
| `NET_INSTALL_ENABLED` | `0` | `0` |
| `NET_INSTALL_AT_POWER_ON` | `0` | `0` |
| `POWER_OFF_ON_HALT` | `1` | `1` |
| `WAIT_FOR_POWER_BUTTON` | `0` | `0` |
Boot order is read from the right. Both profiles avoid scanning the twelve USB
cartridges for firmware boot. Development retains an SD rescue path. Both disable
network-install keyboard detection; Raspberry Pi documents that this detection
adds USB initialization and enumeration work. [Official bootloader configuration](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#bootloader-configuration).
On Pi 5, the power-off setting requests PMIC standby on halt; the dedicated power
button remains the wake mechanism. The wait setting leaves cold power-on boot
enabled. After shutdown, check that attached power hardware follows the selected policy. [Official power settings](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#POWER_OFF_ON_HALT).
The source profiles are [production.conf](../config/eeprom/production.conf) and
[development.conf](../config/eeprom/development.conf). Review these files before choosing a profile.
## Preserve the machine's settings
In a Raspberry Pi maintenance environment with the official EEPROM utilities,
collect its configuration and version information before changing firmware:
```sh
rpi-eeprom-config > pi-current.conf
rpi-eeprom-update > pi-eeprom-status.txt
```
Retain the matching original firmware image separately. The status report helps
identify it; it is not a binary backup. Copy the saved inputs to the workstation,
then prepare a new directory:
```sh
./tools/configure-pi-eeprom \
--profile production \
--base-image /absolute/path/to/original-pi5-firmware.bin \
--current-config /absolute/path/to/pi-current.conf \
--output-directory /absolute/path/to/new-eeprom-review
```
The base must be a regular 2 MiB Pi 5 image, not a device node. The output directory
must not already exist. The helper preserves unrelated configuration lines and
conditional sections. It replaces all occurrences of the six managed settings,
including conditional overrides, with the selected profile's final `[all]`
settings. Review that change in `review.diff`; the exact original conditional
configuration remains in `original.conf` and `rollback.bin`.
The upstream parser reads the generated images back before success is reported.
This checks file construction and configuration roundtrip, not hardware
compatibility. No secure-boot key, fuse, customer signature or OTP setting is
modified by this helper.
## Apply and roll back
Apply the reviewed configuration from a Raspberry Pi maintenance environment
with the official EEPROM utilities, then follow the updater's restart instructions.
```sh
sudo rpi-eeprom-config --apply ./configured.conf ./base.bin
```
The updater may program EEPROM immediately or schedule an update depending on
its platform and update method. Follow its output and verify the resulting
configuration/version after the required restart. To restore the supplied
configuration on the same supplied firmware:
```sh
sudo rpi-eeprom-config --apply ./original.conf ./base.bin
```
If the firmware version itself changed, use the saved matching original firmware
for the rollback operation. Keep the maintenance SD and an external EEPROM rescue
route available; internal recovery cannot repair an EEPROM that prevents internal
boot. The official [EEPROM update and recovery guide](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-boot-eeprom)
describes that physical workflow.
-92
View File
@@ -1,92 +0,0 @@
# Build and start FDS
This guide creates a bootable FDS SYSTEM image and starts it in QEMU. Use an
x86_64 Arch Linux workstation for the complete OS build. To build application
cartridges on another Linux distribution, use the [workstation guide](workstation.md).
A prepared set of kernel, initramfs and SYSTEM images can be run on a Linux
workstation without building the OS locally.
## Prepare the workstation
Use a normal user account, a Git checkout with its submodule metadata, and a
checkout path without spaces. Initial preparation needs Internet access. The
host must support unprivileged user namespaces and namespaced binfmt_misc
(Linux 6.7 or newer). Full builds and VM tests use tens of GiB per build tree;
check free space with `df -h .` and use [cleanup](cleanup.md) between build batches.
From the checkout directory, install host prerequisites:
```sh
sudo pacman -S --needed bash coreutils binutils git curl make file tar xz gzip zstd \
bubblewrap rustup ca-certificates findutils diffutils grep sed gawk util-linux \
python e2fsprogs libarchive lz4
bwrap --ro-bind / / --unshare-user --uid 0 --gid 0 true
```
Python must be 3.14 or newer. The namespace check exits successfully without
output. Build commands below run as your normal user, sequentially; they share
one project-local Void build container.
```sh
make bootstrap
make smoke-test
make check
```
Bootstrap prepares pinned Rust and XBPS tools and the Void build container.
The smoke test checks the AArch64 build toolchains; `make check` validates the
build guardrails. Stop and resolve any failed command before continuing. Logs
are in `out/logs/`. Repeated preparation reuses verified downloads and caches.
## Build the images
```sh
make rootfs PROFILE=cli
make system-card PROFILE=cli
make initramfs
make workstation
```
The rootfs build includes the kernel and base packages. These are the three
inputs needed by the emulator:
| File | Purpose |
| --- | --- |
| `out/kernel/boot/kernel_2712.img` | ARM kernel |
| `out/fds-initramfs.img` | Early startup and SYSTEM discovery |
| `out/fds-system-cli.img` | Read-only operating system cartridge |
`make rootfs PROFILE=development` followed by `make system-card PROFILE=development`
builds a SYSTEM with compilers, Git, Vim, debuggers and display diagnostics.
Both profiles include WindowMaker and the Dasung daemon. The desktop starts on
request. `make all` also assembles recovery, boot and internal-storage images;
see [internal installation](internal-storage.md) when preparing a physical machine.
## Start the emulator
With the project-local QEMU tools:
```sh
./tools/in-void xbps-install -y qemu-img
out/workstation/fds-emulator --session out/my-emulator start --qemu-runner tools/in-void
out/workstation/fds-emulator --session out/my-emulator console
```
If QEMU is installed directly on the workstation, omit `--qemu-runner`.
The session directory must be new. At the `FDS>` prompt:
```sh
fds info
fds bays
fds --help
```
The shell runs as the ordinary `fds` user. Press **Ctrl-]** to detach from the
console; the VM remains running. Stop it from the workstation:
```sh
out/workstation/fds-emulator --session out/my-emulator stop
```
Continue with [building and inserting software cartridges](workstation.md).
That guide also covers using copied boot images, DATA overlays and USB writing.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

-240
View File
@@ -1,240 +0,0 @@
# Internal storage and machine settings
[Documentation index](README.md) · [Boot images](developer/boot.md) · [Recovery](recovery.md)
Internal storage contains boot files, an independent recovery system and
persistent machine settings. The operating system itself lives on a removable
SYSTEM cartridge. This guide covers preparing the internal disk and updating
its settings from recovery.
## Build the complete disk image
Build the component images first, in this order:
```sh
make kernel
make initramfs
make boot-volume BOOT_MODE=production
make recovery
make internal-image
```
The result is `out/fds-internal.img`, a **complete GPT disk image**. Its versioned
build directory contains `layout.json`, source payload checksums, and readback
verification. This differs from `out/fds-boot.img` (a raw FAT partition) and
`out/fds-recovery.img` (a raw EROFS partition).
| Partition | Format | Default allocation | Purpose |
| --- | --- | --- | --- |
| 1: FDS_BOOT | FAT32, EFI system type | 512 MiB | Pi firmware, kernel, DTBs, configuration and initramfs |
| 2: FDS_RECOVERY | EROFS | 1 GiB | Complete independent maintenance system |
| 3: FDS_INTERNAL | ext4 | 256 MiB | Machine settings and explicitly saved diagnostics |
The builder pads the recovery partition without changing its EROFS contents,
aligns partitions to 1 MiB, writes both GPT copies, verifies each payload, and
checks the table independently with `sfdisk`. Internal ext4 is fully initialized
before deployment. It contains root-owned `config/` and private `diagnostics/`.
Unused capacity beyond this initial layout is not automatically expanded.
For a 2 GiB recovery allocation or larger settings partition:
```sh
./image/build-internal --recovery-mib 2048 --internal-mib 512
```
## Install the internal disk
Use an NVMe enclosure or another Linux machine that can access the target drive
while the Pi is off. First verify a downloaded release using a separately trusted
public key as described in [Release signatures](releases.md). Its complete disk
is named `fds-internal-0.1.0.img`; a local build uses `out/fds-internal.img`.
Use the complete disk image, not the separate BOOT or RECOVERY payload.
List disks before and after connecting the intended drive:
```sh
lsblk -d -o NAME,PATH,MODEL,SERIAL,SIZE,TRAN,LOG-SEC
ls -l /dev/disk/by-id/
```
Match the physical model, serial and capacity. Choose a persistent **whole-disk**
`/dev/disk/by-id/` path without a `-partN` suffix; never guess a `/dev/sdX` name.
Unmount every target partition and disable any swap on it. Do not select the
workstation's system disk. The current images require 512-byte logical sectors
and a disk at least as large as the image; 4 KiB logical-sector media is not
supported by this layout.
Open Bash (`bash`), replace both paths below, then run the block. It checks the
selected disk again, requires a typed confirmation, writes and reads back the
complete image, and relocates the backup GPT when the disk is larger than the
image. It does not expand any partition or filesystem.
```bash
(
set -euo pipefail
fds_image="$PWD/out/fds-internal.img"
fds_disk=/dev/disk/by-id/REPLACE_WITH_THE_TARGET_DISK
[[ -f "$fds_image" && -s "$fds_image" && -b "$fds_disk" ]]
[[ $(lsblk -dnro TYPE "$fds_disk") == disk ]]
[[ $(sudo blockdev --getss "$fds_disk") == 512 ]]
fds_bytes=$(stat -Lc %s "$fds_image")
(( $(sudo blockdev --getsize64 "$fds_disk") >= fds_bytes ))
lsblk -p -o NAME,TYPE,MODEL,SERIAL,SIZE,MOUNTPOINTS "$fds_disk"
if lsblk -nrpo MOUNTPOINTS "$fds_disk" | grep '[^[:space:]]' >/dev/null; then
echo 'Target has mounted filesystems or swap; stop and release them first.' >&2
exit 1
fi
if lsblk -nrpo TYPE "$fds_disk" | grep -Ev '^(disk|part)$' >/dev/null; then
echo 'Target has device-mapper or other active descendants; stop.' >&2
exit 1
fi
read -r -p "Type ERASE $fds_disk to erase this disk: " fds_confirmation
[[ "$fds_confirmation" == "ERASE $fds_disk" ]]
sudo dd if="$fds_image" of="$fds_disk" bs=4M conv=fsync status=progress
sudo blockdev --flushbufs "$fds_disk"
sudo cmp -n "$fds_bytes" "$fds_image" "$fds_disk"
sudo sfdisk --lock=yes --relocate gpt-bak-std "$fds_disk"
sudo sfdisk --verify "$fds_disk"
sudo blockdev --flushbufs "$fds_disk"
)
```
Every command must succeed. The byte comparison occurs before relocating GPT,
because relocation intentionally changes disk-table headers. GNU `dd`'s `fsync`
flushes output before it returns; `sfdisk`'s `gpt-bak-std` moves the backup header
to the end of the target. See the [GNU dd manual](https://www.gnu.org/s/coreutils/manual/html_node/dd-invocation.html)
and [sfdisk manual](https://man7.org/linux/man-pages/man8/sfdisk.8.html).
These commands use the already documented coreutils, diffutils and util-linux
host tools; they do not add a target daemon.
Confirm that the disk shows `FDS_BOOT`, `FDS_RECOVERY` and `FDS_INTERNAL` in that
order using `lsblk -o NAME,PARTLABEL,FSTYPE,MOUNTPOINTS`. Unmount anything the
desktop automatically mounted, then safely disconnect the enclosure and install
the NVMe in the powered-off Pi. Follow [EEPROM preparation](eeprom.md) for the
separately reviewed NVMe boot settings.
Prepare the **first SYSTEM cartridge on the workstation** using the same guarded
write block, with its two path assignments changed to the chosen SYSTEM image
and a different, empty USB cartridge disk. For a local build select
`out/fds-system-cli.img` or `out/fds-system-development.img`; release files are
`fds-system-cli-0.1.0.img` and `fds-system-development-0.1.0.img`. They are
alternative complete GPT images, each containing one `FDS_SYSTEM` partition.
Readback and backup-GPT relocation apply to this disk too. Check its partition
label, safely disconnect it, and insert exactly one SYSTEM cartridge in the Pi
before normal boot. Subsequent cartridge creation and updates can use FDS's
confirmed [media workflow](developer/media-tools.md) after bay calibration.
## Configure the machine before building
Copy `config/machine/` to your own directory. It contains three files:
- `machine.toml`: `format = 1` and a short human-readable `name`.
- `bays.toml`: the measured controller/port map described in
[Cartridges and bay calibration](cartridges.md).
- `hardware-catalog.toml`: optional USB identification names using the same schema
as the base catalog. Entries are data and cannot run commands.
The supplied bay map is empty. Record actual controller/port identities for
each physical slot before installing settings. USB 2 and USB 3 companion ports
need explicit aliases for the same bay.
```sh
cp -a config/machine out/my-machine
# Edit the three files in out/my-machine using your editor.
make internal-image MACHINE_CONFIG=out/my-machine
```
The builder compiles a native host copy of `fds` and uses the same strict parser
as the target system. You can also validate or pack settings yourself:
```sh
cargo build --locked --offline --release --target x86_64-unknown-linux-gnu -p fds-cli
./target/x86_64-unknown-linux-gnu/release/fds machine validate out/my-machine
./target/x86_64-unknown-linux-gnu/release/fds machine pack out/my-machine out/my-machine.json
```
The three source files become one atomic `config/machine.json` document on
FDS_INTERNAL. Names, lengths, bay aliases, catalog fields and unknown keys are
validated before use. Do not put passwords or private keys in this configuration:
its active snapshot is readable by the local FDS user.
## What happens at boot
The `machine-config` native s6 oneshot precedes `cartridged`. It does **not**
precede the console or Dasung controller. It accepts exactly one non-removable
NVMe disk with the three named partitions in the order above. USB lookalikes are
ignored; multiple eligible NVMe disks are rejected instead of choosing by name.
The settings partition must be clean ext4 with the expected label. Loading uses
`ro,noload,nosuid,nodev,noexec` in a private mount namespace, validates the entire
settings bundle, copies it into `/run/fds/machine/`, and unmounts. No filesystem
repair, journal replay, cache compilation or persistent write occurs during
normal boot. The cartridge service uses that snapshot throughout this boot,
including after a service restart.
If internal storage is missing, unclean, invalid or ambiguous, the service records
an explanation and uses the immutable image's `/etc/fds/` defaults. The console
still opens. Check the actual source before treating bays as calibrated:
```sh
fds machine status
fds --json machine status
fds machine export /tmp/current-machine
```
Export creates a new directory with the three editable source files. It never
overwrites an existing directory. The status source is `internal_nvme` or
`image_defaults`. A temporarily unavailable NVMe is not adopted later in the same
boot; resolve the issue and reboot to load its settings. This prevents changing
bay identities underneath active cartridge operations.
## Update settings from recovery
Bring the edited source directory on a DATA cartridge. At the local root
`RECOVERY#` console, identify its bay with `fds bays`; healthy DATA is mounted
read-only under `/run/fds/media/NN`. For example, if it is in BAY 02:
```sh
fds machine validate /run/fds/media/02/my-machine
fds machine install /run/fds/media/02/my-machine
fds reboot
```
Installation is restricted to root in the recovery image. It writes the complete
validated bundle atomically, saves the old bytes as `config/previous.json`,
flushes and unmounts the internal filesystem, and reports success only after
those steps. A reboot activates the new settings. The currently running bay map
is unchanged, so active media does not move to a different bay mid-operation.
Keep a copy of your previous source directory on DATA or the build host to
reinstall it if the new calibration is wrong.
An invalid existing JSON document can be replaced this way. Wrong ownership,
symlinks, an unclean filesystem or a damaged directory require offline filesystem
maintenance first. Recovery does not automatically repair internal NVMe; its
`fds recovery repair` command is deliberately limited to DATA cartridges.
## Save and retrieve diagnostics
Logs and boot records stay in RAM by default. Root may explicitly save a file
of up to 16 MiB. Saved names cannot contain paths, and existing names are refused.
These operations mount internal ext4 only for the operation and then unmount it.
For example, from recovery:
```sh
fds --json boot-profile >/tmp/boot.json
fds machine store boot-first.json /tmp/boot.json
fds --json bays >/tmp/cartridge-inventory.json
fds machine store cartridges-first.json /tmp/cartridge-inventory.json
bash /usr/share/fds/capture-hardware /tmp/hardware-capture
tar -C /tmp -czf /tmp/hardware-capture.tar.gz hardware-capture
fds machine store hardware-first.tar.gz /tmp/hardware-capture.tar.gz
fds machine fetch boot-first.json /tmp/retrieved-boot.json
```
Use distinct names for subsequent sessions. A failed flush or unmount is an
error, not a successful save. These are machine diagnostics; do not use this
facility as ordinary user storage. The saved cartridge inventory is a persistent
diagnostic snapshot, including metadata already inspected during this boot.
Retrieve it with `fds machine fetch cartridges-first.json /tmp/saved-inventory.json`.
The daemon's live metadata cache remains volatile and is rebuilt from currently
attached devices; saved snapshots are never used to authorize media actions.
-176
View File
@@ -1,176 +0,0 @@
# Recovery and rollback
[Documentation index](README.md) · [Boot images](developer/boot.md) · [Write cartridges](workstation.md#write-a-cartridge-to-usb) · [EEPROM](eeprom.md)
FDS recovery is an independent maintenance system on internal storage. It can
inspect a failed SYSTEM, check DATA and help prepare replacement cartridges.
## Build the recovery image
Use the prepared x86_64 build host described in [Your first build](getting-started.md).
From the repository root, run these commands sequentially:
```sh
make recovery
```
`make recovery` builds the `recovery` rootfs profile independently, configures all
packages and caches at image construction time, then creates EROFS. Its outputs
are:
| File | Meaning |
| --- | --- |
| `out/rootfs-recovery.tar` | Complete recovery rootfs, including package database and compiled s6 configuration |
| `out/fds-recovery.img` | Raw EROFS payload for the internal GPT partition named `FDS_RECOVERY` |
| `out/recovery-build.*/manifest.json` | Rootfs hash, filesystem hash, profile and size |
| `out/manifests/rootfs-latest/` | Exact selected package archives and build input records |
The latest-rootfs pointers also select this recovery build. Profile-specific
rootfs pointers preserve earlier CLI/development builds. A recovery EROFS payload
is not a whole-disk image and is not a SYSTEM cartridge. The SYSTEM builder
rejects a recovery rootfs to prevent confusing those roles.
## Enter recovery
Stage0 can enter recovery in two ways:
1. At its missing-SYSTEM or ambiguous-SYSTEM console, type `recovery` and Enter.
2. For a deliberate maintenance boot, use `fds.boot=recovery` in the boot
partition's single-line `cmdline.txt`, replacing `fds.boot=normal` if present.
Restore normal mode when maintenance is complete.
Stage0 requires exactly one readable `FDS_RECOVERY` partition and mounts it
read-only. It does not silently choose between duplicate partitions.
The local prompt is:
```text
FDS RECOVERY — LOCAL MAINTENANCE CONSOLE
RECOVERY#
```
This is an explicit **root maintenance console**. It reads startup files from the
immutable recovery image and uses a temporary home under `/run/fds`. The root
password remains locked; no SSH or network login service is started. Normal CLI
and development images continue to use the ordinary `fds` account and `FDS>`.
Recovery inspects cartridges read-only. It does not automatically mount DATA
writable, activate an ENVIRONMENT desktop, or enable Ethernet. Those actions
require explicit commands. The Dasung controller remains in the base boot bundle.
## Inspect a failed system
```sh
fds info
fds bays
fds bay 2
fds inspect BAY02
fds topology
fds boot-profile
lsusb -t
lsblk -o NAME,MAJ:MIN,MODEL,SERIAL,SIZE,RO,TYPE,FSTYPE,PARTLABEL,MOUNTPOINTS
dmesg
```
Use the calibrated bay number and the displayed model/serial to identify a
cartridge. Do not infer a bay from `/dev/sda` or from discovery order. A valid
inactive SYSTEM mounts read-only under `/run/fds/media/NN`; `fds bay N` reports
the actual path. A bad filesystem or manifest produces an error instead of
running anything from the cartridge.
## Check and repair DATA
Start with a read-only check. For example, for DATA in bay 2:
```sh
fds recovery check BAY02
```
This command is available only to root in the recovery image. It identifies one
USB disk and one `FDS_DATA` partition, unmounts its read-only view, reserves the
disk exclusively, verifies GPT and the kernel partition identity, and invokes
`e2fsck -f -n`. It does not repair the filesystem. A clean result leaves DATA
unmounted and reports `SAFE TO REMOVE`.
If the check reports problems, review its log and preserve a backup where
possible. Preview the repair with:
```sh
fds recovery repair BAY02
```
The preview displays the model, serial, capacity and a confirmation command.
Copy that exact command only after checking the intended cartridge. Its token
binds the bay, kernel insertion number and current boot. A token from another
insertion or boot is rejected. The preview makes no filesystem changes.
Confirmed repair runs `e2fsck -f -p`, which performs conservative automatic
repairs and stops when manual judgement is required. It then flushes the device,
invalidates its block cache, and runs a second `e2fsck -f -n`. Only a successful
verification produces `DATA REPAIRED AND VERIFIED` and `SAFE TO REMOVE`.
Failed or interrupted checks/repairs retain a quarantine record across cartridge
daemon restarts for the same insertion. They do not inherit an earlier SAFE
status. The checker holds the disk reservation and is killed if its supervising
daemon dies. The command runs synchronously: bay operations and orderly shutdown
wait for it to finish; the local shell remains usable. A client timeout is not
success and does not authorize removing media. Inspect the log and wait for the
service to finish before retrying. Logs are in `/run/fds/recovery/` and are
limited to 16 MiB per invocation.
This narrow recovery command requires a readable GPT and primary ext filesystem
signature. It deliberately does not guess partition boundaries, recreate a
broken GPT, or force answers to destructive `e2fsck` questions. Such cases require
an offline backup and expert use of the included filesystem tools. It cannot
recover data that was never written to storage.
For structured results, prefix the command with `fds --json`. After a successful
check or repair, remove/reinsert DATA to inspect it again. Recovery still keeps
it read-only until `fds data use N` is explicitly requested.
## Prepare replacement SYSTEM media
A known-good SYSTEM image must be supplied on separate source media. Do not use
a damaged image as an update source. For example, source DATA in bay 2 and an
unmounted replacement cartridge in bay 4:
```sh
fds bay 2
fds inspect image /run/fds/media/02/fds-system-cli.img
fds inspect BAY04
fds burn system /run/fds/media/02/fds-system-cli.img BAY04
```
Use the actual source mount shown by `fds bay 2`. If the destination already
contains a mounted cartridge, run `fds eject 4` first. The burn preview identifies
the source checksum, destination capacity/model/serial and confirmation command.
Follow [Media creation and writing](developer/media-tools.md) for confirmation and status commands.
Completion requires device flush, readback and GPT verification. Recovery uses
the same protected writer as the main system: it refuses mounted destinations,
active root storage and disks containing internal FDS partition names.
Keep the previous known-good SYSTEM cartridge. After a successful replacement
write, use `fds poweroff`, swap SYSTEM while powered off, and boot normally.
Rollback means restoring the previous SYSTEM cartridge; it does not undo DATA
file changes, application migrations, internal boot updates or EEPROM changes.
## Capture diagnostics and shut down
```sh
bash /usr/share/fds/capture-hardware /tmp/recovery-capture
cat /tmp/recovery-capture/status.tsv
fds power status
fds poweroff
```
The collector records identities, USB topology, mounts, kernel messages, packages
and boot events with checksums. Its output is temporary unless copied to a
healthy DATA cartridge after explicitly activating that DATA with `fds data use N`.
Do not activate the damaged cartridge merely to save a report. Eject writable
DATA after copying, or let `fds poweroff` perform the normal verified shutdown.
See [Shutdown](developer/power.md) when shutdown reports a blocking DATA error.
Recovery is independent of SYSTEM, but it still depends on working internal
boot storage, the kernel and firmware. An external rescue medium is required
when that layer fails. EEPROM preparation and rollback are described separately
in [EEPROM configuration](eeprom.md); no EEPROM is changed by these recovery tools.
-43
View File
@@ -1,43 +0,0 @@
# Verify a release
Verify a downloaded release before writing its images to a cartridge or internal
disk. Verification checks the release manifest signature and the length and
SHA-256 of every listed artifact. Obtain the signer's public key through a
trusted channel independent of the download.
On a workstation, build the verifier with `make signing` after bootstrap. On FDS,
`fds-release` is included in the base system and recovery.
## Verify before using images
Obtain the signer's public key through a channel you already trust, or compare
its fingerprint against a separately authenticated value. A public key included
beside an untrusted download does not establish its identity by itself.
On FDS, including recovery:
```sh
fds-release verify /data/downloads/fds-release --key /data/keys/fds-release.pub
```
On the workstation, use the host executable instead:
```sh
target/x86_64-unknown-linux-gnu/release/fds-release verify \
/path/to/release-directory --key /path/to/trusted-fds-release.pub
```
Signature verification is an explicit pre-installation step. It is not enforced
by Pi firmware or stage0 during boot.
Success reports `VERIFIED` and identifies the release. Failure returns a nonzero
exit code with an explanation. Keep files unchanged between verification and
writing. Files absent from the signed manifest are outside its scope.
The cartridge writer separately checks image layout, destination identity and
readback. Release verification does not install images; follow the
[USB-writing workflow](workstation.md#write-a-cartridge-to-usb) or
[internal installation](internal-storage.md).
Release maintainers can find key creation, signing and format details in the
[developer reference](developer/releases.md).
-107
View File
@@ -1,107 +0,0 @@
# Software cartridge format
A PROGRAM cartridge is a complete GPT disk image with one EROFS metadata partition
and one or more EROFS payload partitions. New media uses catalogue format 2 and
stores installed Void package trees. Source recipes and build tools remain on
the workstation; FDS executes the installed files directly.
## Software source recipe
```toml
format = 2
id = "example.hello"
name = "Hello"
version = "1.0"
[commands]
hello = "usr/bin/hello"
[source]
package = "hello"
```
`package` names a source package in the selected `void-packages` checkout. For a
custom package, also set `template = "void"`: that directory must contain a
normal Void `template` and any `files/` or `patches/` directories it uses. Paths
are relative to this recipe. The [included hello package](../examples/software/hello/void/template)
is a complete example.
Custom sources are copied into an untracked `srcpkgs/PACKAGE` directory. Existing
tracked Void sources are never overwritten. If a generated source copy differs,
inspect it, preserve any independent edits, then remove that generated copy and
retry. An unchanged copy can be reused.
`fds-cartridge` builds with `xbps-src -a aarch64`, installs the resulting package
and runtime dependencies into a fresh tree, and records installed XBPS versions.
Use package dependency declarations to request runtime libraries and utilities.
Each software tree is self-contained with respect to its installed dependencies;
there is no cross-cartridge package resolver.
Software IDs and public command names use lowercase ASCII letters, digits,
periods, hyphens and underscores, with a maximum length of 64. Command paths are
relative to the installed root and must resolve to executable files within it.
## Cartridge recipe
```toml
format = 2
id = "example.tools"
name = "Example tools"
version = "1.0"
[[payload]]
sources = ["hello/software.toml", "report/software.toml"]
```
Each payload section creates one partition. Sources may be recipe paths or
previously built directories containing `software.toml` and `root/`. The metadata
partition records which software belongs to which partition. Software IDs and
cartridge identity must satisfy the metadata validation rules.
## On-disk contents
```text
Partition 1: FDS_METADATA
FDS/CARTRIDGE.TOML
FDS/SOFTWARE.TOML
Partition 2: FDS_PAYLOAD02
programs/example.hello/usr/bin/hello
programs/example.hello/usr/lib/...
programs/example.hello/var/db/xbps/...
programs/example.report/...
```
The software catalogue records identity, architecture, commands, partition,
installed package versions, entry count, byte count and SHA-256. `installed=true`
selects the direct-tree contract. The retained `unpacked_bytes` field records
installed file bytes; it does not imply an archive or guest extraction.
The tree digest covers sorted paths, entry types, modes, contents and symlink
targets. Root-relative package symlinks are relocated to relative in-tree links
when building. Escaping links, special files, privileged/writeable executable
trees, wrong-architecture ELF files and inconsistent metadata are rejected.
Current per-tree bounds are 1 GiB of file data and 65,536 entries.
## Runtime behavior
FDS verifies the cartridge's geometry, identity, inventory and tree integrity
on insertion. Payloads are mounted read-only with `nosuid,nodev`. No software
command runs automatically. Published command aliases appear in `/run/fds/bin`.
Programs run as UID/GID 1000 in the bay's managed process group. `FDS_APP` names
the installed tree; PATH, library paths and data search paths include its
`usr/bin`, `usr/lib` and `usr/share`. Dynamically linked ELF entry points use the
package tree's own loader. Programs that hard-code unrelated system paths still
need to be compatible with FDS; the tree is not a container or a writable root.
Safe eject stops consumers and releases every payload before reporting SAFE.
A digest detects corruption, not publisher identity: choose software sources
you trust. Distribution signatures are covered by [release verification](releases.md).
## Older media
The reader retains support for catalogue format 1 with xz payloads and legacy
single-partition PROGRAM cartridges containing `app/`. Old xz payloads retain
their temporary extraction behavior and limits. Creation commands produce only
the installed-tree format. Rebuild old software from its Void source recipe to
use direct execution.
-37
View File
@@ -1,37 +0,0 @@
# Troubleshooting
Read the complete command error and its log before retrying. Build logs are in
`out/logs/`; emulator `console.log` and `qemu.log` are in the selected session.
On FDS, `fds bay N`, `fds profiles` and `fds power status` report the relevant
operation state. Append `--help` to a command for accepted options.
| Symptom | Resolution |
| --- | --- |
| Build disk is full | Stop builds and VMs; run `make clean-preview`, then `make clean`. See [retention rules](cleanup.md). |
| Missing host utility | Install the named utility or select the documented image/XBPS/QEMU runner. `doctor` checks image and emulator prerequisites. |
| Bubblewrap/user namespace failure | Check that the host permits unprivileged namespaces. Run the check in [setup](getting-started.md); do not switch software builds to root. |
| Void checkout is unprepared | Bootstrap the checkout using upstream xbps-src instructions, then select it with `--void-packages`. |
| Stale generated overlay/source package | Compare the source and generated copy. Preserve independent edits, remove only that generated copy, and retry. Never overwrite tracked upstream templates. |
| Output already exists | Use a new output directory, cartridge image, preview or emulator session. Creation does not overwrite existing outputs. |
| Executable architecture or tree integrity error | Rebuild the source package and cartridge. FDS cannot run an invalid payload. |
| A command is unavailable | Wait for `MOUNTED READ ONLY`, inspect `fds --json bay N`, and use its qualified command alias. Run `hash -r` after removal if Bash retained an old path. |
| Empty or unconfigured physical bays | Run `fds topology` and calibrate the machine's bay map. Do not infer slots from disk names. |
| Duplicate cartridge ID | Give the second cartridge a distinct identity and rebuild it. |
| Console already in use | Detach the emulator console with Ctrl-] before `guest`, safe eject or shutdown. |
| Guest command timed out | Inspect the console log. It may still be running; do not repeat a write until its state is known. |
| Desktop activation fails | Inspect `fds profiles` and `/run/log/xserver/current`, `/run/log/desktop/current`. Check the display configuration. |
| Control panel cannot connect | Check `/run/log/cartridged/current` and that the cartridge service is running. The panel reconnects automatically. |
| Eject is blocked | Close programs, shells and extra mounts using the cartridge, then retry. Wait for SAFE before removal. |
| Shutdown is blocked | Read `fds power status`; resolve the reported storage problem and retry poweroff. `fds power resume` cancels a recoverable pending shutdown. |
| DATA needs checking | Use the [recovery workflow](recovery.md); preserve a backup before repair where possible. |
To inspect cartridge service output on FDS:
```sh
tail -80 /run/log/cartridged/current
fds --json bay 01
```
To inspect a stopped emulator, read its saved logs directly on the workstation.
Rebooting uses a new session directory; existing DATA overlays remain in the old
session and can be [exported](workstation.md#save-emulator-data).
-232
View File
@@ -1,232 +0,0 @@
# 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 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. 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
prepares `vendor/void-packages` and `.host/xbps/usr/bin`. 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-<bay>-<id>.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.
Submodule
+1
Submodule fds-os.wiki added at 51edcf8714
+1 -1
View File
@@ -99,4 +99,4 @@ for name, source in [('fds-boot.img', work.name+'/boot.fat'), ('boot-volume', wo
target.symlink_to(source)
target.replace(project/'out'/name)
print(f'PASS: 512 MiB FDS_BOOT FAT32 partition image, all files verified: {work}')
print('SKIP: firmware boot, NVMe and display mode validation require a physical Pi')
print('SKIP: firmware boot, native SD and display mode validation require a physical Pi')
+1 -1
View File
@@ -21,7 +21,7 @@ args = parser.parse_args()
payloads = {'fds-stage0': args.stage0.resolve(strict=True), 'dasungd': project/'out/dasungd'}
for tool in ('lz4', 'zstd'):
if not shutil.which(tool): sys.exit(f'ERROR: missing host {tool}; see docs/developer/m4-work.md')
if not shutil.which(tool): sys.exit(f'ERROR: missing host {tool}; see fds-os.wiki/Developer-M4-Work.md')
for executable in ('fds-stage0', 'dasungd'):
subprocess.run([str(project/'tools/verify-elf'), str(payloads[executable]), 'aarch64', 'static'], check=True)
epoch = int(subprocess.check_output(['git', '-C', str(project/'vendor/void-packages'), 'show', '-s', '--format=%ct', 'HEAD']))
+3 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Create a complete internal NVMe GPT image as a new ordinary file."""
"""Create a complete internal SD/NVMe GPT image as a new ordinary file."""
import argparse
import json
import os
@@ -93,5 +93,5 @@ if args.output_directory is None:
link = project / 'out/fds-internal.img.next'
link.symlink_to(work.name + '/internal.img')
link.replace(project / 'out/fds-internal.img')
print(f'PASS: complete internal NVMe image, three verified GPT payloads: {work}')
print('SKIP: no physical disk was written; Pi firmware/NVMe boot requires hardware')
print(f'PASS: complete internal SD/NVMe image, three verified GPT payloads: {work}')
print('SKIP: no physical disk was written; Pi firmware/SD boot requires hardware')
+6 -1
View File
@@ -3,6 +3,7 @@
import argparse
import io
import json
import shlex
from pathlib import Path
import subprocess
import sys
@@ -12,6 +13,7 @@ import uuid
project = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(project/'tools'))
from fds_version import validate
from image_formats import digest, gpt, LINUX_FILESYSTEM, NAMESPACE
parser = argparse.ArgumentParser(description=__doc__)
@@ -21,6 +23,8 @@ parser.add_argument('--output-directory', type=Path)
args = parser.parse_args()
rootfs = args.rootfs.resolve(strict=True)
with tarfile.open(rootfs) as archive:
identity = dict(line.split('=', 1) for line in shlex.split(archive.extractfile('usr/lib/os-release').read().decode(), comments=True))
image_version = validate(identity['VERSION_ID'])
embedded = archive.extractfile('usr/share/fds/image-profile').read(64).decode().strip()
try:
root = archive.getmember('.')
@@ -34,7 +38,7 @@ args.profile = embedded
work = args.output_directory or Path(tempfile.mkdtemp(prefix='system-build.', dir=project/'out'))
if not work.is_dir() or any(work.iterdir()): parser.error('output directory must exist and be empty')
epoch = int(subprocess.check_output(['git', '-C', str(project/'vendor/void-packages'), 'show', '-s', '--format=%ct', 'HEAD']))
manifest = f'''format = 1\n[cartridge]\nid = "fds.system.{args.profile}"\nname = "FDS/OS {args.profile.upper()}"\nclass = "system"\nversion = "0.1.0"\n[media]\nwritable = false\n'''.encode()
manifest = f'''format = 1\n[cartridge]\nid = "fds.system.{args.profile}"\nname = "FDS/OS {args.profile.upper()}"\nclass = "system"\nversion = "{image_version}"\n[media]\nwritable = false\n'''.encode()
with tarfile.open(rootfs) as source, tarfile.open(work/'system.tar', 'w', format=tarfile.PAX_FORMAT) as output:
for member in source:
if member.name in ('FDS', 'FDS/CARTRIDGE.TOML'): continue
@@ -57,6 +61,7 @@ layout = gpt(work/'system.img', [('FDS_SYSTEM', LINUX_FILESYSTEM, work/'system.e
layout['rootfs_sha256'] = digest(rootfs)
layout['image_sha256'] = digest(work/'system.img')
layout['profile'] = args.profile
layout['version'] = image_version
(work/'layout.json').write_text(json.dumps(layout, indent=2)+'\n')
# An independent implementation checks the actual on-disk partition table.
observed = json.loads(subprocess.check_output(['sfdisk', '--json', str(work/'system.img')]))['partitiontable']
+1 -2
View File
@@ -1,9 +1,8 @@
# FDS/OS Raspberry Pi 5: internal NVMe supplies boot files.
# FDS/OS Raspberry Pi 5: native microSD supplies boot files.
[pi5]
arm_64bit=1
kernel=kernel_2712.img
initramfs fds-initramfs.img followkernel
dtparam=pciex1
dtoverlay=vc4-kms-v3d,noaudio
camera_auto_detect=0
display_auto_detect=0
+1 -1
View File
@@ -1 +1 @@
FDS/OS 0.1
FDS/OS @FDS_VERSION@
+3 -3
View File
@@ -1,6 +1,6 @@
NAME="FDS/OS"
PRETTY_NAME="FDS/OS 0.1 (M2 development rootfs)"
PRETTY_NAME="FDS/OS @FDS_VERSION@"
ID=fds
ID_LIKE=void
VERSION_ID=0.1
VERSION="0.1 (M2)"
VERSION_ID="@FDS_VERSION@"
VERSION="@FDS_VERSION@"
+2 -1
View File
@@ -1,5 +1,5 @@
pkgname=fds-base-files
version=0.1.0
version=$(cat "${XBPS_DISTDIR}/fds-version")
revision=1
archs="aarch64"
depends="xbps-triggers"
@@ -22,6 +22,7 @@ do_install() {
vinstall "${FILESDIR}/${f}" 600 etc
done
vinstall "${FILESDIR}/os-release" 644 usr/lib
sed -i "s/@FDS_VERSION@/${version}/g" "${DESTDIR}/usr/lib/os-release" "${DESTDIR}/etc/issue"
ln -s ../usr/lib/os-release "${DESTDIR}/etc/os-release"
ln -s /run/fds/resolv.conf "${DESTDIR}/etc/resolv.conf"
ln -s /proc/self/mounts "${DESTDIR}/etc/mtab"
+1 -1
View File
@@ -1,6 +1,6 @@
# Explicit FDS userspace; never depend on Void's base-system metapackage.
pkgname=fds-base
version=0.1.0
version=$(cat "${XBPS_DISTDIR}/fds-version")
revision=1
archs="aarch64"
metapackage=yes
+1 -1
View File
@@ -1,3 +1,3 @@
# Intentionally uncalibrated: no physical Pi wiring has been measured.
# Use fds topology, then follow docs/cartridges.md to assign controller/ports.
# Use fds topology, then follow fds-os.wiki/Cartridges.md to assign controller/ports.
# USB 2 and USB 3 companion roots need explicit aliases for the same bays.
+1 -1
View File
@@ -1,5 +1,5 @@
pkgname=fds-cartridged
version=0.1.0
version=$(cat "${XBPS_DISTDIR}/fds-version")
revision=1
archs="aarch64"
nostrip=yes
+1 -1
View File
@@ -1,6 +1,6 @@
# Static payloads are built and checked on the host before packaging.
pkgname=fds-cli
version=0.1.0
version=$(cat "${XBPS_DISTDIR}/fds-version")
revision=1
archs="aarch64"
nostrip=yes
+1 -1
View File
@@ -1,6 +1,6 @@
# FDS-owned overlay. The build helper supplies the verified static ARM payload.
pkgname=fds-dasungd
version=0.1.0
version=$(cat "${XBPS_DISTDIR}/fds-version")
revision=1
archs="aarch64"
nostrip=yes
+1 -1
View File
@@ -1,5 +1,5 @@
pkgname=fds-eink
version=0.1.0
version=$(cat "${XBPS_DISTDIR}/fds-version")
revision=1
archs="aarch64"
depends="xorg-server xf86-input-libinput WindowMaker terminus-font xterm xset xsetroot fontconfig"
+1 -1
View File
@@ -1,6 +1,6 @@
# Image-time s6 configuration. The native upstream binaries own PID 1.
pkgname=fds-init
version=0.1.0
version=$(cat "${XBPS_DISTDIR}/fds-version")
revision=1
archs="aarch64"
depends="iproute2 s6-linux-init s6-rc s6 execline coreutils util-linux bash eudev fds-dasungd fds-cli fds-cartridged"
+10
View File
@@ -33,6 +33,16 @@ CONFIG_PINCTRL_RP1=y
CONFIG_COMMON_CLK_RP1=y
CONFIG_BCM2712_IOMMU=y
CONFIG_BLK_DEV_NVME=y
# Native Pi SD must be available before modules can be read from SYSTEM.
CONFIG_MMC=y
CONFIG_MMC_BLOCK=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_PLTFM=y
CONFIG_MMC_SDHCI_IPROC=y
CONFIG_MMC_SDHCI_BRCMSTB=y
CONFIG_MMC_BCM2835=y
# Generic PCI SDHCI exercises native MMC discovery in the ARM VM fixture.
CONFIG_MMC_SDHCI_PCI=y
CONFIG_HID=y
CONFIG_HID_GENERIC=y
CONFIG_USB_HID=y
+56
View File
@@ -0,0 +1,56 @@
# Local source build: invoked by tools/build-workstation with an explicit checkout.
pkgname=fds-tools
_fds_source=${FDS_SOURCE_DIR:?Run make workstation from the FDS checkout}
pkgver=$("$_fds_source/tools/version")
pkgrel=1
pkgdesc='FDS/OS workstation cartridge builder, disk flasher and twelve-bay ARM emulator'
arch=('x86_64' 'aarch64')
license=('MIT')
depends=('glibc' 'gcc-libs' 'xz')
makedepends=('rust' 'python' 'git')
optdepends=(
'bubblewrap: cartridge creation and inspection in a private namespace'
'erofs-utils: create and inspect cartridge payload filesystems'
'e2fsprogs: create DATA images with fds-flash'
'qemu-system-aarch64: run FDS in the emulator'
'qemu-img: writable emulator DATA overlays'
)
options=('!debug' '!strip')
build() {
cd "$_fds_source"
./tools/build-workstation-binaries
# Includes the notices of locked dependencies; this is an intentionally
# complete workspace inventory rather than a claim they all link into tools.
if [[ -f "$srcdir/RUST-NOTICES.txt" ]]; then rm "$srcdir/RUST-NOTICES.txt"; fi
python3 tools/rust-notices "$srcdir/RUST-NOTICES.txt"
}
check() {
local tool
for tool in fds-cartridge fds-emulator fds-flash; do
[[ $("$_fds_source/out/workstation/$tool" --version) == "$tool $pkgver" ]]
"$_fds_source/out/workstation/$tool" --help >/dev/null
done
}
package() {
local tool
for tool in fds-cartridge fds-emulator fds-flash; do
install -Dm755 "$_fds_source/out/workstation/$tool" "$pkgdir/usr/bin/$tool"
done
install -Dm644 "$_fds_source/LICENSE" "$pkgdir/usr/share/licenses/fds-tools/LICENSE"
install -Dm644 "$srcdir/RUST-NOTICES.txt" "$pkgdir/usr/share/licenses/fds-tools/RUST-NOTICES.txt"
install -Dm644 "$_fds_source/README.md" "$pkgdir/usr/share/doc/fds-tools/README.md"
# Package the authoritative manual without submodule Git metadata.
install -d "$pkgdir/usr/share/doc/fds-tools/fds-os.wiki"
install -m644 "$_fds_source/fds-os.wiki/"*.md "$_fds_source/fds-os.wiki/"*.png \
"$pkgdir/usr/share/doc/fds-tools/fds-os.wiki/"
# Ship source recipes, excluding any locally compiled example output.
local example
for example in hello report; do
install -Dm644 "$_fds_source/examples/software/$example/software.toml" "$pkgdir/usr/share/doc/fds-tools/examples/software/$example/software.toml"
cp -a "$_fds_source/examples/software/$example/void" "$pkgdir/usr/share/doc/fds-tools/examples/software/$example/"
done
install -Dm644 "$_fds_source/examples/software/cartridge.toml" "$pkgdir/usr/share/doc/fds-tools/examples/software/cartridge.toml"
}
+2 -1
View File
@@ -1,6 +1,7 @@
[package]
name = "dasungd"
version = "0.1.0"
version.workspace = true
build = "../../tools/version-build.rs"
edition = "2024"
description = "DASUNG Paperlike USB keepalive, control socket and Linux display profile daemon"
license = "MIT"
+1 -1
View File
@@ -23,5 +23,5 @@ FDS changes: workspace integration, target filesystem paths, native s6 packaging
and a portable simulator binary path. The original AMD/systemd installer and
workstation configuration are not part of the target package.
See [the FDS guide](../../docs/dasung.md) and the historical
See [the FDS guide](../../fds-os.wiki/Dasung.md) and the historical
[hardware validation record](VALIDATION.md).
+1 -1
View File
@@ -4,7 +4,7 @@ The controller for the user's grayscale Dasung Paperlike 13K is maintained as an
FDS Rust workspace member. It provides USB keepalive, reconnect/watchdog handling,
configuration, and a local control socket.
Use the [FDS integration and usage guide](../../docs/dasung.md) for build commands,
Use the [FDS integration and usage guide](../../fds-os.wiki/Dasung.md) for build commands,
base packaging, native s6 supervision, Pi display configuration, and validation
limits. From the repository root, run `make dasung` followed by `make dasung-test`.
These targets do not operate the workstation's live monitor.
+2 -2
View File
@@ -31,7 +31,7 @@ use std::{
};
#[derive(Parser)]
#[command(version, about)]
#[command(version = env!("FDS_BUILD_VERSION"), about)]
struct Cli {
#[arg(long, global = true, default_value = "/etc/dasungd.toml")]
config: PathBuf,
@@ -286,7 +286,7 @@ fn daemon(config: Config) -> Result<()> {
let mut last_error = String::new();
eprintln!(
"dasungd {} ready; waiting for {}",
env!("CARGO_PKG_VERSION"),
env!("FDS_BUILD_VERSION"),
config.monitor_serial
);
while running.load(Ordering::Relaxed) {
+2 -1
View File
@@ -1,6 +1,7 @@
[package]
name = "fds-boottrace"
version = "0.1.0"
version.workspace = true
build = "../../tools/version-build.rs"
edition = "2024"
license = "MIT"
description = "Measured FDS boot events and regression reports"
+1 -1
View File
@@ -80,7 +80,7 @@ fn adopt() -> Result<()> {
}
#[derive(Parser)]
#[command(
version,
version = env!("FDS_BUILD_VERSION"),
about = "Record and compare measured boot events",
after_help = "Measurements use Linux CLOCK_BOOTTIME, excluding firmware and power-on."
)]

Some files were not shown because too many files have changed in this diff Show More