Some checks failed
build image / build (push) Has been cancelled
The first run produced one job leg with an empty variant, and the build rejected it. The prepare job was fine — it set variants=["minimal"] and the output evaluated correctly — but Gitea's runner resolves strategy.matrix while planning the workflow, before `needs` has produced anything, so fromJSON(needs.prepare.outputs.variants) saw an empty string. Dynamic matrices from needs outputs are not supported here. Drop the prepare job and loop in shell instead: one job, no expression-engine dependency, and a non-zero exit only after every requested variant has been attempted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
156 lines
5.6 KiB
YAML
156 lines
5.6 KiB
YAML
name: build image
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
tags: ['v*']
|
|
pull_request:
|
|
workflow_dispatch:
|
|
inputs:
|
|
variant:
|
|
description: which variant to build
|
|
type: choice
|
|
default: minimal
|
|
options: [minimal, workstation, both]
|
|
|
|
# Deliberately no strategy.matrix here.
|
|
#
|
|
# The obvious shape for this is a `prepare` job emitting a JSON list and a
|
|
# matrix built from `fromJSON(needs.prepare.outputs.variants)`. Gitea's runner
|
|
# evaluates strategy.matrix when it plans the workflow — before `needs` has run
|
|
# — so that expression resolves to an empty string and you get exactly one job
|
|
# leg with an empty variant. The prepare job succeeds and sets its output
|
|
# correctly; the matrix simply never sees it.
|
|
#
|
|
# Looping in shell is less elegant and entirely reliable.
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: metal
|
|
timeout-minutes: 600
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Report runner
|
|
run: |
|
|
echo "host arch : $(uname -m)"
|
|
echo "kernel : $(uname -r)"
|
|
echo "user : $(id -un) (uid $(id -u))"
|
|
echo "podman : $(podman --version 2>/dev/null || echo MISSING)"
|
|
df -h .
|
|
|
|
# Building an aarch64 root filesystem means executing aarch64 rpm
|
|
# scriptlets, which needs a binfmt_misc handler in the host kernel. A
|
|
# container cannot register one for itself.
|
|
- name: Ensure aarch64 emulation
|
|
run: |
|
|
handler=/proc/sys/fs/binfmt_misc/qemu-aarch64
|
|
if [ ! -e "$handler" ]; then
|
|
echo "no aarch64 binfmt handler, attempting to install one"
|
|
sudo dnf install -y qemu-user-static-aarch64
|
|
sudo systemctl restart systemd-binfmt
|
|
fi
|
|
if [ ! -e "$handler" ]; then
|
|
echo "::error::aarch64 emulation is unavailable on this runner." \
|
|
"See docs/runner-setup.md for the one-time host setup."
|
|
exit 1
|
|
fi
|
|
cat "$handler"
|
|
|
|
# Both of these live outside the job workspace, which is wiped between
|
|
# runs. On metal runners a host path persists for free and avoids
|
|
# shuttling multi-gigabyte caches through Gitea's cache store — the
|
|
# tradeoff being that they are per-runner, so a job landing on a runner
|
|
# that has not built before starts cold.
|
|
- name: Prepare persistent build state
|
|
run: |
|
|
echo "CACHE_DIR=/var/tmp/c630-build/dnf" >> "$GITHUB_ENV"
|
|
echo "WORK_DIR=/var/tmp/c630-build/work" >> "$GITHUB_ENV"
|
|
mkdir -p /var/tmp/c630-build/dnf /var/tmp/c630-build/work
|
|
# Keep it bounded: drop cached rpms nothing has touched in a month.
|
|
find /var/tmp/c630-build/dnf -type f -atime +30 -delete 2>/dev/null || true
|
|
du -sh /var/tmp/c630-build/* 2>/dev/null || true
|
|
|
|
- name: Select variants
|
|
run: |
|
|
if [ "${{ startsWith(github.ref, 'refs/tags/') }}" = "true" ]; then
|
|
variants="minimal workstation"
|
|
else
|
|
case "${{ inputs.variant }}" in
|
|
both) variants="minimal workstation" ;;
|
|
workstation) variants="workstation" ;;
|
|
*) variants="minimal" ;;
|
|
esac
|
|
fi
|
|
echo "VARIANTS=$variants" | tee -a "$GITHUB_ENV"
|
|
|
|
- name: Build
|
|
run: |
|
|
rc=0
|
|
for variant in $VARIANTS; do
|
|
case "$variant" in
|
|
workstation) size=16384 ;;
|
|
*) size=8192 ;;
|
|
esac
|
|
echo "::group::build $variant (${size} MiB)"
|
|
# No --fresh: the stamp in stage2.sh hashes the package lists, so a
|
|
# change there invalidates the staged base on its own.
|
|
if ./build/build-image.sh \
|
|
--variant "$variant" \
|
|
--size "$size" \
|
|
--cache "$CACHE_DIR" \
|
|
--work "$WORK_DIR"; then
|
|
echo "$variant ok"
|
|
else
|
|
echo "::error::build failed for $variant"
|
|
rc=1
|
|
fi
|
|
echo "::endgroup::"
|
|
done
|
|
exit $rc
|
|
|
|
- name: Checksums
|
|
if: always()
|
|
run: cat output/*.sha256 2>/dev/null || echo "no images produced"
|
|
|
|
- uses: actions/upload-artifact@v4
|
|
if: always()
|
|
with:
|
|
name: fedora-lenovo-yoga-c630
|
|
path: |
|
|
output/*.img.zst
|
|
output/*.sha256
|
|
retention-days: 14
|
|
compression-level: 0 # already zstd
|
|
if-no-files-found: warn
|
|
|
|
- name: Attach to release
|
|
if: startsWith(github.ref, 'refs/tags/')
|
|
env:
|
|
TOKEN: ${{ github.token }}
|
|
SERVER: ${{ github.server_url }}
|
|
REPO: ${{ github.repository }}
|
|
TAG: ${{ github.ref_name }}
|
|
run: |
|
|
set -euo pipefail
|
|
api="$SERVER/api/v1/repos/$REPO"
|
|
auth="Authorization: token $TOKEN"
|
|
# python3 rather than jq — jq is not guaranteed on a Fedora Server runner.
|
|
field() { python3 -c 'import json,sys; print(json.load(sys.stdin).get(sys.argv[1],""))' "$1"; }
|
|
|
|
id=$(curl -sf -H "$auth" "$api/releases/tags/$TAG" | field id || true)
|
|
if [ -z "${id:-}" ]; then
|
|
id=$(curl -sf -X POST -H "$auth" -H 'Content-Type: application/json' \
|
|
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\"}" \
|
|
"$api/releases" | field id)
|
|
fi
|
|
echo "release id: $id"
|
|
|
|
for f in output/*.img.zst output/*.sha256; do
|
|
echo "uploading $(basename "$f")"
|
|
curl -sf -X POST -H "$auth" \
|
|
-F "attachment=@${f}" \
|
|
"$api/releases/$id/assets?name=$(basename "$f")" >/dev/null
|
|
done
|