Files
minstrel/ci-requirements.md
T
bvandeusenandClaude Opus 5.5 c96231106f docs: setup files and the client, shared and CI docs describe what is (#6277)
.dockerignore, .gitignore, deploy/initdb, ci-requirements.md,
client/README.md, shared/README.md and two web/scripts test names: dates,
issue/rule/spike ids and the stories behind each exclusion removed.

Corrected:
- client/README.md: the dev lane bundles its own APK; :main no longer
  exists; main bundles the recorded sidecar instead of re-deriving the
  version name. Its smoke-test step wrote a bare "v0.2.0", which the
  server rejects as a sidecar that is not JSON; it now writes the JSON
  shape, which the README documents.
- ci-requirements.md: one workflow, not four; the android lane builds no
  debug APK; main reads the recorded sidecar.
- .gitignore: CI fills client/ on every image build, not tag releases only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:04:02 -04:00

6.1 KiB
Raw Blame History

CI Requirements — Minstrel

Copy of the template. Family-wide policy: ci-runners.md in the FabledRulebook.

Runtime images

Minstrel's one workflow, .gitea/workflows/release.yml, consumes two CI images:

git.fabledsword.com/bvandeusen/ci-go:1.26
git.fabledsword.com/bvandeusen/ci-android:36
  • ci-go:1.26 — the go, integration and web lanes, release-assets, and the container build (image-release). All CI lives in that one workflow so every publishing job can depend on every lane.
  • golang:1.26-bookworm (Docker Hub) — the govulncheck lane. Deliberately the same image as the Dockerfile's builder stage rather than ci-go, so the standard library it checks is the one in the shipped binary. Keep the two in step.
  • ci-android:36 — native Kotlin/Compose client: ktlint + detekt + unit tests (the android lane), and the signed release APK (android-release).

No job uses ci-flutter; nothing in this repo needs a Flutter toolchain. The flutter-ci runner label is still used: the Android jobs schedule on it while pulling ci-android:36, per the label/image split below.

Image deps used

From ci-go:1.26

  • Go (1.26 toolchain) — go vet, go test -race, go build, go mod.
  • Node + npm — npm ci, npm audit, npm test and npm run check in the web lane.
  • golangci-lint — lint pass in the go lane.
  • docker CLI — bridge-IP discovery of the per-job Postgres service container in the integration lane (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-android:36

  • JDK 25 — Gradle launcher + Android build. Requires Gradle 9.1.0+ in android/gradle/wrapper; older Gradle rejects JDK 25 with an opaque "25.0.3" error. The workflows also set JAVA_TOOL_OPTIONS=--enable-native-access=ALL-UNNAMED to silence Gradle's launcher-JVM restricted-method warning.
  • Android SDK + cmdline-tools + build-tools 36.0.0 — APK assembly + signing. No NDK: the app has no C/C++ sources.
  • ktlint + detekt — ./gradlew ktlintCheck and ./gradlew detekt in the android lane. Image pins track android/gradle/libs.versions.toml so local and CI checks agree.
  • git — actions/checkout@v4 baseline (and any shell git operations).
  • base64 + curl — keystore decode + release-asset upload in release.yml's android-release job.

Per-job tool installs

None.

Notes

  • Label/image split. Workflows keep runs-on: go-ci / runs-on: flutter-ci as the scheduling label per the ci-runners.md "label = scheduling handle, image = container.image" pattern. The labels are intentional handles, not toolchain assertions — which is why the Android jobs still schedule on flutter-ci while pulling ci-android:36. Switch them to android-ci if that runner label is ever registered; nothing breaks either way.

  • Integration-job docker-socket dependency. The integration lane 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's minstrel-postgres-* containers are explicitly skipped as belt-and-suspenders. Depends on act_runner.valid_volumes whitelisting the socket; if that ever stops auto-mounting, integration tests fail at the docker inspect step.

  • Go toolchain pin. go.mod is on go 1.25.0 because golang.org/x/crypto v0.51.0 declares 1.25 as its minimum. ci-go:1.26 satisfies this with headroom. Future x/crypto bumps that move the Go floor should be paired with an image-tag bump in this file + the workflows.

  • In-app update channel — needs:, not polling. release.yml's image-release job declares needs: [android-release], so on dev and tag pushes the signed APK is guaranteed present before the image build starts — no polling window, no race. On main pushes android-release is skipped and image-release instead pulls the most recent release's APK and the version sidecar recorded beside it, so :latest never ships without an update channel. It degrades to an empty client/ — never a wrong version — if no release or APK asset can be resolved.

  • Cache server reachability. The web lane does NOT use cache: 'npm' on actions/setup-node — the Gitea Actions cache server isn't reachable from this runner's container network, so setup-node would spend minutes on ETIMEDOUT before failing open. setup-node is not used at all (Node is in the image). The cache concern reappears if a change introduces a network-dependent action.

  • Artifacts — stock actions/upload-artifact@v7 and actions/download-artifact@v8; never @v3.

    uses: actions/upload-artifact@v7
    uses: actions/download-artifact@v8
    

    Stock works on this forge because gitea/runner 3.x edits the actions' client-side isGhes() refusal out of their bundles; upload v4–v7 and download v4–v8 are proven.

    @v3 is still broken: it reports success, and Gitea serves artifacts back only through the v4 API (content_encoding = application/zip), so a v3 upload is stored but invisible to every retrieval path.

    The upload and download majors need not match. Every download major v4–v8 reads the artifacts of every upload major v4–v7, by name and by pattern; the only protocol break is v3 → v4. Every CI-runner image carries Node 24, and the runner runs actions with the image's node.

    Upload steps set if-no-files-found: error rather than the default warn, 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}/artifacts for the id (global run id, not the repo-scoped run number), then …/actions/artifacts/{id}/zip. The workstation has no unzip — use python3 -m zipfile -e.

  • Friction asks. None pending. The two images cover everything Minstrel needs.