CI and releases
Gitflow + semver: a release is a vX.Y.Z tag on main. Workflows are in .forgejo/workflows/.
Mac app release (release-mac.yml)
Section titled “Mac app release (release-mac.yml)”Runs on v*.*.* tags (creates/updates the Forgejo release and attaches the .dmg) and on manual runs (artifact only).
Steps, all in tools/ci/build_mac.sh:
- Get the original game files from private R2 (
BLUR_GAME_KEY, defaultgame/blur-modules.tar.gz). tools/recomp/build/from_game.sh modules: extract and md5-check the five modules the recompiler needs (Blur.exe 1.2, binkw32.dll, msvcr90.dll, d3dx9_42.dll, D3DCompiler_42.dll). Same sources as the app’s installer.- P-code from the R2 cache
recomp-cache/<key>.tar.gz, or fresh Ghidra (from_game.sh pcode, ~8 min on an M-series Mac), then cached. Key = hash(module files + Ghidra-side tooling in git + Ghidra version), so emitter changes don’t rerun Ghidra. from_game.sh gen(gen.sh) -> CMake/Ninja Release withBLUR_ONLINE_KEYcompiled in ->apptarget -> dmg. The app icon is extracted from Blur.exe at build time (tools/app/make_icon.py).
Local: tools/ci/build_mac.sh --local packages from the existing work/recomp/m3/gen and work/app/Blur.icns.
Version: exact tag vX.Y.Z[-pre] -> X.Y.Z[-pre], otherwise 0.0.0-dev+<sha> (blurhost --version).
Private storage: Cloudflare R2
Section titled “Private storage: Cloudflare R2”Everything game-derived lives in one private R2 bucket, never in git or a public package:
| Key | Content |
|---|---|
game/blur-modules.tar.gz |
the five original modules (~18 MB), uploaded once by you |
game/<name>.iso |
optional alternative: a full Blur ISO (set the Actions variable BLUR_GAME_KEY to its key) |
recomp-cache/<key>.tar.gz |
P-code cache written by CI |
tools/ci/r2.sh get|put|exists talks to R2’s S3 API with plain curl (SigV4, credentials passed on stdin, never logged).
A single PUT is limited to 5 GiB, so an ISO (5.6 GB) must be uploaded with a multipart client (rclone, aws s3 cp).
Setup:
- Cloudflare dashboard -> R2 -> create a bucket (e.g.
blur-private), no public access, no custom domain. - R2 -> Manage API tokens -> Create API token: permission Object Read & Write, scoped to that bucket only.
Note the Access Key ID, Secret Access Key and the S3 endpoint
https://<account id>.r2.cloudflarestorage.com. - Upload the game modules once, from your own disc/ISO/install:
export R2_ENDPOINT=https://<account id>.r2.cloudflarestorage.com R2_BUCKET=blur-private R2_ACCESS_KEY_ID=... R2_SECRET_ACCESS_KEY=...tools/ci/upload_game_inputs.sh /path/to/Blur.iso # or a disc folder or an installed game folder
Secrets (blur repo -> Settings -> Actions)
Section titled “Secrets (blur repo -> Settings -> Actions)”| Name | Kind | Use |
|---|---|---|
BLUR_ONLINE_KEY |
secret | online service key compiled into the client |
ANSIBLE_VAULT_PASS |
secret | server deploy (Ansible vault) |
REGISTRY_TOKEN |
secret | PAT with package + repository write: container registry, releases |
R2_ENDPOINT, R2_BUCKET, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY |
secrets | private game files + P-code cache |
BLUR_GAME_KEY |
variable (optional) | R2 key of the game input, default game/blur-modules.tar.gz |
Forgejo settings: release attachments must allow the dmg (~60 MB; [repository.release] FILE_MAX_SIZE, MB) and .dmg
(ALLOWED_TYPES, empty = all).
macOS runner (none registered yet)
Section titled “macOS runner (none registered yet)”Apple Silicon Mac, logged-in user account, with:
- Xcode Command Line Tools,
brew install cmake ninja python node openjdk@21(node foractions/checkout) - Ghidra 12.1.4 (
GHIDRA, default~/Applications/ghidra_12.1.4_PUBLIC),JAVA_HOMEfor JDK 21 (default Homebrew openjdk@21) - forgejo-runner registered with label
macos:host:
forgejo-runner register --instance https://git.mintopia.net --token <runner token> --name mac --labels macos:host --no-interactiveforgejo-runner daemonFresh Ghidra discovery runs five analyses in parallel (~8 min); later builds hit the cache (gen ~3 min + build ~3 min).
Signing and notarization (optional)
Section titled “Signing and notarization (optional)”Without these secrets the dmg is ad-hoc signed (Gatekeeper blocks it; users must right-click > Open). With them, tools/ci/sign_mac.sh
re-signs the app with Developer ID (hardened runtime, no entitlements), signs the dmg, notarizes it with notarytool and staples it.
A temporary keychain is created per job and deleted afterwards, so nothing persists on the runner.
| Secret | Value |
|---|---|
MACOS_CERT_P12 |
base64 -i cert.p12 of the “Developer ID Application” certificate + private key |
MACOS_CERT_PASSWORD |
password chosen when exporting the .p12 |
NOTARY_KEY_P8 |
base64 -i AuthKey_XXXX.p8 (App Store Connect API key) |
NOTARY_KEY_ID |
that key’s Key ID |
NOTARY_ISSUER_ID |
Issuer ID shown on the App Store Connect API keys page |
Runner needs Xcode Command Line Tools (codesign, notarytool, stapler).
Docs site (docs.yml)
Section titled “Docs site (docs.yml)”Builds website/ (Starlight) from docs/ on every docs change; broken internal links or anchors fail the build. Pushes
to main deploy to production and develop to the Pages preview branch. Needs CLOUDFLARE_API_TOKEN and
CLOUDFLARE_ACCOUNT_ID secrets and optionally a PAGES_PROJECT variable (default blur); without the secrets only the build runs.
Locally: cd website && npm ci && npm run dev. Node is needed for docs only.