diff --git a/.gitea/workflows/android.yml b/.gitea/workflows/android.yml index 9e7a3f02..fcce50e6 100644 --- a/.gitea/workflows/android.yml +++ b/.gitea/workflows/android.yml @@ -80,10 +80,16 @@ jobs: - name: Upload debug APK if: github.event_name == 'push' && github.ref == 'refs/heads/main' - # Gitea Actions runs in GHES-emulation mode; @actions/artifact v2+ - # (i.e. upload-artifact@v4+) errors with "GHESNotSupportedError". - # Pin to @v3 until act_runner or the artifact backend catches up. - uses: actions/upload-artifact@v3 + # Mirrored action, never actions/upload-artifact. @v4+ throws + # GHESNotSupportedError client-side on the hostname (no server setting + # reaches that check), and @v3 is worse — it reports success while Gitea + # serves artifacts back only through the v4 API, so the upload is stored + # and invisible to every retrieval path. @v3 is what left 72 unreachable + # artifacts on this repo. Pinned by SHA because the mirror auto-syncs; + # full URL because DEFAULT_ACTIONS_URL sends bare owner/repo to github.com. + # See Scribe issues 2255 / 2270. + uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245 with: name: minstrel-android-debug-${{ github.sha }} path: android/app/build/outputs/apk/debug/app-debug.apk + if-no-files-found: error diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index e7b39993..577b5b45 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -131,12 +131,19 @@ jobs: -PMINSTREL_VERSION_CODE=${{ steps.ver.outputs.code }} - name: Upload APK as workflow artifact - # @v3 because Gitea Actions emulates GHES and the v2 artifact - # backend used by upload-artifact@v4 errors with GHESNotSupportedError. - uses: actions/upload-artifact@v3 + # Mirrored action, never actions/upload-artifact — @v4+ refuses on the + # hostname, @v3 uploads something Gitea will never serve back. This is + # the producing half of a pair: image-release downloads `minstrel-apk` + # below with the matching download-artifact mirror. Both must stay on + # the v4 protocol — mixing a v3 upload with a v4 download (or the + # reverse) yields an empty listing, not an error. See Scribe 2255 / 2270. + uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245 with: name: minstrel-apk path: android/app/build/outputs/apk/release/app-release.apk + # error, not the default warn: image-release hard-depends on this + # artifact existing, so an empty upload must fail here, not there. + if-no-files-found: error - name: Attach APK to gitea Release shell: bash @@ -238,7 +245,20 @@ jobs: # Tag pushes only — android-release just produced this. Non-tag # builds take the "Bundle latest release APK" path below instead. if: steps.guard.outputs.ready == 'true' && startsWith(github.ref, 'refs/tags/v') - uses: actions/download-artifact@v3 + # Consuming half of the pair — never actions/download-artifact. Same fork, + # same reason: upstream's client-side GHES check rejects this hostname + # before it connects. bvandeusen/download-artifact mirrors + # code.forgejo.org/forgejo/download-artifact. + # + # SHA below is that fork's `v6` tag. Match on @actions/artifact, NOT on + # the action's own version number — the two actions release on unrelated + # cadences, and download v5 would pair a ^2.3.2 client with this file's + # ^4.0.0 uploader. v6 is the tag whose bundled library major (^4.0.0) is + # the same one proven against this instance by the upload side. + # Deliberately NOT v7: it moves to node24 and upstream requires runner + # >= 2.327.1 for it, which act_runner does not claim to satisfy. + # Pinned, not tagged — the mirror auto-syncs every 8h. + uses: https://git.fabledsword.com/bvandeusen/download-artifact@8d4e9521a5f7e5f8b6351f341f719f9f45a92a3a with: name: minstrel-apk path: client/ diff --git a/ci-requirements.md b/ci-requirements.md index 05e9ebbc..712ccda0 100644 --- a/ci-requirements.md +++ b/ci-requirements.md @@ -44,4 +44,64 @@ None. - **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 polling.** `release.yml` polls Gitea's release-asset API for up to 15 min on tag pushes to fetch the APK that `flutter.yml` is concurrently attaching to the same release. The asset eventually appears because `flutter.yml` and `release.yml` run 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.yml` does NOT use `cache: 'npm'` on `actions/setup-node` — the Gitea Actions cache server isn't reachable from this runner's container network and `setup-node` was burning ~4m41s on ETIMEDOUT before failing open. With the migration to `ci-go:1.26`, `setup-node` is 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`.** + ```yaml + uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245 + uses: https://git.fabledsword.com/bvandeusen/download-artifact@8d4e9521a5f7e5f8b6351f341f719f9f45a92a3a + ``` + Upstream's `@v4+` cannot work against this instance and no server-side change + will help: `isGhes()` rejects any hostname that isn't `github.com` / + `*.ghe.com` / `*.localhost` and throws before it opens a connection, so the + server is never asked what it supports. `@v3` is 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. + + **Match the pins on `@actions/artifact`, not on the actions' own version + numbers.** The two actions release on unrelated cadences, so equal version + numbers do NOT mean a compatible pair — upload `v5` bundles `@actions/artifact` + ^4.0.0 while download `v5` bundles ^2.3.2. The pins above are upload **v5** and + download **v6**, which is the pairing that puts ^4.0.0 on both sides. This + matters because `release.yml` is a producer/consumer pair — `android-release` + uploads `minstrel-apk`, `image-release` downloads it — and a protocol mismatch + across it yields an empty listing rather than an error, exactly the silent + failure this entry exists to prevent. + + | tag | `@actions/artifact` | runtime | + |---|---|---| + | upload v4 | ^2.1.1 | node20 | + | **upload v5** ← pinned | **^4.0.0** | node20 | + | download v4 | ^2.1.1 | node20 | + | download v5 | ^2.3.2 | node20 | + | **download v6** ← pinned | **^4.0.0** | node20 | + | download v7 | ^5.0.0 | **node24** | + + The only true protocol break in this history was **v3 → v4** (upstream: + "Downloading artifacts that were created from `actions/upload-artifact@v3` and + below are not supported"); v4-and-up are one family. Later majors are mostly + ergonomics and runtime — upload v4 forbids re-uploading a name and caps a job + at 500 artifacts; download v5 made by-ID extraction match by-name. + + **Do not jump the download pin to v7.** That major is a runner requirement, not + a feature change: it moves to `runs.using: node24` and upstream states it + "requires a minimum Actions Runner version of 2.327.1 … if you are using + self-hosted runners, ensure they are updated before upgrading." act_runner is + not GitHub's runner and makes no such version claim, so node24 is unverified + here. Everything currently pinned is node20. + + 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.