ci: smoke-test built images; build on labeled PRs, weekly and on demand
ci / cook (push) Successful in 12s
ci / cook (pull_request) Successful in 11s

smoke-test.sh checks an image from build-image.sh in two steps:
- static, read-only: if the image declares SELinux, no kernel command line
  (/etc/kernel/cmdline, ESP and /boot loader entries) may carry selinux=0 and
  files must be labeled. This catches images built on a host with SELinux
  disabled, where anaconda inherits the host's selinux=0 and overrides the
  kickstart's `selinux --enforcing` (CI run 237);
- boot: KVM with -snapshot, no network, waits for qemu-guest-agent to
  answer, so it does not depend on a serial console in the image.
Verified on the runner: the run-237 image fails the static check, a fresh
build passes both (agent answered after 27s).

build-image.yaml now smoke-tests each edition before compressing it, and
besides v*.*.* tags also runs:
- on PRs into main labeled `build-image` (the review gate: Gitea does not
  let authors approve their own PRs), re-running on new pushes while the
  label stays, only for image-affecting paths, and not when an unrelated
  label is added;
- weekly (Mondays 03:00), to catch upstream Fedora breakage;
- via workflow_dispatch.
Non-tag runs keep the images as 7-day artifacts. README updated, including
the stale "every push to main" sentence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lukas Greve
2026-09-23 20:04:32 +02:00
co-authored by Claude Opus 5.5
parent c611bbf2c2
commit f7b6623b25
3 changed files with 267 additions and 7 deletions
+51 -6
View File
@@ -1,11 +1,29 @@
name: build-image
# Only a release tag builds images: a full build takes ~25 min on the runner,
# too costly to repeat on every push to main. Recipes are still linted and
# validated on every push by ci.yml.
# A full build takes ~25 min on the runner and executes the checked-out code
# as root there, so it never runs on a plain push. Recipes are still linted
# and validated on every push and PR by ci.yml. Images are built:
# - on a release tag (v*.*.*): build, smoke test, publish as a release;
# - on a PR into main carrying the `build-image` label, i.e. once a
# maintainer has reviewed it (Gitea does not let authors approve their
# own PRs, so approval cannot be the gate). Re-runs on every new push to
# the PR while the label stays. Only for changes that affect images;
# - weekly, to catch breakage from upstream Fedora package changes;
# - by hand (workflow_dispatch) on any branch.
on:
push:
tags: ["v*.*.*"]
pull_request:
branches: [main]
types: [labeled, synchronize, reopened]
paths:
- "cook/**"
- "build-image.sh"
- "smoke-test.sh"
- ".gitea/workflows/build-image.yaml"
schedule:
- cron: "0 3 * * 1" # Mondays 03:00
workflow_dispatch:
# Builds raw disk images of the two default-tier editions (see
# cook/recipes_manifest.yaml) with build-image.sh, i.e.
@@ -15,10 +33,19 @@ on:
# The build job runs on the registered `fedora:host` runner (a Fedora VM, jobs
# run as root), not in a container: --make-disk needs real loop devices and a
# live systemd-udevd. The runner needs lorax-lmc-novirt, qemu-img, make,
# python3-pip, pykickstart and xz installed.
# python3-pip, pykickstart and xz installed, plus qemu-system-x86_64,
# /dev/kvm and edk2-ovmf for smoke-test.sh. It must run with SELinux enabled:
# anaconda inherits the runner's own selinux=0, if any (smoke-test.sh fails
# such images).
jobs:
validate:
# On PRs, only once the `build-image` label is set (see `on:` above), and
# not again when some other label is added to an already-labeled PR.
if: >-
github.event_name != 'pull_request' ||
(contains(github.event.pull_request.labels.*.name, 'build-image') &&
(github.event.action != 'labeled' || github.event.label.name == 'build-image'))
runs-on: fedora
defaults:
run:
@@ -68,10 +95,12 @@ jobs:
- name: Check runner prerequisites
run: |
missing=0
for tool in livemedia-creator qemu-img make pip xz sudo; do
for tool in livemedia-creator qemu-img make pip xz sudo qemu-system-x86_64 debugfs; do
command -v "$tool" >/dev/null || { echo "missing on runner: $tool"; missing=1; }
done
[ -e /dev/loop-control ] || { echo "missing on runner: /dev/loop-control"; missing=1; }
[ -e /dev/kvm ] || { echo "missing on runner: /dev/kvm"; missing=1; }
[ -e /usr/share/edk2/ovmf/OVMF_CODE.fd ] || { echo "missing on runner: edk2-ovmf"; missing=1; }
[ "$missing" = 0 ] || exit 1
- name: Install Python deps
@@ -85,7 +114,23 @@ jobs:
dish="$(basename "$(ls cook/dishes/${{ matrix.edition }}_*.cfg)" .cfg)"
echo "DISH=$dish" >> "$GITHUB_ENV"
./build-image.sh --dish "$dish" --root-size ${{ matrix.root_size }}
xz -T0 "build/$dish.img"
- name: Smoke test (static checks + boot)
run: ./smoke-test.sh "build/$DISH.img"
- name: Compress image
run: xz -T0 "build/$DISH.img"
- name: Upload image and kickstart as artifact
if: "!startsWith(github.ref, 'refs/tags/')"
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.edition }}
path: |
build/${{ env.DISH }}.img.xz
cook/dishes/${{ env.DISH }}.cfg
if-no-files-found: error
retention-days: 7
- name: Publish image and kickstart as release
if: startsWith(github.ref, 'refs/tags/')