Files
c630/.gitea/workflows/build-image.yaml
rob thijssen 4d1fd98683
Some checks failed
build image / prepare (push) Successful in 0s
build image / build (push) Failing after 29s
Cache the expensive half of the build so iteration is cheap
The dnf transaction is essentially the whole cost of a build — emulated rpm
scriptlets for 502 packages on minimal, 1929 on workstation. Everything after
it is minutes. Getting this laptop to boot will take several attempts at the
kernel command line and the dracut driver list, and paying for a reinstall each
time is not tenable.

Stage the post-dnf tree under <work>/base, keyed on a hash of the package
lists, release and variant, and copy it per build with --reflink=auto (a CoW
clone on btrfs). Config and overlay edits now reuse it; package list edits
invalidate it on their own, so --fresh is only needed to force the issue.

Keep downloaded rpms in a cachedir outside the install root, so even --fresh
re-runs the scriptlets without re-downloading. keepcache=0 was exactly the
wrong setting for a build meant to be run repeatedly.

Add --work so CI can put both outside the job workspace, which is wiped between
runs, and default the build container to the gongfoo aarch64 build base so the
assembly tooling is not installed under emulation every time. That image is a
speedup, not a dependency: fall back to stock Fedora when it is unreachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
2026-07-27 11:58:12 +03:00

144 lines
5.1 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]
jobs:
# Which variants to build. Pushes and PRs build minimal only — it is the fast
# one and it exercises the entire pipeline. Tags build everything.
prepare:
runs-on: metal
outputs:
variants: ${{ steps.pick.outputs.variants }}
steps:
- id: pick
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_OUTPUT"
build:
needs: prepare
runs-on: metal
timeout-minutes: 600
strategy:
fail-fast: false
matrix:
variant: ${{ fromJSON(needs.prepare.outputs.variants) }}
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,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: Build
run: |
case "${{ matrix.variant }}" in
workstation) size=16384 ;;
*) size=8192 ;;
esac
# No --fresh: the stamp in stage2.sh hashes the package lists, so a
# change there invalidates the staged base on its own. Checkout is
# shallow here anyway, so diffing against HEAD~1 would not be reliable.
./build/build-image.sh \
--variant "${{ matrix.variant }}" \
--size "$size" \
--cache "$CACHE_DIR" \
--work "$WORK_DIR"
- name: Checksums
run: cat output/*.sha256
- uses: actions/upload-artifact@v4
with:
name: fedora-${{ matrix.variant }}-lenovo-yoga-c630
path: |
output/*.img.zst
output/*.sha256
retention-days: 14
compression-level: 0 # already zstd
- 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