Release pipeline — setup runbook (internal)
One-time provisioning for the tag-driven store-release pipeline (see
docs/release-pipeline.md for how it works). Do these once on the DFXswiss/btc-wallet
repo; afterwards every vX.Y.Z tag ships to TestFlight + Play internal automatically.
No secret values belong in this file or the repo — this only says where to get each key. Add every secret under: GitHub repo → Settings → Secrets and variables → Actions → New repository secret (or
gh secret set NAME --repo DFXswiss/btc-wallet).
Accounts / access you need first
- Apple Developer Program membership (paid) + admin on the DFX team
Y4QBY6387T. - App Store Connect access (Admin or App Manager) for the same team.
- Google Play Console admin for the
swiss.dfx.bitcoinapp. - GitHub admin on
DFXswiss/btc-wallet(to add secrets) and rights to createDFXswiss/btc-wallet-certificates. - A macOS machine with Xcode +
bundle install(inios/) for the one-timematchseed.
Secrets
Tagging
| Secret | What it is | Where to get it |
|---|---|---|
TAG_DEPLOY_KEY |
SSH private key with write access, so auto-tag's tag push triggers release.yml (a GITHUB_TOKEN push wouldn't). |
ssh-keygen -t ed25519 -C "btc-wallet auto-tag" -f tag_deploy_key -N "". Add tag_deploy_key.pub under repo Settings → Deploy keys → Add deploy key with Allow write access ticked. Put the private file's contents in this secret. |
iOS — App Store Connect API key
Used by upload_to_testflight, deliver, and match.
| Secret | What it is | Where to get it |
|---|---|---|
APP_STORE_CONNECT_KEY |
The .p8 API key file contents. |
App Store Connect → Users and Access → Integrations → App Store Connect API → Generate API Key (role App Manager or Admin). Download the .p8 (only offered once.) Paste the whole file as the secret. |
APP_STORE_CONNECT_KEY_ID |
The key's ID. | Shown next to the key you just made (also in the .p8 filename AuthKey_<KEYID>.p8). |
APP_STORE_CONNECT_ISSUER_ID |
The team's issuer ID. | Top of the same Integrations / Keys page ("Issuer ID"). |
iOS — code signing (match)
| Secret | What it is | Where to get it |
|---|---|---|
MATCH_SSH_KEY |
SSH private key that can read the certs repo. | ssh-keygen -t ed25519 -C "btc-wallet match" -f match_key -N "". Add match_key.pub as a deploy key on DFXswiss/btc-wallet-certificates (write access). Private contents → this secret. |
MATCH_PASSWORD |
The passphrase match uses to encrypt/decrypt the certs. |
You invent it when seeding (below). Store it in the password manager and as this secret. |
Android — Play upload
| Secret | What it is | Where to get it |
|---|---|---|
PLAY_STORE_JSON_BASE64 |
Play service-account JSON, base64-encoded. | Google Play Console → Setup → API access → link/create a Google Cloud project → create a service account → in Play Console Users & permissions invite that service-account email and grant release rights. In Google Cloud, create a JSON key for it and download. Encode: base64 -i service-account.json → paste output as the secret. |
Android — signing (already configured for the v* release flow)
KEYSTORE_FILE_HEX, KEYSTORE_PASSWORD, KEYSTORE_KEY_PASSWORD, KEYSTORE_ALIAS,
TRANSPARENCY_KEYSTORE_HEX, TRANSPARENCY_PASSWORD, TRANSPARENCY_ALIAS.
These already exist (used by build-release-apk.yml). The new pipeline reuses them — nothing to do.
(For reference, the *_HEX ones are xxd -plain keystore.jks.)
Crash reporting (Sentry)
Used by both platforms' Release-configuration builds to upload debug symbols / source
maps (docs/crash-reports.md covers the app-side setup). A missing/empty value fails
the build intentionally — see the guards in scripts/build-release-apk.sh and
ios/fastlane/Fastfile.
| Secret / value | What it is | Where to get it |
|---|---|---|
SENTRY_AUTH_TOKEN (secret) |
Auth token for sentry-cli uploads. |
sentry.dfxserve.com → Settings → Auth Tokens (org-level if available), scopes project:releases, project:read, org:read. |
SENTRY_URL, SENTRY_ORG, SENTRY_PROJECT (plain values, not secrets) |
Where to upload to — sentry-cli defaults to sentry.io without these. |
Already fixed for this self-hosted instance/project: https://sentry.dfxserve.com/, sentry, btc-taro. Set as variables, or plain env: values as the existing workflows already do. |
Spark Lightning (Breez)
Used by both platforms' Release-configuration builds (release.yml iOS + Android,
deploy-develop.yml, build-release-apk.yml). The key is written into a generated
overlay .env (never a tracked .env file) and baked into the binary via
react-native-config. A missing/empty value fails the build intentionally — see
the guards in scripts/build-release-apk.sh and ios/fastlane/Fastfile.
| Secret | What it is | Where to get it |
|---|---|---|
BREEZ_API_KEY (secret) |
Breez SDK API key so Spark Lightning can start. | Request from Breez (https://breez.technology/contact/apikey or the request form); the key is emailed. Store only as this secret — do not commit it to tracked .env files. |
Optional variable
| Name | When needed | Where |
|---|---|---|
ASC_TEAM_ID |
Only if the Apple ID belongs to several App Store Connect teams. | App Store Connect → team picker / Membership. Add as a variable (not secret), read by ios/fastlane/Appfile. |
One-time external setup
0. App ID capabilities (enable FIRST)
The iOS build requests NFC Tag Reading, Push Notifications, and App Groups. The
App Store provisioning profile match generates must include all three, so before seeding
match (step 1), in the Apple Developer portal → Certificates, Identifiers & Profiles → Identifiers:
- Open the
swiss.dfx.bitcoinApp ID and enable NFC Tag Reading, Push Notifications, App Groups. - Under App Groups, register
group.swiss.dfx.bitcoinand assign it to the App ID.
If any of these is missing, the first build fails with "provisioning profile doesn't include the … capability" — the same error class we hit signing on a device. Enabling them up front avoids it.
1. match certificates repo (iOS signing)
- Create an empty private repo
DFXswiss/btc-wallet-certificates. - Add the
matchdeploy key public half to it (write) — seeMATCH_SSH_KEYabove. - On a Mac, from
ios/, with the ASC API key env +MATCH_PASSWORDset, run:
This generates the distribution certificate + App Store profile in the Apple account, encrypts them withbundle install bundle exec fastlane match appstoreMATCH_PASSWORD, and commits them to the certs repo. (Matchfilealready points at the repo +swiss.dfx.bitcoin.) CI runsmatchread-only afterwards.
2. App Store Connect
- Create the API key (see secrets above).
- Create the app's first App Store version manually in App Store Connect.
delivercannot create the very first version via API — until it exists, the listing push is skipped (best-effort) but the TestFlight upload still works. After the first version exists, every tag syncs the listing automatically.
3. Google Play Console
- Ensure the
swiss.dfx.bitcoinapp exists. - Create the service account + grant release permissions (see
PLAY_STORE_JSON_BASE64). - Make sure an Internal Testing ("internal") track exists.
4. Store listing content (unblocks the preflight)
The preflight (scripts/check-store-metadata.sh) blocks a release while any FIXME- remains. Fill these for both de-DE and en-US:
- iOS:
ios/fastlane/metadata/<locale>/{description,promotional_text,marketing_url,privacy_url,support_url}.txt - Android:
android/fastlane/metadata/android/<locale>/full_description.txt - Screenshots:
ios/fastlane/screenshots/<locale>/andandroid/fastlane/metadata/android/<locale>/images/phoneScreenshots/. Limits enforced: iOS name/subtitle 30, keywords 100, promo 170, description 4000; Android title 50, short 80, full 4000; URLs ≤ 255.
First run / dry run
- Add all secrets; complete steps 1–4.
- Push a test tag (e.g.
v0.0.1) or merge todevelop(auto-tag creates the next tag). - The
releaseworkflow runs: guard → preflight → iOS (TestFlight) + Android (Play internal) → GitHub release. - Expect the iOS signing step may need a small first-run tweak (normal for CI iOS). Promotion to production stays a manual click in each console.