android.yml and release.yml uploaded via actions/upload-artifact@v3, which
reports success while Gitea stores the result in a format its v4-only
artifact API will never serve back — 72 artifacts on this repo are on disk,
have valid DB rows, and are invisible to every retrieval path. Green jobs
producing nothing retrievable.
release.yml is a producer/consumer pair: android-release uploads
minstrel-apk and image-release downloads it to bundle into the container.
Swapping only the upload would have left download-artifact@v3 reading the
v1/v3 listing and finding nothing, so mirror the download side too —
bvandeusen/download-artifact, pull mirror of forgejo/download-artifact,
pinned at its v5 tag to match the upload pin's major.
Not actions/{upload,download}-artifact@v4: isGhes() throws on the hostname
before opening a connection, so no server-side change reaches it.
Upload steps also set if-no-files-found: error — image-release hard-depends
on minstrel-apk existing, so an empty upload must fail where it happens.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5.8 KiB
CI Requirements — Minstrel
Copy of the template. Family-wide policy:
ci-runners.mdin the FabledRulebook.
Runtime images
Minstrel's four workflows consume two CI images:
git.fabledsword.com/bvandeusen/ci-go:1.26
git.fabledsword.com/bvandeusen/ci-flutter:3.44
ci-go:1.26— Go server tests (.gitea/workflows/test-go.yml), web SPA tests (.gitea/workflows/test-web.yml), and the release container build (.gitea/workflows/release.yml).ci-flutter:3.44— Flutter client tests + debug/release APK builds (.gitea/workflows/flutter.yml).
Image deps used
From ci-go:1.26
- Go (1.26 toolchain) —
go vet,go test -race,go build,go mod. - Node + npm —
npm ciandnpm test/npm run checkintest-web.yml. - golangci-lint — lint pass in
test-go.yml. - docker CLI — bridge-IP discovery of the per-job Postgres service container in
test-go.ymlintegration job (via the runner's shared/var/run/docker.sock). - docker buildx — release container build + push in
release.yml. - curl — release-asset polling / upload in
release.yml.
From ci-flutter:3.44
- Flutter (3.44 stable channel) —
flutter pub get,flutter analyze --fatal-infos,flutter test,flutter build apk(debug + signed release). - Dart —
dart run tool/gen_tokens.dart,dart run build_runner build(drift codegen). - Android SDK + NDK + cmdline-tools + build-tools — APK assembly + signing.
- Java 25 — Gradle / Android build.
- git —
actions/checkout@v4baseline (and any shell git operations). - base64 + curl — keystore decode + release-asset upload in the tag-build path.
Per-job tool installs
None.
Notes
-
Label/image split. Workflows keep
runs-on: go-ci/runs-on: flutter-cias the scheduling label per theci-runners.md"label = scheduling handle, image =container.image" pattern. The labels are intentional handles, not toolchain assertions. -
Integration-job docker-socket dependency.
test-go.yml's integration job uses the runner's shared docker socket (/var/run/docker.sock) to bridge-IP-discover the per-job Postgres service container by name + network intersection — the dev compose'sminstrel-postgres-*containers are explicitly skipped as belt-and-suspenders. Depends onact_runner.valid_volumeswhitelisting the socket; if that ever stops auto-mounting, integration tests fail at thedocker inspectstep. -
Go toolchain pin.
go.modis ongo 1.25.0becausegolang.org/x/crypto v0.51.0declares 1.25 as its minimum.ci-go:1.26satisfies this with headroom. Futurex/cryptobumps that move the Go floor should be paired with an image-tag bump in this file + the workflows. -
In-app update channel polling.
release.ymlpolls Gitea's release-asset API for up to 15 min on tag pushes to fetch the APK thatflutter.ymlis concurrently attaching to the same release. The asset eventually appears becauseflutter.ymlandrelease.ymlrun in parallel on the same tag; if the polling times out, the server image ships without the bundled update channel (graceful degradation, not a build failure). -
Cache server reachability.
test-web.ymldoes NOT usecache: 'npm'onactions/setup-node— the Gitea Actions cache server isn't reachable from this runner's container network andsetup-nodewas burning ~4m41s on ETIMEDOUT before failing open. With the migration toci-go:1.26,setup-nodeis removed entirely (Node is in the image). The cache concern reappears if a future change re-introduces a network-dependent action. -
Artifacts — use the mirrored actions, never
actions/{upload,download}-artifact.uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245 uses: https://git.fabledsword.com/bvandeusen/download-artifact@1314311ddb542af343a82d478ac786ceada4143aUpstream's
@v4+cannot work against this instance and no server-side change will help:isGhes()rejects any hostname that isn'tgithub.com/*.ghe.com/*.localhostand throws before it opens a connection, so the server is never asked what it supports.@v3is worse — it reports success, and Gitea then serves artifacts back only through the v4 API (content_encoding = application/zip), so a v3 upload is stored but invisible to every retrieval path. A green job producing nothing retrievable; that is how 72 unreachable artifacts accumulated on this repo. Scribe issues 2255 / 2270.Both are pull mirrors of the Forgejo project's forks (
code.forgejo.org/forgejo/{upload,download}-artifact, one commit on upstream disabling that check), mirrored so CI depends on commits we hold and pinned by SHA because the mirrors auto-sync every 8h — a moved upstream tag would otherwise silently change what runs. The pins are upstream'sv5tags on both sides; keep the majors matched.release.ymlis a producer/consumer pair —android-releaseuploadsminstrel-apk,image-releasedownloads it — and mixing protocol versions across that pair yields an empty listing rather than an error, which is the failure mode this whole entry exists to prevent.Upload steps set
if-no-files-found: errorrather than the defaultwarn, so an upload that matches nothing fails its own job instead of failing the consumer later.Retrieval:
GET /api/v1/repos/{owner}/{repo}/actions/runs/{run_id}/artifactsfor the id (global run id, not the repo-scoped run number), then…/actions/artifacts/{id}/zip. The workstation has nounzip— usepython3 -m zipfile -e. -
Friction asks. None pending. The two images cover everything Minstrel needs.