Skip to content

CI and releases

Gitflow + semver: a release is a vX.Y.Z tag on main. Workflows are in .forgejo/workflows/.

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:

  1. Get the original game files from private R2 (BLUR_GAME_KEY, default game/blur-modules.tar.gz).
  2. 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.
  3. 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.
  4. from_game.sh gen (gen.sh) -> CMake/Ninja Release with BLUR_ONLINE_KEY compiled in -> app target -> 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).

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:

  1. Cloudflare dashboard -> R2 -> create a bucket (e.g. blur-private), no public access, no custom domain.
  2. 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.
  3. 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).

Apple Silicon Mac, logged-in user account, with:

  • Xcode Command Line Tools, brew install cmake ninja python node openjdk@21 (node for actions/checkout)
  • Ghidra 12.1.4 (GHIDRA, default ~/Applications/ghidra_12.1.4_PUBLIC), JAVA_HOME for 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-interactive
forgejo-runner daemon

Fresh Ghidra discovery runs five analyses in parallel (~8 min); later builds hit the cache (gen ~3 min + build ~3 min).

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).

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.