.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>
85 lines
6.1 KiB
Markdown
85 lines
6.1 KiB
Markdown
# CI Requirements — Minstrel
|
||
|
||
> Copy of [the template](https://git.fabledsword.com/bvandeusen/CI-runner/src/branch/main/docs/requirements-sheet.template.md).
|
||
> 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`](https://…/FabledRulebook/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`.**
|
||
```yaml
|
||
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.
|