chore(ci): migrate workflows to gitea + consolidate keystore secret
flutter / analyze-test-build (push) Failing after 2s
test-go / test (push) Successful in 33s
android / Build + lint + test (push) Failing after 41s
test-web / test (push) Successful in 41s
android / Build signed release APK (push) Has been skipped
test-go / integration (push) Successful in 11m16s

- rename .forgejo/workflows/ to .gitea/workflows/ (git mv preserves
  history); Gitea Actions reads either path, but the directory now
  matches the active platform
- collapse ANDROID_STORE_PASSWORD + ANDROID_KEY_PASSWORD into a
  single ANDROID_KEYSTORE_PASSWORD secret (PKCS12 keystores require
  the two passwords to be identical, so the duplication carried no
  information); build.gradle.kts still reads two env vars to stay
  format-agnostic, both now sourced from the same secret in CI
- ignore android/*.keystore, android/*.keystore.*, android/*.jks,
  android/keystore.properties so the regenerated signing material
  never reaches a remote on accident
- update prose references from Forgejo to Gitea in CLAUDE.md and
  the docs; the historical "migrated from Forgejo" note in CLAUDE.md
  is kept intentionally; forgejo/upload-artifact@v3 action refs are
  left untouched (canonical artifact action, resolves cleanly under
  Gitea Actions)

Operator side: ANDROID_KEY_ALIAS, ANDROID_KEYSTORE_PASSWORD, and
ANDROID_KEYSTORE_B64 secrets registered in Gitea before this lands.
This commit is contained in:
2026-05-31 23:05:03 -04:00
parent 96caac2f06
commit 3cf829752b
12 changed files with 41 additions and 26 deletions
@@ -11,7 +11,7 @@ on:
tags: ['v*']
paths:
- 'android/**'
- '.forgejo/workflows/android.yml'
- '.gitea/workflows/android.yml'
pull_request:
branches: [main]
paths:
@@ -96,9 +96,12 @@ jobs:
working-directory: android
env:
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
# PKCS12 keystores collapse store + key password into a single
# value, so both env vars map to one secret. build.gradle still
# reads them separately to stay format-agnostic.
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
steps:
- name: Checkout
@@ -18,7 +18,7 @@ on:
- 'web/src/lib/styles/tokens.json'
- 'web/src/lib/styles/error-copy.json'
- 'web/static/placeholders/album-fallback.svg'
- '.forgejo/workflows/flutter.yml'
- '.gitea/workflows/flutter.yml'
pull_request:
branches: [main]
paths:
@@ -26,7 +26,7 @@ on:
- 'web/src/lib/styles/tokens.json'
- 'web/src/lib/styles/error-copy.json'
- 'web/static/placeholders/album-fallback.svg'
- '.forgejo/workflows/flutter.yml'
- '.gitea/workflows/flutter.yml'
workflow_dispatch:
jobs:
@@ -113,9 +113,12 @@ jobs:
# the operator's persistent keystore.
shell: bash
env:
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
# PKCS12 keystores collapse store + key password into a single
# value, so both env vars map to one secret. build.gradle still
# reads them separately to stay format-agnostic.
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
run: |
TAG="${GITHUB_REF#refs/tags/v}"
flutter build apk --release --build-name="${TAG}"
@@ -1,6 +1,6 @@
name: release
# Builds and pushes the minstrel container image to the Forgejo registry.
# Builds and pushes the minstrel container image to the Gitea registry.
#
# push to main → :main
# push tag vX.Y.Z → :vX.Y.Z and :latest
@@ -30,7 +30,7 @@ on:
- 'internal/**'
- 'cmd/**'
- '.golangci.yml'
- '.forgejo/workflows/test-go.yml'
- '.gitea/workflows/test-go.yml'
pull_request:
branches: [main]
paths:
@@ -41,7 +41,7 @@ on:
- 'internal/**'
- 'cmd/**'
- '.golangci.yml'
- '.forgejo/workflows/test-go.yml'
- '.gitea/workflows/test-go.yml'
jobs:
test:
@@ -9,12 +9,12 @@ on:
branches: [dev, main]
paths:
- 'web/**'
- '.forgejo/workflows/test-web.yml'
- '.gitea/workflows/test-web.yml'
pull_request:
branches: [main]
paths:
- 'web/**'
- '.forgejo/workflows/test-web.yml'
- '.gitea/workflows/test-web.yml'
jobs:
test:
+8
View File
@@ -67,3 +67,11 @@ android/app/build/
android/local.properties
android/*.iml
android/app/*.iml
# Release signing material — keystore + extracted credentials live local
# only; the canonical copy is the Gitea repo secret (ANDROID_KEYSTORE_B64
# + the alias/password secrets). Losing the local file is fine; losing
# the secret means rebuilding the keystore + reinstalling all clients.
android/*.keystore
android/*.keystore.*
android/*.jks
android/keystore.properties
+4 -3
View File
@@ -44,14 +44,15 @@ exists in Flutter:
## Repo conventions (already in force)
- **No GitHub — Forgejo only.** PR/issue ops via the forgejo MCP; CI runs under
`.forgejo/workflows/`. Never use `gh` or github.com URLs.
- **No GitHub — Gitea only.** PR/issue ops via the gitea MCP (server lives at
git.fabledsword.com, migrated from Forgejo); CI runs under `.gitea/workflows/`.
Never use `gh` or github.com URLs.
- **Git flow:** work on `dev` → PR to protected `main` → tag release. Never push
to `main`.
- **No in-task tests/builds.** Do not run flutter/gradle/npm `test`/`build`/
`analyze` during implementation — CI verifies. Codegen scripts only.
- **CI is operator-side.** After `git push origin dev`, the operator reports the
result; don't poll Forgejo.
result; don't poll Gitea.
- **Specs/plans/audits/parity-map live local.** `docs/superpowers/` is
`.gitignored` — save there for operator review; never commit those.
- **detekt gates CI.** Watch the recurring ones: 60-line `LongMethod`,
+2 -2
View File
@@ -91,7 +91,7 @@ Two concurrent dev processes:
truncates your dev `minstrel` data (admin user, library, likes). It
brings up the compose Postgres and creates the test DB if missing.
- CI runs both: a fast `go test -short -race` gate plus an integration
job with its own ephemeral Postgres (`.forgejo/workflows/test-go.yml`).
job with its own ephemeral Postgres (`.gitea/workflows/test-go.yml`).
### Production build
@@ -101,7 +101,7 @@ Two concurrent dev processes:
- Day-to-day work happens on `dev` (or feature branches merged into `dev`).
- `main` is **protected** — changes land via PR from `dev`.
- Releases are cut by tagging `v*` off `main`; the release workflow builds and pushes the container image to the Forgejo registry.
- Releases are cut by tagging `v*` off `main`; the release workflow builds and pushes the container image to the Gitea registry.
Task and milestone tracking: Fable (`Minstrel` project, id 12).
+4 -4
View File
@@ -12,8 +12,8 @@ git.fabledsword.com/bvandeusen/ci-go:1.26
git.fabledsword.com/bvandeusen/ci-flutter:3.44
```
- `ci-go:1.26` — Go server tests (`.forgejo/workflows/test-go.yml`), web SPA tests (`.forgejo/workflows/test-web.yml`), and the release container build (`.forgejo/workflows/release.yml`).
- `ci-flutter:3.44` — Flutter client tests + debug/release APK builds (`.forgejo/workflows/flutter.yml`).
- `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
@@ -42,6 +42,6 @@ None.
- **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.
- **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'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 polling.** `release.yml` polls Forgejo'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 Forgejo 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.
- **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.
- **Friction asks.** None pending. The two images cover everything Minstrel needs.
+1 -1
View File
@@ -8,7 +8,7 @@ in-app update flow (#397).
CI populates this directory on tag releases:
1. `flutter.yml` builds `app-release.apk` and attaches it to the
Forgejo release as `minstrel-<TAG>.apk`.
Gitea release as `minstrel-<TAG>.apk`.
2. `release.yml` waits for that asset to appear, downloads it into
this directory as `minstrel.apk`, writes the tag string to
`minstrel.apk.version`, and `docker buildx build` includes both
+1 -1
View File
@@ -8,7 +8,7 @@ version: "3.9"
# tests at it:
# make test-integration
# (CI runs the same suite against its own ephemeral Postgres service —
# see .forgejo/workflows/test-go.yml.)
# see .gitea/workflows/test-go.yml.)
#
# Full stack (server + db):
# docker compose up --build
+3 -3
View File
@@ -51,10 +51,10 @@ and re-run `./tool/sync_shared.sh`.
## CI
`.forgejo/workflows/flutter.yml` runs on push to dev/main, tag pushes,
`.gitea/workflows/flutter.yml` runs on push to dev/main, tag pushes,
PR to main, and manual dispatch — path-filtered to `flutter_client/**`
plus the shared web inputs. Steps: sync, analyze, test, build APK.
Debug APKs upload as artifacts; release APKs attach to the Forgejo
Debug APKs upload as artifacts; release APKs attach to the Gitea
release on tag.
## Versioning
@@ -67,7 +67,7 @@ when a server-side change requires a paired client update.
## Distribution
- **Android:** APK on the Forgejo release page. No Play Store for v1.
- **Android:** APK on the Gitea release page. No Play Store for v1.
- **iOS:** TestFlight on tag (manual upload v1, automate later).
## Out-of-scope for slice 1