diff --git a/.changeset/imager-device-metadata.md b/.changeset/imager-device-metadata.md new file mode 100644 index 00000000..5474297d --- /dev/null +++ b/.changeset/imager-device-metadata.md @@ -0,0 +1,27 @@ +--- +"ftw": patch +--- + +Raspberry Pi Imager: the repository manifest now declares the hardware it +supports, so the documented install flow reaches the FTW image instead of +dead-ending on an empty device chooser. + +Imager 2.x opens on "Select your Raspberry Pi device" and builds that list +solely from `imager.devices` in whichever manifest it was pointed at — a +custom repository replaces the stock manifest rather than extending it. FTW +published neither an `imager` object nor a `devices` array, so `HWListModel` +bailed with "missing imager", the device list stayed empty, and **Next** +stayed disabled: the OS list had loaded, so the offline escape hatch +(`osListUnavailable && hwlist.count === 0`) did not apply. Nothing on the +device step could be clicked and the FTW entry was never reached. + +The entry itself was also untagged. Imager keeps a `devices`-less OS entry +only when the selected device matches inclusively, so a Pi 5 — which matches +exclusively — would have filtered FTW out even once a device was selectable. +`devices` is required by Imager's published schema for a direct-image entry. + +The manifest now ships `imager.devices` (Pi 5, Pi 4, and "No filtering") and +tags the FTW entry `pi5-64bit` / `pi4-64bit`, matching the arm64 image it +actually builds. Both workflows that publish the manifest assert the two +lists are present and agree, so an entry tagged for a device the chooser +never offers fails CI rather than shipping unreachable. diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml index 0968dda6..7cf58a83 100644 --- a/.github/workflows/release-assets.yml +++ b/.github/workflows/release-assets.yml @@ -212,6 +212,18 @@ jobs: and (.[0].url | contains("/releases/download/rpi-installer/")) and (.[0].extract_sha256 | test("^[0-9a-f]{64}$")) ' installer/os_list.json >/dev/null + # Imager 2.x needs a device chooser (imager.devices) and device tags + # on the entry itself, or the wizard dead-ends before reaching FTW. + # Warn rather than fail: the installer image is independent of + # application releases, so an image published before this metadata + # landed must not block an unrelated version release. Rebuilding via + # rpi-image-build.yml clears it. + if ! jq -e ' + (.imager.devices | length) > 0 + and (.os_list[0].devices | length) > 0 + ' installer/os_list.json >/dev/null; then + echo "::warning::Published os_list.json has no imager.devices/os_list[].devices; Raspberry Pi Imager 2.x will dead-end on an empty device step. Rerun rpi-image-build.yml to republish the installer metadata." + fi gh release upload "${TAG}" installer/os_list.json \ --repo "${GITHUB_REPOSITORY}" --clobber diff --git a/.github/workflows/rpi-image-build.yml b/.github/workflows/rpi-image-build.yml index 5ffb0e89..695486d3 100644 --- a/.github/workflows/rpi-image-build.yml +++ b/.github/workflows/rpi-image-build.yml @@ -82,6 +82,17 @@ jobs: and (.[0].extract_sha256 | contains("__EXTRACT_SHA256__")) ' deploy/imager/os_list.json >/dev/null + # Imager 2.x builds its device chooser from imager.devices and drops + # os_list entries that claim no device tags. Without both halves the + # wizard dead-ends on an empty device step, so assert they agree. + jq -e ' + (.imager.devices | length) > 0 + and all(.imager.devices[]; has("name") and has("description") and has("tags")) + and (.os_list[0].devices | length) > 0 + and ([.imager.devices[].tags[]] as $offered + | .os_list[0].devices | all(. as $t | $offered | index($t))) + ' deploy/imager/os_list.json >/dev/null + - name: Validate embedded Compose file run: docker compose -f docker-compose.yml config --quiet diff --git a/deploy/imager/os_list.json b/deploy/imager/os_list.json index aeacf737..f7891074 100644 --- a/deploy/imager/os_list.json +++ b/deploy/imager/os_list.json @@ -1,4 +1,31 @@ { + "imager": { + "devices": [ + { + "name": "Raspberry Pi 5", + "description": "Raspberry Pi 5, 500 / 500+, and Compute Module 5", + "icon": "https://downloads.raspberrypi.com/imager/icons/RPi_5.png", + "tags": ["pi5-64bit", "pi5-32bit"], + "matching_type": "exclusive", + "capabilities": [] + }, + { + "name": "Raspberry Pi 4", + "description": "Raspberry Pi 4 Model B, 400, and Compute Module 4 / 4S", + "icon": "https://downloads.raspberrypi.com/imager/icons/RPi_4.png", + "tags": ["pi4-64bit", "pi4-32bit"], + "matching_type": "exclusive", + "capabilities": [] + }, + { + "name": "No filtering", + "description": "Show every possible image", + "tags": [], + "matching_type": "inclusive", + "capabilities": [] + } + ] + }, "os_list": [ { "name": "FTW", @@ -10,6 +37,7 @@ "image_download_size": 0, "release_date": "1970-01-01", "init_format": "cloudinit-rpi", + "devices": ["pi5-64bit", "pi4-64bit"], "website": "https://ftw.sourceful.energy" } ] diff --git a/docs/rpi-image.md b/docs/rpi-image.md index 00285a84..bea3961c 100644 --- a/docs/rpi-image.md +++ b/docs/rpi-image.md @@ -8,18 +8,24 @@ credentials and Wi-Fi before writing the card. 1. Install [Raspberry Pi Imager](https://www.raspberrypi.com/software/) 2.0 or newer. -2. Open **App Options → Content Repository → Edit → Use custom file**. -3. Enter: +2. Open **App Options → Content Repository → Edit → Use custom URL**. +3. Paste: ```text https://github.com/srcfl/ftw/releases/download/rpi-installer/os_list.json ``` -4. Apply and restart Imager. -5. Choose **FTW**, choose the SD card, and set hostname, SSH user/password and +4. Click **Apply & Restart**. +5. Choose **Raspberry Pi 5** or **Raspberry Pi 4** on the device step. +6. Choose **FTW**, choose the SD card, and set hostname, SSH user/password and Wi-Fi in OS customisation. -6. Write the card, insert it and power on the Pi. -7. After first-boot provisioning, open `http://ftw.local/` on the same network. +7. Write the card, insert it and power on the Pi. +8. After first-boot provisioning, open `http://ftw.local/` on the same network. + +Pick **Use custom URL**, not **Use custom file**. They are separate options in +the same dialog: **Use custom file** opens a file picker for a manifest already +on your computer and offers no field to paste a web address into. The FTW +manifest is hosted, so it belongs in **Use custom URL**. `rpi-installer` is the permanent repository URL and is the recommended value to save in Imager. Stable application releases also mirror this small file, so @@ -120,6 +126,19 @@ The output path and exact base image are defined by those scripts and the image workflow. CI runs structural checks on changes; full images are built on the installer schedule or explicit dispatch. -The Imager repository entry intentionally has no `devices` filter. Custom -repositories do not receive Raspberry Pi Imager's stock hardware list, so a -filter would hide FTW from the chooser even though the image supports Pi 4/5. +A custom repository replaces Imager's stock manifest rather than extending it, +so [`deploy/imager/os_list.json`](../deploy/imager/os_list.json) has to carry +both halves of the hardware contract itself: + +- `imager.devices` supplies the device chooser. Imager 2.x builds the first + wizard step from this list alone, and leaves **Next** disabled until a device + is selected. A manifest without it dead-ends the wizard on an empty list. +- `os_list[].devices` marks FTW as compatible with those devices. Imager drops + an entry with no `devices` tags whenever the selected device filters + exclusively, and the field is required by + [Imager's schema](https://github.com/raspberrypi/rpi-imager/blob/main/doc/json-schema/os-list-schema.json). + +The tags are Imager's own (`pi5-64bit`, `pi4-64bit`); FTW ships arm64 only, so +it claims the 64-bit tags for Pi 4 and Pi 5. Keep the two lists consistent when +either changes — an entry tagged for a device the chooser never offers is +unreachable.