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..e6a5c9a4 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,14 @@ 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 — must match the upload's protocol, so it + # uses the download mirror, 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 its `v5` tag, + # the major matching the upload pin. Pinned, not tagged — the mirror + # auto-syncs every 8h and a moved tag would change what runs. + uses: https://git.fabledsword.com/bvandeusen/download-artifact@1314311ddb542af343a82d478ac786ceada4143a with: name: minstrel-apk path: client/ diff --git a/ci-requirements.md b/ci-requirements.md index 05e9ebbc..7d34d5e6 100644 --- a/ci-requirements.md +++ b/ci-requirements.md @@ -44,4 +44,36 @@ 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@1314311ddb542af343a82d478ac786ceada4143a + ``` + 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. The pins are upstream's `v5` tags on both + sides; **keep the majors matched.** `release.yml` is a producer/consumer pair — + `android-release` uploads `minstrel-apk`, `image-release` downloads 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: 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.