Files
ashvale-station/.github/workflows/image.yml
T
kemal bd233d7b27 Fix image build: register static qemu with the F flag
Second failure was debootstrap dying with 'E: Unable to execute target
architecture'. qemu-user-binfmt registers a dynamically linked emulator, which
cannot run once debootstrap chroots into a rootfs that has no loader for it.
The fix is the static binary registered with the F flag, which preloads the
interpreter so it survives the chroot.

Registration runs after apt, because installing binfmt-support re-registers the
dynamic handlers over the top, and the workflow now asserts the F flag is
present rather than discovering its absence an hour into a build.
2026-08-16 18:37:51 +01:00

139 lines
5.5 KiB
YAML

name: Pi image
# Builds a ready-to-flash Raspberry Pi OS Lite image with Ashvale Station
# preinstalled, and attaches it to a GitHub Release.
#
# Built in CI rather than on a laptop on purpose. The artifact is something
# other people flash onto their own hardware, so it should be reproducible from
# a public log by anyone who wants to check what went into it, rather than
# appearing from a machine only I can see.
on:
workflow_dispatch:
inputs:
publish:
description: "Attach the image to a release"
type: boolean
default: false
push:
tags:
- "v*"
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 180
steps:
- name: Check out Ashvale
uses: actions/checkout@v4
with:
path: ashvale
# pi-gen needs about 10 GB and a stock runner does not have it spare.
- name: Reclaim disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \
/usr/local/share/boost "$AGENT_TOOLSDIRECTORY"
df -h / | tail -1
- name: Install build dependencies
run: |
sudo apt-get update
# Taken from pi-gen's own `depends` file at the pinned commit rather
# than guessed. My first attempt installed qemu-user-static, and
# pi-gen wants qemu-user-binfmt: it checks for the binfmt handler, not
# the emulator binary, and failed in under a second.
sudo apt-get install -y --no-install-recommends \
quilt parted coreutils qemu-user-binfmt debootstrap zerofree zip \
dosfstools e2fsprogs libcap2-bin libarchive-tools grep rsync \
xz-utils curl xxd file git kmod bc gpg pigz arch-test
# qemu-user-static too: qemu-user-binfmt registers a dynamically linked
# emulator, which cannot run once debootstrap chroots into a rootfs
# that has no loader for it. That fails as "E: Unable to execute
# target architecture" several steps later.
sudo apt-get install -y --no-install-recommends qemu-user-static binfmt-support
# Registers the static handlers with the F flag, which preloads the
# emulator so it survives the chroot. Run after apt, because installing
# binfmt-support re-registers the dynamic handlers over the top.
- name: Register qemu binfmt handlers
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- name: Verify the aarch64 handler is usable in a chroot
run: |
F=/proc/sys/fs/binfmt_misc/qemu-aarch64
test -f "$F" || { echo "no aarch64 binfmt handler registered"; exit 1; }
cat "$F"
# The F flag is the whole point: without it the interpreter is resolved
# inside the chroot, where it does not exist.
grep -q 'flags:.*F' "$F" || { echo "handler lacks the F flag; chroot will fail"; exit 1; }
echo "aarch64 handler OK"
# Pinned to a commit, not a branch. An image other people flash should not
# change because an upstream branch moved between builds.
- name: Check out pi-gen
uses: actions/checkout@v4
with:
repository: RPi-Distro/pi-gen
ref: ca8aeed0ae300c2a89f55ce9617d5f96a27e99e5 # arm64 branch, pinned
path: pi-gen
fetch-depth: 1
- name: Assemble the custom stage
run: |
cp ashvale/deploy/pi-image/config pi-gen/config
cp -r ashvale/deploy/pi-image/stage-ashvale pi-gen/stage-ashvale
# Lite only: everything from stage3 up is the desktop.
touch pi-gen/stage3/SKIP pi-gen/stage4/SKIP pi-gen/stage5/SKIP
touch pi-gen/stage4/SKIP_IMAGES pi-gen/stage5/SKIP_IMAGES
# stage2 stops exporting so ours is the only image produced.
rm -f pi-gen/stage2/EXPORT_IMAGE
echo "ASHVALE_SRC=${GITHUB_WORKSPACE}/ashvale" >> pi-gen/config
echo "--- config ---" && cat pi-gen/config
- name: Build
working-directory: pi-gen
run: sudo -E ./build.sh
- name: Collect artifact
id: artifact
run: |
IMG=$(find pi-gen/deploy -name '*.img.xz' | head -1)
test -n "$IMG" || { echo "no image produced"; ls -R pi-gen/deploy; exit 1; }
mkdir -p out && mv "$IMG" out/
cd out
NAME=$(basename *.img.xz)
sha256sum "$NAME" > "$NAME.sha256"
echo "name=$NAME" >> "$GITHUB_OUTPUT"
ls -lh
# A release asset is capped at 2 GB; Lite compresses to well under that,
# but fail loudly here rather than at upload time.
SIZE=$(stat -c%s "$NAME")
echo "compressed size: $((SIZE/1024/1024)) MiB"
test "$SIZE" -lt 2000000000 || { echo "image exceeds the 2 GB release limit"; exit 1; }
- name: Upload as a workflow artifact
uses: actions/upload-artifact@v4
with:
name: ashvale-pi-image
path: out/*
retention-days: 14
- name: Attach to release
if: startsWith(github.ref, 'refs/tags/') || inputs.publish
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="${GITHUB_REF_NAME}"
gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 \
|| gh release create "$TAG" --repo "$GITHUB_REPOSITORY" \
--title "$TAG" --notes "Ashvale Station image for Raspberry Pi."
gh release upload "$TAG" out/* --repo "$GITHUB_REPOSITORY" --clobber