feat(ci): builder images move to the LAN registry, content-keyed; fan-out scoped by paths

The five builder images now live on home-ci-core's LAN registry
(192.168.1.58:5010) under content keys — a hash of the ci/ tree (+
rust-toolchain.toml for the cross image). docker.yml builds one only when its
key has no manifest yet, so a push that doesn't touch ci/ costs a curl per
image instead of seven WAN pushes and a set of per-SHA tags that no plain
prune could ever reclaim. Releases pin builders by copying the key manifest
to a vX.Y.Z tag via the registry API — no rebuild, no bytes moved.

Around that: deb/rpm/arch/android/apple/decky get path filters so docs-only
pushes stop lighting up the whole fleet (branch pushes only — tag runs match
tags:, as flatpak/windows-msix releases have proven for months); the
report-only bench job moves to bench.yml (nightly + dispatch) and stops
occupying a fleet slot per push; flatpak caches its Flathub runtimes and
builder state instead of re-downloading multi-GB every run; rpm's cargo
registry cache gets its own key namespace instead of sharing the Ubuntu
jobs'; audit caches cargo bin+registry rather than the whole toolchain dir;
docker-prune.sh loses the local act-cache cap/burst-clear (the cache is
central now — deleting it under disk pressure was how runner-2 ended up
cold-building everything) and gains a leaked-network prune.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 21:04:05 +02:00
co-authored by Claude Fable 5
parent 90c84ef4bc
commit fcdb90c39b
15 changed files with 345 additions and 125 deletions
+19
View File
@@ -20,11 +20,30 @@ concurrency:
on:
push:
branches: [main]
# Scope canary builds to what this artifact is built FROM — a docs-only or
# web-only push should not light up the whole fleet. Applies to branch pushes;
# tag runs are matched by `tags:` (proven by flatpak/windows-msix releases).
paths:
- 'crates/**'
- 'clients/android/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'scripts/ci/**'
- '.gitea/workflows/android.yml'
# Single project version: a `vX.Y.Z` tag is THE release (uploads to Play's `alpha` closed
# track for manual promotion + attaches the .aab/.apk to the unified Gitea Release). A main
# push is canary (Play `internal`).
tags: ['v*']
pull_request:
paths:
- 'crates/**'
- 'clients/android/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'scripts/ci/**'
- '.gitea/workflows/android.yml'
workflow_dispatch:
jobs:
+21
View File
@@ -20,7 +20,28 @@ concurrency:
on:
push:
branches: [main]
# Scope canary builds to what this artifact is built FROM — a docs-only or
# web-only push should not light up the whole fleet. Applies to branch pushes;
# tag runs are matched by `tags:` (proven by flatpak/windows-msix releases).
paths:
- 'crates/**'
- 'clients/apple/**'
- 'scripts/build-xcframework.sh'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'scripts/ci/**'
- '.gitea/workflows/apple.yml'
pull_request:
paths:
- 'crates/**'
- 'clients/apple/**'
- 'scripts/build-xcframework.sh'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'scripts/ci/**'
- '.gitea/workflows/apple.yml'
workflow_dispatch:
jobs:
+18
View File
@@ -26,6 +26,24 @@ concurrency:
on:
push:
branches: [main]
# Scope canary builds to what this artifact is built FROM — a docs-only or
# web-only push should not light up the whole fleet. Applies to branch pushes;
# tag runs are matched by `tags:` (proven by flatpak/windows-msix releases).
paths:
- 'crates/**'
- 'clients/linux/**'
- 'clients/session/**'
- 'clients/shared/**'
- 'clients/cli/**'
- 'web/**'
- 'sdk/**'
- 'packaging/arch/**'
- 'packaging/gamescope/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'scripts/ci/**'
- '.gitea/workflows/arch.yml'
# Single project version: a `vX.Y.Z` tag is THE release. main publishes to the
# `punktfunk-canary` pacman repo as X.Y.Z-0.<run#> (sorts below the eventual X.Y.Z-1),
# tags to `punktfunk` — separate repos, so neither channel can shadow the other.
+8 -4
View File
@@ -49,14 +49,16 @@ jobs:
cargo-audit:
runs-on: ubuntu-24.04
container:
image: git.unom.io/unom/punktfunk-rust-ci:latest
image: 192.168.1.58:5010/punktfunk-rust-ci:latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
# Cache /usr/local/cargo so the cargo-audit binary (and the advisory DB clone) persist.
- uses: actions/cache@v4
with:
path: /usr/local/cargo
path: |
/usr/local/cargo/bin
/usr/local/cargo/registry
key: cargo-audit-${{ hashFiles('Cargo.lock') }}
restore-keys: cargo-audit-
- name: cargo audit
@@ -139,13 +141,15 @@ jobs:
license-gate:
runs-on: ubuntu-24.04
container:
image: git.unom.io/unom/punktfunk-rust-ci:latest
image: 192.168.1.58:5010/punktfunk-rust-ci:latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: /usr/local/cargo
path: |
/usr/local/cargo/bin
/usr/local/cargo/registry
key: cargo-about-0.9.1
restore-keys: cargo-about-
- name: cargo about license gate (host + driver workspaces)
+34
View File
@@ -0,0 +1,34 @@
# Report-only CPU benchmarks, moved out of ci.yml: they never fail the build (shared CI
# hardware is too noisy to gate on), so running them per-push only occupied a fleet slot
# during fan-out storms. Nightly + on demand is exactly as much signal at none of the
# queue cost. The tight regression gate + the real encode/stream path live on the
# self-hosted GPU runner (Tier 3, bench-gpu.yml).
name: bench
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
schedule:
- cron: '30 4 * * *'
workflow_dispatch:
jobs:
bench:
# Tier-1 (criterion microbenchmarks) + Tier-2 (FEC loss recovery) — GPU-free, so they run here.
runs-on: ubuntu-24.04
container:
image: 192.168.1.58:5010/punktfunk-rust-ci:latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Prep
run: |
git config --global --add safe.directory "$PWD"
command -v python3 >/dev/null || { apt-get update && apt-get install -y --no-install-recommends python3; }
- name: Tier-1 microbenchmarks (criterion)
run: cargo bench -p punktfunk-core --bench pipeline -- --warm-up-time 1 --measurement-time 3
- name: Tier-2 FEC loss recovery (loss-harness)
run: cargo run -q -p loss-harness
- name: Compare vs baseline (report-only)
run: python3 scripts/bench/compare.py --threshold 0.5
+8 -27
View File
@@ -1,7 +1,10 @@
# CI for punktfunk (Gitea Actions). Linux jobs run on the `ubuntu-latest` runner; the Rust
# job runs inside the prebuilt builder image (ci/rust-ci.Dockerfile — system FFmpeg 8,
# CI for punktfunk (Gitea Actions). Linux jobs run on the `ubuntu-24.04` fleet label; the
# Rust job runs inside the prebuilt builder image (ci/rust-ci.Dockerfile — system FFmpeg 8,
# PipeWire, GL/GBM, libcuda link stub, pinned-channel rustup) so the workspace links the
# same libs as the dev boxes. Apple client CI lives in apple.yml (macOS runner).
# same libs as the dev boxes. Builder images come from the LAN registry on home-ci-core
# (content-keyed, docker.yml) — never the WAN. Apple client CI lives in apple.yml (macOS
# runner). The report-only benchmarks moved to bench.yml (nightly + dispatch) so they stop
# occupying a fleet slot on every push.
name: ci
# One pending run per workflow+ref: a newer push supersedes the queued/running one and cancels
# it (a canary only needs the latest commit; each release tag is its own ref so tag runs never
@@ -21,7 +24,7 @@ jobs:
rust:
runs-on: ubuntu-24.04
container:
image: git.unom.io/unom/punktfunk-rust-ci:latest
image: 192.168.1.58:5010/punktfunk-rust-ci:latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
@@ -152,7 +155,7 @@ jobs:
rust-arm64:
runs-on: ubuntu-24.04
container:
image: git.unom.io/unom/punktfunk-rust-ci-arm64cross:latest
image: 192.168.1.58:5010/punktfunk-rust-ci-arm64cross:latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
@@ -235,25 +238,3 @@ jobs:
run: bun run build
- name: Typecheck
run: bun run lint
bench:
# Tier-1 (criterion microbenchmarks) + Tier-2 (FEC loss recovery) — GPU-free, so they run here.
# Report-only: prints the numbers + a diff vs the committed baseline to the job summary and never
# fails the build (shared CI hardware is too noisy to gate on). The tight regression gate + the
# real encode/stream path live on the self-hosted GPU runner (Tier 3, bench-gpu.yml).
runs-on: ubuntu-24.04
container:
image: git.unom.io/unom/punktfunk-rust-ci:latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Prep
run: |
git config --global --add safe.directory "$PWD"
command -v python3 >/dev/null || { apt-get update && apt-get install -y --no-install-recommends python3; }
- name: Tier-1 microbenchmarks (criterion)
run: cargo bench -p punktfunk-core --bench pipeline -- --warm-up-time 1 --measurement-time 3
- name: Tier-2 FEC loss recovery (loss-harness)
run: cargo run -q -p loss-harness
- name: Compare vs baseline (report-only)
run: python3 scripts/bench/compare.py --threshold 0.5
+21 -3
View File
@@ -35,6 +35,24 @@ concurrency:
on:
push:
branches: [main]
# Scope canary builds to what this artifact is built FROM — a docs-only or
# web-only push should not light up the whole fleet. Applies to branch pushes;
# tag runs are matched by `tags:` (proven by flatpak/windows-msix releases).
paths:
- 'crates/**'
- 'clients/linux/**'
- 'clients/session/**'
- 'clients/shared/**'
- 'clients/cli/**'
- 'web/**'
- 'sdk/**'
- 'packaging/debian/**'
- 'packaging/linux/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'scripts/ci/**'
- '.gitea/workflows/deb.yml'
# Single project version: a `vX.Y.Z` tag is THE release for every platform (see
# docs-site channels.md). The old version-shadow (a client tag shipping a host package
# that outranked rolling builds) is now structurally impossible — main publishes to the
@@ -51,7 +69,7 @@ jobs:
build-publish:
runs-on: ubuntu-24.04
container:
image: git.unom.io/unom/punktfunk-rust-ci:latest
image: 192.168.1.58:5010/punktfunk-rust-ci:latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
@@ -197,7 +215,7 @@ jobs:
build-publish-host:
runs-on: ubuntu-24.04
container:
image: git.unom.io/unom/punktfunk-rust-ci-noble:latest
image: 192.168.1.58:5010/punktfunk-rust-ci-noble:latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
@@ -304,7 +322,7 @@ jobs:
build-publish-client-arm64:
runs-on: ubuntu-24.04
container:
image: git.unom.io/unom/punktfunk-rust-ci-arm64cross:latest
image: 192.168.1.58:5010/punktfunk-rust-ci-arm64cross:latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
+7
View File
@@ -32,6 +32,13 @@ concurrency:
on:
push:
branches: [main]
# Scope canary builds to what this artifact is built FROM — a docs-only or
# web-only push should not light up the whole fleet. Applies to branch pushes;
# tag runs are matched by `tags:` (proven by flatpak/windows-msix releases).
paths:
- 'clients/decky/**'
- 'scripts/ci/**'
- '.gitea/workflows/decky.yml'
tags: ['v*']
workflow_dispatch:
+158 -66
View File
@@ -1,17 +1,28 @@
# Build + push the dockerized pieces to the Gitea container registry:
# punktfunk-web — management console (web/Dockerfile, repo-root context)
# punktfunk-docs — documentation site (docs-site/Dockerfile)
# punktfunk-rust-ci — Rust CI builder image consumed by ci.yml
# punktfunk-rust-ci-arm64cross — the above + an arm64 sysroot, for the aarch64 client legs
# punktfunk-fedora-rpm — Fedora 43 builder image consumed by rpm.yml (Bazzite RPM)
# Build + push the dockerized pieces.
#
# Two very different image families now:
#
# BUILDER images (punktfunk-rust-ci{,-noble,-arm64cross}, punktfunk-fedora{,44}-rpm)
# live on the LAN registry (home-ci-core, 192.168.1.58:5010 — unom/infra
# runners/ci-core/) and are CONTENT-KEYED: the tag is a hash of what they are built
# from (the ci/ tree, + rust-toolchain.toml for the cross image), and a build only
# happens when that key has no manifest yet. A push that doesn't touch ci/ costs one
# curl per image (~seconds), pushes nothing over the WAN, and mints no per-SHA tag
# debris on the runners — the failure mode that filled the fleet's disks. `:latest`
# is re-pushed alongside every new key and is what the consuming workflows pin.
#
# APP images (punktfunk-web, punktfunk-docs) are deployables: they keep going to the
# Gitea registry (git.unom.io) with :latest + :sha-<8> (+ :vX.Y.Z on tags), because
# unom-1 deploys pull from there and releases pin them.
#
# Host and clients are intentionally NOT containerized (see CLAUDE.md "What's left").
#
# REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope.
# REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope (app images only —
# the LAN registry is unauthenticated inside the LAN).
#
# Bootstrap note: ci.yml's rust job pulls punktfunk-rust-ci:latest from the registry, so
# this workflow (or a manual push) must have succeeded once before that job can run; on
# the same push, ci.yml builds against the PREVIOUS image. All three were seeded manually
# on 2026-06-12.
# Bootstrap note: consuming workflows pull <LAN>/punktfunk-rust-ci:latest, so the LAN
# registry must hold a seeded :latest once (done 2026-07-29 from the last Gitea-registry
# images); after that, this workflow keeps :latest current whenever ci/ changes.
name: docker
# One pending run per workflow+ref: a newer push supersedes the queued/running one and cancels
# it (a canary only needs the latest commit; each release tag is its own ref so tag runs never
@@ -31,9 +42,142 @@ on:
env:
REGISTRY: git.unom.io
OWNER: unom
CI_REGISTRY: 192.168.1.58:5010
jobs:
build-push:
builders:
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
matrix:
include:
- image: punktfunk-rust-ci
dockerfile: ci/rust-ci.Dockerfile
# Ubuntu 24.04 LTS host builder: same purpose as rust-ci but lowers the host .deb's glibc
# floor to 2.39 and bundles a from-source FFmpeg 8, so the package installs on 24.04 LTS
# (rust-ci's 26.04 build is uninstallable there). Consumed by deb.yml's build-publish-host job.
- image: punktfunk-rust-ci-noble
dockerfile: ci/rust-ci-noble.Dockerfile
- image: punktfunk-fedora-rpm
dockerfile: ci/fedora-rpm.Dockerfile
# Fedora 44 builder (Fedora KDE spin): same Dockerfile, newer base → libavcodec.so.62.
- image: punktfunk-fedora44-rpm
dockerfile: ci/fedora-rpm.Dockerfile
buildargs: --build-arg FEDORA_VERSION=44
keysuffix: -f44
steps:
- uses: actions/checkout@v4
# The key is the git TREE HASH of ci/ — every byte any of these Dockerfiles can see
# (they all use ci/ as build context). One key for the whole family on purpose: a
# change to any of them re-keys all four, and a spurious rebuild of a sibling is
# cheap, rare, and infinitely better than a stale one.
- name: Content key
run: |
git config --global --add safe.directory "$PWD"
echo "KEY=ck-$(git rev-parse HEAD:ci | cut -c1-12)${{ matrix.keysuffix }}" >> "$GITHUB_ENV"
- name: Check whether this key already exists
id: exists
run: |
ACCEPT='Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json'
if curl -sf -o /dev/null -H "$ACCEPT" \
"http://$CI_REGISTRY/v2/${{ matrix.image }}/manifests/$KEY"; then
echo "hit=true" >> "$GITHUB_OUTPUT"
echo "::notice::${{ matrix.image }}:$KEY already in the LAN registry — nothing to build"
else
echo "hit=false" >> "$GITHUB_OUTPUT"
fi
- name: Build
if: steps.exists.outputs.hit == 'false'
# --pull is cheap now: base images come through the ci-core pull-through mirror.
run: |
docker build --pull ${{ matrix.buildargs }} \
-f "${{ matrix.dockerfile }}" \
-t "$CI_REGISTRY/${{ matrix.image }}:$KEY" \
-t "$CI_REGISTRY/${{ matrix.image }}:latest" \
ci
- name: Push
if: steps.exists.outputs.hit == 'false'
run: |
docker push "$CI_REGISTRY/${{ matrix.image }}:$KEY"
docker push "$CI_REGISTRY/${{ matrix.image }}:latest"
# A release pins reproducible builder images without any rebuild: copy the key's
# manifest to a vX.Y.Z tag via the registry API (no image bytes move).
- name: Tag for release
if: startsWith(github.ref, 'refs/tags/v')
run: |
ACCEPT='Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json'
MT=$(curl -sfI -H "$ACCEPT" "http://$CI_REGISTRY/v2/${{ matrix.image }}/manifests/$KEY" \
| tr -d '\r' | sed -n 's/^[Cc]ontent-[Tt]ype: //p')
curl -sf -H "$ACCEPT" -o /tmp/manifest.json \
"http://$CI_REGISTRY/v2/${{ matrix.image }}/manifests/$KEY"
curl -sf -X PUT -H "Content-Type: $MT" --data-binary @/tmp/manifest.json \
"http://$CI_REGISTRY/v2/${{ matrix.image }}/manifests/$GITHUB_REF_NAME"
# The aarch64 CROSS builder — a SEPARATE job because it is `FROM punktfunk-rust-ci:latest`
# (the LAN copy) and so must not race the matrix entry that publishes that base. Consumed
# by the arm64 client legs in ci.yml/deb.yml. Its key also folds in rust-toolchain.toml:
# the Dockerfile installs the aarch64 target against the toolchain the workspace pins.
builders-arm64cross:
runs-on: ubuntu-24.04
needs: builders
timeout-minutes: 60
env:
IMAGE: punktfunk-rust-ci-arm64cross
steps:
- uses: actions/checkout@v4
- name: Content key
run: |
git config --global --add safe.directory "$PWD"
echo "KEY=ck-$(printf '%s%s' "$(git rev-parse HEAD:ci)" "$(git rev-parse HEAD:rust-toolchain.toml)" | sha256sum | cut -c1-12)" >> "$GITHUB_ENV"
- name: Check whether this key already exists
id: exists
run: |
ACCEPT='Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json'
if curl -sf -o /dev/null -H "$ACCEPT" \
"http://$CI_REGISTRY/v2/$IMAGE/manifests/$KEY"; then
echo "hit=true" >> "$GITHUB_OUTPUT"
echo "::notice::$IMAGE:$KEY already in the LAN registry — nothing to build"
else
echo "hit=false" >> "$GITHUB_OUTPUT"
fi
- name: Build
if: steps.exists.outputs.hit == 'false'
# Root context: it needs rust-toolchain.toml to install the target against the
# toolchain the workspace actually pins.
run: |
docker build --pull \
-f ci/rust-ci-arm64cross.Dockerfile \
-t "$CI_REGISTRY/$IMAGE:$KEY" \
-t "$CI_REGISTRY/$IMAGE:latest" \
.
- name: Push
if: steps.exists.outputs.hit == 'false'
run: |
docker push "$CI_REGISTRY/$IMAGE:$KEY"
docker push "$CI_REGISTRY/$IMAGE:latest"
- name: Tag for release
if: startsWith(github.ref, 'refs/tags/v')
run: |
ACCEPT='Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json'
MT=$(curl -sfI -H "$ACCEPT" "http://$CI_REGISTRY/v2/$IMAGE/manifests/$KEY" \
| tr -d '\r' | sed -n 's/^[Cc]ontent-[Tt]ype: //p')
curl -sf -H "$ACCEPT" -o /tmp/manifest.json \
"http://$CI_REGISTRY/v2/$IMAGE/manifests/$KEY"
curl -sf -X PUT -H "Content-Type: $MT" --data-binary @/tmp/manifest.json \
"http://$CI_REGISTRY/v2/$IMAGE/manifests/$GITHUB_REF_NAME"
# Deployable app images — unchanged flow, Gitea registry, per-SHA + release tags.
apps:
runs-on: ubuntu-24.04
timeout-minutes: 45
strategy:
@@ -45,23 +189,6 @@ jobs:
- image: punktfunk-docs
dockerfile: docs-site/Dockerfile
context: docs-site
- image: punktfunk-rust-ci
dockerfile: ci/rust-ci.Dockerfile
context: ci
# Ubuntu 24.04 LTS host builder: same purpose as rust-ci but lowers the host .deb's glibc
# floor to 2.39 and bundles a from-source FFmpeg 8, so the package installs on 24.04 LTS
# (rust-ci's 26.04 build is uninstallable there). Consumed by deb.yml's build-publish-host job.
- image: punktfunk-rust-ci-noble
dockerfile: ci/rust-ci-noble.Dockerfile
context: ci
- image: punktfunk-fedora-rpm
dockerfile: ci/fedora-rpm.Dockerfile
context: ci
# Fedora 44 builder (Fedora KDE spin): same Dockerfile, newer base → libavcodec.so.62.
- image: punktfunk-fedora44-rpm
dockerfile: ci/fedora-rpm.Dockerfile
context: ci
buildargs: --build-arg FEDORA_VERSION=44
steps:
- uses: actions/checkout@v4
@@ -76,7 +203,7 @@ jobs:
# On a release tag, also tag the image vX.Y.Z so a release pins reproducible web/docs images.
EXTRA=""
case "$GITHUB_REF" in refs/tags/v*) EXTRA="-t $REGISTRY/$OWNER/${{ matrix.image }}:${GITHUB_REF_NAME}" ;; esac
docker build --pull ${{ matrix.buildargs }} \
docker build --pull \
-f "${{ matrix.dockerfile }}" \
-t "$REGISTRY/$OWNER/${{ matrix.image }}:latest" \
-t "$REGISTRY/$OWNER/${{ matrix.image }}:sha-${GITHUB_SHA::8}" \
@@ -89,48 +216,13 @@ jobs:
docker push "$REGISTRY/$OWNER/${{ matrix.image }}:latest"
case "$GITHUB_REF" in refs/tags/v*) docker push "$REGISTRY/$OWNER/${{ matrix.image }}:${GITHUB_REF_NAME}" ;; esac
# The aarch64 CROSS builder — a SEPARATE job because it is `FROM punktfunk-rust-ci:latest`
# and so must not race the matrix entry that publishes that base. Consumed by the arm64
# client legs in deb.yml/rpm.yml/arch.yml. Root context: it needs rust-toolchain.toml to
# install the target against the toolchain the workspace actually pins.
build-push-arm64cross:
runs-on: ubuntu-24.04
needs: build-push
timeout-minutes: 45
env:
IMAGE: punktfunk-rust-ci-arm64cross
steps:
- uses: actions/checkout@v4
- name: Login to registry
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" \
| docker login "$REGISTRY" -u enricobuehler --password-stdin
- name: Build
run: |
EXTRA=""
case "$GITHUB_REF" in refs/tags/v*) EXTRA="-t $REGISTRY/$OWNER/$IMAGE:${GITHUB_REF_NAME}" ;; esac
docker build --pull \
-f ci/rust-ci-arm64cross.Dockerfile \
-t "$REGISTRY/$OWNER/$IMAGE:latest" \
-t "$REGISTRY/$OWNER/$IMAGE:sha-${GITHUB_SHA::8}" \
$EXTRA \
.
- name: Push
run: |
docker push "$REGISTRY/$OWNER/$IMAGE:sha-${GITHUB_SHA::8}"
docker push "$REGISTRY/$OWNER/$IMAGE:latest"
case "$GITHUB_REF" in refs/tags/v*) docker push "$REGISTRY/$OWNER/$IMAGE:${GITHUB_REF_NAME}" ;; esac
# Deploy the docs site to unom-1, the DMZ services VM website/cms also deploy to
# (docs.punktfunk.unom.io via Caddy on home-reverse-proxy-1 -> :3220). Same secret set
# as unom/website's deploy: DEPLOY_HOST/DEPLOY_USER/DEPLOY_PORT/DEPLOY_SSH_KEY (the
# unom-ci-deploy key).
deploy-docs:
runs-on: ubuntu-24.04
needs: build-push
needs: apps
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
+19
View File
@@ -135,6 +135,25 @@ jobs:
https://dl.flathub.org/repo/flathub.flatpakrepo
git config --global --add safe.directory "$PWD"
# This job was the fleet's single heaviest network consumer: every run re-downloaded
# the GNOME runtime + SDK + llvm/rust/ffmpeg extensions (multi-GB from Flathub) and
# every crate source. Both live in well-defined directories, both are idempotently
# verified/extended by the steps below, and the central cache server restores them
# at LAN speed — so cache them. Keyed on what actually pins them: the manifest tree
# (runtimes/extensions) and manifest+Cargo.lock (crate sources + builder state).
- name: Cache Flathub runtimes
uses: actions/cache@v4
with:
path: ~/.local/share/flatpak
key: flatpak-runtimes-${{ hashFiles('packaging/flatpak/**') }}
restore-keys: flatpak-runtimes-
- name: Cache flatpak-builder state (crate sources, ccache)
uses: actions/cache@v4
with:
path: .flatpak-builder
key: flatpak-builder-state-${{ hashFiles('Cargo.lock', 'packaging/flatpak/**') }}
restore-keys: flatpak-builder-state-
- name: Version + channel
# Tag vX.Y.Z -> X.Y.Z on the OSTree `stable` branch (a real release); a main push ->
# <next-minor>-ciN.g<sha> on the `canary` branch (base one minor ahead of the latest stable
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-24.04
# Same image as ci.yml/deb.yml — already carries the Rust toolchain + GTK/SDL build deps.
container:
image: git.unom.io/unom/punktfunk-rust-ci:latest
image: 192.168.1.58:5010/punktfunk-rust-ci:latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
+18 -3
View File
@@ -21,6 +21,21 @@ concurrency:
on:
push:
branches: [main]
# Scope canary builds to what this artifact is built FROM — a docs-only or
# web-only push should not light up the whole fleet. Applies to branch pushes;
# tag runs are matched by `tags:` (proven by flatpak/windows-msix releases).
paths:
- 'crates/**'
- 'web/**'
- 'sdk/**'
- 'packaging/rpm/**'
- 'packaging/gamescope/**'
- 'packaging/bazzite/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'scripts/ci/**'
- '.gitea/workflows/rpm.yml'
# Single project version: a `vX.Y.Z` tag is THE release. main publishes to the `*-canary` rpm
# groups, tags to the base groups (`bazzite`/`fedora-44`) — separate repos, so the old
# version-shadow (a release outranking rolling builds in one group) is structurally gone.
@@ -48,7 +63,7 @@ jobs:
group: fedora-44
fedver: 44
container:
image: git.unom.io/unom/${{ matrix.image }}:latest
image: 192.168.1.58:5010/${{ matrix.image }}:latest
timeout-minutes: 90
env:
CARGO_HOME: /usr/local/cargo
@@ -83,8 +98,8 @@ jobs:
- uses: actions/cache@v4
with:
path: /usr/local/cargo/registry
key: cargo-home-${{ hashFiles('Cargo.lock') }}
restore-keys: cargo-home-
key: cargo-home-fedora-${{ hashFiles('Cargo.lock') }}
restore-keys: cargo-home-fedora-
- name: Version + channel
# vX.Y.Z tag -> X.Y.Z-1 in the base group (a real release); main push -> <next-minor>-0.ciN.g<sha>
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
sbom:
runs-on: ubuntu-24.04
container:
image: git.unom.io/unom/punktfunk-rust-ci:latest
image: 192.168.1.58:5010/punktfunk-rust-ci:latest
timeout-minutes: 20
steps:
# fetch-depth 0: the dispatch path derives the canary base from the tag history
+1 -1
View File
@@ -16,7 +16,7 @@
# explicit `Architectures:` or apt tries to fetch arm64 from the amd64 mirror and 404s.
#
# Built from the REPO ROOT context (not ci/) — see the rust-toolchain.toml copy below.
FROM git.unom.io/unom/punktfunk-rust-ci:latest
FROM 192.168.1.58:5010/punktfunk-rust-ci:latest
ENV DEBIAN_FRONTEND=noninteractive
+11 -19
View File
@@ -9,16 +9,12 @@
set -u
export PATH=/usr/bin:/bin:/usr/local/bin:$PATH
# The cache-server's blob store is a HOST directory: the fleet runs a standalone cache-server
# service (compose.yml) that bind-mounts this path to /data, and every replica points at it over
# HTTP (`external_server`). It used to live inside a runner container's writable layer, which is
# why this reached in with `docker exec` — that is no longer where it is, and the container name it
# looked for (`gitea-runner-runner`) does not exist either now that the replicas are named
# `gitea-runner-fleet-runner-N-1`. Both halves silently did nothing: the filter matched zero
# containers, so the cap and the burst-clear below were dead code. A plain host path needs neither.
CACHE_DIR=${CACHE_DIR:-/home/runner/gitea-runner-fleet/cache}
CAP_MB=${CAP_MB:-20000} # clear the cache store once it exceeds ~20 GB
BURST_PCT=${BURST_PCT:-80} # full clear once the disk is this % full
# The actions cache no longer lives on this box AT ALL: home-ci-core (192.168.1.58, see
# unom/infra runners/ci-core/) serves every runner host, sized and GC'd there. The old local
# store cap + burst-clear are gone with it — they were self-defeating anyway: under disk
# pressure they deleted exactly the cache that made the next job smaller, which is how
# runner-2 ended up cold-building every Rust job with an empty 28 KB cache dir.
BURST_PCT=${BURST_PCT:-80} # burst-clear docker debris once the disk is this % full
MIN_FREE_GB=${MIN_FREE_GB:-60} # ...or this little is left, whichever trips first.
# 60, not 45: this has to fire BEFORE the disk is
# actually tight, because the clear only reclaims idle
@@ -36,14 +32,11 @@ docker builder prune -af --filter until=2h || true
docker buildx prune -af --filter until=2h || true
docker container prune -f --filter until=2h || true
# 2) Cap the cache-server store. Clearing the blobs is safe — act_runner repopulates it and cache
# keys are content-hashed, so this only drops stale entries.
if [ -d "$CACHE_DIR" ]; then
SZ=$(du -sm "$CACHE_DIR" 2>/dev/null | cut -f1)
if [ -n "${SZ:-}" ] && [ "$SZ" -ge "$CAP_MB" ]; then
rm -rf "${CACHE_DIR:?}"/* && echo "cache-server store cleared (was ${SZ} MB)"
fi
fi
# 2) Leaked job networks. act_runner leaks per-job GITEA-ACTIONS-TASK-* bridges when jobs are
# killed; enough of them exhausted the docker address pool once (it then swallowed the DMZ
# subnet — see unom/infra runners/ci-core/README.md) and each one is another interface for
# the host dnsmasq to bind. until=2h protects the networks of live jobs.
docker network prune -f --filter until=2h || true
# 3) Burst guard: a push-storm fills the disk WITHIN one interval — three concurrent Rust builds,
# each with a multi-GB target/, on top of a ~40 GB containerd image baseline. Trigger on a free
@@ -65,5 +58,4 @@ if [ "$BURST" = 1 ]; then
docker image prune -af || true
docker builder prune -af || true
docker buildx prune -af || true
[ -d "$CACHE_DIR" ] && rm -rf "${CACHE_DIR:?}"/* || true
fi