Compare commits
3
Commits
8a4788fca8
...
0d42dc5acc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d42dc5acc | ||
|
|
f5d9f96409 | ||
|
|
6bfcce8070 |
@@ -23,7 +23,8 @@ claim Pi boot or physical power-cycle recovery is verified.
|
|||||||
commands and options with typed parsers; do not hand-parse argument vectors.
|
commands and options with typed parsers; do not hand-parse argument vectors.
|
||||||
- No systemd, runit, BusyBox, shell PID1, boot-time cache compilation, or sleeps
|
- No systemd, runit, BusyBox, shell PID1, boot-time cache compilation, or sleeps
|
||||||
to hide races in the target system.
|
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.
|
- Explain every new dependency and keep documentation accurate.
|
||||||
- Run `make bootstrap`, `make smoke-test`, and `make check` for M0 build changes.
|
- Run `make bootstrap`, `make smoke-test`, and `make check` for M0 build changes.
|
||||||
Report skips and missing hardware honestly. Do not invent boot benchmarks.
|
Report skips and missing hardware honestly. Do not invent boot benchmarks.
|
||||||
|
|||||||
Generated
+13
-13
@@ -236,7 +236,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dasungd"
|
name = "dasungd"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -320,7 +320,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-boottrace"
|
name = "fds-boottrace"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"fds-common",
|
"fds-common",
|
||||||
@@ -330,7 +330,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-burn"
|
name = "fds-burn"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"fds-common",
|
"fds-common",
|
||||||
@@ -342,7 +342,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-cartridged"
|
name = "fds-cartridged"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"fds-burn",
|
"fds-burn",
|
||||||
@@ -355,7 +355,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-cli"
|
name = "fds-cli"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"fds-burn",
|
"fds-burn",
|
||||||
@@ -366,7 +366,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-common"
|
name = "fds-common"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -376,7 +376,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-control"
|
name = "fds-control"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"fds-common",
|
"fds-common",
|
||||||
@@ -387,7 +387,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-release"
|
name = "fds-release"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
@@ -401,11 +401,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-smoketest"
|
name = "fds-smoketest"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-software"
|
name = "fds-software"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fds-common",
|
"fds-common",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -417,7 +417,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-stage0"
|
name = "fds-stage0"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"fds-common",
|
"fds-common",
|
||||||
@@ -427,7 +427,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-test-helpers"
|
name = "fds-test-helpers"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -435,7 +435,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fds-workstation"
|
name = "fds-workstation"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"fds-burn",
|
"fds-burn",
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ members = [
|
|||||||
"tests/helpers",
|
"tests/helpers",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
# Cargo requires three numeric components; public build versions come from Git.
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ help:
|
|||||||
'make clean-preview Show obsolete build/test directories and Rust output selected for cleanup' \
|
'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 Remove that output; keep current images, latest tests, caches and releases' \
|
||||||
'make clean-test Test cleanup protections using small disposable fixtures' \
|
'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 workstation-test Verify software builds, multi-partition images and file write/readback' \
|
||||||
'make emulator-test Boot FDS and test cartridge hotplug with the public emulator' \
|
'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 bootstrap Prepare Arch x86_64 host and Void build container' \
|
||||||
@@ -91,6 +93,7 @@ smoke-test:
|
|||||||
./tools/smoke-test 2>&1 | tee out/logs/smoke-test.log
|
./tools/smoke-test 2>&1 | tee out/logs/smoke-test.log
|
||||||
|
|
||||||
check:
|
check:
|
||||||
|
python3 tests/integration/version-checks.py
|
||||||
./tests/integration/m0-checks
|
./tests/integration/m0-checks
|
||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
$(MAKE) clean-test
|
$(MAKE) clean-test
|
||||||
@@ -241,14 +244,23 @@ release-contract-test:
|
|||||||
@mkdir -p out/logs
|
@mkdir -p out/logs
|
||||||
python3 tests/integration/m12-release-build.py 2>&1 | tee out/logs/release-contract-checks.log
|
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:
|
workstation:
|
||||||
@mkdir -p out/logs
|
@mkdir -p out/logs
|
||||||
./tools/build-workstation 2>&1 | tee out/logs/workstation-build.log
|
./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
|
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-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
|
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
|
python3 tests/integration/workstation-emulator.py --cli out/workstation/fds-emulator --qemu-runner tools/in-void 2>&1 | tee out/logs/workstation-emulator.log
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ the desktop or any ENVIRONMENT cartridge.
|
|||||||
|
|
||||||
## Choose a starting point
|
## Choose a starting point
|
||||||
|
|
||||||
- **Build and try FDS:** follow [Build and start FDS](docs/getting-started.md).
|
- **Run on your Raspberry Pi 5:** follow [the real-hardware setup guide](docs/hardware-setup.md), including HDMI-plus-USB Dasung wiring, disk installation and first boot.
|
||||||
|
- **Build and try the emulator:** follow [Build and start FDS](docs/getting-started.md).
|
||||||
- **Build an application cartridge:** use the [software and emulator walkthrough](docs/workstation.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).
|
- **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).
|
- **Install or maintain a machine:** see [internal storage](docs/internal-storage.md), [release verification](docs/releases.md), and [recovery](docs/recovery.md).
|
||||||
@@ -55,7 +56,9 @@ Run these from the repository root:
|
|||||||
|
|
||||||
| Command | Result |
|
| Command | Result |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `make workstation` | Native Linux `fds-cartridge` and `fds-emulator` executables |
|
| `make workstation` | Arch `fds-tools` package containing both workstation commands |
|
||||||
|
| `make workstation-install` | Install that package using pacman |
|
||||||
|
| `make workstation-binaries` | Native commands for other Linux distributions |
|
||||||
| `make bootstrap` | Prepares the complete OS build environment on x86_64 Arch Linux |
|
| `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 rootfs PROFILE=cli` | Builds the ARM base filesystem and its packages |
|
||||||
| `make system-card PROFILE=cli` | Creates a whole-disk SYSTEM cartridge image |
|
| `make system-card PROFILE=cli` | Creates a whole-disk SYSTEM cartridge image |
|
||||||
@@ -71,3 +74,5 @@ The workstation walkthrough includes prerequisites, complete example commands,
|
|||||||
expected outputs, emulator lifecycle and the confirmed USB-writing workflow.
|
expected outputs, emulator lifecycle and the confirmed USB-writing workflow.
|
||||||
Append `--help` to either native tool for its command reference; run `make help`
|
Append `--help` to either native tool for its command reference; run `make help`
|
||||||
for the complete build target list.
|
for the complete build target list.
|
||||||
|
|
||||||
|
Build identities come from Git release tags. See [versioning](docs/developer/versioning.md) for release, development and source-export behavior.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ operating guides run at the FDS console unless stated otherwise.
|
|||||||
| Guide | What you will learn |
|
| Guide | What you will learn |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| [Overview](../README.md) | How the computer, internal storage and cartridges fit together |
|
| [Overview](../README.md) | How the computer, internal storage and cartridges fit together |
|
||||||
|
| [Run on a Raspberry Pi 5](hardware-setup.md) | Wire HDMI/USB/power, prepare NVMe and SYSTEM, reach the first console, calibrate bays and diagnose hardware |
|
||||||
| [Build and start FDS](getting-started.md) | Prepare the OS build workstation, create images and boot the emulator |
|
| [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 |
|
| [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 |
|
| [Using cartridges](cartridges.md) | Inspect bays, run applications, resolve command names and eject safely |
|
||||||
|
|||||||
+2
-1
@@ -44,7 +44,8 @@ Unknown names are left alone, including ad hoc diagnostic experiments.
|
|||||||
- User-created cartridge images, installed software trees and personal emulator sessions
|
- User-created cartridge images, installed software trees and personal emulator sessions
|
||||||
such as `out/my-emulator/`, including their persistent DATA overlays.
|
such as `out/my-emulator/`, including their persistent DATA overlays.
|
||||||
- `out/logs/`, `out/manifests/`, packages, downloads, `out/cache/`, `.host/`, the
|
- `out/logs/`, `out/manifests/`, packages, downloads, `out/cache/`, `.host/`, the
|
||||||
Void checkout/build container, and Cargo/Rustup caches outside this checkout.
|
writable Void checkout/build container under `.host/void-packages/`, and
|
||||||
|
Cargo/Rustup caches outside this checkout.
|
||||||
- Git-tracked files and any recognized workspace containing a `.fds-keep` entry.
|
- 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
|
To retain an older generated workspace for investigation, put a marker in it
|
||||||
|
|||||||
+150
-2
@@ -4,14 +4,42 @@ FDS includes `dasungd` for the configured Dasung Paperlike 13K grayscale monitor
|
|||||||
The controller starts during early boot and remains supervised in the base system,
|
The controller starts during early boot and remains supervised in the base system,
|
||||||
including console-only use. No GUI or ENVIRONMENT cartridge is required.
|
including console-only use. No GUI or ENVIRONMENT cartridge is required.
|
||||||
|
|
||||||
|
For installation, disk preparation and first power-on, start with
|
||||||
|
[Run FDS on a Raspberry Pi 5](hardware-setup.md).
|
||||||
|
|
||||||
The supplied profile uses the dedicated monitor's EDID and USB companion identity,
|
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;
|
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.
|
do not use its identity as a blanket match for other CH340 USB devices.
|
||||||
|
|
||||||
|
## HDMI plus USB-C on the Pi
|
||||||
|
|
||||||
|
Use the Pi's **micro-HDMI output** for video to the monitor's HDMI input. Use a
|
||||||
|
separate **USB-A-to-USB-C data cable** from a Pi host port to the monitor for
|
||||||
|
controller access, and power the monitor with its supplied DC adapter. The
|
||||||
|
Pi's own USB-C connector is its power input; it does not output USB-C video.
|
||||||
|
See the [wiring table](hardware-setup.md#2-wire-hdmi-video-usb-control-and-power-separately),
|
||||||
|
[official Pi display guidance](https://www.raspberrypi.com/documentation/computers/getting-started.html#display),
|
||||||
|
and [DASUNG's power guidance](https://shop.dasung.com/pages/q-a).
|
||||||
|
|
||||||
|
HDMI video and USB controller access are separate checks. The daemon talks USB
|
||||||
|
regardless of the video cable, but the HDMI-plus-USB arrangement still needs to
|
||||||
|
expose the expected UART and companion bridge on this monitor. HDMI alone does
|
||||||
|
not carry the daemon's control protocol. A charge-only cable or a USB-C lead
|
||||||
|
connected only to a charger cannot provide host USB control.
|
||||||
|
|
||||||
|
The image currently supplies a firmware EDID override for 3200 × 2400 at about
|
||||||
|
37 Hz, recorded on the original workstation. DASUNG says some HDMI hosts need
|
||||||
|
27 Hz instead; that does not establish which mode the Pi and this cable will
|
||||||
|
accept. The forced profile can prevent using the monitor's native HDMI mode
|
||||||
|
list. Use the diagnostic procedure below if needed; neither Pi HDMI compatibility
|
||||||
|
nor physical power-cycle recovery is recorded as passed yet.
|
||||||
|
[Manufacturer HDMI note](https://shop.dasung.com/pages/dasung-paperlike-13k-the-worlds-first-13-3-inch-37hz-3k-e-ink-monitor).
|
||||||
|
|
||||||
## Inspect the controller
|
## Inspect the controller
|
||||||
|
|
||||||
The controller's socket is restricted to root. From a root maintenance console
|
The controller's socket is restricted to root. Use the local `RECOVERY#` console
|
||||||
on FDS:
|
described in [Enter recovery](recovery.md#enter-recovery); the ordinary `FDS>`
|
||||||
|
account cannot access it. On FDS recovery:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dasungd status
|
dasungd status
|
||||||
@@ -40,6 +68,126 @@ Inspect `/run/log/dasungd/current` and the reported status. Discovery requires
|
|||||||
the configured DRM EDID and matching USB companion topology. An ambiguous match
|
the configured DRM EDID and matching USB companion topology. An ambiguous match
|
||||||
is rejected. Set `usb_path` only after identifying the intended device topology.
|
is rejected. Set `usb_path` only after identifying the intended device topology.
|
||||||
|
|
||||||
|
At `RECOVERY#`, start with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
lsusb
|
||||||
|
lsusb -t
|
||||||
|
dasungd status
|
||||||
|
dasungd query
|
||||||
|
tail -80 /run/log/dasungd/current
|
||||||
|
```
|
||||||
|
|
||||||
|
The existing profile expects UART `1a86:7523` alongside SPI bridge `1a86:5512`
|
||||||
|
under the same hub. If neither appears, check monitor power, its USB data port,
|
||||||
|
the cable and the Pi host connection. If only one appears, preserve the USB tree
|
||||||
|
and log: the cable/input combination has not yet satisfied the current profile.
|
||||||
|
Do not disable companion checks or match every CH340 device to hide the failure.
|
||||||
|
|
||||||
|
`status` separates `connected` from `responsive` and includes `usb_error`,
|
||||||
|
`display_error`, `displays`, `rx_frames` and `last_rx_ms_ago`. `query` queues
|
||||||
|
requests; run `status` again after replies arrive. Keepalive packets being sent
|
||||||
|
are not themselves evidence that the monitor answered.
|
||||||
|
|
||||||
|
Capture the actual DRM connector names and EDID at `RECOVERY#`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir /tmp/dasung-hdmi
|
||||||
|
cat /proc/cmdline > /tmp/dasung-hdmi/cmdline.txt
|
||||||
|
for fds_connector in /sys/class/drm/card*-*; do
|
||||||
|
[ -f "$fds_connector/status" ] || continue
|
||||||
|
fds_connector_name=${fds_connector##*/}
|
||||||
|
printf '%s: ' "$fds_connector_name"
|
||||||
|
cat "$fds_connector/status"
|
||||||
|
cat "$fds_connector/modes"
|
||||||
|
cp "$fds_connector/status" "/tmp/dasung-hdmi/$fds_connector_name.status"
|
||||||
|
cp "$fds_connector/modes" "/tmp/dasung-hdmi/$fds_connector_name.modes"
|
||||||
|
cp "$fds_connector/edid" "/tmp/dasung-hdmi/$fds_connector_name.edid"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a new directory for each capture and save it before shutdown using
|
||||||
|
[machine diagnostics](internal-storage.md#save-and-retrieve-diagnostics).
|
||||||
|
An empty EDID on a disconnected connector is expected. The `modes` file lists
|
||||||
|
available resolution names, not proof of the active refresh rate or picture
|
||||||
|
quality. With `drm.edid_firmware` active, the EDID you read may be the supplied
|
||||||
|
override; capture again without it to learn what HDMI actually advertises.
|
||||||
|
|
||||||
|
The configured DRM serial is `L56051794302`. Native HDMI EDID might identify the
|
||||||
|
display differently from the recorded USB-C profile. With `require_display=true`,
|
||||||
|
an identity mismatch can leave USB control disconnected even when HDMI video
|
||||||
|
works. Retain the native EDID, USB topology and errors for a deliberate profile
|
||||||
|
update; do not silently remove identity checks. Config changes must ultimately
|
||||||
|
be rebuilt into both early boot and base/recovery images. The read-only
|
||||||
|
`/etc/dasungd.toml` is not a persistent settings editor.
|
||||||
|
|
||||||
|
## Try the monitor's own HDMI EDID
|
||||||
|
|
||||||
|
Use this when the stock forced timing gives a blank or unstable picture, or when
|
||||||
|
temporarily connecting a different HDMI display. The override currently applies
|
||||||
|
to connected DRM outputs without a connector qualifier, so attaching another
|
||||||
|
screen while retaining it is not an independent native-mode test.
|
||||||
|
|
||||||
|
Follow the offline edit procedure below. In the disk's **single-line**
|
||||||
|
`cmdline.txt`, remove only this token for the EDID experiment:
|
||||||
|
|
||||||
|
```text
|
||||||
|
drm.edid_firmware=edid/dasung-paperlike13k-37hz.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
For verbose diagnosis with a USB keyboard and display console, a complete
|
||||||
|
command line based on the current production template is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
console=tty1 rdinit=/init ro loglevel=7 vt.global_cursor_default=0 fds.boot=normal fds.debug=1
|
||||||
|
```
|
||||||
|
|
||||||
|
For the serial boot mode, retain `console=ttyAMA10,115200` after `console=tty1`
|
||||||
|
instead. Use `fds.boot=recovery` in place of `fds.boot=normal` when intentionally
|
||||||
|
booting recovery; never append a second conflicting `fds.boot` option. Do not add
|
||||||
|
`fds.emulator=1` on real hardware or replace stage0 with a Raspberry Pi OS
|
||||||
|
`root=…` command line.
|
||||||
|
|
||||||
|
Reboot and repeat the DRM/USB capture. Removing the override allows the monitor's
|
||||||
|
HDMI EDID to supply modes; it does **not** guarantee 27 Hz or fix missing USB
|
||||||
|
control. Record any change in picture and controller status. Restore the saved
|
||||||
|
command line to return to the shipped profile. Keep both captures so a future
|
||||||
|
HDMI profile change can be based on observed timings and identity.
|
||||||
|
|
||||||
|
### Edit the BOOT command line offline
|
||||||
|
|
||||||
|
Shut FDS down and disconnect Pi power. Connect NVMe to the Linux workstation, or
|
||||||
|
boot the separate maintenance SD and access the inactive NVMe there. Identify
|
||||||
|
the same disk by model/serial used when flashing it. Mount only its first
|
||||||
|
partition, labeled `FDS_BOOT`; FDS does not normally mount it at
|
||||||
|
`/boot/firmware` as Raspberry Pi OS does.
|
||||||
|
|
||||||
|
On that **workstation or maintenance OS**, replacing the partition path below:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
lsblk -p -o NAME,MODEL,SERIAL,SIZE,TYPE,PARTLABEL,MOUNTPOINTS
|
||||||
|
sudo mkdir -p /mnt/fds-boot
|
||||||
|
sudo mount -o nosuid,nodev,noexec \
|
||||||
|
/dev/disk/by-id/REPLACE_WITH_VERIFIED_NVME-part1 /mnt/fds-boot
|
||||||
|
sudo cp -n /mnt/fds-boot/cmdline.txt /mnt/fds-boot/cmdline.before-hardware.txt
|
||||||
|
sudoedit /mnt/fds-boot/cmdline.txt
|
||||||
|
cat /mnt/fds-boot/cmdline.txt
|
||||||
|
sudo umount /mnt/fds-boot
|
||||||
|
```
|
||||||
|
|
||||||
|
Check the identity before the mount and stop on any failed step. If the partition
|
||||||
|
was automounted, unmount that instance first. Keep `/mnt/fds-boot` dedicated to
|
||||||
|
this operation. The no-clobber copy retains an existing backup; if repeating the
|
||||||
|
experiment, inspect that backup and skip the copy step if it already exists.
|
||||||
|
Do not assume an older backup contains your latest settings. Keep `cmdline.txt`
|
||||||
|
on one line and record its final contents with the
|
||||||
|
test session. Safely disconnect the enclosure before moving NVMe back to the Pi.
|
||||||
|
|
||||||
|
To undo the edit, mount the same verified partition again, restore
|
||||||
|
`cmdline.before-hardware.txt` over `cmdline.txt`, and unmount before rebooting.
|
||||||
|
These edits change the flashed copy only; they do not change the source images
|
||||||
|
or their recorded hashes. Reflashing restores the image's original command line.
|
||||||
|
|
||||||
FDS uses the daemon for USB control. Its configuration disables the original
|
FDS uses the daemon for USB control. Its configuration disables the original
|
||||||
workstation-specific display hotplug procedure. Video mode and cabling are part
|
workstation-specific display hotplug procedure. Video mode and cabling are part
|
||||||
of the machine's boot/display configuration. The detailed protocol, build
|
of the machine's boot/display configuration. The detailed protocol, build
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ the inputs named in those reports; a changed source tree needs fresh checks.
|
|||||||
|
|
||||||
## Design and build references
|
## Design and build references
|
||||||
|
|
||||||
|
- [Clean upstream and writable Void workspace](void-workspace.md)
|
||||||
|
|
||||||
- [Master plan](master-plan.md)
|
- [Master plan](master-plan.md)
|
||||||
- [Build host and dependencies](build-host.md)
|
- [Build host and dependencies](build-host.md)
|
||||||
- [Development workflow](development.md)
|
- [Development workflow](development.md)
|
||||||
@@ -78,3 +80,5 @@ cartridge creation: new payloads contain installed Void package trees.
|
|||||||
- [troubleshooting](troubleshooting.md)
|
- [troubleshooting](troubleshooting.md)
|
||||||
- [workstation-tooling-plan](workstation-tooling-plan.md)
|
- [workstation-tooling-plan](workstation-tooling-plan.md)
|
||||||
- [workstation](workstation.md)
|
- [workstation](workstation.md)
|
||||||
|
|
||||||
|
- [Git-derived build versions and Arch packaging](versioning.md)
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ are never used as cartridge identities.
|
|||||||
|
|
||||||
The software boot path has passed on an ARM virtual machine using the actual
|
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
|
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
|
power or physical boot speed. Those checks still require physical testing; follow
|
||||||
assembled. M5 now provides an ordinary-user `FDS>` console with a temporary home
|
the [real-hardware setup guide](../hardware-setup.md). M5 now provides an
|
||||||
|
ordinary-user `FDS>` console with a temporary home
|
||||||
and measured boot events. Password login remains locked; this is an intentional
|
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
|
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).
|
development shell. See [native init](init.md) and [performance reports](performance.md).
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ indexing also explicitly sets `XBPS_ARCH=aarch64`.
|
|||||||
|
|
||||||
## Generic Linux workstation tools and desktop additions
|
## Generic Linux workstation tools and desktop additions
|
||||||
|
|
||||||
`make workstation` builds native `fds-cartridge` and `fds-emulator`. Runtime
|
`make workstation` builds the Arch `fds-tools` package; `make workstation-install`
|
||||||
|
installs it with pacman. `make workstation-binaries` builds native `fds-cartridge`
|
||||||
|
and `fds-emulator` on generic Linux. Python 3 and Git resolve build versions;
|
||||||
|
Dasung builds install these tools in the project-local Void container as well. Runtime
|
||||||
requirements are QEMU, erofs-utils, bubblewrap and native XBPS with a prepared
|
requirements are QEMU, erofs-utils, bubblewrap and native XBPS with a prepared
|
||||||
Void source checkout for software creation. `fds-cartridge` invokes xbps-src,
|
Void source checkout for software creation. `fds-cartridge` invokes xbps-src,
|
||||||
installs runtime dependencies and creates direct EROFS trees. The Rust tar reader
|
installs runtime dependencies and creates direct EROFS trees. The Rust tar reader
|
||||||
@@ -112,7 +115,7 @@ equivalent of:
|
|||||||
```sh
|
```sh
|
||||||
export PATH="$PWD/.host/xbps/usr/bin:$PATH"
|
export PATH="$PWD/.host/xbps/usr/bin:$PATH"
|
||||||
export XBPS_ARCH=x86_64
|
export XBPS_ARCH=x86_64
|
||||||
cd vendor/void-packages
|
cd .host/void-packages
|
||||||
./xbps-src -A x86_64 binary-bootstrap
|
./xbps-src -A x86_64 binary-bootstrap
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -131,7 +134,7 @@ To reproduce the two builds individually from the repository root:
|
|||||||
```
|
```
|
||||||
|
|
||||||
The latter command sets the local XBPS PATH, checks the Void pin, and executes
|
The latter command sets the local XBPS PATH, checks the Void pin, and executes
|
||||||
this command within the submodule:
|
this command within the writable build checkout:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./xbps-src -a aarch64 pkg hello
|
./xbps-src -a aarch64 pkg hello
|
||||||
@@ -186,9 +189,11 @@ tests accumulate. [Cleanup](cleanup.md) explains the retained current images,
|
|||||||
release/input archives and caches. Full image builds and VM suites need tens of
|
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.
|
GiB per working tree, beyond the initial M0 smoke test's requirements.
|
||||||
|
|
||||||
|
- `vendor/void-packages/`: clean, pinned upstream submodule; no generated overlays.
|
||||||
|
- `.host/void-packages/`: independent local build checkout at the same pin.
|
||||||
- `.host/xbps/`: project-local host tools, not installed into `/usr`.
|
- `.host/xbps/`: project-local host tools, not installed into `/usr`.
|
||||||
- `vendor/void-packages/masterdir-x86_64/`: glibc build container.
|
- `.host/void-packages/masterdir-x86_64/`: glibc build container.
|
||||||
- `vendor/void-packages/hostdir/`: download caches, cross packages, and build output.
|
- `.host/void-packages/hostdir/`: download caches, cross packages, and build output.
|
||||||
- `out/packages/`: exported aarch64 test package and its repository index.
|
- `out/packages/`: exported aarch64 test package and its repository index.
|
||||||
- `out/manifests/void-build-packages.txt`: packages remaining installed after the
|
- `out/manifests/void-build-packages.txt`: packages remaining installed after the
|
||||||
build. Cross toolchains and temporary dependencies removed by xbps-src remain
|
build. Cross toolchains and temporary dependencies removed by xbps-src remain
|
||||||
@@ -197,12 +202,20 @@ GiB per working tree, beyond the initial M0 smoke test's requirements.
|
|||||||
- `out/manifests/artifacts.sha256`: validation artifact digests.
|
- `out/manifests/artifacts.sha256`: validation artifact digests.
|
||||||
- `out/logs/`: actual command logs, including logs from failed attempts.
|
- `out/logs/`: actual command logs, including logs from failed attempts.
|
||||||
|
|
||||||
Place FDS package overlays at `packages/fds-*/template`. `tools/prepare-void`
|
Place FDS package overlays at `packages/fds-*/template`. `tools/prepare-void-workspace`
|
||||||
copies only directories containing a template and refuses to replace tracked
|
clones the pinned upstream locally without shared Git objects or network access.
|
||||||
|
`tools/prepare-void` copies directories containing a template into that writable checkout and refuses to replace tracked
|
||||||
upstream packages. These copies are visible inside the build container. Base,
|
upstream packages. These copies are visible inside the build container. Base,
|
||||||
base-files, init, CLI, kernel and Dasung overlays are active; future directories
|
base-files, init, CLI, kernel and Dasung overlays are active; future directories
|
||||||
without templates remain inactive.
|
without templates remain inactive.
|
||||||
|
|
||||||
|
Bootstrap migrates matching old generated copies, configuration, `hostdir` and
|
||||||
|
`masterdir-*` out of the submodule using renames, preserving cached downloads
|
||||||
|
and container files. Unknown files, staged edits, changed copies or destination
|
||||||
|
conflicts stop migration for explicit reconciliation. Python 3 is used for
|
||||||
|
checked migration and locking; it is already a documented FDS host prerequisite.
|
||||||
|
The workstation builder also rejects submodules as writable build targets.
|
||||||
|
|
||||||
If an overlay changes and preparation reports a stale copy, inspect and preserve
|
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
|
any unique edits before moving the specific generated copy out of `srcpkgs/` and
|
||||||
retrying. Modified upstream tracked files cause immediate failure. Conflicting
|
retrying. Modified upstream tracked files cause immediate failure. Conflicting
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ No world-writable USB rule, systemd unit, or runit service is installed.
|
|||||||
The video timing was confirmed on the original AMD workstation, **not on a Pi**.
|
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,
|
The original USB-C video connection is not a Pi cabling prescription. Pi connector,
|
||||||
cable/adapter, mode acceptance, and picture stability require hardware validation.
|
cable/adapter, mode acceptance, and picture stability require hardware validation.
|
||||||
|
For the current HDMI video plus USB control procedure, including separate monitor
|
||||||
|
power and a reversible native-EDID experiment, use the [display guide](../dasung.md).
|
||||||
Desktop 2× scaling is separate from EDID timing; a console uses its own font size.
|
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
|
FDS configures `display.enabled=false` and `hotplug="none"`: the daemon handles
|
||||||
|
|||||||
@@ -453,7 +453,10 @@ Record:
|
|||||||
VOID_PACKAGES_COMMIT
|
VOID_PACKAGES_COMMIT
|
||||||
```
|
```
|
||||||
|
|
||||||
Inject FDS packages through an overlay.
|
Keep this submodule clean. Prepare a writable checkout at `.host/void-packages`
|
||||||
|
from the same pinned commit. Inject FDS packages from `packages/` into that
|
||||||
|
build checkout; keep generated sources, caches and build containers outside
|
||||||
|
the submodule.
|
||||||
|
|
||||||
Required relationship:
|
Required relationship:
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ The JSON document has exactly these fields:
|
|||||||
|
|
||||||
This is a schema illustration, not a usable manifest: the size and hash
|
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
|
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
|
accepts a bounded numeric-leading release/development version, a positive source epoch, a 40-character Void
|
||||||
commit, and 1–64 nonempty artifacts. Filenames must be flat safe ASCII names;
|
commit, and 1–64 nonempty artifacts. Filenames must be flat safe ASCII names;
|
||||||
paths, duplicates, hidden names, reserved manifest filenames and unknown fields
|
paths, duplicates, hidden names, reserved manifest filenames and unknown fields
|
||||||
are rejected. The manifest is bounded to 1 MiB. Files must be regular files,
|
are rejected. The manifest is bounded to 1 MiB. Files must be regular files,
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ Normal builds still use Void's signed rolling repositories. A frozen build uses
|
|||||||
the selected package files and build environment from one explicit snapshot.
|
the selected package files and build environment from one explicit snapshot.
|
||||||
It does not silently consult a newer repository if an input is missing.
|
It does not silently consult a newer repository if an input is missing.
|
||||||
|
|
||||||
|
Current builds keep the upstream reference in `vendor/void-packages/` and use
|
||||||
|
`.host/void-packages/` for overlays and cached build state. New snapshots and
|
||||||
|
restores use this separate layout. Restoring an older
|
||||||
|
snapshot retains its historical layout and saved build scripts; its files and
|
||||||
|
acceptance are not rewritten. See [the workspace change](void-workspace.md) for
|
||||||
|
fresh validation of this migration.
|
||||||
|
|
||||||
## What is frozen
|
## What is frozen
|
||||||
|
|
||||||
`tools/frozen-inputs create NEW_DIRECTORY` records:
|
`tools/frozen-inputs create NEW_DIRECTORY` records:
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ 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.
|
normal OS continues to use glibc; no target musl runtime package is added.
|
||||||
|
|
||||||
For native Linux software packaging and QEMU hotplug, use `make workstation`
|
For native Linux software packaging and QEMU hotplug, use `make workstation`
|
||||||
and the [workstation guide](workstation.md). Those Clap tools are host binaries;
|
on Arch, or `make workstation-binaries` on other Linux distributions, and follow
|
||||||
|
the [current workstation guide](../workstation.md). Those Clap tools are host binaries;
|
||||||
`make tooling` below builds the static ARM runtime tools.
|
`make tooling` below builds the static ARM runtime tools.
|
||||||
|
|
||||||
## Build and test
|
## Build and test
|
||||||
|
|||||||
@@ -94,14 +94,14 @@ normal. Never use a broad reset or clean command without checking what it remove
|
|||||||
For `etc/conf differs from config/xbps-src.conf`, compare the two:
|
For `etc/conf differs from config/xbps-src.conf`, compare the two:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
diff -u vendor/void-packages/etc/conf config/xbps-src.conf
|
diff -u .host/void-packages/etc/conf config/xbps-src.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
Put the desired settings in `config/xbps-src.conf`, then follow
|
Put the desired settings in `config/xbps-src.conf`, then follow
|
||||||
[the explicit synchronization procedure](development.md#change-build-configuration-deliberately).
|
[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.
|
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-*`
|
For `Stale overlay`, compare the named `.host/void-packages/srcpkgs/fds-*`
|
||||||
directory with its source under `packages/`. Preserve any unique edits in the
|
directory with its source under `packages/`. Preserve any unique edits in the
|
||||||
source first. Move that specific generated copy into a backup directory under
|
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
|
`out/`, outside `srcpkgs/`, then run `./tools/prepare-void` again. Do not move or
|
||||||
@@ -121,7 +121,7 @@ space before assuming that a quiet download is a deadlock:
|
|||||||
```sh
|
```sh
|
||||||
tail -n 40 out/logs/bootstrap.log
|
tail -n 40 out/logs/bootstrap.log
|
||||||
tail -n 40 out/logs/xbps-hello.log
|
tail -n 40 out/logs/xbps-hello.log
|
||||||
du -sh out/downloads vendor/void-packages/hostdir
|
du -sh out/downloads .host/void-packages/hostdir
|
||||||
df -h .
|
df -h .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Build versions and the workstation package
|
||||||
|
|
||||||
|
`tools/version` is the authoritative FDS build-version resolver. It reads this
|
||||||
|
repository's Git history, never the Void submodule's history. All FDS Rust
|
||||||
|
commands, base XBPS packages, login/boot banners and `/etc/os-release` use it.
|
||||||
|
SYSTEM cartridge metadata copies the version already embedded in its rootfs,
|
||||||
|
so wrapping an older rootfs does not mislabel it as the current source version.
|
||||||
|
|
||||||
|
Release tags are numeric `MAJOR.MINOR` or `MAJOR.MINOR.PATCH`, optionally prefixed
|
||||||
|
with `v`. Unrelated tags are ignored. The nearest reachable release tag is used:
|
||||||
|
|
||||||
|
| Source state | Example build version |
|
||||||
|
| --- | --- |
|
||||||
|
| Exactly release tag `1.0`, clean checkout | `1.0` |
|
||||||
|
| Two commits after `1.0` | `1.0.r2.g123456789abc` |
|
||||||
|
| No reachable release tag | `0.dev.g123456789abc` |
|
||||||
|
| Uncommitted changes to tracked files | Append `.dirty` |
|
||||||
|
|
||||||
|
Untracked files do not affect the version, matching Git’s usual dirty-checkout
|
||||||
|
convention. Add new release sources to Git before tagging.
|
||||||
|
|
||||||
|
Versions are bounded to 32 letters, digits and periods, starting with a digit,
|
||||||
|
for the cartridge metadata, XBPS and Arch package formats. A shallow clone
|
||||||
|
without a reachable release tag gets a development version; fetch the intended
|
||||||
|
release tag/history before building a release. No value is silently invented
|
||||||
|
when both Git identity and an exported identity are missing.
|
||||||
|
|
||||||
|
Each Rust crate uses the shared build script, including direct Cargo builds.
|
||||||
|
Cargo notices tag/ref changes, index changes and source edits, avoiding an old
|
||||||
|
version string in a cached binary. Cargo's workspace metadata uses the SemVer
|
||||||
|
form `1.0.0`; the public build version comes from Git. Protocol/schema versions,
|
||||||
|
application recipe versions, the Linux kernel version and third-party package
|
||||||
|
versions are independent and remain unchanged.
|
||||||
|
|
||||||
|
`tools/prepare-void` writes the resolved value to the writable build checkout's
|
||||||
|
`fds-version` file. FDS-owned XBPS templates read it; base-files substitutes it
|
||||||
|
into `/usr/lib/os-release` and `/etc/issue`. `/etc/os-release` links to that
|
||||||
|
installed identity file. Runtime checks compare installed command versions
|
||||||
|
against `VERSION_ID`. Superseded FDS packages are archived under
|
||||||
|
`out/package-history/` so the active repository cannot accidentally select an
|
||||||
|
older development package whose version sorts above a release.
|
||||||
|
|
||||||
|
Frozen source exports save a validated `FDS_VERSION` file before inventorying
|
||||||
|
and hashing their contents. Their builds use that saved value without Git or
|
||||||
|
network access. Legacy 0.1.0 snapshots retain their original scripts and layout;
|
||||||
|
the historical release stays unchanged. Release verification accepts supported
|
||||||
|
version strings independently of the verifier's own version, so newer tools can
|
||||||
|
still authenticate an older signed release using the supplied trust key.
|
||||||
|
|
||||||
|
## Build and install on Arch
|
||||||
|
|
||||||
|
`packaging/arch/PKGBUILD` packages the local checkout. `make workstation` invokes
|
||||||
|
makepkg, building both binaries in the existing Cargo cache and recording the
|
||||||
|
result in `out/workstation/package-path.txt`. It builds without implicit package
|
||||||
|
installation; required build commands are checked directly. Runtime feature
|
||||||
|
packages are declared as optional because project-local runners are supported.
|
||||||
|
`make workstation-install` explicitly invokes pacman to install the exact
|
||||||
|
recorded package. Generic Linux users use `make workstation-binaries`.
|
||||||
|
|
||||||
|
The package includes binaries, the user/developer manuals, source examples,
|
||||||
|
MIT licensing and dependency notices. It has no service, hook or installation
|
||||||
|
script. The workflow follows the official [PKGBUILD specification](https://man.archlinux.org/man/PKGBUILD.5.en)
|
||||||
|
and [makepkg interface](https://man.archlinux.org/man/makepkg.8.en).
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
`make check` includes release/no-tag/dirty/export and real Cargo cache-invalidation
|
||||||
|
checks. `make workstation-test` exercises the installed-tree cartridge creator;
|
||||||
|
`make emulator-test` covers stopping and starting the same directory, preserved
|
||||||
|
logs/overlays/mappings, and rejecting a restart of a live VM.
|
||||||
|
`make workstation-package-test` installs the real Arch package with pacman in a
|
||||||
|
disposable root and executes both installed commands. After building matching
|
||||||
|
images, `python3 tests/integration/version-images.py` reads the identity and
|
||||||
|
metadata directly from the GPT/EROFS image and executes its twelve FDS commands
|
||||||
|
under ARM emulation to compare their versions. Image acceptance
|
||||||
|
requires a new CLI rootfs and SYSTEM image: inspect their identity, execute the
|
||||||
|
ARM tools and run the VM checks. A VM result does not establish physical Pi or
|
||||||
|
Dasung power-cycle behavior.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# Clean upstream and writable Void workspace
|
||||||
|
|
||||||
|
FDS keeps upstream source separate from its build state:
|
||||||
|
|
||||||
|
| Path | Ownership and purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `vendor/void-packages/` | Clean upstream Git submodule at `VOID_PACKAGES_COMMIT` |
|
||||||
|
| `packages/` | Authoritative FDS base-package sources, including Dasung |
|
||||||
|
| `examples/software/*/void/` | Authoritative example PROGRAM package sources |
|
||||||
|
| `.host/void-packages/` | Generated writable checkout, package copies, local configuration, caches and build container |
|
||||||
|
|
||||||
|
`make bootstrap` creates the build checkout locally at the pinned commit. It
|
||||||
|
copies Git objects independently; there is no network fetch for this step, no
|
||||||
|
Git alternates dependency and no hardlinks into the upstream source. Normal
|
||||||
|
package bootstrap still uses its configured repositories.
|
||||||
|
|
||||||
|
`tools/prepare-void` copies FDS package overlays into the writable checkout and
|
||||||
|
retains the existing checks against replacing upstream packages or overwriting
|
||||||
|
changed generated copies. Workstation builds default to the same checkout.
|
||||||
|
Explicitly selecting a Git submodule with `--void-packages` is rejected before
|
||||||
|
source files or build state can be written there.
|
||||||
|
|
||||||
|
The workspace helper migrates old, matching generated files and build caches by
|
||||||
|
renaming them. It compares every file, mode and symlink in each known source
|
||||||
|
copy first. Unknown upstream files, staged changes, independent edits and
|
||||||
|
conflicting destinations stop migration. Run builds and migration sequentially.
|
||||||
|
The workspace lock prevents two preparations from changing the layout together.
|
||||||
|
Python is already a build-host prerequisite; this introduces no new dependency.
|
||||||
|
|
||||||
|
For this checkout, the 58 staged additions matched the main-repository sources.
|
||||||
|
Their local commit `47580322e801ef195c9bf279fd1fe4d8a1f723e9` and both staged
|
||||||
|
patches were saved under `.host/backups/void-overlays-20260922-134611/` before
|
||||||
|
unstaging those copies. The parent gitlink was restored to upstream commit
|
||||||
|
`02a3cbc132c3c4a3a9d59e9b98f517af5dd11cd1`, matching the declared pin. All eleven
|
||||||
|
package directories, `hostdir`, `masterdir-x86_64` and `etc/conf` were moved into
|
||||||
|
the build checkout. No upstream tracked source was changed.
|
||||||
|
|
||||||
|
An explicit future pin update advances the writable checkout without replacing
|
||||||
|
its caches. Git refuses updates that would overwrite local files. Continue to
|
||||||
|
record the parent gitlink and `VOID_PACKAGES_COMMIT` together.
|
||||||
|
|
||||||
|
Frozen-input creation uses the separate build checkout. New restores recreate
|
||||||
|
that layout. Older saved projects retain their
|
||||||
|
original layout and scripts; the frozen 0.1.0 release is untouched. Restore
|
||||||
|
compatibility tests do not imply a new reproducibility or physical-hardware claim.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Fresh checks for this change passed:
|
||||||
|
|
||||||
|
- `make bootstrap`, `make smoke-test`, and `make check`: migrated container use,
|
||||||
|
ARM/glibc package compilation, static-musl compilation, clean upstream state,
|
||||||
|
exact overlay/cache preservation, shallow pin updates, rejection of independent
|
||||||
|
edits and source conflicts, and cleanup protections.
|
||||||
|
- `make workstation-test`: `out/workstation-images.ckkodex6`; real Void source
|
||||||
|
builds from `.host/void-packages`, image validation and confirmed file write/readback.
|
||||||
|
Selecting the upstream submodule as a build target is rejected without writes.
|
||||||
|
- `make emulator-test`: `out/emu-test.op5azhlm`; full twelve-bay PROGRAM/DATA,
|
||||||
|
foreground terminal, eject/unplug, legacy reading and service-restart checks.
|
||||||
|
- `make release-contract-test`: `out/m12-release-contracts.wmsfrcl0`; both separate
|
||||||
|
and legacy frozen-layout restores, retained source caches, local offline
|
||||||
|
repositories and existing tamper/release-comparison checks.
|
||||||
|
- Documentation links, formatting, Python syntax and a final empty upstream Git
|
||||||
|
status, including ignored files, were checked.
|
||||||
|
|
||||||
|
Logs are in `out/logs/void-workspace/`; source and log hashes, pins, native binary
|
||||||
|
hashes and fixture results are recorded in `out/manifests/void-workspace-acceptance.json`.
|
||||||
|
The optional host-native ARM execution shortcut was skipped in smoke-test. ARM
|
||||||
|
execution was exercised by the full emulator suite. Physical hardware was not used.
|
||||||
|
|
||||||
|
The first emulator attempt (`out/emu-test.463emq8u`) failed on an empty cartridge
|
||||||
|
IPC response during initial discovery in its admin VM. The complete rerun passed
|
||||||
|
without target-system changes. SYSTEM, kernel and initramfs hashes matched the
|
||||||
|
previous accepted inputs. The cause of that first response failure was not
|
||||||
|
established; its command/serial logs are retained. Future failures now collect
|
||||||
|
live service diagnostics before VM shutdown, without retrying failed commands
|
||||||
|
or relaxing their acceptance conditions.
|
||||||
@@ -22,3 +22,8 @@ unchanged. Build new matching artifacts and record their hashes and source input
|
|||||||
See [user instructions](../workstation.md) and [format reference](../software-format.md)
|
See [user instructions](../workstation.md) and [format reference](../software-format.md)
|
||||||
for the supported interface. Historical xz acceptance remains in
|
for the supported interface. Historical xz acceptance remains in
|
||||||
[the earlier workstation record](workstation-validation.md).
|
[the earlier workstation record](workstation-validation.md).
|
||||||
|
|
||||||
|
The Arch distribution package is `fds-tools`, built by `make workstation` and
|
||||||
|
installed by `make workstation-install`. `make workstation-binaries` preserves
|
||||||
|
the generic Linux build. Stopped emulator directories support a fresh boot while
|
||||||
|
retaining logs, DATA overlays and archived cartridge mappings.
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
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.
|
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
|
This page records the earlier archive-based software prototype. For installed
|
||||||
|
Void software trees, the Arch `fds-tools` package, and reusable emulator sessions,
|
||||||
|
follow the [current walkthrough](../workstation.md).
|
||||||
|
|
||||||
|
This historical 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
|
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
|
how to write the same complete image to USB. All commands run from the checkout
|
||||||
root unless marked as guest commands.
|
root unless marked as guest commands.
|
||||||
|
|||||||
+10
-2
@@ -6,6 +6,9 @@ cartridges on another Linux distribution, use the [workstation guide](workstatio
|
|||||||
A prepared set of kernel, initramfs and SYSTEM images can be run on a Linux
|
A prepared set of kernel, initramfs and SYSTEM images can be run on a Linux
|
||||||
workstation without building the OS locally.
|
workstation without building the OS locally.
|
||||||
|
|
||||||
|
For the physical computer, follow [Run FDS on a Raspberry Pi 5](hardware-setup.md).
|
||||||
|
It covers HDMI-plus-USB monitor wiring, both disks, EEPROM, first boot and recovery.
|
||||||
|
|
||||||
## Prepare the workstation
|
## Prepare the workstation
|
||||||
|
|
||||||
Use a normal user account, a Git checkout with its submodule metadata, and a
|
Use a normal user account, a Git checkout with its submodule metadata, and a
|
||||||
@@ -34,6 +37,9 @@ make check
|
|||||||
```
|
```
|
||||||
|
|
||||||
Bootstrap prepares pinned Rust and XBPS tools and the Void build container.
|
Bootstrap prepares pinned Rust and XBPS tools and the Void build container.
|
||||||
|
The upstream reference stays clean in `vendor/void-packages/`. Generated package
|
||||||
|
copies, build configuration and caches live in `.host/void-packages/`; edit
|
||||||
|
FDS package sources in `packages/`, not in either generated checkout.
|
||||||
The smoke test checks the AArch64 build toolchains; `make check` validates the
|
The smoke test checks the AArch64 build toolchains; `make check` validates the
|
||||||
build guardrails. Stop and resolve any failed command before continuing. Logs
|
build guardrails. Stop and resolve any failed command before continuing. Logs
|
||||||
are in `out/logs/`. Repeated preparation reuses verified downloads and caches.
|
are in `out/logs/`. Repeated preparation reuses verified downloads and caches.
|
||||||
@@ -60,7 +66,7 @@ inputs needed by the emulator:
|
|||||||
builds a SYSTEM with compilers, Git, Vim, debuggers and display diagnostics.
|
builds a SYSTEM with compilers, Git, Vim, debuggers and display diagnostics.
|
||||||
Both profiles include WindowMaker and the Dasung daemon. The desktop starts on
|
Both profiles include WindowMaker and the Dasung daemon. The desktop starts on
|
||||||
request. `make all` also assembles recovery, boot and internal-storage images;
|
request. `make all` also assembles recovery, boot and internal-storage images;
|
||||||
see [internal installation](internal-storage.md) when preparing a physical machine.
|
see [real-hardware setup](hardware-setup.md) when preparing a physical machine.
|
||||||
|
|
||||||
## Start the emulator
|
## Start the emulator
|
||||||
|
|
||||||
@@ -73,7 +79,9 @@ out/workstation/fds-emulator --session out/my-emulator console
|
|||||||
```
|
```
|
||||||
|
|
||||||
If QEMU is installed directly on the workstation, omit `--qemu-runner`.
|
If QEMU is installed directly on the workstation, omit `--qemu-runner`.
|
||||||
The session directory must be new. At the `FDS>` prompt:
|
Use a new session directory for the first run; after `stop`, you can `start` the
|
||||||
|
same session again. Logs and DATA overlays are retained, and bays start empty.
|
||||||
|
At the `FDS>` prompt:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
fds info
|
fds info
|
||||||
|
|||||||
@@ -0,0 +1,392 @@
|
|||||||
|
# Run FDS on a Raspberry Pi 5
|
||||||
|
|
||||||
|
[User manual](README.md) · [Dasung wiring and diagnostics](dasung.md) · [Recovery](recovery.md)
|
||||||
|
|
||||||
|
This guide takes a partly assembled Pi from prepared disks to its first FDS
|
||||||
|
console, then adds monitor control and physical bay numbering. Start with one
|
||||||
|
SYSTEM cartridge; the twelve-bay assembly and a desktop are not prerequisites.
|
||||||
|
These are instructions for physical testing, not a record of a successful Pi
|
||||||
|
boot. Existing QEMU results do not establish HDMI compatibility, USB power
|
||||||
|
stability, monitor recovery or physical boot times.
|
||||||
|
|
||||||
|
Follow steps 1–6 to reach the first console. Steps 7–9 check the monitor, install
|
||||||
|
bay mappings and save results. If the screen stays blank, jump to
|
||||||
|
[first-boot troubleshooting](#when-the-first-boot-fails).
|
||||||
|
|
||||||
|
Commands marked **workstation** run on the Linux build computer, **maintenance
|
||||||
|
OS** means a separate Raspberry Pi OS rescue installation, and **FDS** means the
|
||||||
|
console on the Pi. FDS does not start SSH or networking automatically.
|
||||||
|
|
||||||
|
## 1. Prepare the minimum hardware
|
||||||
|
|
||||||
|
You need:
|
||||||
|
|
||||||
|
- A Raspberry Pi 5 with cooling and a suitable USB-C power supply.
|
||||||
|
- A PCIe NVMe adapter/HAT and NVMe drive for internal boot and recovery storage.
|
||||||
|
- One USB storage device for SYSTEM, a USB keyboard, and the Paperlike 13K.
|
||||||
|
- HDMI video cabling, a USB-A-to-USB-C **data** cable for the monitor controller,
|
||||||
|
and the monitor's supplied power adapter.
|
||||||
|
- A Linux workstation and an NVMe enclosure/adapter for writing the disks.
|
||||||
|
A separate maintenance microSD card is useful for EEPROM and display diagnosis.
|
||||||
|
|
||||||
|
The current FDS installation uses **two disks**:
|
||||||
|
|
||||||
|
| Disk | Contents | When it is needed |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Internal PCIe NVMe | `FDS_BOOT`, `FDS_RECOVERY`, `FDS_INTERNAL` | Every FDS boot; also recovery without SYSTEM |
|
||||||
|
| USB SYSTEM cartridge | `FDS_SYSTEM` | Normal FDS operation |
|
||||||
|
|
||||||
|
PROGRAM, DATA and ENVIRONMENT cartridges can wait until the console works.
|
||||||
|
If only microSD is connected today, use it for Raspberry Pi OS maintenance and
|
||||||
|
cable checks. This guide's complete FDS installation requires NVMe: persistent
|
||||||
|
machine settings deliberately accept PCIe NVMe only. Writing the internal image
|
||||||
|
to microSD or leaving NVMe in a USB enclosure does not provide that supported
|
||||||
|
layout. A SYSTEM image alone contains no Pi firmware boot partition.
|
||||||
|
|
||||||
|
Connect or reseat the PCIe ribbon and HAT with power disconnected, following the
|
||||||
|
adapter's instructions. For the first boot, attach SYSTEM directly to a Pi USB
|
||||||
|
port if the cartridge hubs are not ready. Changing the hub wiring later requires
|
||||||
|
a fresh bay calibration.
|
||||||
|
|
||||||
|
## 2. Wire HDMI video, USB control and power separately
|
||||||
|
|
||||||
|
The Pi's micro-HDMI ports are **video outputs**; the monitor receives that video.
|
||||||
|
The Pi 5's USB-C socket supplies power to the Pi and does not provide DisplayPort
|
||||||
|
Alt Mode video. A USB-C display cable alone cannot replace HDMI on this board.
|
||||||
|
See [Raspberry Pi's display connection guide](https://www.raspberrypi.com/documentation/computers/getting-started.html#display).
|
||||||
|
|
||||||
|
| Connection | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| Pi micro-HDMI output → Paperlike HDMI video input | Picture; use the correct HDMI connector sizes for both ends |
|
||||||
|
| Pi USB-A host port → Paperlike USB-C data port | Candidate USB control path for `dasungd`; verify enumeration in step 7 |
|
||||||
|
| Supplied Dasung power adapter → monitor power input | Monitor power, separate from USB control |
|
||||||
|
| Pi power supply → Pi USB-C power input | Pi power |
|
||||||
|
|
||||||
|
Use the monitor's HDMI input selection, if its controls offer one. A charging-only
|
||||||
|
USB cable will not expose the controller. This HDMI-plus-USB arrangement is the
|
||||||
|
route to test; whether this unit exposes both controller interfaces while HDMI
|
||||||
|
is active still needs observation. HDMI can show a picture even when USB control
|
||||||
|
is missing, and USB replies do not prove the picture is correct.
|
||||||
|
|
||||||
|
Use the supplied Dasung DC power connection: the manufacturer specifically warns
|
||||||
|
that ordinary USB power is insufficient for reliable 13K operation.
|
||||||
|
[DASUNG power guidance](https://shop.dasung.com/pages/q-a).
|
||||||
|
For Pi 5, a supply supporting 5 V/5 A permits a larger USB power budget; a 3 A
|
||||||
|
supply limits downstream peripherals to 600 mA. Use powered hubs for the cartridge
|
||||||
|
assembly rather than budgeting the monitor and twelve cartridges from the Pi.
|
||||||
|
[Raspberry Pi power guidance](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#power-supply).
|
||||||
|
|
||||||
|
The shipped FDS boot configuration forces this monitor's recorded 3200 × 2400
|
||||||
|
timing at about 37 Hz. That timing was established on the original workstation.
|
||||||
|
DASUNG documents that some HDMI hosts instead use 27 Hz. FDS's forced EDID means
|
||||||
|
you should not assume automatic fallback. If HDMI is blank or unstable, use the
|
||||||
|
[native-EDID diagnostic boot](dasung.md#try-the-monitors-own-hdmi-edid) before
|
||||||
|
changing monitor identity or USB matching rules.
|
||||||
|
[DASUNG HDMI refresh-rate guidance](https://shop.dasung.com/pages/dasung-paperlike-13k-the-worlds-first-13-3-inch-37hz-3k-e-ink-monitor).
|
||||||
|
|
||||||
|
## 3. Build a matching image set on the workstation
|
||||||
|
|
||||||
|
Complete [Prepare the workstation](getting-started.md#prepare-the-workstation),
|
||||||
|
including bootstrap, smoke test and build checks. From the repository root, run
|
||||||
|
the following commands **sequentially**, stopping if any fails:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make rootfs PROFILE=cli
|
||||||
|
make system-card PROFILE=cli
|
||||||
|
make recovery
|
||||||
|
make initramfs
|
||||||
|
make boot-volume BOOT_MODE=production
|
||||||
|
make internal-image
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds one normal SYSTEM and independent recovery, then combines the current
|
||||||
|
kernel, initramfs and recovery into the internal disk. The CLI rootfs includes
|
||||||
|
the matching kernel modules, native s6 and the base Dasung controller. Rebuild
|
||||||
|
the complete sequence after a source change; existing convenience links in
|
||||||
|
`out/` can otherwise point to components from different builds.
|
||||||
|
|
||||||
|
`BOOT_MODE=production` puts the interactive console on the HDMI display with
|
||||||
|
USB keyboard input. It is suitable for first bring-up without a serial cable.
|
||||||
|
`PROFILE=development` is a separate choice that adds compilers and debugging
|
||||||
|
tools to SYSTEM; it does not select the console transport. The smaller CLI
|
||||||
|
profile is sufficient here, and includes the optional desktop too.
|
||||||
|
|
||||||
|
| Output | Write it to |
|
||||||
|
| --- | --- |
|
||||||
|
| `out/fds-internal.img` | Whole NVMe disk |
|
||||||
|
| `out/fds-system-cli.img` | Whole USB SYSTEM disk |
|
||||||
|
| `out/fds-boot.img` | Raw FAT partition payload; already included in the internal image |
|
||||||
|
| `out/fds-recovery.img` | Raw EROFS partition payload; already included in the internal image |
|
||||||
|
|
||||||
|
Do not write the last two files as whole disks. `make all` builds additional
|
||||||
|
profiles and release tooling; it is unnecessary for this first-boot image set.
|
||||||
|
Use [cleanup](cleanup.md) to review retained build trees when space is tight.
|
||||||
|
|
||||||
|
Record the inputs before flashing. On the **workstation**, choose a new session
|
||||||
|
directory and retain the printed build logs/manifests with it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir out/hardware-first
|
||||||
|
cp tests/hardware/session-template.json out/hardware-first/session.json
|
||||||
|
git rev-parse HEAD > out/hardware-first/source-commit.txt
|
||||||
|
git diff --binary HEAD > out/hardware-first/source-changes.patch
|
||||||
|
./tools/version > out/hardware-first/build-version.txt
|
||||||
|
sha256sum out/fds-internal.img out/fds-system-cli.img \
|
||||||
|
out/fds-boot.img out/fds-recovery.img out/fds-initramfs.img \
|
||||||
|
out/kernel/boot/kernel_2712.img > out/hardware-first/images.sha256
|
||||||
|
cp out/boot-volume/files/cmdline.txt out/hardware-first/cmdline-built.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep any untracked build inputs separately too. These checksums identify your
|
||||||
|
local build; they are not a release signature. For a downloaded release, follow
|
||||||
|
[signature verification](releases.md) and use its matching complete image set.
|
||||||
|
The historical `0.1.0` release and its acceptance records remain separate from
|
||||||
|
new local builds.
|
||||||
|
|
||||||
|
## 4. Check NVMe boot configuration
|
||||||
|
|
||||||
|
If the Pi already boots from this NVMe adapter, first record its EEPROM settings;
|
||||||
|
changing firmware is not a prerequisite just to try FDS. Otherwise, use a
|
||||||
|
separate Raspberry Pi OS maintenance microSD with the official EEPROM utilities.
|
||||||
|
Keep that rescue medium available throughout bring-up.
|
||||||
|
|
||||||
|
In the **maintenance OS**, before editing:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rpi-eeprom-config > pi-current.conf
|
||||||
|
rpi-eeprom-update > pi-eeprom-status.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy these files to the workstation and preserve the matching original firmware
|
||||||
|
as described in [EEPROM preparation and rollback](eeprom.md). To review the
|
||||||
|
installed boot configuration, the official editor is:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo rpi-eeprom-config --edit
|
||||||
|
```
|
||||||
|
|
||||||
|
For initial FDS bring-up, `BOOT_ORDER=0xf16` means NVMe first, SD fallback, then
|
||||||
|
repeat (read from the right). A non-HAT+ NVMe adapter may also require
|
||||||
|
`PCIE_PROBE=1`; follow the adapter's instructions and the
|
||||||
|
[official PCIe boot procedure](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#boot-from-pcie).
|
||||||
|
Preserve unrelated board settings, follow the updater's restart instructions,
|
||||||
|
then read the configuration back. FDS already supplies `dtparam=pciex1` in
|
||||||
|
`config.txt`; EEPROM discovery and the kernel's PCIe enablement are separate.
|
||||||
|
|
||||||
|
The [FDS EEPROM development profile](eeprom.md#profile-behavior) also keeps SD
|
||||||
|
fallback and enables firmware UART logging. Its offline preview is not a backup
|
||||||
|
of your board and does not apply itself. Do not switch to NVMe-only production
|
||||||
|
boot order until the rescue and normal boot paths have been checked.
|
||||||
|
|
||||||
|
## 5. Write the two disks
|
||||||
|
|
||||||
|
On the **workstation**, follow the complete
|
||||||
|
[guarded disk-writing procedure](internal-storage.md#install-the-internal-disk).
|
||||||
|
It includes disk identification by model/serial, an explicit erase confirmation,
|
||||||
|
readback, and backup-GPT relocation. Run it separately for these assignments:
|
||||||
|
|
||||||
|
| Pass | `fds_image` | `fds_disk` |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Internal disk | `$PWD/out/fds-internal.img` | The verified NVMe enclosure's whole-disk `/dev/disk/by-id/…` path |
|
||||||
|
| SYSTEM | `$PWD/out/fds-system-cli.img` | A different, verified USB cartridge's whole-disk `/dev/disk/by-id/…` path |
|
||||||
|
|
||||||
|
Both writes erase the selected disk. Follow all checks in that procedure; a
|
||||||
|
`-part1` path is not a whole-disk destination. The disks must use 512-byte logical
|
||||||
|
sectors and be at least as large as their respective images.
|
||||||
|
|
||||||
|
Confirm the three internal partition labels and the USB's single `FDS_SYSTEM`
|
||||||
|
label after writing. Safely disconnect the disks, install NVMe on the powered-off
|
||||||
|
Pi, and connect exactly one SYSTEM. Do not leave the NVMe enclosure connected as
|
||||||
|
a duplicate source of FDS partitions.
|
||||||
|
|
||||||
|
## 6. Power on and reach the console
|
||||||
|
|
||||||
|
Power the monitor and any required hub, attach video, USB control, keyboard and
|
||||||
|
SYSTEM, then power the Pi. Remove the maintenance SD for this attempt so an SD
|
||||||
|
fallback cannot be mistaken for an FDS boot.
|
||||||
|
|
||||||
|
The expected sequence is Pi firmware → kernel and stage0 from NVMe → read-only
|
||||||
|
SYSTEM from USB → native s6 → `FDS>`. The production image is quiet, so a missing
|
||||||
|
splash or scrolling kernel log is not by itself a failure. At **FDS**, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fds info
|
||||||
|
cat /usr/share/fds/image-profile
|
||||||
|
cat /proc/cmdline
|
||||||
|
findmnt -no SOURCE,FSTYPE,OPTIONS /
|
||||||
|
ps -p 1 -o pid,comm,args
|
||||||
|
fds machine status
|
||||||
|
fds bays
|
||||||
|
fds topology
|
||||||
|
fds boot-profile
|
||||||
|
```
|
||||||
|
|
||||||
|
Expect the intended version/profile, an EROFS root mounted read-only, and native
|
||||||
|
s6 as PID 1. `fds machine status` should report `internal_nvme` for valid internal
|
||||||
|
settings; `image_defaults` means settings loading fell back and needs diagnosis.
|
||||||
|
An empty initial map still legitimately reports `UNCONFIGURED` bays. Stage0
|
||||||
|
discovers SYSTEM by its GPT partition label before bay mapping, so that does not
|
||||||
|
prevent reaching the console. Do not unplug the running SYSTEM even if it has
|
||||||
|
not yet acquired a bay number.
|
||||||
|
|
||||||
|
If no SYSTEM is found, stage0 waits for media. Its prompt accepts `list`,
|
||||||
|
`rescan`, `recovery`, `reboot` and `poweroff`; inserting one valid SYSTEM should
|
||||||
|
resume boot. Two matching SYSTEMs are ambiguous: remove the unintended one.
|
||||||
|
This prompt is an early-boot command interface, not Bash.
|
||||||
|
|
||||||
|
The normal console is the ordinary `fds` user (UID 1000), with a temporary home
|
||||||
|
and locked account passwords. For root diagnostics, shut down with `fds poweroff`,
|
||||||
|
remove SYSTEM after shutdown, boot again and type `recovery` at stage0's
|
||||||
|
missing-SYSTEM prompt. The independent recovery image opens `RECOVERY#`.
|
||||||
|
Use that root console for monitor diagnostics and initial machine configuration;
|
||||||
|
there is no default root password to enter at `FDS>`.
|
||||||
|
|
||||||
|
## 7. Verify HDMI and the Dasung controller
|
||||||
|
|
||||||
|
At **`RECOVERY#`**, with both HDMI and USB data connected:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
lsusb
|
||||||
|
lsusb -t
|
||||||
|
dasungd status
|
||||||
|
dasungd query
|
||||||
|
tail -80 /run/log/dasungd/current
|
||||||
|
```
|
||||||
|
|
||||||
|
The existing profile expects UART `1a86:7523` and companion bridge `1a86:5512`
|
||||||
|
under the same USB hub, plus a connected DRM output matching the configured
|
||||||
|
monitor serial. Query again and check status after replies arrive: `connected`
|
||||||
|
alone is not the same as `responsive`. See [Dasung diagnostics](dasung.md) for
|
||||||
|
DRM capture, blank HDMI, missing USB interfaces and the 37 Hz override.
|
||||||
|
|
||||||
|
Confirm visible text entry, a refresh, and parameter readback separately. Once
|
||||||
|
basic operation works, test a **monitor-only** off/on cycle while retaining Pi
|
||||||
|
power, SYSTEM and hub power; record the picture, USB re-enumeration and daemon
|
||||||
|
reconnection. This is not permission to unplug writable storage. A passing VM
|
||||||
|
controller test cannot establish physical monitor recovery.
|
||||||
|
|
||||||
|
## 8. Install the first bay map without a DATA cartridge
|
||||||
|
|
||||||
|
Stay at **`RECOVERY#`**. Keep the hubs in their intended final ports. Insert one
|
||||||
|
identifiable test USB device at a time and record `fds topology` for each physical
|
||||||
|
slot. Test USB 2 and USB 3 connections separately where supported. Map only the
|
||||||
|
slots currently assembled; unmapped slots can remain unconfigured.
|
||||||
|
|
||||||
|
Create editable settings in RAM:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fds machine export /tmp/my-machine
|
||||||
|
cat /tmp/my-machine/machine.toml
|
||||||
|
fds topology
|
||||||
|
```
|
||||||
|
|
||||||
|
Recovery includes Bash but no full-screen editor. Use a here-document to enter
|
||||||
|
the measured map. **The path below is a syntax example, not a Pi port identity**:
|
||||||
|
replace it and the port number with your actual observation before installing.
|
||||||
|
If the observed device path ends in `:usb2/1/3`, the parent hub ends in
|
||||||
|
`:usb2/1` and downstream port `3` maps to your chosen bay number:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cat > /tmp/my-machine/bays.toml <<'EOF'
|
||||||
|
[first_usb2]
|
||||||
|
hub = "platform/REPLACE_WITH_OBSERVED_CONTROLLER:usb2/1"
|
||||||
|
[first_usb2.ports]
|
||||||
|
3 = 1
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Add separate groups for the measured USB 3 aliases and other hubs following
|
||||||
|
[the mapping reference](developer/cartridges.md#calibrate-the-physical-bay-map).
|
||||||
|
Do not map keyboard/monitor connections as cartridge bays or copy the emulator's
|
||||||
|
map. Validation checks syntax and conflicts; only your insertion tests establish
|
||||||
|
that a mapping matches the physical labels.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cat /tmp/my-machine/bays.toml
|
||||||
|
fds machine validate /tmp/my-machine
|
||||||
|
fds machine install /tmp/my-machine
|
||||||
|
fds reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
Installation writes the complete settings bundle to NVMe and retains the previous
|
||||||
|
one; it does not change the active mapping during this boot. With SYSTEM still
|
||||||
|
absent, select `recovery` again after reboot. Verify `fds machine status`, then
|
||||||
|
`fds bay 01` as you move the test device through the configured slots. Use
|
||||||
|
`fds eject N` and wait for SAFE before removing storage managed in a mapped bay.
|
||||||
|
|
||||||
|
Later, [export a backup and update settings from DATA](internal-storage.md#update-settings-from-recovery).
|
||||||
|
Do not reflash the whole NVMe merely to change bay numbering: that would replace
|
||||||
|
saved settings and diagnostics. For normal boot, shut down, insert SYSTEM,
|
||||||
|
and power on again.
|
||||||
|
|
||||||
|
## 9. Save evidence and shut down
|
||||||
|
|
||||||
|
At **`RECOVERY#`**, collect a fresh capture and explicitly save it to NVMe before
|
||||||
|
losing the RAM filesystem:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash /usr/share/fds/capture-hardware /tmp/hardware-first
|
||||||
|
cat /tmp/hardware-first/status.tsv
|
||||||
|
dasungd status > /tmp/hardware-first/dasung-status.json
|
||||||
|
cp /run/log/dasungd/current /tmp/hardware-first/dasung.log
|
||||||
|
tar -C /tmp -czf /tmp/hardware-first.tar.gz hardware-first
|
||||||
|
fds machine store hardware-first.tar.gz /tmp/hardware-first.tar.gz
|
||||||
|
fds power status
|
||||||
|
fds poweroff
|
||||||
|
```
|
||||||
|
|
||||||
|
Stored names must be new and files must be at most 16 MiB. The collector's
|
||||||
|
`SHA256SUMS` covers its own original outputs; the two manually added Dasung files
|
||||||
|
are additional evidence. Retrieve the archive later with
|
||||||
|
`fds machine fetch hardware-first.tar.gz /tmp/retrieved-hardware-first.tar.gz`
|
||||||
|
from recovery, then copy it to healthy, explicitly activated DATA and safely eject.
|
||||||
|
This capture describes the recovery boot; collect another capture in normal FDS
|
||||||
|
on DATA when that path is ready. `/tmp`, `/run`, logs and the normal home are
|
||||||
|
otherwise lost at shutdown.
|
||||||
|
|
||||||
|
Fill the workstation session record with observed results, wiring, EEPROM version,
|
||||||
|
image hashes and remaining failures. Leave unperformed cases `not_run`. Log
|
||||||
|
events measure software stages; physical power-on-to-picture and power-off timing
|
||||||
|
need an external observation. Follow [physical acceptance testing](developer/stress-testing.md#physical-acceptance-sequence)
|
||||||
|
when the complete assembly is ready.
|
||||||
|
|
||||||
|
Use `fds poweroff` and wait for shutdown before disconnecting power or swapping
|
||||||
|
SYSTEM. If shutdown is blocked, inspect `fds power status` and resolve the
|
||||||
|
reported storage problem. An E-Ink picture can remain visible after power is
|
||||||
|
removed, so the retained picture is not proof that the Pi is still running.
|
||||||
|
After basic console/display checks, try the [optional desktop](desktop.md) and
|
||||||
|
[cartridge workflow](cartridges.md).
|
||||||
|
|
||||||
|
## When the first boot fails
|
||||||
|
|
||||||
|
| Observation | Next check |
|
||||||
|
| --- | --- |
|
||||||
|
| Firmware cannot find NVMe | Adapter/ribbon seating, supply, EEPROM boot order, and conditional `PCIE_PROBE=1`; return to the maintenance SD |
|
||||||
|
| Stage0 says SYSTEM is missing | Whole GPT SYSTEM image, exactly one `FDS_SYSTEM`, USB power and `list`; bay calibration is not required at this stage |
|
||||||
|
| HDMI is blank or unstable | Monitor input/power, cable connector sizes, then [native HDMI EDID](dasung.md#try-the-monitors-own-hdmi-edid) or the serial console below |
|
||||||
|
| Kernel text appears but keyboard cannot reach a prompt | Check whether `BOOT_MODE=development` made UART the primary console |
|
||||||
|
| Picture works, `dasungd` does not | USB data cable, both USB IDs, DRM serial, permissions and daemon log; see [Dasung diagnostics](dasung.md) |
|
||||||
|
| `fds bays` is unconfigured | Complete step 8 using physical topology observations |
|
||||||
|
| `fds machine status` uses image defaults | Check the reported reason, NVMe transport, partition layout and clean internal ext4; do not run DATA repair against internal NVMe |
|
||||||
|
| USB resets or disappears under load | Reduce attached devices and check the Pi, monitor and hub supplies separately |
|
||||||
|
|
||||||
|
### Use a serial console when HDMI is unavailable
|
||||||
|
|
||||||
|
Use the Pi 5's dedicated debug UART connector and a compatible 3.3 V UART/debug
|
||||||
|
probe, following the [official UART documentation](https://www.raspberrypi.com/documentation/computers/configuration.html#configuring-uarts).
|
||||||
|
Connect the probe's host USB to the workstation, then open its serial terminal at
|
||||||
|
**115200 baud, 8 data bits, no parity, 1 stop bit, no flow control**. This is a
|
||||||
|
separate connection from the monitor's USB cable. Save the terminal log.
|
||||||
|
|
||||||
|
For an initial, still-disposable disk build, replace step 3's boot mode with
|
||||||
|
`make boot-volume BOOT_MODE=development` before `make internal-image`. This
|
||||||
|
enables verbose kernel output and selects `ttyAMA10` as the primary userspace
|
||||||
|
console. The development EEPROM profile separately enables firmware UART output.
|
||||||
|
Boot mode does not grant root; select recovery for a root shell.
|
||||||
|
|
||||||
|
For an already provisioned NVMe, use the
|
||||||
|
[offline BOOT edit procedure](dasung.md#edit-the-boot-command-line-offline) and
|
||||||
|
copy the single line from [cmdline-development.txt](../image/pi5/cmdline-development.txt)
|
||||||
|
into the disk's `cmdline.txt`; preserve any deliberate native-EDID diagnostic
|
||||||
|
change. This avoids overwriting settings to switch consoles. Restore the saved
|
||||||
|
command line to return to display/keyboard operation after debugging.
|
||||||
@@ -7,6 +7,9 @@ persistent machine settings. The operating system itself lives on a removable
|
|||||||
SYSTEM cartridge. This guide covers preparing the internal disk and updating
|
SYSTEM cartridge. This guide covers preparing the internal disk and updating
|
||||||
its settings from recovery.
|
its settings from recovery.
|
||||||
|
|
||||||
|
For the complete first-install sequence, including cabling, EEPROM and a first
|
||||||
|
bay map without DATA, use [the real-hardware setup guide](hardware-setup.md).
|
||||||
|
|
||||||
## Build the complete disk image
|
## Build the complete disk image
|
||||||
|
|
||||||
Build the component images first, in this order:
|
Build the component images first, in this order:
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
FDS recovery is an independent maintenance system on internal storage. It can
|
FDS recovery is an independent maintenance system on internal storage. It can
|
||||||
inspect a failed SYSTEM, check DATA and help prepare replacement cartridges.
|
inspect a failed SYSTEM, check DATA and help prepare replacement cartridges.
|
||||||
|
|
||||||
|
For a machine whose bays have never been configured, follow
|
||||||
|
[initial bay calibration](hardware-setup.md#8-install-the-first-bay-map-without-a-data-cartridge)
|
||||||
|
at the root recovery console before relying on a DATA cartridge for settings.
|
||||||
|
|
||||||
## Build the recovery image
|
## Build the recovery image
|
||||||
|
|
||||||
Use the prepared x86_64 build host described in [Your first build](getting-started.md).
|
Use the prepared x86_64 build host described in [Your first build](getting-started.md).
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ 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)
|
are relative to this recipe. The [included hello package](../examples/software/hello/void/template)
|
||||||
is a complete example.
|
is a complete example.
|
||||||
|
|
||||||
Custom sources are copied into an untracked `srcpkgs/PACKAGE` directory. Existing
|
Custom sources are copied into `srcpkgs/PACKAGE` inside the selected writable
|
||||||
|
build checkout. FDS uses `.host/void-packages/`; its `vendor/void-packages/`
|
||||||
|
submodule remains unchanged. The builder rejects a Git submodule as its build
|
||||||
|
checkout. Existing
|
||||||
tracked Void sources are never overwritten. If a generated source copy differs,
|
tracked Void sources are never overwritten. If a generated source copy differs,
|
||||||
inspect it, preserve any independent edits, then remove that generated copy and
|
inspect it, preserve any independent edits, then remove that generated copy and
|
||||||
retry. An unchanged copy can be reused.
|
retry. An unchanged copy can be reused.
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ Read the complete command error and its log before retrying. Build logs are in
|
|||||||
On FDS, `fds bay N`, `fds profiles` and `fds power status` report the relevant
|
On FDS, `fds bay N`, `fds profiles` and `fds power status` report the relevant
|
||||||
operation state. Append `--help` to a command for accepted options.
|
operation state. Append `--help` to a command for accepted options.
|
||||||
|
|
||||||
|
For physical Pi startup, use [first-boot troubleshooting](hardware-setup.md#when-the-first-boot-fails).
|
||||||
|
For the Paperlike on HDMI plus USB, use [Dasung diagnostics](dasung.md#diagnose-a-connection-problem).
|
||||||
|
|
||||||
| Symptom | Resolution |
|
| Symptom | Resolution |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Build disk is full | Stop builds and VMs; run `make clean-preview`, then `make clean`. See [retention rules](cleanup.md). |
|
| Build disk is full | Stop builds and VMs; run `make clean-preview`, then `make clean`. See [retention rules](cleanup.md). |
|
||||||
@@ -12,7 +15,7 @@ operation state. Append `--help` to a command for accepted options.
|
|||||||
| 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. |
|
| 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`. |
|
| 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. |
|
| 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. |
|
| Output already exists | Use a new output directory, cartridge image or preview. A stopped emulator session can be restarted; see below. |
|
||||||
| Executable architecture or tree integrity error | Rebuild the source package and cartridge. FDS cannot run an invalid payload. |
|
| 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. |
|
| 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. |
|
| Empty or unconfigured physical bays | Run `fds topology` and calibrate the machine's bay map. Do not infer slots from disk names. |
|
||||||
@@ -33,5 +36,7 @@ fds --json bay 01
|
|||||||
```
|
```
|
||||||
|
|
||||||
To inspect a stopped emulator, read its saved logs directly on the workstation.
|
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
|
Run `start` again with the same session directory and boot options after `stop`.
|
||||||
session and can be [exported](workstation.md#save-emulator-data).
|
Logs and DATA overlays are retained; bays start empty. Saved DATA can be
|
||||||
|
[exported](workstation.md#save-emulator-data). If restart reports a live process,
|
||||||
|
check `status` and stop the existing VM before retrying.
|
||||||
|
|||||||
+50
-8
@@ -7,18 +7,45 @@ as guest commands.
|
|||||||
|
|
||||||
## Prepare the tools
|
## Prepare the tools
|
||||||
|
|
||||||
The native tools are `fds-cartridge` and `fds-emulator`. Build them with Rust,
|
The native tools are `fds-cartridge` and `fds-emulator`. On Arch Linux, build
|
||||||
a C linker and Make; the repository selects its pinned Rust toolchain:
|
and install their `fds-tools` package from the checkout:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
sudo pacman -S --needed base-devel rustup python git
|
||||||
make workstation
|
make workstation
|
||||||
mkdir -p out
|
make workstation-install
|
||||||
|
```
|
||||||
|
|
||||||
|
`make workstation` produces `out/workstation/fds-tools-VERSION-1-ARCH.pkg.tar.zst`
|
||||||
|
with both commands, documentation, examples and license notices. It does not
|
||||||
|
install packages. `make workstation-install` builds that package and invokes
|
||||||
|
`sudo pacman -U` to install it into `/usr/bin`. Pacman tracks upgrades and removal;
|
||||||
|
remove it later with `sudo pacman -R fds-tools`. The exact latest package path is
|
||||||
|
recorded in `out/workstation/package-path.txt`.
|
||||||
|
|
||||||
|
The package requires glibc, GCC runtime libraries and xz. EROFS, bubblewrap and
|
||||||
|
QEMU are optional package dependencies because the tools can use the project-local
|
||||||
|
Void runners instead. For all features using native Arch utilities, install:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo pacman -S --needed bubblewrap erofs-utils qemu-system-aarch64 qemu-img
|
||||||
|
```
|
||||||
|
|
||||||
|
On other Linux distributions, install Rust, a C linker, Make, Python 3 and Git,
|
||||||
|
then build the same native commands without Arch packaging:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make workstation-binaries
|
||||||
export PATH="$PWD/out/workstation:$PATH"
|
export PATH="$PWD/out/workstation:$PATH"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Python and Git resolve the build version; neither is needed to run the installed
|
||||||
|
Rust commands. The repository selects its pinned Rust toolchain. XBPS and a
|
||||||
|
prepared Void checkout are still separate source-build prerequisites.
|
||||||
|
|
||||||
| Prerequisite | Used for |
|
| Prerequisite | Used for |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Prepared Void `void-packages` checkout and native XBPS utilities | Build source packages and install their AArch64 runtime dependencies |
|
| Prepared writable Void `void-packages` checkout and native XBPS utilities | Build source packages and install their AArch64 runtime dependencies |
|
||||||
| `mkfs.erofs`, `fsck.erofs` | Create and inspect read-only payload filesystems |
|
| `mkfs.erofs`, `fsck.erofs` | Create and inspect read-only payload filesystems |
|
||||||
| `bwrap` | Inspect filesystems and install package trees in a user namespace |
|
| `bwrap` | Inspect filesystems and install package trees in a user namespace |
|
||||||
| `xz` | Read older software cartridges and package/source archives |
|
| `xz` | Read older software cartridges and package/source archives |
|
||||||
@@ -31,12 +58,14 @@ 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).
|
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
|
Complete `xbps-src binary-bootstrap` for the workstation architecture before
|
||||||
using it. The workstation itself need not run Void. Select that checkout with
|
using it. Keep this writable checkout outside Git submodules. The workstation itself need not run Void. Select that checkout with
|
||||||
`--void-packages /path/to/void-packages`; native `xbps-*` tools must be in PATH,
|
`--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.
|
or selected with `--xbps-bin /path/to/bin`. Builds target AArch64/glibc.
|
||||||
|
|
||||||
For the complete FDS build workstation, [bootstrap](getting-started.md) already
|
For the complete FDS build workstation, [bootstrap](getting-started.md) already
|
||||||
prepares `vendor/void-packages` and `.host/xbps/usr/bin`. Reuse its local tools:
|
keeps `vendor/void-packages` as a clean upstream reference and prepares the
|
||||||
|
writable `.host/void-packages` checkout plus `.host/xbps/usr/bin`. The native
|
||||||
|
builder defaults to `.host/void-packages`. Reuse its local tools:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./tools/prepare-image-tools
|
./tools/prepare-image-tools
|
||||||
@@ -136,7 +165,9 @@ fds-emulator --session out/my-emulator start \
|
|||||||
--system /path/to/fds-system-cli.img
|
--system /path/to/fds-system-cli.img
|
||||||
```
|
```
|
||||||
|
|
||||||
The session directory must be new and private. Keep its absolute path short
|
The session directory is created private (0700). After stopping, reuse the same
|
||||||
|
directory with the same `start` command; an existing directory must contain a
|
||||||
|
valid stopped FDS session owned by you. Keep its absolute path short
|
||||||
(under 90 bytes) for Unix sockets. Start waits for the FDS prompt and uses two
|
(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
|
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.
|
PROGRAM images are read-only. The session holds logs and any DATA overlays.
|
||||||
@@ -179,7 +210,18 @@ fds-emulator --session out/my-emulator stop
|
|||||||
|
|
||||||
Eject asks FDS to stop programs and release storage before removing the virtual
|
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
|
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.
|
FDS shutdown and waits for QEMU to exit. Start the same session again:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fds-emulator --session out/my-emulator start --qemu-runner tools/in-void
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass any custom boot-image or memory options again. This is a fresh boot with
|
||||||
|
empty cartridge bays. Console and QEMU logs are appended, DATA overlay files
|
||||||
|
remain in place, and `previous-*.json` preserves the previous session's image
|
||||||
|
and overlay mapping. Insert cartridges again as needed. A running session,
|
||||||
|
unknown directory, insecure permissions, or an outstanding control operation
|
||||||
|
prevents restart; the tool never clears a live session to make room.
|
||||||
|
|
||||||
`unplug BAY` simulates a physical pull, and `stop --force` cuts virtual power.
|
`unplug BAY` simulates a physical pull, and `stop --force` cuts virtual power.
|
||||||
Use these only for deliberate failure simulation; DATA writes can be lost.
|
Use these only for deliberate failure simulation; DATA writes can be lost.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
import shlex
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -12,6 +13,7 @@ import uuid
|
|||||||
|
|
||||||
project = Path(__file__).resolve().parents[1]
|
project = Path(__file__).resolve().parents[1]
|
||||||
sys.path.insert(0, str(project/'tools'))
|
sys.path.insert(0, str(project/'tools'))
|
||||||
|
from fds_version import validate
|
||||||
from image_formats import digest, gpt, LINUX_FILESYSTEM, NAMESPACE
|
from image_formats import digest, gpt, LINUX_FILESYSTEM, NAMESPACE
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
@@ -21,6 +23,8 @@ parser.add_argument('--output-directory', type=Path)
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
rootfs = args.rootfs.resolve(strict=True)
|
rootfs = args.rootfs.resolve(strict=True)
|
||||||
with tarfile.open(rootfs) as archive:
|
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()
|
embedded = archive.extractfile('usr/share/fds/image-profile').read(64).decode().strip()
|
||||||
try:
|
try:
|
||||||
root = archive.getmember('.')
|
root = archive.getmember('.')
|
||||||
@@ -34,7 +38,7 @@ args.profile = embedded
|
|||||||
work = args.output_directory or Path(tempfile.mkdtemp(prefix='system-build.', dir=project/'out'))
|
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')
|
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']))
|
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:
|
with tarfile.open(rootfs) as source, tarfile.open(work/'system.tar', 'w', format=tarfile.PAX_FORMAT) as output:
|
||||||
for member in source:
|
for member in source:
|
||||||
if member.name in ('FDS', 'FDS/CARTRIDGE.TOML'): continue
|
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['rootfs_sha256'] = digest(rootfs)
|
||||||
layout['image_sha256'] = digest(work/'system.img')
|
layout['image_sha256'] = digest(work/'system.img')
|
||||||
layout['profile'] = args.profile
|
layout['profile'] = args.profile
|
||||||
|
layout['version'] = image_version
|
||||||
(work/'layout.json').write_text(json.dumps(layout, indent=2)+'\n')
|
(work/'layout.json').write_text(json.dumps(layout, indent=2)+'\n')
|
||||||
# An independent implementation checks the actual on-disk partition table.
|
# An independent implementation checks the actual on-disk partition table.
|
||||||
observed = json.loads(subprocess.check_output(['sfdisk', '--json', str(work/'system.img')]))['partitiontable']
|
observed = json.loads(subprocess.check_output(['sfdisk', '--json', str(work/'system.img')]))['partitiontable']
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
FDS/OS 0.1
|
FDS/OS @FDS_VERSION@
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
NAME="FDS/OS"
|
NAME="FDS/OS"
|
||||||
PRETTY_NAME="FDS/OS 0.1 (M2 development rootfs)"
|
PRETTY_NAME="FDS/OS @FDS_VERSION@"
|
||||||
ID=fds
|
ID=fds
|
||||||
ID_LIKE=void
|
ID_LIKE=void
|
||||||
VERSION_ID=0.1
|
VERSION_ID="@FDS_VERSION@"
|
||||||
VERSION="0.1 (M2)"
|
VERSION="@FDS_VERSION@"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
pkgname=fds-base-files
|
pkgname=fds-base-files
|
||||||
version=0.1.0
|
version=$(cat "${XBPS_DISTDIR}/fds-version")
|
||||||
revision=1
|
revision=1
|
||||||
archs="aarch64"
|
archs="aarch64"
|
||||||
depends="xbps-triggers"
|
depends="xbps-triggers"
|
||||||
@@ -22,6 +22,7 @@ do_install() {
|
|||||||
vinstall "${FILESDIR}/${f}" 600 etc
|
vinstall "${FILESDIR}/${f}" 600 etc
|
||||||
done
|
done
|
||||||
vinstall "${FILESDIR}/os-release" 644 usr/lib
|
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 ../usr/lib/os-release "${DESTDIR}/etc/os-release"
|
||||||
ln -s /run/fds/resolv.conf "${DESTDIR}/etc/resolv.conf"
|
ln -s /run/fds/resolv.conf "${DESTDIR}/etc/resolv.conf"
|
||||||
ln -s /proc/self/mounts "${DESTDIR}/etc/mtab"
|
ln -s /proc/self/mounts "${DESTDIR}/etc/mtab"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Explicit FDS userspace; never depend on Void's base-system metapackage.
|
# Explicit FDS userspace; never depend on Void's base-system metapackage.
|
||||||
pkgname=fds-base
|
pkgname=fds-base
|
||||||
version=0.1.0
|
version=$(cat "${XBPS_DISTDIR}/fds-version")
|
||||||
revision=1
|
revision=1
|
||||||
archs="aarch64"
|
archs="aarch64"
|
||||||
metapackage=yes
|
metapackage=yes
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
pkgname=fds-cartridged
|
pkgname=fds-cartridged
|
||||||
version=0.1.0
|
version=$(cat "${XBPS_DISTDIR}/fds-version")
|
||||||
revision=1
|
revision=1
|
||||||
archs="aarch64"
|
archs="aarch64"
|
||||||
nostrip=yes
|
nostrip=yes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Static payloads are built and checked on the host before packaging.
|
# Static payloads are built and checked on the host before packaging.
|
||||||
pkgname=fds-cli
|
pkgname=fds-cli
|
||||||
version=0.1.0
|
version=$(cat "${XBPS_DISTDIR}/fds-version")
|
||||||
revision=1
|
revision=1
|
||||||
archs="aarch64"
|
archs="aarch64"
|
||||||
nostrip=yes
|
nostrip=yes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# FDS-owned overlay. The build helper supplies the verified static ARM payload.
|
# FDS-owned overlay. The build helper supplies the verified static ARM payload.
|
||||||
pkgname=fds-dasungd
|
pkgname=fds-dasungd
|
||||||
version=0.1.0
|
version=$(cat "${XBPS_DISTDIR}/fds-version")
|
||||||
revision=1
|
revision=1
|
||||||
archs="aarch64"
|
archs="aarch64"
|
||||||
nostrip=yes
|
nostrip=yes
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
pkgname=fds-eink
|
pkgname=fds-eink
|
||||||
version=0.1.0
|
version=$(cat "${XBPS_DISTDIR}/fds-version")
|
||||||
revision=1
|
revision=1
|
||||||
archs="aarch64"
|
archs="aarch64"
|
||||||
depends="xorg-server xf86-input-libinput WindowMaker terminus-font xterm xset xsetroot fontconfig"
|
depends="xorg-server xf86-input-libinput WindowMaker terminus-font xterm xset xsetroot fontconfig"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Image-time s6 configuration. The native upstream binaries own PID 1.
|
# Image-time s6 configuration. The native upstream binaries own PID 1.
|
||||||
pkgname=fds-init
|
pkgname=fds-init
|
||||||
version=0.1.0
|
version=$(cat "${XBPS_DISTDIR}/fds-version")
|
||||||
revision=1
|
revision=1
|
||||||
archs="aarch64"
|
archs="aarch64"
|
||||||
depends="iproute2 s6-linux-init s6-rc s6 execline coreutils util-linux bash eudev fds-dasungd fds-cli fds-cartridged"
|
depends="iproute2 s6-linux-init s6-rc s6 execline coreutils util-linux bash eudev fds-dasungd fds-cli fds-cartridged"
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# 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 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'
|
||||||
|
'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; 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; 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"
|
||||||
|
cp -a "$_fds_source/docs" "$pkgdir/usr/share/doc/fds-tools/"
|
||||||
|
# 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"
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dasungd"
|
name = "dasungd"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "DASUNG Paperlike USB keepalive, control socket and Linux display profile daemon"
|
description = "DASUNG Paperlike USB keepalive, control socket and Linux display profile daemon"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(version, about)]
|
#[command(version = env!("FDS_BUILD_VERSION"), about)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
#[arg(long, global = true, default_value = "/etc/dasungd.toml")]
|
#[arg(long, global = true, default_value = "/etc/dasungd.toml")]
|
||||||
config: PathBuf,
|
config: PathBuf,
|
||||||
@@ -286,7 +286,7 @@ fn daemon(config: Config) -> Result<()> {
|
|||||||
let mut last_error = String::new();
|
let mut last_error = String::new();
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"dasungd {} ready; waiting for {}",
|
"dasungd {} ready; waiting for {}",
|
||||||
env!("CARGO_PKG_VERSION"),
|
env!("FDS_BUILD_VERSION"),
|
||||||
config.monitor_serial
|
config.monitor_serial
|
||||||
);
|
);
|
||||||
while running.load(Ordering::Relaxed) {
|
while running.load(Ordering::Relaxed) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-boottrace"
|
name = "fds-boottrace"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Measured FDS boot events and regression reports"
|
description = "Measured FDS boot events and regression reports"
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ fn adopt() -> Result<()> {
|
|||||||
}
|
}
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
version,
|
version = env!("FDS_BUILD_VERSION"),
|
||||||
about = "Record and compare measured boot events",
|
about = "Record and compare measured boot events",
|
||||||
after_help = "Measurements use Linux CLOCK_BOOTTIME, excluding firmware and power-on."
|
after_help = "Measurements use Linux CLOCK_BOOTTIME, excluding firmware and power-on."
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-burn"
|
name = "fds-burn"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Verified cartridge images and protected FDS media writes"
|
description = "Verified cartridge images and protected FDS media writes"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::{fs::OpenOptions, os::unix::fs::OpenOptionsExt, path::PathBuf, process:
|
|||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
version,
|
version = env!("FDS_BUILD_VERSION"),
|
||||||
about = "Create cartridge images and preview confirmed media writes",
|
about = "Create cartridge images and preview confirmed media writes",
|
||||||
after_help = "Creation writes a new regular file. Writes require confirmation tied to the selected insertion and image hash."
|
after_help = "Creation writes a new regular file. Writes require confirmation tied to the selected insertion and image hash."
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-cartridged"
|
name = "fds-cartridged"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "FDS event-driven cartridge and physical bay manager"
|
description = "FDS event-driven cartridge and physical bay manager"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use std::{path::PathBuf, process::ExitCode};
|
|||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
version,
|
version = env!("FDS_BUILD_VERSION"),
|
||||||
about = "Run the root cartridge service or inspect USB topology"
|
about = "Run the root cartridge service or inspect USB topology"
|
||||||
)]
|
)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
|||||||
@@ -226,7 +226,8 @@ fn session() -> Result<()> {
|
|||||||
}
|
}
|
||||||
#[derive(clap::Parser)]
|
#[derive(clap::Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
version,
|
name = "fds-profile",
|
||||||
|
version = env!("FDS_BUILD_VERSION"),
|
||||||
about = "Control the desktop and network profile",
|
about = "Control the desktop and network profile",
|
||||||
after_help = "Examples: fds-profile activate windowmaker; fds-profile deactivate"
|
after_help = "Examples: fds-profile activate windowmaker; fds-profile deactivate"
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-cli"
|
name = "fds-cli"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "FDS system and cartridge command interface"
|
description = "FDS system and cartridge command interface"
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ pub struct Cli {
|
|||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum Applet {
|
pub enum Applet {
|
||||||
#[command(version, about = "FDS/OS system and cartridge control")]
|
#[command(version = env!("FDS_BUILD_VERSION"), about = "FDS/OS system and cartridge control")]
|
||||||
Fds(FdsArgs),
|
Fds(FdsArgs),
|
||||||
/// Inspect a bay, image, or cartridge manifest.
|
/// Inspect a bay, image, or cartridge manifest.
|
||||||
#[command(version)]
|
#[command(version = env!("FDS_BUILD_VERSION"))]
|
||||||
FdsInspect {
|
FdsInspect {
|
||||||
#[arg(long, global = true)]
|
#[arg(long, global = true)]
|
||||||
json: bool,
|
json: bool,
|
||||||
@@ -23,7 +23,7 @@ pub enum Applet {
|
|||||||
args: InspectArgs,
|
args: InspectArgs,
|
||||||
},
|
},
|
||||||
/// Unmount a cartridge before removal.
|
/// Unmount a cartridge before removal.
|
||||||
#[command(version)]
|
#[command(version = env!("FDS_BUILD_VERSION"))]
|
||||||
FdsEject {
|
FdsEject {
|
||||||
#[arg(long, global = true)]
|
#[arg(long, global = true)]
|
||||||
json: bool,
|
json: bool,
|
||||||
@@ -31,7 +31,7 @@ pub enum Applet {
|
|||||||
args: BayArgs,
|
args: BayArgs,
|
||||||
},
|
},
|
||||||
/// Prepare native shutdown or inspect its status.
|
/// Prepare native shutdown or inspect its status.
|
||||||
#[command(version)]
|
#[command(version = env!("FDS_BUILD_VERSION"))]
|
||||||
FdsPower {
|
FdsPower {
|
||||||
#[arg(long, global = true)]
|
#[arg(long, global = true)]
|
||||||
json: bool,
|
json: bool,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ struct Invocation {
|
|||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
name = "fds-program",
|
name = "fds-program",
|
||||||
version,
|
version = env!("FDS_BUILD_VERSION"),
|
||||||
disable_help_subcommand = true,
|
disable_help_subcommand = true,
|
||||||
about = "Run a published cartridge command in the foreground"
|
about = "Run a published cartridge command in the foreground"
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-common"
|
name = "fds-common"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Shared bounded configuration and Linux discovery for FDS tools"
|
description = "Shared bounded configuration and Linux discovery for FDS tools"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub mod trace;
|
|||||||
|
|
||||||
use std::{fmt, fs::File, io::Read, path::Path, str::FromStr};
|
use std::{fmt, fs::File, io::Read, path::Path, str::FromStr};
|
||||||
|
|
||||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const VERSION: &str = env!("FDS_BUILD_VERSION");
|
||||||
pub const MAX_CONFIG_BYTES: u64 = 64 * 1024;
|
pub const MAX_CONFIG_BYTES: u64 = 64 * 1024;
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-control"
|
name = "fds-control"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Native grayscale X11 cartridge control panel"
|
description = "Native grayscale X11 cartridge control panel"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
|||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
version,
|
version = env!("FDS_BUILD_VERSION"),
|
||||||
about = "FDS cartridge control panel for X11",
|
about = "FDS cartridge control panel for X11",
|
||||||
after_help = "Select a bay to inspect its cartridge. Run opens a terminal; Eject stops its programs and releases the cartridge. Remove media only after SAFE appears."
|
after_help = "Select a bay to inspect its cartridge. Run opens a terminal; Eject stops its programs and releases the cartridge. Remove media only after SAFE appears."
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-release"
|
name = "fds-release"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "FDS release manifest signing and explicit-key verification"
|
description = "FDS release manifest signing and explicit-key verification"
|
||||||
|
|||||||
@@ -63,7 +63,13 @@ pub fn filename(value: &str) -> Result<()> {
|
|||||||
impl Manifest {
|
impl Manifest {
|
||||||
pub fn validate(&self) -> Result<()> {
|
pub fn validate(&self) -> Result<()> {
|
||||||
if self.format != 1
|
if self.format != 1
|
||||||
|| self.version != fds_common::VERSION
|
|| self.version.is_empty()
|
||||||
|
|| self.version.len() > 32
|
||||||
|
|| !self.version.starts_with(|c: char| c.is_ascii_digit())
|
||||||
|
|| !self
|
||||||
|
.version
|
||||||
|
.bytes()
|
||||||
|
.all(|b| b.is_ascii_alphanumeric() || b == b'.')
|
||||||
|| self.source_epoch == 0
|
|| self.source_epoch == 0
|
||||||
|| !hex_valid(&self.source_sha256, 64)
|
|| !hex_valid(&self.source_sha256, 64)
|
||||||
|| !hex_valid(&self.void_commit, 40)
|
|| !hex_valid(&self.void_commit, 40)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::{path::PathBuf, process::ExitCode};
|
|||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
version,
|
version = env!("FDS_BUILD_VERSION"),
|
||||||
about = "Sign and verify FDS release artifacts",
|
about = "Sign and verify FDS release artifacts",
|
||||||
after_help = "Keys and signatures are never overwritten. Verification checks every listed artifact.
|
after_help = "Keys and signatures are never overwritten. Verification checks every listed artifact.
|
||||||
An independently trusted public key is required; a bundled key is not automatically trusted."
|
An independently trusted public key is required; a bundled key is not automatically trusted."
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-smoketest"
|
name = "fds-smoketest"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-software"
|
name = "fds-software"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Verified installed software trees and legacy archive reading"
|
description = "Verified installed software trees and legacy archive reading"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-stage0"
|
name = "fds-stage0"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Static FDS early boot discovery and root handoff"
|
description = "Static FDS early boot discovery and root handoff"
|
||||||
|
|||||||
@@ -364,7 +364,10 @@ pub fn boot() -> Result<()> {
|
|||||||
let events = netlink()?; // Bind before scanning so insertion cannot fall into a gap.
|
let events = netlink()?; // Bind before scanning so insertion cannot fall into a gap.
|
||||||
let signals = child_events()?;
|
let signals = child_events()?;
|
||||||
let mut monitor = start_monitor(options.debug)?;
|
let mut monitor = start_monitor(options.debug)?;
|
||||||
println!("FELIS DATA SYSTEMS\nPORTABLE COMPUTER FP-85\n\nFDS BOOT ROM 0.1");
|
println!(
|
||||||
|
"FELIS DATA SYSTEMS\nPORTABLE COMPUTER FP-85\n\nFDS BOOT ROM {}",
|
||||||
|
fds_common::VERSION
|
||||||
|
);
|
||||||
println!("Commands while waiting: list, rescan, recovery, reboot, poweroff");
|
println!("Commands while waiting: list, rescan, recovery, reboot, poweroff");
|
||||||
let mut previous = String::new();
|
let mut previous = String::new();
|
||||||
let mut pending = ConsoleInput::default();
|
let mut pending = ConsoleInput::default();
|
||||||
@@ -397,7 +400,10 @@ pub fn boot() -> Result<()> {
|
|||||||
if options.debug {
|
if options.debug {
|
||||||
println!("FDS_STAGE0_ROOT: {}", options.root_label());
|
println!("FDS_STAGE0_ROOT: {}", options.root_label());
|
||||||
}
|
}
|
||||||
println!("MEMORY ........ READY\nSYSTEM ........ FDS/OS 0.1");
|
println!(
|
||||||
|
"MEMORY ........ READY\nSYSTEM ........ FDS/OS {}",
|
||||||
|
fds_common::VERSION
|
||||||
|
);
|
||||||
return handoff(monitor, options.debug);
|
return handoff(monitor, options.debug);
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::{path::PathBuf, process::ExitCode};
|
|||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
version,
|
version = env!("FDS_BUILD_VERSION"),
|
||||||
about = "Early boot discovery and native s6 handoff",
|
about = "Early boot discovery and native s6 handoff",
|
||||||
after_help = "With no arguments, root PID 1 boots FDS. Diagnostic options only read their supplied inputs."
|
after_help = "With no arguments, root PID 1 boots FDS. Diagnostic options only read their supplied inputs."
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-workstation"
|
name = "fds-workstation"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Linux workstation software and cartridge tools"
|
description = "Linux workstation software and cartridge tools"
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ use fds_workstation::emulator::Session;
|
|||||||
use std::{path::PathBuf, process::ExitCode};
|
use std::{path::PathBuf, process::ExitCode};
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
version,
|
name = "fds-emulator",
|
||||||
|
version = env!("FDS_BUILD_VERSION"),
|
||||||
about = "Boot FDS/OS and hotplug virtual USB cartridges on a Linux workstation",
|
about = "Boot FDS/OS and hotplug virtual USB cartridges on a Linux workstation",
|
||||||
after_help = "QEMU virt tests FDS software, not Raspberry Pi firmware or physical hardware. DATA uses retained temporary overlays; source images remain unchanged."
|
after_help = "QEMU virt tests FDS software, not Raspberry Pi firmware or physical hardware. DATA uses retained temporary overlays; source images remain unchanged."
|
||||||
)]
|
)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
/// Private session directory. Choose a new directory for each boot.
|
/// Private session directory. A stopped session can be started again.
|
||||||
#[arg(long, global = true, default_value = "out/emulator")]
|
#[arg(long, global = true, default_value = "out/emulator")]
|
||||||
session: PathBuf,
|
session: PathBuf,
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ use std::{
|
|||||||
fs::{self, File, OpenOptions},
|
fs::{self, File, OpenOptions},
|
||||||
io::Write,
|
io::Write,
|
||||||
os::{
|
os::{
|
||||||
fd::AsRawFd,
|
fd::{AsRawFd, FromRawFd, OwnedFd},
|
||||||
unix::fs::{DirBuilderExt, MetadataExt, OpenOptionsExt},
|
unix::{
|
||||||
|
fs::{DirBuilderExt, MetadataExt, OpenOptionsExt},
|
||||||
|
net::UnixStream,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Stdio,
|
process::Stdio,
|
||||||
@@ -90,6 +93,43 @@ fn lock(root: &Path) -> Result<File> {
|
|||||||
}
|
}
|
||||||
Ok(file)
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
// A pidfd remains attached to the original process even if its PID is reused.
|
||||||
|
// QMP EOF alone does not establish that QEMU finished releasing its files.
|
||||||
|
fn process(root: &Path) -> Result<Option<OwnedFd>> {
|
||||||
|
let text = match fs::read_to_string(root.join("qemu.pid")) {
|
||||||
|
Ok(text) => text,
|
||||||
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
|
||||||
|
Err(error) => return Err(error.into()),
|
||||||
|
};
|
||||||
|
let pid: i32 = text
|
||||||
|
.trim()
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| Error("Invalid QEMU pid file".into()))?;
|
||||||
|
if pid <= 0 {
|
||||||
|
return Err(Error("Invalid QEMU pid file".into()));
|
||||||
|
}
|
||||||
|
let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, 0) } as i32;
|
||||||
|
if fd < 0 {
|
||||||
|
let error = std::io::Error::last_os_error();
|
||||||
|
if error.raw_os_error() == Some(libc::ESRCH) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
return Err(error.into());
|
||||||
|
}
|
||||||
|
Ok(Some(unsafe { OwnedFd::from_raw_fd(fd) }))
|
||||||
|
}
|
||||||
|
fn exited(process: &OwnedFd, timeout_ms: i32) -> Result<bool> {
|
||||||
|
let mut event = libc::pollfd {
|
||||||
|
fd: process.as_raw_fd(),
|
||||||
|
events: libc::POLLIN,
|
||||||
|
revents: 0,
|
||||||
|
};
|
||||||
|
let result = unsafe { libc::poll(&mut event, 1, timeout_ms) };
|
||||||
|
if result < 0 {
|
||||||
|
return Err(std::io::Error::last_os_error().into());
|
||||||
|
}
|
||||||
|
Ok(result > 0 && event.revents & libc::POLLIN != 0)
|
||||||
|
}
|
||||||
impl Session {
|
impl Session {
|
||||||
pub fn load(root: &Path) -> Result<Self> {
|
pub fn load(root: &Path) -> Result<Self> {
|
||||||
ordinary()?;
|
ordinary()?;
|
||||||
@@ -159,17 +199,70 @@ impl Session {
|
|||||||
"Use a shorter session path (under 90 bytes), without commas or line breaks".into(),
|
"Use a shorter session path (under 90 bytes), without commas or line breaks".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
fs::DirBuilder::new()
|
let runner = runner.map(regular).transpose()?;
|
||||||
.mode(0o700)
|
let guard = match fs::DirBuilder::new().mode(0o700).create(&root) {
|
||||||
.create(&root)
|
Ok(()) => lock(&root)?,
|
||||||
.map_err(|e| {
|
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {
|
||||||
Error(format!(
|
let previous = Self::load(&root)?;
|
||||||
"Create a new session directory {}: {e}",
|
if process(&root)?
|
||||||
root.display()
|
.as_ref()
|
||||||
))
|
.map(|pid| exited(pid, 0))
|
||||||
})?;
|
.transpose()?
|
||||||
|
== Some(false)
|
||||||
|
{
|
||||||
|
return Err(Error(
|
||||||
|
"Session still has a live process; stop it before starting again".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for name in ["qmp.sock", "console.sock"] {
|
||||||
|
match UnixStream::connect(root.join(name)) {
|
||||||
|
Ok(_) => {
|
||||||
|
return Err(Error(
|
||||||
|
"Session still has an active socket; stop it before starting again"
|
||||||
|
.into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(error)
|
||||||
|
if matches!(
|
||||||
|
error.kind(),
|
||||||
|
std::io::ErrorKind::NotFound
|
||||||
|
| std::io::ErrorKind::ConnectionRefused
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
()
|
||||||
|
}
|
||||||
|
Err(error) => return Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Keep the previous cartridge-to-overlay mapping as well as the
|
||||||
|
// overlay files. A fresh boot starts with empty cartridge bays.
|
||||||
|
let archive = root.join(format!(
|
||||||
|
"previous-{}.json",
|
||||||
|
image::hex(&image::random_id()?)
|
||||||
|
));
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.mode(0o600)
|
||||||
|
.open(archive)?;
|
||||||
|
file.write_all(
|
||||||
|
&serde_json::to_vec_pretty(&previous.state)
|
||||||
|
.map_err(|e| Error(e.to_string()))?,
|
||||||
|
)?;
|
||||||
|
file.sync_all()?;
|
||||||
|
for name in ["qmp.sock", "console.sock", "qemu.pid"] {
|
||||||
|
match fs::remove_file(root.join(name)) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => (),
|
||||||
|
Err(error) => return Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previous._lock
|
||||||
|
}
|
||||||
|
Err(error) => return Err(error.into()),
|
||||||
|
};
|
||||||
let session = Self {
|
let session = Self {
|
||||||
_lock: lock(&root)?,
|
_lock: guard,
|
||||||
root,
|
root,
|
||||||
state: State {
|
state: State {
|
||||||
format: 1,
|
format: 1,
|
||||||
@@ -177,7 +270,7 @@ impl Session {
|
|||||||
kernel,
|
kernel,
|
||||||
initramfs,
|
initramfs,
|
||||||
system,
|
system,
|
||||||
qemu_runner: runner.map(regular).transpose()?,
|
qemu_runner: runner,
|
||||||
cartridges: BTreeMap::new(),
|
cartridges: BTreeMap::new(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -193,7 +286,7 @@ impl Session {
|
|||||||
.arg("-initrd").arg(&session.state.initramfs)
|
.arg("-initrd").arg(&session.state.initramfs)
|
||||||
.args(["-append","console=ttyAMA0 rdinit=/init ro quiet loglevel=3 fds.emulator=1","-blockdev"]).arg(block.to_string())
|
.args(["-append","console=ttyAMA0 rdinit=/init ro quiet loglevel=3 fds.emulator=1","-blockdev"]).arg(block.to_string())
|
||||||
.args(["-device","virtio-blk-pci,drive=system","-device","qemu-xhci,id=xhci,addr=05.0,p2=12,p3=12"])
|
.args(["-device","virtio-blk-pci,drive=system","-device","qemu-xhci,id=xhci,addr=05.0,p2=12,p3=12"])
|
||||||
.stdin(Stdio::null()).stdout(Stdio::null()).stderr(File::create(session.root.join("qemu.log"))?);
|
.stdin(Stdio::null()).stdout(Stdio::null()).stderr(OpenOptions::new().create(true).append(true).mode(0o600).custom_flags(libc::O_NOFOLLOW).open(session.root.join("qemu.log"))?);
|
||||||
crate::success(&mut command)
|
crate::success(&mut command)
|
||||||
.map_err(|e| Error(format!("{e}; inspect {}/qemu.log", session.root.display())))?;
|
.map_err(|e| Error(format!("{e}; inspect {}/qemu.log", session.root.display())))?;
|
||||||
let boot = (|| -> Result<()> {
|
let boot = (|| -> Result<()> {
|
||||||
@@ -344,16 +437,26 @@ impl Session {
|
|||||||
}
|
}
|
||||||
pub fn stop(&self, force: bool) -> Result<Value> {
|
pub fn stop(&self, force: bool) -> Result<Value> {
|
||||||
let mut qmp = self.qmp()?;
|
let mut qmp = self.qmp()?;
|
||||||
if force {
|
let process = process(&self.root)?.ok_or_else(|| {
|
||||||
qmp.execute("quit", json!({}))?;
|
Error("QEMU process record is unavailable; cannot confirm shutdown".into())
|
||||||
|
})?;
|
||||||
|
let response = if force {
|
||||||
|
// QEMU may reset QMP before acknowledging quit. The pidfd,
|
||||||
|
// rather than transport EOF or a reply, establishes actual exit.
|
||||||
|
qmp.execute("quit", json!({})).map(|_| ())
|
||||||
} else {
|
} else {
|
||||||
let output =
|
let output =
|
||||||
Serial::connect(&self.root, 120)?.command(&["fds".into(), "poweroff".into()])?;
|
Serial::connect(&self.root, 120)?.command(&["fds".into(), "poweroff".into()])?;
|
||||||
if output.status != 0 {
|
if output.status != 0 {
|
||||||
return Err(Error(format!("Guest refused shutdown: {}", output.output)));
|
return Err(Error(format!("Guest refused shutdown: {}", output.output)));
|
||||||
}
|
}
|
||||||
|
qmp.closed()
|
||||||
|
};
|
||||||
|
if !exited(&process, 30_000)? {
|
||||||
|
return Err(response
|
||||||
|
.err()
|
||||||
|
.unwrap_or_else(|| Error("QEMU has not exited; session remains in use".into())));
|
||||||
}
|
}
|
||||||
qmp.closed()?;
|
|
||||||
Ok(json!({"stopped":self.root,"forced":force,"logs_and_data_overlays_retained":true}))
|
Ok(json!({"stopped":self.root,"forced":force,"logs_and_data_overlays_retained":true}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ use std::{path::PathBuf, process::ExitCode};
|
|||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
version,
|
name = "fds-cartridge",
|
||||||
|
version = env!("FDS_BUILD_VERSION"),
|
||||||
about = "Build Void source packages and ready-to-run cartridge images on Linux"
|
about = "Build Void source packages and ready-to-run cartridge images on Linux"
|
||||||
)]
|
)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use std::{
|
|||||||
|
|
||||||
#[derive(clap::Args, Debug, Clone)]
|
#[derive(clap::Args, Debug, Clone)]
|
||||||
pub struct BuildOptions {
|
pub struct BuildOptions {
|
||||||
/// Prepared Void source checkout used by xbps-src (not the workstation OS).
|
/// Writable Void build checkout; keep it outside Git submodules.
|
||||||
#[arg(long, global = true, default_value = "vendor/void-packages")]
|
#[arg(long, global = true, default_value = ".host/void-packages")]
|
||||||
pub void_packages: PathBuf,
|
pub void_packages: PathBuf,
|
||||||
/// Optional rootless XBPS wrapper, for example this checkout's tools/in-void.
|
/// Optional rootless XBPS wrapper, for example this checkout's tools/in-void.
|
||||||
#[arg(long, global = true)]
|
#[arg(long, global = true)]
|
||||||
@@ -81,6 +81,16 @@ pub fn build(recipe: &Path, output: &Path, options: &BuildOptions) -> Result<Sof
|
|||||||
"--void-packages must name a prepared Void source checkout".into(),
|
"--void-packages must name a prepared Void source checkout".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let superproject = Command::new("git")
|
||||||
|
.arg("-C")
|
||||||
|
.arg(&checkout)
|
||||||
|
.args(["rev-parse", "--show-superproject-working-tree"])
|
||||||
|
.output()?;
|
||||||
|
if !superproject.status.success() || !superproject.stdout.is_empty() {
|
||||||
|
return Err(Error(
|
||||||
|
"Use a writable Void build checkout outside Git submodules; FDS prepares .host/void-packages with make bootstrap".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
let destination = checkout.join("srcpkgs").join(&input.source.package);
|
let destination = checkout.join("srcpkgs").join(&input.source.package);
|
||||||
if let Some(template) = input.source.template {
|
if let Some(template) = input.source.template {
|
||||||
let source = parent(recipe)?.join(template).canonicalize()?;
|
let source = parent(recipe)?.join(template).canonicalize()?;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Physical acceptance records
|
# Physical acceptance records
|
||||||
|
|
||||||
Physical tests are deferred until the Pi and its attached hardware are available.
|
Start with [real-hardware setup](../../docs/hardware-setup.md) on the assembled Pi.
|
||||||
Follow [the complete procedure](../../docs/developer/stress-testing.md), including bay
|
Then follow [the complete procedure](../../docs/developer/stress-testing.md), including bay
|
||||||
calibration, repeatable device populations, explicit SAFE handling and separate
|
calibration, repeatable device populations, explicit SAFE handling and separate
|
||||||
external timing measurements.
|
external timing measurements.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fds-test-helpers"
|
name = "fds-test-helpers"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
|
build = "../../tools/version-build.rs"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
publish = false
|
publish = false
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Install the actual Arch package into a disposable pacman root, without host writes."""
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
project=Path(__file__).resolve().parents[2]
|
||||||
|
package=Path((project/'out/workstation/package-path.txt').read_text().strip())
|
||||||
|
work=Path(tempfile.mkdtemp(prefix='arch-package-test.',dir=project/'out'))
|
||||||
|
root=work/'root';root.mkdir()
|
||||||
|
(root/'var/lib/pacman').mkdir(parents=True)
|
||||||
|
(root/'var/cache/pacman/pkg').mkdir(parents=True)
|
||||||
|
config=work/'pacman.conf'
|
||||||
|
config.write_text('[options]\nArchitecture = auto\nSigLevel = Never\nDownloadUser = root\n')
|
||||||
|
wrapper=work/'bin';wrapper.mkdir()
|
||||||
|
# Run the actual installer. In this private namespace it is already uid 0;
|
||||||
|
# redirect only pacman's installation root and disable dependency resolution.
|
||||||
|
(wrapper/'pacman').write_text('#!/bin/bash\nexec /usr/bin/pacman --config '+str(config)+' --root '+str(root)+' --dbpath '+str(root/'var/lib/pacman')+' --cachedir '+str(root/'var/cache/pacman/pkg')+' --logfile '+str(work/'pacman.log')+' --nodeps --nodeps --noconfirm "$@"\n')
|
||||||
|
(wrapper/'pacman').chmod(0o755)
|
||||||
|
command=['bwrap','--unshare-user','--uid','0','--gid','0','--ro-bind','/','/','--bind',str(work),str(work),'--dev','/dev','--proc','/proc','--setenv','PATH',str(wrapper)+':'+os.environ['PATH'],str(project/'tools/install-workstation')]
|
||||||
|
result=subprocess.run(command,capture_output=True,text=True)
|
||||||
|
(work/'install.log').write_text(result.stdout+result.stderr)
|
||||||
|
assert result.returncode==0,(result.stdout,result.stderr)
|
||||||
|
version=subprocess.check_output([str(project/'tools/version')],text=True).strip()
|
||||||
|
for tool in ['fds-cartridge','fds-emulator']:
|
||||||
|
output=subprocess.check_output([str(root/'usr/bin'/tool),'--version'],text=True).strip()
|
||||||
|
assert output==f'{tool} {version}',output
|
||||||
|
subprocess.run([str(root/'usr/bin'/tool),'--help'],check=True,stdout=subprocess.DEVNULL)
|
||||||
|
assert (root/'usr/share/doc/fds-tools/docs/workstation.md').is_file()
|
||||||
|
assert (root/'usr/share/licenses/fds-tools/RUST-NOTICES.txt').stat().st_size>0
|
||||||
|
installed=subprocess.check_output(['pacman','--config',str(config),'--root',str(root),'--dbpath',str(root/'var/lib/pacman'),'-Q','fds-tools'],text=True).strip()
|
||||||
|
assert installed==f'fds-tools {version}-1',installed
|
||||||
|
(work/'acceptance.json').write_text(json.dumps(dict(status='passed',package=str(package),installed=installed,installer='tools/install-workstation',host_install=False),indent=2)+'\n')
|
||||||
|
(project/'out/arch-package-current.txt').write_text(str(work)+'\n')
|
||||||
|
print('PASS: real pacman installation and both installed commands:',work)
|
||||||
@@ -61,7 +61,8 @@ class CleanupTests(unittest.TestCase):
|
|||||||
self.file('out/manifests/acceptance.json'),
|
self.file('out/manifests/acceptance.json'),
|
||||||
self.file('out/.gitkeep'),
|
self.file('out/.gitkeep'),
|
||||||
self.file('.host/xbps/tool'),
|
self.file('.host/xbps/tool'),
|
||||||
self.file('vendor/void-packages/hostdir/download'),
|
self.file('.host/void-packages/hostdir/download'),
|
||||||
|
self.file('vendor/void-packages/xbps-src'),
|
||||||
]
|
]
|
||||||
(self.root / 'out/rootfs-cli.tar').symlink_to('rootfs-build.NEW123/rootfs.tar')
|
(self.root / 'out/rootfs-cli.tar').symlink_to('rootfs-build.NEW123/rootfs.tar')
|
||||||
preview = self.run_cli('--dry-run')
|
preview = self.run_cli('--dry-run')
|
||||||
|
|||||||
@@ -30,31 +30,66 @@ expect_failure 'unsupported bootstrap option' tools/bootstrap-host --unknown
|
|||||||
|
|
||||||
# Use a disposable minimal checkout to exercise pin and dirty-tree detection.
|
# Use a disposable minimal checkout to exercise pin and dirty-tree detection.
|
||||||
mkdir -p "$scratch/repo/tools" "$scratch/repo/vendor/void-packages"
|
mkdir -p "$scratch/repo/tools" "$scratch/repo/vendor/void-packages"
|
||||||
cp tools/lib.sh tools/prepare-void "$scratch/repo/tools/"
|
cp tools/lib.sh tools/prepare-void tools/prepare-void-workspace tools/version tools/fds_version.py "$scratch/repo/tools/"
|
||||||
|
printf "1.0\n" >"$scratch/repo/FDS_VERSION"
|
||||||
fake="$scratch/repo/vendor/void-packages"
|
fake="$scratch/repo/vendor/void-packages"
|
||||||
git -C "$fake" init -q
|
git -C "$fake" init -q
|
||||||
printf '# fixture\n' >"$fake/xbps-src"
|
printf '# fixture\n' >"$fake/xbps-src"
|
||||||
mkdir -p "$fake/srcpkgs/fds-upstream" "$fake/etc" "$scratch/repo/config" "$scratch/repo/packages/fds-fixture"
|
mkdir -p "$fake/srcpkgs/fds-upstream" "$fake/etc" "$scratch/repo/config" "$scratch/repo/packages/fds-fixture"
|
||||||
printf '# upstream\n' >"$fake/srcpkgs/fds-upstream/template"
|
printf '# upstream\n' >"$fake/srcpkgs/fds-upstream/template"
|
||||||
printf 'etc/conf\n' >"$fake/.gitignore"
|
printf 'etc/conf\nhostdir/\nmasterdir-*/\n' >"$fake/.gitignore"
|
||||||
cp config/xbps-src.conf "$scratch/repo/config/"
|
cp config/xbps-src.conf "$scratch/repo/config/"
|
||||||
printf '# inert overlay fixture\n' >"$scratch/repo/packages/fds-fixture/template"
|
printf '# inert overlay fixture\n' >"$scratch/repo/packages/fds-fixture/template"
|
||||||
git -C "$fake" add .
|
git -C "$fake" add .
|
||||||
git -C "$fake" -c user.name=FDS -c user.email=test@example.invalid commit -qm fixture
|
git -C "$fake" -c user.name=FDS -c user.email=test@example.invalid commit -qm fixture
|
||||||
git -C "$fake" rev-parse HEAD >"$scratch/repo/VOID_PACKAGES_COMMIT"
|
git -C "$fake" rev-parse HEAD >"$scratch/repo/VOID_PACKAGES_COMMIT"
|
||||||
bash -c 'source "$1"; check_void_pin' _ "$scratch/repo/tools/lib.sh"
|
bash -c 'source "$1"; check_void_source' _ "$scratch/repo/tools/lib.sh"
|
||||||
printf 'PASS: accepts exact clean Void commit\n'
|
printf 'PASS: accepts exact clean Void commit\n'
|
||||||
|
# Existing generated files and caches move without copying their payloads.
|
||||||
|
cp -a "$scratch/repo/packages/fds-fixture" "$fake/srcpkgs/"
|
||||||
|
cp config/xbps-src.conf "$fake/etc/conf"
|
||||||
|
mkdir -p "$fake/hostdir" "$fake/masterdir-x86_64"
|
||||||
|
printf 'cached input\n' >"$fake/hostdir/cache"
|
||||||
|
cache_inode=$(stat -c %i "$fake/hostdir/cache")
|
||||||
"$scratch/repo/tools/prepare-void"
|
"$scratch/repo/tools/prepare-void"
|
||||||
cmp "$scratch/repo/packages/fds-fixture/template" "$fake/srcpkgs/fds-fixture/template"
|
build="$scratch/repo/.host/void-packages"
|
||||||
|
[[ -z $(git -C "$fake" status --porcelain --untracked-files=all) ]] || die 'Migration dirtied upstream'
|
||||||
|
[[ ! -e $fake/hostdir && ! -e $fake/etc/conf && ! -e $fake/srcpkgs/fds-fixture ]] || die 'Migration left build state in upstream'
|
||||||
|
[[ $(stat -c %i "$build/hostdir/cache") == "$cache_inode" ]] || die 'Migration copied cache data'
|
||||||
|
[[ ! -f $build/.git/objects/info/alternates ]] || die 'Build checkout depends on upstream object storage'
|
||||||
|
bash -c 'source "$1"; check_void_pin' _ "$scratch/repo/tools/lib.sh"
|
||||||
|
cmp "$scratch/repo/packages/fds-fixture/template" "$build/srcpkgs/fds-fixture/template"
|
||||||
"$scratch/repo/tools/prepare-void"
|
"$scratch/repo/tools/prepare-void"
|
||||||
printf 'PASS: overlay copied and repeat preparation is idempotent\n'
|
printf 'PASS: overlays and caches moved outside clean upstream; preparation is idempotent\n'
|
||||||
printf '# changed\n' >>"$scratch/repo/packages/fds-fixture/template"
|
printf '# changed\n' >>"$scratch/repo/packages/fds-fixture/template"
|
||||||
expect_failure 'stale generated overlay' "$scratch/repo/tools/prepare-void"
|
expect_failure 'stale generated overlay' "$scratch/repo/tools/prepare-void"
|
||||||
cp "$fake/srcpkgs/fds-fixture/template" "$scratch/repo/packages/fds-fixture/template"
|
cp "$build/srcpkgs/fds-fixture/template" "$scratch/repo/packages/fds-fixture/template"
|
||||||
|
# Preserve unexpected upstream additions and independently edited legacy copies.
|
||||||
|
printf 'personal source\n' >"$fake/personal-file"
|
||||||
|
expect_failure 'unknown upstream addition' "$scratch/repo/tools/prepare-void"
|
||||||
|
[[ -f $fake/personal-file ]] || die 'Unknown file was removed'
|
||||||
|
rm "$fake/personal-file"
|
||||||
|
cp -a "$scratch/repo/packages/fds-fixture" "$fake/srcpkgs/"
|
||||||
|
printf '# independent edit\n' >>"$fake/srcpkgs/fds-fixture/template"
|
||||||
|
expect_failure 'edited legacy overlay' "$scratch/repo/tools/prepare-void"
|
||||||
|
grep -q 'independent edit' "$fake/srcpkgs/fds-fixture/template"
|
||||||
|
rm -r "$fake/srcpkgs/fds-fixture"
|
||||||
|
# An explicit upstream pin update advances the build checkout and keeps caches.
|
||||||
|
printf 'new upstream revision\n' >"$fake/README"
|
||||||
|
git -C "$fake" add README
|
||||||
|
git -C "$fake" -c user.name=FDS -c user.email=test@example.invalid commit -qm update
|
||||||
|
git -C "$fake" rev-parse HEAD >"$scratch/repo/VOID_PACKAGES_COMMIT"
|
||||||
|
# Production initializes upstream with depth 1; updating that boundary must work.
|
||||||
|
git -C "$fake" rev-parse HEAD >"$fake/.git/shallow"
|
||||||
|
expect_failure 'outdated build checkout' bash -c 'source "$1"; check_void_pin' _ "$scratch/repo/tools/lib.sh"
|
||||||
|
"$scratch/repo/tools/prepare-void"
|
||||||
|
[[ $(stat -c %i "$build/hostdir/cache") == "$cache_inode" ]] || die 'Pin update replaced cache'
|
||||||
|
[[ $(git -C "$build" rev-parse HEAD) == "$(cat "$scratch/repo/VOID_PACKAGES_COMMIT")" ]] || die 'Build pin not advanced'
|
||||||
mkdir -p "$scratch/repo/packages/fds-upstream"
|
mkdir -p "$scratch/repo/packages/fds-upstream"
|
||||||
printf '# collision\n' >"$scratch/repo/packages/fds-upstream/template"
|
printf '# collision\n' >"$scratch/repo/packages/fds-upstream/template"
|
||||||
expect_failure 'overlay replacing upstream package' "$scratch/repo/tools/prepare-void"
|
expect_failure 'overlay replacing upstream package' "$scratch/repo/tools/prepare-void"
|
||||||
printf '# changed config\n' >>"$fake/etc/conf"
|
rm -r "$scratch/repo/packages/fds-upstream"
|
||||||
|
printf '# changed config\n' >>"$build/etc/conf"
|
||||||
expect_failure 'overwriting local xbps configuration' "$scratch/repo/tools/prepare-void"
|
expect_failure 'overwriting local xbps configuration' "$scratch/repo/tools/prepare-void"
|
||||||
printf '\n# dirty\n' >>"$fake/xbps-src"
|
printf '\n# dirty\n' >>"$fake/xbps-src"
|
||||||
expect_failure 'modified Void source' bash -c 'source "$1"; check_void_pin' _ "$scratch/repo/tools/lib.sh"
|
expect_failure 'modified Void source' bash -c 'source "$1"; check_void_pin' _ "$scratch/repo/tools/lib.sh"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
project = Path(__file__).resolve().parents[2]
|
project = Path(__file__).resolve().parents[2]
|
||||||
|
sys.path.insert(0, str(project / 'tools'))
|
||||||
loader = importlib.machinery.SourceFileLoader('fds_frozen_fixture', str(project / 'tools/frozen-inputs'))
|
loader = importlib.machinery.SourceFileLoader('fds_frozen_fixture', str(project / 'tools/frozen-inputs'))
|
||||||
spec = importlib.util.spec_from_loader(loader.name, loader)
|
spec = importlib.util.spec_from_loader(loader.name, loader)
|
||||||
frozen = importlib.util.module_from_spec(spec)
|
frozen = importlib.util.module_from_spec(spec)
|
||||||
@@ -131,4 +132,46 @@ command[-1] = linked
|
|||||||
result = invoke(*command, ok=False)
|
result = invoke(*command, ok=False)
|
||||||
assert 'not a symlink' in result.stderr
|
assert 'not a symlink' in result.stderr
|
||||||
print(f'PASS: release output preservation and private-key symlink rejection: {work}')
|
print(f'PASS: release output preservation and private-key symlink rejection: {work}')
|
||||||
|
|
||||||
|
# Restore small real Git bundles through both workspace layouts. These fixtures
|
||||||
|
# exercise path selection and cache preservation, not an operating-system build.
|
||||||
|
upstream = work / 'upstream'
|
||||||
|
upstream.mkdir()
|
||||||
|
invoke('git', 'init', '-q', upstream)
|
||||||
|
(upstream / 'xbps-src').write_text('# inert source fixture\n')
|
||||||
|
(upstream / '.gitignore').write_text('hostdir/\nmasterdir-*/\netc/conf\netc/xbps.d/custom/\n')
|
||||||
|
invoke('git', '-C', upstream, 'add', '.')
|
||||||
|
invoke('git', '-C', upstream, '-c', 'user.name=FDS', '-c', 'user.email=test@example.invalid', 'commit', '-qm', 'fixture')
|
||||||
|
pin = invoke('git', '-C', upstream, 'rev-parse', 'HEAD').stdout.strip()
|
||||||
|
epoch = int(invoke('git', '-C', upstream, 'show', '-s', '--format=%ct', 'HEAD').stdout)
|
||||||
|
for layout in ('separate', 'legacy'):
|
||||||
|
fixture = work / ('restore-input-' + layout)
|
||||||
|
(fixture / 'project/tools').mkdir(parents=True)
|
||||||
|
(fixture / 'project/config').mkdir()
|
||||||
|
(fixture / 'project/VOID_PACKAGES_COMMIT').write_text(pin + '\n')
|
||||||
|
(fixture / 'project/config/xbps-src.conf').write_text('# fixture\n')
|
||||||
|
if layout == 'separate':
|
||||||
|
shutil.copy2(project / 'tools/prepare-void-workspace', fixture / 'project/tools/prepare-void-workspace')
|
||||||
|
invoke('git', '-C', upstream, 'bundle', 'create', fixture / 'void.bundle', 'HEAD')
|
||||||
|
for name in ('masterdir/etc/xbps.d', 'sources', 'repositories/build',
|
||||||
|
'repositories/cli', 'repositories/development', 'repositories/recovery',
|
||||||
|
'xbps', 'image-tools', 'eeprom', 'cache', 'downloads', 'cargo-vendor', 'rust-toolchain'):
|
||||||
|
(fixture / name).mkdir(parents=True, exist_ok=True)
|
||||||
|
(fixture / 'sources/retained.tar.gz').write_bytes(b'cached source fixture')
|
||||||
|
restore_lock = dict(lock, void_commit=pin, source_epoch=epoch)
|
||||||
|
restore_lock['files'] = frozen.inventory(fixture)
|
||||||
|
restore_lock['source_sha256'] = frozen.source_digest(restore_lock['files'])
|
||||||
|
(fixture / 'lock.json').write_text(json.dumps(restore_lock))
|
||||||
|
destination = work / ('restored-' + layout)
|
||||||
|
invoke(verify, 'restore', fixture, destination)
|
||||||
|
build = destination / ('.host/void-packages' if layout == 'separate' else 'vendor/void-packages')
|
||||||
|
assert (build / 'hostdir/sources/retained.tar.gz').read_bytes() == b'cached source fixture'
|
||||||
|
assert (build / 'masterdir-x86_64/etc/xbps.d/05-fds-frozen-local.conf').read_text() == 'repository=/host/frozen-repository\n'
|
||||||
|
assert invoke('git', '-C', build, 'rev-parse', 'HEAD').stdout.strip() == pin
|
||||||
|
if layout == 'separate':
|
||||||
|
reference = destination / 'vendor/void-packages'
|
||||||
|
assert not (reference / 'hostdir').exists()
|
||||||
|
assert not invoke('git', '-C', reference, 'status', '--porcelain', '--untracked-files=all').stdout.strip()
|
||||||
|
invoke(verify, 'verify', fixture)
|
||||||
|
print('PASS: separate-workspace and legacy frozen restores retain source caches and offline repositories', flush=True)
|
||||||
print('NOTE: these are small contract fixtures; actual source-to-image offline reproduction remains a separate gate')
|
print('NOTE: these are small contract fixtures; actual source-to-image offline reproduction remains a separate gate')
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ def invoke(prefix, arguments, ok=True):
|
|||||||
def check_all(directory, key, ok=True):
|
def check_all(directory, key, ok=True):
|
||||||
for label, command in commands:
|
for label, command in commands:
|
||||||
result = invoke(command, ['verify', directory, '--key', key], ok)
|
result = invoke(command, ['verify', directory, '--key', key], ok)
|
||||||
if ok: assert 'VERIFIED FDS/OS 0.1.0' in result
|
if ok:
|
||||||
|
expected=json.loads((directory/'manifest.json').read_text())['version']
|
||||||
|
assert f'VERIFIED FDS/OS {expected}' in result
|
||||||
|
|
||||||
key_prefix = work / 'test-key'
|
key_prefix = work / 'test-key'
|
||||||
invoke([str(host)], ['keygen', key_prefix])
|
invoke([str(host)], ['keygen', key_prefix])
|
||||||
@@ -55,6 +57,13 @@ manifest = {'format': 1, 'version': '0.1.0', 'source_epoch': 1,
|
|||||||
invoke([str(host)], ['sign', release, '--key', private])
|
invoke([str(host)], ['sign', release, '--key', private])
|
||||||
check_all(release, public)
|
check_all(release, public)
|
||||||
invoke([str(host)], ['sign', release, '--key', private], False)
|
invoke([str(host)], ['sign', release, '--key', private], False)
|
||||||
|
# Keep the historical release fixture, and also sign the current Git-derived identity.
|
||||||
|
current = work / 'current-version'; current.mkdir()
|
||||||
|
shutil.copy2(release / 'image.img', current / 'image.img')
|
||||||
|
current_manifest = dict(manifest, version=subprocess.check_output([str(project/'tools/version')], text=True).strip())
|
||||||
|
(current / 'manifest.json').write_text(json.dumps(current_manifest))
|
||||||
|
invoke([str(host)], ['sign', current, '--key', private])
|
||||||
|
check_all(current, public)
|
||||||
|
|
||||||
# Independent standard Ed25519 verification/signing over the exact domain prefix
|
# Independent standard Ed25519 verification/signing over the exact domain prefix
|
||||||
# and raw manifest bytes. Test-only DER files are private and never printed.
|
# and raw manifest bytes. Test-only DER files are private and never printed.
|
||||||
@@ -111,7 +120,7 @@ for name in ['traversal', 'duplicate', 'unknown', 'wrong-version', 'unbounded']:
|
|||||||
if name == 'traversal': value['files'][0]['name'] = '../image.img'
|
if name == 'traversal': value['files'][0]['name'] = '../image.img'
|
||||||
elif name == 'duplicate': value['files'].append(copy.deepcopy(value['files'][0]))
|
elif name == 'duplicate': value['files'].append(copy.deepcopy(value['files'][0]))
|
||||||
elif name == 'unknown': value['execute'] = 'sh'
|
elif name == 'unknown': value['execute'] = 'sh'
|
||||||
elif name == 'wrong-version': value['version'] = '999'
|
elif name == 'wrong-version': value['version'] = '../invalid'
|
||||||
(bad / 'manifest.json').write_text(json.dumps(value) + (' ' * (1024 * 1024) if name == 'unbounded' else ''))
|
(bad / 'manifest.json').write_text(json.dumps(value) + (' ' * (1024 * 1024) if name == 'unbounded' else ''))
|
||||||
(bad / 'manifest.sig').write_bytes((release / 'manifest.sig').read_bytes())
|
(bad / 'manifest.sig').write_bytes((release / 'manifest.sig').read_bytes())
|
||||||
check_all(bad, public, False)
|
check_all(bad, public, False)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import subprocess
|
|||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
project = Path(__file__).resolve().parents[2]
|
project = Path(__file__).resolve().parents[2]
|
||||||
|
version = subprocess.check_output([str(project/'tools/version')], text=True).strip()
|
||||||
|
|
||||||
def run(binary, *args, ok=True):
|
def run(binary, *args, ok=True):
|
||||||
result = subprocess.run([str(project/'tools/in-void'), 'qemu-aarch64',
|
result = subprocess.run([str(project/'tools/in-void'), 'qemu-aarch64',
|
||||||
@@ -22,11 +23,11 @@ with tempfile.TemporaryDirectory(prefix='m3-', dir=project/'out') as directory:
|
|||||||
for helper in ['fds', 'fds-burn', 'fds-inspect', 'fds-eject', 'fds-power',
|
for helper in ['fds', 'fds-burn', 'fds-inspect', 'fds-eject', 'fds-power',
|
||||||
'fds-stage0', 'fds-boottrace', 'fds-cartridged', 'fds-profile', 'fds-release']:
|
'fds-stage0', 'fds-boottrace', 'fds-cartridged', 'fds-profile', 'fds-release']:
|
||||||
assert f'Usage: {helper}' in run(helper, '--help')
|
assert f'Usage: {helper}' in run(helper, '--help')
|
||||||
assert '0.1.0' in run(helper, '--version')
|
assert version in run(helper, '--version')
|
||||||
assert 'activate windowmaker' in run('fds-profile', '--help')
|
assert 'activate windowmaker' in run('fds-profile', '--help')
|
||||||
run('fds-profile', 'activate', 'windowmaker', ok=False)
|
run('fds-profile', 'activate', 'windowmaker', ok=False)
|
||||||
assert '0.1.0' in run('fds', '--version')
|
assert version in run('fds', '--version')
|
||||||
assert '0.1.0' in run('fds-stage0', '--version')
|
assert version in run('fds-stage0', '--version')
|
||||||
run('fds-stage0', ok=False) # Must refuse root-switch operations outside PID 1.
|
run('fds-stage0', ok=False) # Must refuse root-switch operations outside PID 1.
|
||||||
assert json.loads(run('fds', '--json', 'info'))['target'] == 'aarch64 static-musl'
|
assert json.loads(run('fds', '--json', 'info'))['target'] == 'aarch64 static-musl'
|
||||||
fixture = project/'tests/fixtures/manifests/windowmaker.toml'
|
fixture = project/'tests/fixtures/manifests/windowmaker.toml'
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check release, development, dirty, exported and cached Cargo version behavior."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
project = Path(__file__).resolve().parents[2]
|
||||||
|
sys.path.insert(0, str(project/'tools'))
|
||||||
|
from fds_version import version
|
||||||
|
|
||||||
|
class Versions(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.temporary = tempfile.TemporaryDirectory(prefix='fds-version-')
|
||||||
|
self.addCleanup(self.temporary.cleanup)
|
||||||
|
self.root = Path(self.temporary.name)
|
||||||
|
self.git('init', '-q')
|
||||||
|
self.git('config', 'user.name', 'FDS test')
|
||||||
|
self.git('config', 'user.email', 'test@example.invalid')
|
||||||
|
(self.root/'source').write_text('first\n')
|
||||||
|
self.commit()
|
||||||
|
|
||||||
|
def git(self, *args):
|
||||||
|
return subprocess.check_output(['git', '-C', str(self.root), *args], text=True).strip()
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
self.git('add', '.')
|
||||||
|
self.git('commit', '-qm', 'fixture')
|
||||||
|
|
||||||
|
def test_git_versions(self):
|
||||||
|
revision=self.git('rev-parse','--short=12','HEAD')
|
||||||
|
self.assertEqual(version(self.root), f'0.dev.g{revision}')
|
||||||
|
self.git('tag','unrelated')
|
||||||
|
self.assertEqual(version(self.root), f'0.dev.g{revision}')
|
||||||
|
self.git('tag','-a','1.0','-m','Release 1.0')
|
||||||
|
self.assertEqual(version(self.root),'1.0')
|
||||||
|
(self.root/'source').write_text('changed\n')
|
||||||
|
self.assertEqual(version(self.root),'1.0.dirty')
|
||||||
|
self.commit()
|
||||||
|
self.assertEqual(version(self.root),f'1.0.r1.g{self.git("rev-parse","--short=12","HEAD")}')
|
||||||
|
self.git('tag','v1.1')
|
||||||
|
self.assertEqual(version(self.root),'1.1')
|
||||||
|
(self.root/'new').write_text('new\n')
|
||||||
|
self.assertEqual(version(self.root),'1.1')
|
||||||
|
self.git('add', 'new')
|
||||||
|
self.assertEqual(version(self.root),'1.1.dirty')
|
||||||
|
|
||||||
|
def test_export_is_independent_of_containing_git(self):
|
||||||
|
self.git('tag','1.0')
|
||||||
|
exported=self.root/'export';exported.mkdir()
|
||||||
|
(exported/'FDS_VERSION').write_text('0.dev.gabcdef123456\n')
|
||||||
|
self.assertEqual(version(exported),'0.dev.gabcdef123456')
|
||||||
|
(exported/'FDS_VERSION').write_text('../bad\n')
|
||||||
|
with self.assertRaises(ValueError):version(exported)
|
||||||
|
(exported/'FDS_VERSION').unlink()
|
||||||
|
with self.assertRaises(FileNotFoundError):version(exported)
|
||||||
|
|
||||||
|
def test_cargo_rebuilds_on_new_tags_and_edits(self):
|
||||||
|
(self.root/'tools').mkdir()
|
||||||
|
for name in ['version','fds_version.py','version-build.rs']:
|
||||||
|
shutil.copy2(project/'tools'/name,self.root/'tools'/name)
|
||||||
|
crate=self.root/'rust/probe';(crate/'src').mkdir(parents=True)
|
||||||
|
(self.root/'Cargo.toml').write_text('[workspace]\nmembers=["rust/probe"]\nresolver="2"\n')
|
||||||
|
(self.root/'.gitignore').write_text('/target/\n__pycache__/\n')
|
||||||
|
(crate/'Cargo.toml').write_text('[package]\nname="probe"\nversion="0.0.0"\nedition="2024"\nbuild="../../tools/version-build.rs"\n')
|
||||||
|
(crate/'src/main.rs').write_text('fn main() { println!("{}", env!("FDS_BUILD_VERSION")); }\n')
|
||||||
|
self.commit()
|
||||||
|
def run():
|
||||||
|
return subprocess.check_output(['cargo','run','--quiet','--offline','-p','probe'],cwd=self.root,text=True).strip()
|
||||||
|
self.assertEqual(run(),version(self.root))
|
||||||
|
self.commit() # Cargo generated its lockfile on the first run.
|
||||||
|
self.git('tag','1.0')
|
||||||
|
self.assertEqual(run(),'1.0')
|
||||||
|
(self.root/'source').write_text('edited\n')
|
||||||
|
self.assertEqual(run(),'1.0.dirty')
|
||||||
|
self.git('checkout','--','source')
|
||||||
|
self.assertEqual(run(),'1.0')
|
||||||
|
self.git('tag','-d','1.0')
|
||||||
|
self.assertEqual(run(),version(self.root))
|
||||||
|
# A frozen source tree builds with its saved identity without Git.
|
||||||
|
with tempfile.TemporaryDirectory(prefix='fds-export-') as directory:
|
||||||
|
exported=Path(directory)/'source'
|
||||||
|
shutil.copytree(self.root,exported,ignore=shutil.ignore_patterns('.git','target','__pycache__'))
|
||||||
|
(exported/'FDS_VERSION').write_text('1.0\n')
|
||||||
|
output=subprocess.check_output(['cargo','run','--quiet','--offline','-p','probe'],cwd=exported,text=True).strip()
|
||||||
|
self.assertEqual(output,'1.0')
|
||||||
|
|
||||||
|
if __name__=='__main__': unittest.main()
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify the current version in actual GPT/EROFS bytes and execute its ARM commands."""
|
||||||
|
from pathlib import Path
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
|
import tomllib
|
||||||
|
|
||||||
|
project=Path(__file__).resolve().parents[2]
|
||||||
|
sys.path.insert(0,str(project/'tools'))
|
||||||
|
from fds_version import version
|
||||||
|
expected=version(project)
|
||||||
|
image=(project/'out/fds-system-cli.img').resolve(strict=True)
|
||||||
|
work=Path(tempfile.mkdtemp(prefix='version-image-test.',dir=project/'out'))
|
||||||
|
table=json.loads(subprocess.check_output(['sfdisk','--json',str(image)]))['partitiontable']
|
||||||
|
assert len(table['partitions'])==1 and table['partitions'][0]['name']=='FDS_SYSTEM'
|
||||||
|
offset=table['partitions'][0]['start']*table['sectorsize']
|
||||||
|
|
||||||
|
def extract(path,name):
|
||||||
|
target=work/name
|
||||||
|
subprocess.run([str(project/'tools/in-image-tools'),'fsck.erofs',f'--offset={offset}',f'--path=/{path}',f'--extract={target}',str(image)],check=True)
|
||||||
|
return target
|
||||||
|
|
||||||
|
identity=extract('usr/lib/os-release','os-release').read_text()
|
||||||
|
values=dict(line.split('=',1) for line in shlex.split(identity,comments=True))
|
||||||
|
assert values['VERSION_ID']==values['VERSION']==expected,values
|
||||||
|
assert values['PRETTY_NAME']==f'FDS/OS {expected}',values
|
||||||
|
assert extract('etc/issue','issue').read_text()==f'FDS/OS {expected}\n'
|
||||||
|
metadata=tomllib.loads(extract('FDS/CARTRIDGE.TOML','CARTRIDGE.TOML').read_text())
|
||||||
|
assert metadata['cartridge']['version']==expected,metadata
|
||||||
|
with tarfile.open(project/'out/rootfs-cli.tar') as archive:
|
||||||
|
assert archive.getmember('etc/os-release').linkname=='../usr/lib/os-release'
|
||||||
|
assert archive.extractfile('usr/lib/os-release').read().decode()==identity
|
||||||
|
commands={}
|
||||||
|
for name in ['fds','fds-control','fds-program','fds-boottrace','fds-cartridged','fds-profile','fds-burn','fds-release','fds-inspect','fds-eject','fds-power','dasungd']:
|
||||||
|
binary=extract(f'usr/bin/{name}',name)
|
||||||
|
output=subprocess.check_output([str(project/'tools/in-void'),'qemu-aarch64',str(binary),'--version'],text=True).strip()
|
||||||
|
assert output==f'{name} {expected}',output
|
||||||
|
commands[name]=output
|
||||||
|
with image.open('rb') as stream:
|
||||||
|
checksum=hashlib.file_digest(stream,'sha256').hexdigest()
|
||||||
|
record=dict(status='passed',version=expected,image=str(image),image_sha256=checksum,identity=values,cartridge_version=metadata['cartridge']['version'],arm_commands=commands,physical_hardware=False)
|
||||||
|
(work/'acceptance.json').write_text(json.dumps(record,indent=2)+'\n')
|
||||||
|
(project/'out/version-image-current.txt').write_text(str(work)+'\n')
|
||||||
|
print(f'PASS: {expected} in GPT/EROFS identity, cartridge metadata and all installed FDS commands: {work}')
|
||||||
@@ -39,6 +39,16 @@ def invoke(*command, ok=True):
|
|||||||
result = subprocess.run([cli, '--session', str(session), *map(str, command)], capture_output=True, text=True, timeout=240)
|
result = subprocess.run([cli, '--session', str(session), *map(str, command)], capture_output=True, text=True, timeout=240)
|
||||||
log.write(repr(command) + '\n' + result.stdout + result.stderr)
|
log.write(repr(command) + '\n' + result.stdout + result.stderr)
|
||||||
log.flush()
|
log.flush()
|
||||||
|
if (result.returncode == 0) != ok and command and command[0] == 'guest':
|
||||||
|
# Preserve live service evidence before the finally block stops the VM.
|
||||||
|
# Do not retry or relax the failed command's acceptance condition.
|
||||||
|
diagnostics = subprocess.run([
|
||||||
|
cli, '--session', str(session), 'guest', '--', 'sh', '-c',
|
||||||
|
'cat /run/log/cartridged/current; s6-svstat /run/service/cartridged; '
|
||||||
|
'dmesg | tail -40',
|
||||||
|
], capture_output=True, text=True, timeout=60)
|
||||||
|
log.write('Failure diagnostics:\n' + diagnostics.stdout + diagnostics.stderr)
|
||||||
|
log.flush()
|
||||||
assert (result.returncode == 0) == ok, (command, result.returncode, result.stdout, result.stderr)
|
assert (result.returncode == 0) == ok, (command, result.returncode, result.stdout, result.stderr)
|
||||||
return result.stdout
|
return result.stdout
|
||||||
|
|
||||||
@@ -94,13 +104,17 @@ def interactive_program_checks():
|
|||||||
os.write(master, b'\x03')
|
os.write(master, b'\x03')
|
||||||
until(b'RETURN:130\r\n')
|
until(b'RETURN:130\r\n')
|
||||||
until(b'FDS> ')
|
until(b'FDS> ')
|
||||||
os.write(master, b"b01:demo.report:shell -c 'printf \"STOP:%s\\n\" ready; exec tail -f /dev/null'\n")
|
os.write(master, b"b01:demo.report:shell -c 'printf \"STOP:%s\\n\" ready; read -r resumed; printf \"RESUMED:%s\\n\" \"$resumed\"; exec tail -f /dev/null'\n")
|
||||||
until(b'STOP:ready\r\n')
|
until(b'STOP:ready\r\n')
|
||||||
os.write(master, b'\x1a')
|
os.write(master, b'\x1a')
|
||||||
until(b'Stopped')
|
until(b'Stopped')
|
||||||
until(b'FDS> ')
|
until(b'FDS> ')
|
||||||
os.write(master, b'fg\n')
|
os.write(master, b'fg\n')
|
||||||
until(b"exec tail -f /dev/null'\r\n")
|
until(b"exec tail -f /dev/null'\r\n")
|
||||||
|
# The fg command echo precedes terminal handoff and SIGCONT. Require
|
||||||
|
# actual input/output from the resumed foreground program before Ctrl-C.
|
||||||
|
os.write(master, b'continue\n')
|
||||||
|
until(b'RESUMED:continue\r\n')
|
||||||
os.write(master, b'\x03')
|
os.write(master, b'\x03')
|
||||||
until(b'FDS> ')
|
until(b'FDS> ')
|
||||||
os.write(master, b"test \"$(stty -g)\" = \"$(cat /tmp/program-tty-before)\" && printf 'RESTORE:%s\\n' passed\n")
|
os.write(master, b"test \"$(stty -g)\" = \"$(cat /tmp/program-tty-before)\" && printf 'RESTORE:%s\\n' passed\n")
|
||||||
@@ -146,6 +160,9 @@ original_software = digest(software)
|
|||||||
try:
|
try:
|
||||||
report = json.loads(invoke('start', *start_options))
|
report = json.loads(invoke('start', *start_options))
|
||||||
assert report['qemu']['running']
|
assert report['qemu']['running']
|
||||||
|
initial_state = (session / 'session.json').read_bytes()
|
||||||
|
invoke('start', *start_options, ok=False)
|
||||||
|
assert (session / 'session.json').read_bytes() == initial_state
|
||||||
assert guest('id', '-u').strip() == '1000'
|
assert guest('id', '-u').strip() == '1000'
|
||||||
master, slave = pty.openpty()
|
master, slave = pty.openpty()
|
||||||
original_termios = termios.tcgetattr(slave)
|
original_termios = termios.tcgetattr(slave)
|
||||||
@@ -270,6 +287,24 @@ try:
|
|||||||
guest('fds', 'run', 12, '--', 'demo.report:report', 'hold')
|
guest('fds', 'run', 12, '--', 'demo.report:report', 'hold')
|
||||||
invoke('stop')
|
invoke('stop')
|
||||||
assert 'FDS_SHUTDOWN_FINAL' in (session / 'console.log').read_text()
|
assert 'FDS_SHUTDOWN_FINAL' in (session / 'console.log').read_text()
|
||||||
|
retained_log = (session / 'console.log').read_bytes()
|
||||||
|
retained_data = {p: digest(p) for p in session.glob('data-*.qcow2')}
|
||||||
|
assert retained_data
|
||||||
|
previous_state = json.loads((session / 'session.json').read_text())
|
||||||
|
restarted = json.loads(invoke('start', *start_options))
|
||||||
|
assert restarted['qemu']['running'] and restarted['state']['cartridges'] == {}
|
||||||
|
assert restarted['state']['name'] != previous_state['name']
|
||||||
|
assert any(json.loads(p.read_text()) == previous_state for p in session.glob('previous-*.json'))
|
||||||
|
assert (session / 'console.log').read_bytes().startswith(retained_log)
|
||||||
|
assert all(digest(p) == value for p, value in retained_data.items())
|
||||||
|
assert guest('id', '-u').strip() == '1000'
|
||||||
|
assert all(b['state'] == 'empty' for b in query('bays')['bays'])
|
||||||
|
invoke('insert', 1, software); wait_bay(1, 'mounted_read_only')
|
||||||
|
assert 'Hello from' in guest('hello', 'restarted')
|
||||||
|
invoke('stop', '--force')
|
||||||
|
assert json.loads(invoke('start', *start_options))['qemu']['running']
|
||||||
|
assert guest('id', '-u').strip() == '1000'
|
||||||
|
invoke('stop')
|
||||||
finally:
|
finally:
|
||||||
subprocess.run([cli, '--session', str(session), 'stop', '--force'], capture_output=True, timeout=40)
|
subprocess.run([cli, '--session', str(session), 'stop', '--force'], capture_output=True, timeout=40)
|
||||||
|
|
||||||
@@ -330,7 +365,9 @@ record = dict(status='passed', ordinary_guest_uid=1000, public_emulator_cli=True
|
|||||||
safe_eject=True, forced_removal_stops_consumer=True,
|
safe_eject=True, forced_removal_stops_consumer=True,
|
||||||
corrupt_program_and_catalogue_rejected=True, data_overlay_preserves_source=True,
|
corrupt_program_and_catalogue_rejected=True, data_overlay_preserves_source=True,
|
||||||
restart_cleans_mounts_commands_and_consumers=True, extra_mount_blocks_safe=True,
|
restart_cleans_mounts_commands_and_consumers=True, extra_mount_blocks_safe=True,
|
||||||
native_shutdown_with_active_software=True, physical_pi='not tested')
|
native_shutdown_with_active_software=True, same_directory_restart_after_stop=True,
|
||||||
|
same_directory_restart_after_force=True, restart_retains_logs_overlays_and_mapping=True,
|
||||||
|
live_session_restart_rejected=True, physical_pi='not tested')
|
||||||
(work / 'acceptance.json').write_text(json.dumps(record, indent=2) + '\n')
|
(work / 'acceptance.json').write_text(json.dumps(record, indent=2) + '\n')
|
||||||
(project / 'out/workstation-emulator-current.txt').write_text(str(work) + '\n')
|
(project / 'out/workstation-emulator-current.txt').write_text(str(work) + '\n')
|
||||||
print('PASS: public emulator, guest software and lifecycle acceptance:', work)
|
print('PASS: public emulator, guest software and lifecycle acceptance:', work)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from image_formats import gpt, LINUX_FILESYSTEM
|
|||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument('--cli', type=Path, required=True)
|
parser.add_argument('--cli', type=Path, required=True)
|
||||||
parser.add_argument('--image-tool-runner', type=Path)
|
parser.add_argument('--image-tool-runner', type=Path)
|
||||||
parser.add_argument('--void-packages', type=Path, default=project / 'vendor/void-packages')
|
parser.add_argument('--void-packages', type=Path, default=project / '.host/void-packages')
|
||||||
parser.add_argument('--xbps-tool-runner', type=Path)
|
parser.add_argument('--xbps-tool-runner', type=Path)
|
||||||
parser.add_argument('--xbps-bin', type=Path)
|
parser.add_argument('--xbps-bin', type=Path)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -43,6 +43,15 @@ def digest(path):
|
|||||||
return hashlib.file_digest(stream, 'sha256').hexdigest()
|
return hashlib.file_digest(stream, 'sha256').hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
# A user-provided submodule path must fail before copying any package sources.
|
||||||
|
upstream = project / 'vendor/void-packages'
|
||||||
|
assert not subprocess.check_output(['git', '-C', str(upstream), 'status', '--porcelain', '--untracked-files=all'])
|
||||||
|
rejected = subprocess.run([cli[0], '--void-packages', str(upstream), 'software', 'build',
|
||||||
|
str(project / 'examples/software/hello/software.toml'), str(work / 'forbidden-build')],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
assert rejected.returncode != 0 and 'outside Git submodules' in rejected.stderr, rejected.stderr
|
||||||
|
assert not (work / 'forbidden-build').exists()
|
||||||
|
|
||||||
for name in ['hello', 'report']:
|
for name in ['hello', 'report']:
|
||||||
recipe = project / f'examples/software/{name}/software.toml'
|
recipe = project / f'examples/software/{name}/software.toml'
|
||||||
if name == 'report':
|
if name == 'report':
|
||||||
@@ -149,7 +158,8 @@ for case in ['program-corrupt', 'catalogue-mapping']:
|
|||||||
malformed = work / (case + '.img')
|
malformed = work / (case + '.img')
|
||||||
gpt(malformed, [(part['name'], LINUX_FILESYSTEM, filesystem) for part, filesystem in zip(parts, selected)])
|
gpt(malformed, [(part['name'], LINUX_FILESYSTEM, filesystem) for part, filesystem in zip(parts, selected)])
|
||||||
invoke(['inspect', malformed], False)
|
invoke(['inspect', malformed], False)
|
||||||
record = dict(status='passed', work=str(work), cli_sha256=digest(args.cli.resolve()), compiled_aarch64_software=True,
|
assert not subprocess.check_output(['git', '-C', str(upstream), 'status', '--porcelain', '--untracked-files=all'])
|
||||||
|
record = dict(source_checkout_clean=True, submodule_build_rejected=True, status='passed', work=str(work), cli_sha256=digest(args.cli.resolve()), compiled_aarch64_software=True,
|
||||||
void_source_packages=True, installed_runtime_dependencies=True, wrong_elf_and_escaping_symlink_rejected=True, shared_and_separate_payload_partitions=True,
|
void_source_packages=True, installed_runtime_dependencies=True, wrong_elf_and_escaping_symlink_rejected=True, shared_and_separate_payload_partitions=True,
|
||||||
repeat_image_identical=True, direct_installed_erofs_programs=True,
|
repeat_image_identical=True, direct_installed_erofs_programs=True,
|
||||||
exact_and_larger_target_readback=True, wrong_confirmation_unchanged=True,
|
exact_and_larger_target_readback=True, wrong_confirmation_unchanged=True,
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ source /etc/os-release
|
|||||||
[[ ! $FDS_ROOT =~ [[:space:]] ]] || die 'xbps-src requires a checkout path without whitespace'
|
[[ ! $FDS_ROOT =~ [[:space:]] ]] || die 'xbps-src requires a checkout path without whitespace'
|
||||||
if [[ ${1:-} == --install-deps ]]; then
|
if [[ ${1:-} == --install-deps ]]; then
|
||||||
sudo pacman -S --needed bash coreutils binutils git curl make file tar xz gzip zstd \
|
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
|
bubblewrap rustup ca-certificates findutils diffutils grep sed gawk util-linux python
|
||||||
fi
|
fi
|
||||||
for cmd in bash git curl make file readelf ldd tar xz gzip zstd sha256sum bwrap rustup \
|
for cmd in bash git curl make file readelf ldd tar xz gzip zstd sha256sum bwrap rustup \
|
||||||
find diff grep sed awk flock install; do
|
find diff grep sed awk flock install python3; do
|
||||||
need "$cmd"
|
need "$cmd"
|
||||||
done
|
done
|
||||||
# Fail before downloading if unprivileged build containers cannot run.
|
# Fail before downloading if unprivileged build containers cannot run.
|
||||||
@@ -21,7 +21,7 @@ mkdir -p out/downloads out/logs .host
|
|||||||
if [[ ${FDS_OFFLINE:-0} != 1 ]]; then
|
if [[ ${FDS_OFFLINE:-0} != 1 ]]; then
|
||||||
git -c http.lowSpeedLimit=1000 -c http.lowSpeedTime=60 submodule update --init --depth 1 vendor/void-packages
|
git -c http.lowSpeedLimit=1000 -c http.lowSpeedTime=60 submodule update --init --depth 1 vendor/void-packages
|
||||||
fi
|
fi
|
||||||
check_void_pin
|
check_void_source
|
||||||
source config/host-tools.conf
|
source config/host-tools.conf
|
||||||
archive="out/downloads/${XBPS_STATIC_URL##*/}"
|
archive="out/downloads/${XBPS_STATIC_URL##*/}"
|
||||||
if [[ ! -f $archive ]]; then
|
if [[ ! -f $archive ]]; then
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
source "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh"
|
source "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh"
|
||||||
[[ $# == 0 ]] || die 'Usage: tools/build-base-packages'
|
[[ $# == 0 ]] || die 'Usage: tools/build-base-packages'
|
||||||
cd "$FDS_ROOT"
|
cd "$FDS_ROOT"
|
||||||
|
fds_version=$(tools/version)
|
||||||
use_xbps
|
use_xbps
|
||||||
check_void_pin
|
check_void_pin
|
||||||
mkdir -p out/logs out/packages out/manifests
|
mkdir -p out/logs out/packages out/manifests
|
||||||
@@ -12,7 +13,7 @@ flock -n 8 || die 'Another base package build is already running'
|
|||||||
tools/build-fds-package
|
tools/build-fds-package
|
||||||
tools/build-kernel
|
tools/build-kernel
|
||||||
tools/build-dasungd
|
tools/build-dasungd
|
||||||
input="$FDS_VOID/hostdir/sources/fds-init-0.1.0"
|
input="$FDS_VOID/hostdir/sources/fds-init-${fds_version}"
|
||||||
if [[ -d $input ]]; then
|
if [[ -d $input ]]; then
|
||||||
previous=$(mktemp -d "$FDS_ROOT/out/init-source.XXXXXX")
|
previous=$(mktemp -d "$FDS_ROOT/out/init-source.XXXXXX")
|
||||||
mv "$input" "$previous/"
|
mv "$input" "$previous/"
|
||||||
@@ -35,7 +36,9 @@ for package in fds-dhcpcd fds-eink fds-base-files fds-init fds-base; do
|
|||||||
xbps_src -a aarch64 clean "$package"
|
xbps_src -a aarch64 clean "$package"
|
||||||
xbps_src -f -a aarch64 pkg "$package"
|
xbps_src -f -a aarch64 pkg "$package"
|
||||||
) 2>&1 | tee "out/logs/xbps-$package.log"
|
) 2>&1 | tee "out/logs/xbps-$package.log"
|
||||||
cp "$FDS_VOID/hostdir/binpkgs/$package-"*.aarch64.xbps out/packages/
|
package_version=$fds_version
|
||||||
|
[[ $package != fds-dhcpcd ]] || package_version=10.3.2
|
||||||
|
tools/export-package "$FDS_VOID/hostdir/binpkgs/${package}-${package_version}_1.aarch64.xbps"
|
||||||
done
|
done
|
||||||
XBPS_ARCH=aarch64 xbps-rindex -fa out/packages/fds-*.aarch64.xbps
|
XBPS_ARCH=aarch64 xbps-rindex -fa out/packages/fds-*.aarch64.xbps
|
||||||
printf 'PASS: all FDS base packages exported to out/packages/\n'
|
printf 'PASS: all FDS base packages exported to out/packages/\n'
|
||||||
|
|||||||
+6
-6
@@ -2,12 +2,13 @@
|
|||||||
source "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh"
|
source "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh"
|
||||||
[[ $# == 0 ]] || die 'Usage: tools/build-dasungd'
|
[[ $# == 0 ]] || die 'Usage: tools/build-dasungd'
|
||||||
cd "$FDS_ROOT"
|
cd "$FDS_ROOT"
|
||||||
|
fds_version=$(tools/version)
|
||||||
use_xbps
|
use_xbps
|
||||||
check_void_pin
|
check_void_pin
|
||||||
mkdir -p out/logs out/manifests out/packages
|
mkdir -p out/logs out/manifests out/packages
|
||||||
# These are build-container tools; no Arch packages or host services are changed.
|
# These are build-container tools; no Arch packages or host services are changed.
|
||||||
missing=()
|
missing=()
|
||||||
for package in cross-aarch64-linux-musl pkg-config s6-rc; do
|
for package in cross-aarch64-linux-musl pkg-config s6-rc python3 git; do
|
||||||
if ! XBPS_ARCH=x86_64 xbps-query -r "$FDS_VOID/masterdir-x86_64" "$package" >/dev/null 2>&1; then
|
if ! XBPS_ARCH=x86_64 xbps-query -r "$FDS_VOID/masterdir-x86_64" "$package" >/dev/null 2>&1; then
|
||||||
missing+=("$package")
|
missing+=("$package")
|
||||||
fi
|
fi
|
||||||
@@ -26,7 +27,7 @@ tools/in-void env LIBUSB_NO_PKG_CONFIG=1 LIBUDEV_NO_PKG_CONFIG=1 \
|
|||||||
binary=target/aarch64-unknown-linux-musl/release/dasungd
|
binary=target/aarch64-unknown-linux-musl/release/dasungd
|
||||||
tools/verify-elf "$binary" aarch64 static
|
tools/verify-elf "$binary" aarch64 static
|
||||||
cp -- "$binary" out/dasungd
|
cp -- "$binary" out/dasungd
|
||||||
input="$FDS_VOID/hostdir/sources/fds-dasungd-0.1.0"
|
input="$FDS_VOID/hostdir/sources/fds-dasungd-${fds_version}"
|
||||||
mkdir -p "$input"
|
mkdir -p "$input"
|
||||||
cp -- "$binary" "$input/dasungd"
|
cp -- "$binary" "$input/dasungd"
|
||||||
cp rust/dasungd/LICENSE rust/dasungd/profiles/paperlike13k-37hz.edid "$input/"
|
cp rust/dasungd/LICENSE rust/dasungd/profiles/paperlike13k-37hz.edid "$input/"
|
||||||
@@ -62,10 +63,9 @@ tools/prepare-void
|
|||||||
cd "$FDS_VOID"
|
cd "$FDS_VOID"
|
||||||
xbps_src -f -a aarch64 pkg fds-dasungd
|
xbps_src -f -a aarch64 pkg fds-dasungd
|
||||||
) 2>&1 | tee out/logs/xbps-fds-dasungd.log
|
) 2>&1 | tee out/logs/xbps-fds-dasungd.log
|
||||||
package=fds-dasungd-0.1.0_1.aarch64.xbps
|
package="fds-dasungd-${fds_version}_1.aarch64.xbps"
|
||||||
cp "$FDS_VOID/hostdir/binpkgs/$package" out/packages/
|
tools/export-package "$FDS_VOID/hostdir/binpkgs/$package"
|
||||||
XBPS_ARCH=aarch64 xbps-rindex -fa "out/packages/$package"
|
|
||||||
sha256sum out/dasungd "out/packages/$package" >out/manifests/dasung-artifacts.sha256
|
sha256sum out/dasungd "out/packages/$package" >out/manifests/dasung-artifacts.sha256
|
||||||
XBPS_ARCH=x86_64 xbps-query -r "$FDS_VOID/masterdir-x86_64" -l >out/manifests/dasung-build-packages.txt
|
XBPS_ARCH=x86_64 xbps-query -r "$FDS_VOID/masterdir-x86_64" -l >out/manifests/dasung-build-packages.txt
|
||||||
sha256sum Cargo.toml Cargo.lock rust-toolchain.toml .cargo/config.toml tools/cargo-build tools/lib.sh tools/build-dasungd >out/manifests/dasung-rust-inputs.sha256
|
sha256sum tools/version tools/fds_version.py tools/version-build.rs Cargo.toml Cargo.lock rust-toolchain.toml .cargo/config.toml tools/cargo-build tools/lib.sh tools/build-dasungd >out/manifests/dasung-rust-inputs.sha256
|
||||||
printf 'PASS: Dasung static ARM executable, base package, and s6 database built\n'
|
printf 'PASS: Dasung static ARM executable, base package, and s6 database built\n'
|
||||||
|
|||||||
+1
-1
@@ -15,5 +15,5 @@ cp out/fds out/fds-power
|
|||||||
sha256sum out/fds-control out/fds-program out/fds-release out/fds-burn out/fds-inspect out/fds-eject out/fds-power out/fds out/fds-stage0 out/fds-boottrace out/fds-cartridged out/fds-profile >out/manifests/fds-tools.sha256
|
sha256sum out/fds-control out/fds-program out/fds-release out/fds-burn out/fds-inspect out/fds-eject out/fds-power out/fds out/fds-stage0 out/fds-boottrace out/fds-cartridged out/fds-profile >out/manifests/fds-tools.sha256
|
||||||
find rust/fds-control rust/fds-software rust/fds-common rust/fds-cli rust/fds-stage0 rust/fds-boottrace rust/fds-cartridged rust/fds-burn rust/fds-release -type f -print0 | sort -z | \
|
find rust/fds-control rust/fds-software rust/fds-common rust/fds-cli rust/fds-stage0 rust/fds-boottrace rust/fds-cartridged rust/fds-burn rust/fds-release -type f -print0 | sort -z | \
|
||||||
xargs -0 sha256sum >out/manifests/fds-tools-inputs.sha256
|
xargs -0 sha256sum >out/manifests/fds-tools-inputs.sha256
|
||||||
sha256sum Cargo.toml Cargo.lock rust-toolchain.toml .cargo/config.toml tools/cargo-build tools/lib.sh tools/build-fds >>out/manifests/fds-tools-inputs.sha256
|
sha256sum tools/version tools/fds_version.py tools/version-build.rs Cargo.toml Cargo.lock rust-toolchain.toml .cargo/config.toml tools/cargo-build tools/lib.sh tools/build-fds >>out/manifests/fds-tools-inputs.sha256
|
||||||
printf 'PASS: static ARM FDS CLI and stage0 tools built\n'
|
printf 'PASS: static ARM FDS CLI and stage0 tools built\n'
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
source "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh"
|
source "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh"
|
||||||
[[ $# == 0 ]] || die 'Usage: tools/build-fds-package'
|
[[ $# == 0 ]] || die 'Usage: tools/build-fds-package'
|
||||||
cd "$FDS_ROOT"
|
cd "$FDS_ROOT"
|
||||||
|
fds_version=$(tools/version)
|
||||||
use_xbps
|
use_xbps
|
||||||
tools/build-fds
|
tools/build-fds
|
||||||
tools/prepare-void
|
tools/prepare-void
|
||||||
input="$FDS_VOID/hostdir/sources/fds-cli-0.1.0"
|
input="$FDS_VOID/hostdir/sources/fds-cli-${fds_version}"
|
||||||
mkdir -p "$input" out/packages
|
mkdir -p "$input" out/packages
|
||||||
cp out/fds out/fds-control out/fds-program out/fds-boottrace out/fds-burn out/fds-inspect out/fds-eject out/fds-power out/fds-release LICENSE "$input/"
|
cp out/fds out/fds-control out/fds-program out/fds-boottrace out/fds-burn out/fds-inspect out/fds-eject out/fds-power out/fds-release LICENSE "$input/"
|
||||||
notices=$(mktemp -d "$FDS_ROOT/out/rust-notices.XXXXXX")
|
notices=$(mktemp -d "$FDS_ROOT/out/rust-notices.XXXXXX")
|
||||||
@@ -17,9 +18,8 @@ cp "$notices/RUST-NOTICES.txt" "$input/"
|
|||||||
xbps_src -a aarch64 clean fds-cli
|
xbps_src -a aarch64 clean fds-cli
|
||||||
xbps_src -f -a aarch64 pkg fds-cli
|
xbps_src -f -a aarch64 pkg fds-cli
|
||||||
) 2>&1 | tee out/logs/xbps-fds-cli.log
|
) 2>&1 | tee out/logs/xbps-fds-cli.log
|
||||||
cp "$FDS_VOID/hostdir/binpkgs/fds-cli-0.1.0_1.aarch64.xbps" out/packages/
|
tools/export-package "$FDS_VOID/hostdir/binpkgs/fds-cli-${fds_version}_1.aarch64.xbps"
|
||||||
XBPS_ARCH=aarch64 xbps-rindex -fa out/packages/fds-cli-0.1.0_1.aarch64.xbps
|
input="$FDS_VOID/hostdir/sources/fds-cartridged-${fds_version}"
|
||||||
input="$FDS_VOID/hostdir/sources/fds-cartridged-0.1.0"
|
|
||||||
if [[ -d $input ]]; then
|
if [[ -d $input ]]; then
|
||||||
previous=$(mktemp -d "$FDS_ROOT/out/cartridged-source.XXXXXX")
|
previous=$(mktemp -d "$FDS_ROOT/out/cartridged-source.XXXXXX")
|
||||||
mv "$input" "$previous/"
|
mv "$input" "$previous/"
|
||||||
@@ -37,6 +37,5 @@ cp s6/source/boot/contents.d/cartridged "$input/s6-source/boot/contents.d/"
|
|||||||
xbps_src -a aarch64 clean fds-cartridged
|
xbps_src -a aarch64 clean fds-cartridged
|
||||||
xbps_src -f -a aarch64 pkg fds-cartridged
|
xbps_src -f -a aarch64 pkg fds-cartridged
|
||||||
) 2>&1 | tee out/logs/xbps-fds-cartridged.log
|
) 2>&1 | tee out/logs/xbps-fds-cartridged.log
|
||||||
cp "$FDS_VOID/hostdir/binpkgs/fds-cartridged-0.1.0_1.aarch64.xbps" out/packages/
|
tools/export-package "$FDS_VOID/hostdir/binpkgs/fds-cartridged-${fds_version}_1.aarch64.xbps"
|
||||||
XBPS_ARCH=aarch64 xbps-rindex -fa out/packages/fds-cartridged-0.1.0_1.aarch64.xbps
|
|
||||||
printf 'PASS: FDS CLI package built without a dynamic libc dependency\n'
|
printf 'PASS: FDS CLI package built without a dynamic libc dependency\n'
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ if [[ ${FDS_OFFLINE:-0} == 1 ]]; then
|
|||||||
fi
|
fi
|
||||||
python3 tools/rootfs-archive "$root" "$work" "${archive_inputs[@]}"
|
python3 tools/rootfs-archive "$root" "$work" "${archive_inputs[@]}"
|
||||||
{
|
{
|
||||||
|
printf 'version=%s\n' "$(tools/version)"
|
||||||
printf 'profile=%s\nvoid_commit=%s\n' "$profile" "$(cat VOID_PACKAGES_COMMIT)"
|
printf 'profile=%s\nvoid_commit=%s\n' "$profile" "$(cat VOID_PACKAGES_COMMIT)"
|
||||||
printf 'emulation=private-user-namespace-binfmt\n'
|
printf 'emulation=private-user-namespace-binfmt\n'
|
||||||
tools/in-void xbps-query -p pkgver qemu-user-aarch64
|
tools/in-void xbps-query -p pkgver qemu-user-aarch64
|
||||||
|
|||||||
+18
-10
@@ -1,15 +1,23 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Native Linux host binaries; independent of the Pi and Void build container.
|
# Build an installable Arch package without installing host packages implicitly.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
[[ $# == 0 ]] || { printf 'Usage: tools/build-workstation\n' >&2; exit 2; }
|
[[ $# == 0 ]] || { printf 'Usage: tools/build-workstation\n' >&2; exit 2; }
|
||||||
[[ $(uname -s) == Linux ]] || { printf 'A Linux workstation is required\n' >&2; exit 2; }
|
|
||||||
(( EUID != 0 )) || { printf 'Build as an ordinary user\n' >&2; exit 2; }
|
(( EUID != 0 )) || { printf 'Build as an ordinary user\n' >&2; exit 2; }
|
||||||
cd "$(dirname -- "${BASH_SOURCE[0]}")/.."
|
cd "$(dirname -- "${BASH_SOURCE[0]}")/.."
|
||||||
command -v cargo >/dev/null || { printf 'Install Rust with Cargo before building workstation tools\n' >&2; exit 2; }
|
for tool in makepkg pacman cargo rustc python3 git; do
|
||||||
fds_host_target=$(rustc -vV | sed -n 's/^host: //p')
|
command -v "$tool" >/dev/null || { printf 'Missing %s; see docs/workstation.md\n' "$tool" >&2; exit 1; }
|
||||||
[[ $fds_host_target == *-linux-* ]] || { printf 'Rust must target the native Linux host\n' >&2; exit 2; }
|
done
|
||||||
cargo build --locked --release --target "$fds_host_target" -p fds-workstation
|
export FDS_SOURCE_DIR="$PWD"
|
||||||
mkdir -p out/workstation
|
mkdir -p out/workstation out/workstation-package
|
||||||
install -m755 "target/$fds_host_target/release/fds-cartridge" out/workstation/fds-cartridge
|
exec 9>out/.workstation-package.lock
|
||||||
install -m755 "target/$fds_host_target/release/fds-emulator" out/workstation/fds-emulator
|
flock -n 9 || { printf 'Another workstation package build is running\n' >&2; exit 1; }
|
||||||
printf 'Built native Linux tools in out/workstation/\n'
|
cp packaging/arch/PKGBUILD out/workstation-package/PKGBUILD
|
||||||
|
export PKGDEST="$PWD/out/workstation"
|
||||||
|
cd out/workstation-package
|
||||||
|
# Dependencies are checked by their actual build commands. Runtime features
|
||||||
|
# are optional, and package installation is a separate explicit Make target.
|
||||||
|
makepkg --nodeps --force
|
||||||
|
mapfile -t packages < <(makepkg --packagelist)
|
||||||
|
(( ${#packages[@]} == 1 )) && [[ -s ${packages[0]} ]] || { printf 'Expected one fds-tools package\n' >&2; exit 1; }
|
||||||
|
printf '%s\n' "${packages[0]}" >../workstation/package-path.txt
|
||||||
|
printf 'Built Arch package: %s\nInstall with make workstation-install\n' "${packages[0]}"
|
||||||
|
|||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Native Linux host binaries; independent of the Pi and Void build container.
|
||||||
|
set -euo pipefail
|
||||||
|
[[ $# == 0 ]] || { printf 'Usage: tools/build-workstation-binaries\n' >&2; exit 2; }
|
||||||
|
[[ $(uname -s) == Linux ]] || { printf 'A Linux workstation is required\n' >&2; exit 2; }
|
||||||
|
(( EUID != 0 )) || { printf 'Build as an ordinary user\n' >&2; exit 2; }
|
||||||
|
cd "$(dirname -- "${BASH_SOURCE[0]}")/.."
|
||||||
|
command -v cargo >/dev/null || { printf 'Install Rust with Cargo before building workstation tools\n' >&2; exit 2; }
|
||||||
|
fds_host_target=$(rustc -vV | sed -n 's/^host: //p')
|
||||||
|
[[ $fds_host_target == *-linux-* ]] || { printf 'Rust must target the native Linux host\n' >&2; exit 2; }
|
||||||
|
cargo build --locked --release --target "$fds_host_target" -p fds-workstation
|
||||||
|
mkdir -p out/workstation
|
||||||
|
install -m755 "target/$fds_host_target/release/fds-cartridge" out/workstation/fds-cartridge
|
||||||
|
install -m755 "target/$fds_host_target/release/fds-emulator" out/workstation/fds-emulator
|
||||||
|
printf 'Built native Linux tools in out/workstation/\n'
|
||||||
+4
-2
@@ -24,15 +24,17 @@ PREFIXES = (
|
|||||||
'm9-images', 'm9-vm', 'm9-system', 'm10-vm', 'm11-vm', 'm11-faults',
|
'm9-images', 'm9-vm', 'm9-system', 'm10-vm', 'm11-vm', 'm11-faults',
|
||||||
'm12-clock', 'm12-development', 'm12-eeprom', 'm12-internal',
|
'm12-clock', 'm12-development', 'm12-eeprom', 'm12-internal',
|
||||||
'm12-recovery', 'm12-release-contracts', 'm12-signing',
|
'm12-recovery', 'm12-release-contracts', 'm12-signing',
|
||||||
'rust-profiles', 'workstation-images', 'emu-test',
|
'rust-profiles', 'workstation-images', 'emu-test', 'arch-package-test', 'version-image-test',
|
||||||
)
|
)
|
||||||
GENERATED = re.compile(r'(?:' + '|'.join(PREFIXES) + r')\.[A-Za-z0-9_-]{6,8}')
|
GENERATED = re.compile(r'(?:' + '|'.join(PREFIXES) + r')\.[A-Za-z0-9_-]{6,8}')
|
||||||
POINTERS = (
|
POINTERS = (
|
||||||
|
'out/arch-package-current.txt',
|
||||||
|
'out/version-image-current.txt',
|
||||||
'out/workstation-images-current.txt',
|
'out/workstation-images-current.txt',
|
||||||
'out/workstation-emulator-current.txt',
|
'out/workstation-emulator-current.txt',
|
||||||
'out/manifests/dasung-s6-database.txt',
|
'out/manifests/dasung-s6-database.txt',
|
||||||
)
|
)
|
||||||
LOCKS = ('.clean.lock', '.rootfs.lock', '.base-packages.lock', '.image-tools.lock')
|
LOCKS = ('.clean.lock', '.rootfs.lock', '.base-packages.lock', '.image-tools.lock', '.workstation-package.lock')
|
||||||
|
|
||||||
|
|
||||||
def within(path, parent):
|
def within(path, parent):
|
||||||
|
|||||||
Executable
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Export one FDS XBPS package, retaining superseded builds outside the active index."""
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
project = Path(__file__).resolve().parents[1]
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument('package', type=Path)
|
||||||
|
args = parser.parse_args()
|
||||||
|
source = args.package.resolve(strict=True)
|
||||||
|
name, release = source.name.rsplit('-', 1)
|
||||||
|
if not name.startswith('fds-') or not release.endswith('.aarch64.xbps'):
|
||||||
|
parser.error('Expected an FDS AArch64 package')
|
||||||
|
out = project / 'out/packages'
|
||||||
|
out.mkdir(exist_ok=True)
|
||||||
|
old = [p for p in out.glob(f'{name}-*.aarch64.xbps') if p.name.rsplit('-', 1)[0] == name]
|
||||||
|
if old:
|
||||||
|
history = project / 'out/package-history'
|
||||||
|
history.mkdir(exist_ok=True)
|
||||||
|
saved = Path(tempfile.mkdtemp(prefix=f'{name}.', dir=history))
|
||||||
|
for path in old:
|
||||||
|
path.rename(saved / path.name)
|
||||||
|
# Prune entries for archived versions before indexing the selected package.
|
||||||
|
env = dict(os.environ, XBPS_ARCH='aarch64')
|
||||||
|
index = project / '.host/xbps/usr/bin/xbps-rindex'
|
||||||
|
subprocess.run([str(index), '-c', str(out)], env=env, check=True)
|
||||||
|
shutil.copy2(source, out / source.name)
|
||||||
|
subprocess.run([str(index), '-fa', str(out / source.name)], env=env, check=True)
|
||||||
|
print(f'Exported {source.name}; previous builds retained under out/package-history')
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
"""Canonical FDS build version, shared by Cargo, images and package builders."""
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
RELEASE = re.compile(r'v?([0-9]+(?:\.[0-9]+){1,2})\Z')
|
||||||
|
VALID = re.compile(r'[0-9][A-Za-z0-9.]{0,31}\Z')
|
||||||
|
|
||||||
|
|
||||||
|
def validate(value):
|
||||||
|
if not isinstance(value, str) or not VALID.fullmatch(value):
|
||||||
|
raise ValueError(f'Invalid FDS version: {value!r}')
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def git(root, *args):
|
||||||
|
return subprocess.check_output(['git', '-C', str(root), *args], text=True, stderr=subprocess.DEVNULL).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def version(root=ROOT):
|
||||||
|
root = Path(root).resolve()
|
||||||
|
# Source exports carry the value resolved when the export was created.
|
||||||
|
# Never accidentally describe a containing repository during offline builds.
|
||||||
|
if not (root / '.git').exists():
|
||||||
|
return validate((root / 'FDS_VERSION').read_text().strip())
|
||||||
|
revision = git(root, 'rev-parse', '--short=12', 'HEAD')
|
||||||
|
tags = [tag for tag in git(root, 'tag', '--merged', 'HEAD').splitlines() if RELEASE.fullmatch(tag)]
|
||||||
|
if tags:
|
||||||
|
description = git(root, 'describe', '--tags', '--long', '--abbrev=12', *[f'--match={tag}' for tag in tags], 'HEAD')
|
||||||
|
tag, distance, commit = description.rsplit('-', 2)
|
||||||
|
value = RELEASE.fullmatch(tag)[1]
|
||||||
|
if int(distance):
|
||||||
|
value += f'.r{distance}.{commit}'
|
||||||
|
else:
|
||||||
|
value = f'0.dev.g{revision}'
|
||||||
|
if git(root, 'status', '--porcelain', '--untracked-files=no'):
|
||||||
|
value += '.dirty'
|
||||||
|
return validate(value)
|
||||||
|
|
||||||
|
|
||||||
|
def cargo(root=ROOT):
|
||||||
|
print(f'cargo:rustc-env=FDS_BUILD_VERSION={version(root)}')
|
||||||
|
for path in [root / 'tools/fds_version.py', root / 'tools/version', root / 'tools/version-build.rs']:
|
||||||
|
print(f'cargo:rerun-if-changed={path}')
|
||||||
|
if not (root / '.git').exists():
|
||||||
|
print(f'cargo:rerun-if-changed={root / "FDS_VERSION"}')
|
||||||
|
return
|
||||||
|
# Tag creation, ref changes, staging and edits must invalidate cached binaries.
|
||||||
|
for name in ['HEAD', 'index', 'packed-refs', 'refs']:
|
||||||
|
path = git(root, 'rev-parse', '--path-format=absolute', '--git-path', name)
|
||||||
|
print(f'cargo:rerun-if-changed={path}')
|
||||||
|
head = subprocess.run(['git', '-C', str(root), 'symbolic-ref', '-q', 'HEAD'], capture_output=True, text=True)
|
||||||
|
if head.returncode == 0:
|
||||||
|
print('cargo:rerun-if-changed=' + git(root, 'rev-parse', '--path-format=absolute', '--git-path', head.stdout.strip()))
|
||||||
|
paths = git(root, 'ls-files', '--cached', '--others', '--exclude-standard').splitlines()
|
||||||
|
for name in paths:
|
||||||
|
if name != 'vendor/void-packages':
|
||||||
|
print(f'cargo:rerun-if-changed={root / name}')
|
||||||
+18
-4
@@ -12,11 +12,12 @@ import stat
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tomllib
|
import tomllib
|
||||||
|
from fds_version import version, validate
|
||||||
|
|
||||||
project = Path(__file__).resolve().parents[1]
|
project = Path(__file__).resolve().parents[1]
|
||||||
OWNED = ('.cargo', '.gitignore', '.gitmodules', 'AGENTS.md', 'Cargo.lock',
|
OWNED = ('.cargo', '.gitignore', '.gitmodules', 'AGENTS.md', 'Cargo.lock',
|
||||||
'Cargo.toml', 'LICENSE', 'Makefile', 'README.md', 'VOID_PACKAGES_COMMIT',
|
'Cargo.toml', 'LICENSE', 'Makefile', 'README.md', 'VOID_PACKAGES_COMMIT',
|
||||||
'config', 'docs', 'image', 'packages', 'profiles', 'rust-toolchain.toml',
|
'config', 'docs', 'examples', 'image', 'packaging', 'packages', 'profiles', 'rust-toolchain.toml',
|
||||||
'rust', 's6', 'tests', 'tools')
|
'rust', 's6', 'tests', 'tools')
|
||||||
|
|
||||||
|
|
||||||
@@ -79,7 +80,13 @@ def check_pin():
|
|||||||
if pin != actual:
|
if pin != actual:
|
||||||
raise ValueError('Void checkout differs from its pin')
|
raise ValueError('Void checkout differs from its pin')
|
||||||
run('git', '-C', void, 'diff', '--exit-code', 'HEAD', '--')
|
run('git', '-C', void, 'diff', '--exit-code', 'HEAD', '--')
|
||||||
return void, pin
|
build = project / '.host/void-packages'
|
||||||
|
if subprocess.check_output(['git', '-C', str(build), 'rev-parse', 'HEAD'], text=True).strip() != pin:
|
||||||
|
raise ValueError('Void build checkout differs from its pin')
|
||||||
|
run('git', '-C', build, 'diff', '--exit-code', 'HEAD', '--')
|
||||||
|
if subprocess.check_output(['git', '-C', str(void), 'status', '--porcelain', '--untracked-files=all'], text=True).strip():
|
||||||
|
raise ValueError('Void upstream submodule is dirty')
|
||||||
|
return build, pin
|
||||||
|
|
||||||
|
|
||||||
def add_package(source, destination):
|
def add_package(source, destination):
|
||||||
@@ -113,6 +120,7 @@ def create(destination):
|
|||||||
new_directory(destination)
|
new_directory(destination)
|
||||||
for name in OWNED:
|
for name in OWNED:
|
||||||
copy(project / name, destination / 'project' / name)
|
copy(project / name, destination / 'project' / name)
|
||||||
|
(destination / 'project/FDS_VERSION').write_text(version(project) + '\n')
|
||||||
run('git', '-C', void, 'bundle', 'create', destination / 'void.bundle', 'HEAD')
|
run('git', '-C', void, 'bundle', 'create', destination / 'void.bundle', 'HEAD')
|
||||||
# Bundles carry commit objects, but do not retain a shallow checkout's
|
# Bundles carry commit objects, but do not retain a shallow checkout's
|
||||||
# boundary file. Without it, history-aware commands follow missing parents.
|
# boundary file. Without it, history-aware commands follow missing parents.
|
||||||
@@ -199,7 +207,7 @@ def create(destination):
|
|||||||
('tar', ['tar', '--version']), ('rustup', ['rustup', '--version'])]:
|
('tar', ['tar', '--version']), ('rustup', ['rustup', '--version'])]:
|
||||||
host[name] = subprocess.check_output(command, text=True, stderr=subprocess.DEVNULL).splitlines()[0]
|
host[name] = subprocess.check_output(command, text=True, stderr=subprocess.DEVNULL).splitlines()[0]
|
||||||
files = inventory(destination)
|
files = inventory(destination)
|
||||||
lock = {'format': 1, 'version': '0.1.0', 'void_commit': pin, 'source_epoch': epoch,
|
lock = {'format': 1, 'version': version(project), 'void_commit': pin, 'source_epoch': epoch,
|
||||||
'source_sha256': source_digest(files), 'rust_toolchain': toolchain_version,
|
'source_sha256': source_digest(files), 'rust_toolchain': toolchain_version,
|
||||||
'host_prerequisites': host, 'files': files}
|
'host_prerequisites': host, 'files': files}
|
||||||
(destination / 'lock.json').write_text(json.dumps(lock, indent=2) + '\n')
|
(destination / 'lock.json').write_text(json.dumps(lock, indent=2) + '\n')
|
||||||
@@ -213,7 +221,7 @@ def verify(directory):
|
|||||||
if lockfile.is_symlink() or not lockfile.is_file() or lockfile.stat().st_size > 64 * 1024 * 1024:
|
if lockfile.is_symlink() or not lockfile.is_file() or lockfile.stat().st_size > 64 * 1024 * 1024:
|
||||||
raise ValueError('Missing or invalid frozen input lock')
|
raise ValueError('Missing or invalid frozen input lock')
|
||||||
lock = json.loads(lockfile.read_text())
|
lock = json.loads(lockfile.read_text())
|
||||||
if lock.get('format') != 1 or lock.get('version') != '0.1.0':
|
if lock.get('format') != 1 or not validate(lock.get('version')):
|
||||||
raise ValueError('Unsupported frozen input format')
|
raise ValueError('Unsupported frozen input format')
|
||||||
if (set(lock) != {'format', 'version', 'void_commit', 'source_epoch', 'source_sha256',
|
if (set(lock) != {'format', 'version', 'void_commit', 'source_epoch', 'source_sha256',
|
||||||
'rust_toolchain', 'host_prerequisites', 'files'}
|
'rust_toolchain', 'host_prerequisites', 'files'}
|
||||||
@@ -245,6 +253,12 @@ def restore(source, destination):
|
|||||||
epoch = int(subprocess.check_output(['git', '-C', str(void), 'show', '-s', '--format=%ct', 'HEAD']))
|
epoch = int(subprocess.check_output(['git', '-C', str(void), 'show', '-s', '--format=%ct', 'HEAD']))
|
||||||
if epoch != lock['source_epoch']:
|
if epoch != lock['source_epoch']:
|
||||||
raise ValueError('Restored Void commit timestamp differs from the snapshot')
|
raise ValueError('Restored Void commit timestamp differs from the snapshot')
|
||||||
|
# Older frozen projects expect caches under vendor; their saved sources and
|
||||||
|
# release inputs remain unchanged. New projects use the separate workspace.
|
||||||
|
prepare = destination / 'tools/prepare-void-workspace'
|
||||||
|
if prepare.is_file():
|
||||||
|
run(sys.executable, prepare)
|
||||||
|
void = destination / '.host/void-packages'
|
||||||
copy(source / 'masterdir', void / 'masterdir-x86_64')
|
copy(source / 'masterdir', void / 'masterdir-x86_64')
|
||||||
copy(source / 'sources', void / 'hostdir/sources')
|
copy(source / 'sources', void / 'hostdir/sources')
|
||||||
copy(source / 'repositories/build', void / 'hostdir/frozen-repository')
|
copy(source / 'repositories/build', void / 'hostdir/frozen-repository')
|
||||||
|
|||||||
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Only this explicit target changes the host package database.
|
||||||
|
set -euo pipefail
|
||||||
|
[[ $# == 0 ]] || { printf 'Usage: tools/install-workstation\n' >&2; exit 2; }
|
||||||
|
cd "$(dirname -- "${BASH_SOURCE[0]}")/.."
|
||||||
|
package=$(cat out/workstation/package-path.txt)
|
||||||
|
[[ -f $package ]] || { printf 'Build fds-tools with make workstation first\n' >&2; exit 1; }
|
||||||
|
if (( EUID == 0 )); then
|
||||||
|
exec pacman -U -- "$package"
|
||||||
|
else
|
||||||
|
exec sudo pacman -U -- "$package"
|
||||||
|
fi
|
||||||
+15
-6
@@ -3,22 +3,31 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
export LC_ALL=C
|
export LC_ALL=C
|
||||||
FDS_ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
FDS_ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
FDS_VOID="$FDS_ROOT/vendor/void-packages"
|
FDS_VOID_SOURCE="$FDS_ROOT/vendor/void-packages"
|
||||||
|
FDS_VOID="$FDS_ROOT/.host/void-packages"
|
||||||
FDS_XBPS="$FDS_ROOT/.host/xbps/usr/bin"
|
FDS_XBPS="$FDS_ROOT/.host/xbps/usr/bin"
|
||||||
|
|
||||||
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
|
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
|
||||||
need() { command -v "$1" >/dev/null || die "Missing host command: $1 (see docs/developer/build-host.md)"; }
|
need() { command -v "$1" >/dev/null || die "Missing host command: $1 (see docs/developer/build-host.md)"; }
|
||||||
|
|
||||||
check_void_pin() {
|
check_void_source() {
|
||||||
local pin actual
|
local pin actual
|
||||||
pin=$(cat "$FDS_ROOT/VOID_PACKAGES_COMMIT")
|
pin=$(cat "$FDS_ROOT/VOID_PACKAGES_COMMIT")
|
||||||
[[ $pin =~ ^[0-9a-f]{40}$ ]] || die 'Invalid VOID_PACKAGES_COMMIT'
|
[[ $pin =~ ^[0-9a-f]{40}$ ]] || die 'Invalid VOID_PACKAGES_COMMIT'
|
||||||
[[ -f $FDS_VOID/xbps-src ]] || die 'Void submodule is missing; run make bootstrap'
|
[[ -f $FDS_VOID_SOURCE/xbps-src ]] || die 'Void submodule is missing; run make bootstrap'
|
||||||
actual=$(git -C "$FDS_VOID" rev-parse HEAD)
|
actual=$(git -C "$FDS_VOID_SOURCE" rev-parse HEAD)
|
||||||
[[ $actual == "$pin" ]] || die "Void checkout mismatch: expected $pin, found $actual"
|
[[ $actual == "$pin" ]] || die "Void checkout mismatch: expected $pin, found $actual"
|
||||||
git -C "$FDS_VOID" diff --quiet HEAD -- || die 'Tracked Void files were changed; keep FDS changes in packages/'
|
git -C "$FDS_VOID_SOURCE" diff --quiet HEAD -- || die 'Tracked Void files were changed; keep FDS changes in packages/'
|
||||||
export SOURCE_DATE_EPOCH
|
export SOURCE_DATE_EPOCH
|
||||||
SOURCE_DATE_EPOCH=$(git -C "$FDS_VOID" show -s --format=%ct HEAD)
|
SOURCE_DATE_EPOCH=$(git -C "$FDS_VOID_SOURCE" show -s --format=%ct HEAD)
|
||||||
|
}
|
||||||
|
|
||||||
|
check_void_pin() {
|
||||||
|
check_void_source
|
||||||
|
[[ -z $(git -C "$FDS_VOID_SOURCE" status --porcelain --untracked-files=all) ]] || die 'Upstream Void submodule is dirty; run make bootstrap to migrate known build copies'
|
||||||
|
[[ -f $FDS_VOID/xbps-src ]] || die 'Void build checkout is missing; run make bootstrap'
|
||||||
|
[[ $(git -C "$FDS_VOID" rev-parse HEAD) == "$(cat "$FDS_ROOT/VOID_PACKAGES_COMMIT")" ]] || die 'Void build checkout pin changed; run make bootstrap'
|
||||||
|
git -C "$FDS_VOID" diff --quiet HEAD -- || die 'Tracked build-checkout files changed; keep FDS sources in packages/'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Keep the upstream entry point unchanged. Offline builds retain normal
|
# Keep the upstream entry point unchanged. Offline builds retain normal
|
||||||
|
|||||||
+4
-1
@@ -1,10 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
source "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh"
|
source "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh"
|
||||||
[[ $# == 0 ]] || die 'Usage: tools/prepare-void'
|
[[ $# == 0 ]] || die 'Usage: tools/prepare-void'
|
||||||
|
"$FDS_ROOT/tools/prepare-void-workspace"
|
||||||
check_void_pin
|
check_void_pin
|
||||||
|
"$FDS_ROOT/tools/version" >"$FDS_VOID/fds-version.next"
|
||||||
|
mv "$FDS_VOID/fds-version.next" "$FDS_VOID/fds-version"
|
||||||
# xbps-src reads this ignored local configuration instead of user-global settings.
|
# xbps-src reads this ignored local configuration instead of user-global settings.
|
||||||
if [[ -f $FDS_VOID/etc/conf ]] && ! cmp -s "$FDS_ROOT/config/xbps-src.conf" "$FDS_VOID/etc/conf"; then
|
if [[ -f $FDS_VOID/etc/conf ]] && ! cmp -s "$FDS_ROOT/config/xbps-src.conf" "$FDS_VOID/etc/conf"; then
|
||||||
die 'vendor/void-packages/etc/conf differs from config/xbps-src.conf; reconcile it explicitly'
|
die '.host/void-packages/etc/conf differs from config/xbps-src.conf; reconcile it explicitly'
|
||||||
fi
|
fi
|
||||||
cp -- "$FDS_ROOT/config/xbps-src.conf" "$FDS_VOID/etc/conf"
|
cp -- "$FDS_ROOT/config/xbps-src.conf" "$FDS_VOID/etc/conf"
|
||||||
# Only directories containing a template are active overlays.
|
# Only directories containing a template are active overlays.
|
||||||
|
|||||||
Executable
+125
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Prepare a writable Void build checkout outside the upstream submodule."""
|
||||||
|
import fcntl
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def git(directory, *arguments):
|
||||||
|
return subprocess.check_output(['git', '-C', str(directory), *arguments], text=True).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def inventory(root):
|
||||||
|
result = {}
|
||||||
|
for path in sorted(root.rglob('*')):
|
||||||
|
name = path.relative_to(root).as_posix()
|
||||||
|
if path.is_symlink():
|
||||||
|
result[name] = ('link', os.readlink(path))
|
||||||
|
elif path.is_file():
|
||||||
|
with path.open('rb') as stream:
|
||||||
|
result[name] = ('file', path.stat().st_mode & 0o777, hashlib.file_digest(stream, 'sha256').hexdigest())
|
||||||
|
elif path.is_dir():
|
||||||
|
result[name] = ('directory',)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Unexpected special source file: {path}')
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def prepare(project):
|
||||||
|
source = project / 'vendor/void-packages'
|
||||||
|
workspace = project / '.host/void-packages'
|
||||||
|
pin = (project / 'VOID_PACKAGES_COMMIT').read_text().strip()
|
||||||
|
if not re.fullmatch('[0-9a-f]{40}', pin) or git(source, 'rev-parse', 'HEAD') != pin:
|
||||||
|
raise ValueError('Upstream Void checkout differs from VOID_PACKAGES_COMMIT')
|
||||||
|
if git(source, 'diff', 'HEAD', '--'):
|
||||||
|
raise ValueError('Upstream Void has staged or tracked changes; preserve them outside the submodule first')
|
||||||
|
if source.is_symlink() or workspace.is_symlink():
|
||||||
|
raise ValueError('Void source and build checkout must be real directories')
|
||||||
|
|
||||||
|
# Migrate only known generated copies after comparing every entry. Unknown
|
||||||
|
# or independently edited source files are never deleted or overwritten.
|
||||||
|
overlays = {path.parent.name: path.parent for path in (project / 'packages').glob('*/template')}
|
||||||
|
for name in ('hello', 'report'):
|
||||||
|
example = project / f'examples/software/{name}/void'
|
||||||
|
if (example / 'template').is_file():
|
||||||
|
overlays[f'fds-demo-{name}'] = example
|
||||||
|
moves = []
|
||||||
|
duplicates = []
|
||||||
|
known = set()
|
||||||
|
for package, original in overlays.items():
|
||||||
|
old = source / 'srcpkgs' / package
|
||||||
|
if not old.exists() and not old.is_symlink():
|
||||||
|
continue
|
||||||
|
if git(source, 'ls-tree', 'HEAD', f'srcpkgs/{package}'):
|
||||||
|
raise ValueError(f'FDS overlay collides with upstream package: {package}')
|
||||||
|
if old.is_symlink() or not old.is_dir() or inventory(old) != inventory(original):
|
||||||
|
raise ValueError(f'Preserve independent edits in {old} before migrating it')
|
||||||
|
known.add(f'srcpkgs/{package}/')
|
||||||
|
new = workspace / 'srcpkgs' / package
|
||||||
|
if new.exists() or new.is_symlink():
|
||||||
|
if new.is_symlink() or not new.is_dir() or inventory(new) != inventory(original):
|
||||||
|
raise ValueError(f'Conflicting generated overlay: {new}')
|
||||||
|
duplicates.append(old)
|
||||||
|
else:
|
||||||
|
moves.append((old, new))
|
||||||
|
for name in git(source, 'ls-files', '--others', '--exclude-standard').splitlines():
|
||||||
|
if not any(name.startswith(prefix) for prefix in known):
|
||||||
|
raise ValueError(f'Untracked upstream file must be moved outside the submodule: {name}')
|
||||||
|
for old in [source / 'hostdir', *sorted(source.glob('masterdir-*')), source / 'etc/conf']:
|
||||||
|
if not old.exists() and not old.is_symlink():
|
||||||
|
continue
|
||||||
|
new = workspace / old.relative_to(source)
|
||||||
|
if old.is_symlink() or new.exists() or new.is_symlink():
|
||||||
|
raise ValueError(f'Cannot migrate {old}: symlink or existing destination {new}; reconcile explicitly')
|
||||||
|
if old == source / 'etc/conf' and old.read_bytes() != (project / 'config/xbps-src.conf').read_bytes():
|
||||||
|
raise ValueError('Old Void configuration differs from config/xbps-src.conf; reconcile explicitly')
|
||||||
|
moves.append((old, new))
|
||||||
|
|
||||||
|
if not workspace.exists():
|
||||||
|
# Independent objects keep this cache usable without Git alternates or
|
||||||
|
# hardlinks back into the submodule. No network access is needed.
|
||||||
|
with tempfile.TemporaryDirectory(prefix='void-checkout.', dir=workspace.parent) as temporary:
|
||||||
|
checkout = Path(temporary) / 'checkout'
|
||||||
|
subprocess.run(['git', 'clone', '--quiet', '--no-hardlinks', '--no-checkout', str(source), str(checkout)], check=True)
|
||||||
|
subprocess.run(['git', '-C', str(checkout), 'checkout', '--quiet', '--detach', pin], check=True)
|
||||||
|
subprocess.run(['git', '-C', str(checkout), 'remote', 'remove', 'origin'], check=True)
|
||||||
|
checkout.rename(workspace)
|
||||||
|
else:
|
||||||
|
if not (workspace / '.git').is_dir() or git(workspace, 'diff', 'HEAD', '--'):
|
||||||
|
raise ValueError('Build checkout has tracked changes; preserve them before preparing it')
|
||||||
|
if git(workspace, 'rev-parse', 'HEAD') != pin:
|
||||||
|
subprocess.run(['git', '-C', str(workspace), 'fetch', '--quiet', '--update-shallow', str(source), pin], check=True)
|
||||||
|
# Git refuses an update that would overwrite local files.
|
||||||
|
subprocess.run(['git', '-C', str(workspace), 'checkout', '--quiet', '--detach', pin], check=True)
|
||||||
|
for old, new in moves:
|
||||||
|
new.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
old.rename(new)
|
||||||
|
print(f'Moved {old.relative_to(project)} to {new.relative_to(project)}', flush=True)
|
||||||
|
for old in duplicates:
|
||||||
|
shutil.rmtree(old)
|
||||||
|
if git(source, 'status', '--porcelain', '--untracked-files=all'):
|
||||||
|
raise ValueError('Upstream submodule is not clean after preparation')
|
||||||
|
print('PASS: clean upstream reference; writable build checkout in .host/void-packages', flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
project = Path(__file__).resolve().parents[1]
|
||||||
|
if len(sys.argv) != 1:
|
||||||
|
sys.exit('Usage: tools/prepare-void-workspace')
|
||||||
|
try:
|
||||||
|
(project / '.host').mkdir(exist_ok=True)
|
||||||
|
with (project / '.host/void-workspace.lock').open('a') as lock:
|
||||||
|
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
|
prepare(project)
|
||||||
|
except (OSError, ValueError, subprocess.CalledProcessError) as error:
|
||||||
|
sys.exit(f'ERROR: {error}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from fds_version import validate
|
||||||
|
|
||||||
|
|
||||||
def digest(path):
|
def digest(path):
|
||||||
@@ -42,7 +43,7 @@ def artifacts(project):
|
|||||||
def read_lock(project):
|
def read_lock(project):
|
||||||
path = Path(project) / '.host/frozen/lock.json'
|
path = Path(project) / '.host/frozen/lock.json'
|
||||||
lock = json.loads(path.read_text())
|
lock = json.loads(path.read_text())
|
||||||
if lock.get('format') != 1 or lock.get('version') != '0.1.0':
|
if lock.get('format') != 1 or not validate(lock.get('version')):
|
||||||
raise ValueError('Build does not identify a supported frozen input snapshot')
|
raise ValueError('Build does not identify a supported frozen input snapshot')
|
||||||
return lock, digest(path)
|
return lock, digest(path)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import pathlib
|
|||||||
import stat
|
import stat
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
import shlex
|
||||||
|
from fds_version import validate
|
||||||
|
|
||||||
from rootfs_lib import package_db, rooted
|
from rootfs_lib import package_db, rooted
|
||||||
|
|
||||||
@@ -29,6 +31,12 @@ def audit(root, configured=True):
|
|||||||
assert path.is_dir() and not path.is_symlink(), f"Missing directory: {name}"
|
assert path.is_dir() and not path.is_symlink(), f"Missing directory: {name}"
|
||||||
assert stat.S_IMODE(path.stat().st_mode) == int(mode, 8), f"Wrong directory mode: {name}"
|
assert stat.S_IMODE(path.stat().st_mode) == int(mode, 8), f"Wrong directory mode: {name}"
|
||||||
assert 'ID=fds\n' in (root / "usr/lib/os-release").read_text(), "Wrong OS identity"
|
assert 'ID=fds\n' in (root / "usr/lib/os-release").read_text(), "Wrong OS identity"
|
||||||
|
identity = dict(line.split('=', 1) for line in shlex.split((root / 'usr/lib/os-release').read_text(), comments=True))
|
||||||
|
version = validate(identity['VERSION_ID'])
|
||||||
|
assert identity['VERSION'] == version and identity['PRETTY_NAME'] == f'FDS/OS {version}', 'Inconsistent OS version'
|
||||||
|
assert (root / 'etc/issue').read_text() == f'FDS/OS {version}\n', 'Inconsistent login version'
|
||||||
|
for package in ('fds-base', 'fds-base-files', 'fds-init', 'fds-cli', 'fds-cartridged', 'fds-dasungd', 'fds-eink'):
|
||||||
|
assert db[package]['pkgver'] == f'{package}-{version}_1', f'OS/package version mismatch: {package}'
|
||||||
release = (root / "usr/share/fds/kernel-release").read_text().strip()
|
release = (root / "usr/share/fds/kernel-release").read_text().strip()
|
||||||
assert (root / "usr/lib/modules" / release / "modules.dep").is_file(), "Missing matching kernel module index"
|
assert (root / "usr/lib/modules" / release / "modules.dep").is_file(), "Missing matching kernel module index"
|
||||||
assert (root / "etc/shadow").stat().st_mode & 0o777 == 0o600, "Shadow file is exposed"
|
assert (root / "etc/shadow").stat().st_mode & 0o777 == 0o600, "Shadow file is exposed"
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ trap 'rm -f "$log"' EXIT
|
|||||||
dasungd --config /etc/dasungd.toml check
|
dasungd --config /etc/dasungd.toml check
|
||||||
fds --json info | grep -q aarch64
|
fds --json info | grep -q aarch64
|
||||||
fds-cartridged --help | grep -q SYSFS_ROOT
|
fds-cartridged --help | grep -q SYSFS_ROOT
|
||||||
fds-release --version | grep -qx "fds-release 0.1.0"
|
. /etc/os-release
|
||||||
|
fds-release --version | grep -Fx "fds-release $VERSION_ID"
|
||||||
|
fds --version | grep -Fx "fds $VERSION_ID"
|
||||||
|
dasungd --version | grep -Fx "dasungd $VERSION_ID"
|
||||||
s6-rc-db -c /etc/s6-rc/compiled list all | grep -qx cartridged
|
s6-rc-db -c /etc/s6-rc/compiled list all | grep -qx cartridged
|
||||||
s6-rc-db -c /etc/s6-rc/compiled list all | grep -qx dasungd
|
s6-rc-db -c /etc/s6-rc/compiled list all | grep -qx dasungd
|
||||||
s6-rc-db -c /etc/s6-rc/compiled list all | grep -qx test-echo
|
s6-rc-db -c /etc/s6-rc/compiled list all | grep -qx test-echo
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ else
|
|||||||
printf 'SKIP: ARM execution (optional qemu-aarch64 not installed); ELF verification passed\n'
|
printf 'SKIP: ARM execution (optional qemu-aarch64 not installed); ELF verification passed\n'
|
||||||
fi
|
fi
|
||||||
XBPS_ARCH=x86_64 xbps-query -r "$FDS_VOID/masterdir-x86_64" -l >out/manifests/void-build-packages.txt
|
XBPS_ARCH=x86_64 xbps-query -r "$FDS_VOID/masterdir-x86_64" -l >out/manifests/void-build-packages.txt
|
||||||
find vendor/void-packages/hostdir -type f -name '*.xbps' -print0 \
|
find "$FDS_VOID/hostdir" -type f -name '*.xbps' -print0 \
|
||||||
| sort -z | xargs -0 -r sha256sum >out/manifests/void-package-inputs.sha256
|
| sort -z | xargs -0 -r sha256sum >out/manifests/void-package-inputs.sha256
|
||||||
sha256sum "$package" out/fds-smoketest >out/manifests/artifacts.sha256
|
sha256sum "$package" out/fds-smoketest >out/manifests/artifacts.sha256
|
||||||
printf 'PASS: M0 smoke test complete\n'
|
printf 'PASS: M0 smoke test complete\n'
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user