Skip to content

Commit 01afb51

Browse files
committed
docs: added documentation for release signing
1 parent 97a6bee commit 01afb51

2 files changed

Lines changed: 191 additions & 1 deletion

File tree

mobile/RELEASE_SIGNING.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Release signing setup
2+
3+
One-time setup the repo owner has to do before the first `mobile-vX.Y.Z`
4+
tag can produce signed Android and TestFlight builds. After this is
5+
done, every tag push runs `.github/workflows/mobile-release.yml` end to
6+
end with no further manual steps.
7+
8+
All secrets go to **GitHub → Settings → Secrets and variables →
9+
Actions → New repository secret** (or "Environment secret" if you want
10+
a manual approval gate around release).
11+
12+
---
13+
14+
## Android
15+
16+
### 1. Generate the upload keystore (one time, ever)
17+
18+
```bash
19+
keytool -genkey -v \
20+
-keystore upload.jks \
21+
-alias myfaq-upload \
22+
-keyalg RSA -keysize 4096 -validity 36500 \
23+
-storetype JKS
24+
```
25+
26+
You'll be prompted for:
27+
28+
- **Keystore password** — store it in 1Password / your password manager.
29+
- **Key password** — can be the same as the keystore password.
30+
- Distinguished Name fields — `CN=phpMyFAQ Mobile`, `O=phpMyFAQ`,
31+
rest as you like. The Play Store doesn't show this.
32+
33+
> **Critical:** back up `upload.jks` somewhere durable
34+
> (1Password attachment, encrypted off-site backup). Losing it means
35+
> you can never publish another update under the same Play listing
36+
> without going through Play's "reset upload key" support flow.
37+
38+
### 2. Encode the keystore for GitHub
39+
40+
```bash
41+
base64 -i upload.jks | pbcopy # macOS, copies to clipboard
42+
# or: base64 -i upload.jks > upload.jks.b64
43+
```
44+
45+
### 3. Add the four Android secrets
46+
47+
| Secret name | Value |
48+
|-----------------------------|-------|
49+
| `ANDROID_KEYSTORE_BASE64` | the base64 string from step 2 |
50+
| `ANDROID_KEYSTORE_PASSWORD` | the keystore password from step 1 |
51+
| `ANDROID_KEY_ALIAS` | `myfaq-upload` |
52+
| `ANDROID_KEY_PASSWORD` | the key password from step 1 |
53+
54+
That's it for producing a signed AAB + APK. They'll be attached to
55+
the GitHub Release automatically when you push a `mobile-v*` tag.
56+
57+
### 4. (Optional, deferred) Play Console upload
58+
59+
To skip the manual "upload AAB to Play Console" step:
60+
61+
1. In Play Console → Setup → API access, create a service account.
62+
2. Grant it "Release manager" on the app.
63+
3. Download the JSON key.
64+
4. Add as `PLAY_SERVICE_ACCOUNT_JSON` secret (paste the whole JSON).
65+
66+
The current workflow doesn't yet wire this in (Phase 1.1). Until
67+
then, download the `androidApp-release-aab` artifact from the
68+
release run and upload it manually to the internal track.
69+
70+
---
71+
72+
## iOS / TestFlight
73+
74+
You need an Apple Developer account on the phpMyFAQ team and
75+
admin access to App Store Connect.
76+
77+
### 1. Create the app record (one time)
78+
79+
In App Store Connect → My Apps → "+":
80+
81+
- Platform: iOS
82+
- Name: **MyFAQ.app**
83+
- Bundle ID: **app.myfaq.ios** (must match the locked bundle ID;
84+
create it under Certificates, Identifiers & Profiles → Identifiers
85+
first if it doesn't exist)
86+
- SKU: anything unique, e.g. `myfaq-ios`
87+
88+
### 2. Distribution certificate
89+
90+
In Xcode → Settings → Accounts → your team → Manage Certificates:
91+
92+
1. "+" → **Apple Distribution**.
93+
2. Right-click the new cert → **Export Certificate…** → save as
94+
`dist.p12`, set a password (store it in 1Password).
95+
3. Encode and add to GitHub:
96+
```bash
97+
base64 -i dist.p12 | pbcopy
98+
```
99+
100+
| Secret name | Value |
101+
|---|---|
102+
| `IOS_DIST_CERT_P12_BASE64` | base64 of `dist.p12` |
103+
| `IOS_DIST_CERT_PASSWORD` | the export password |
104+
105+
### 3. App Store provisioning profile
106+
107+
In the Apple Developer portal → Profiles → "+":
108+
109+
- Type: **App Store** (Distribution)
110+
- App ID: `app.myfaq.ios`
111+
- Certificate: the Apple Distribution cert from step 2
112+
- Name: `MyFAQ App Store`
113+
114+
Download the `.mobileprovision`, then:
115+
116+
```bash
117+
base64 -i MyFAQ_App_Store.mobileprovision | pbcopy
118+
```
119+
120+
| Secret name | Value |
121+
|---|---|
122+
| `IOS_PROVISIONING_PROFILE_BASE64` | base64 of the `.mobileprovision` |
123+
124+
### 4. App Store Connect API key (for the TestFlight upload)
125+
126+
In App Store Connect → Users and Access → Integrations → App Store
127+
Connect API → "+":
128+
129+
- Name: `MyFAQ CI`
130+
- Access: **App Manager** (or Developer if you want the minimum)
131+
- Download the `.p8` immediately — Apple shows it exactly once.
132+
133+
Note the **Key ID** (10-char string) and the **Issuer ID** (UUID at
134+
the top of the page) shown next to the key.
135+
136+
```bash
137+
base64 -i AuthKey_XXXXXXXXXX.p8 | pbcopy
138+
```
139+
140+
| Secret name | Value |
141+
|---|---|
142+
| `APP_STORE_CONNECT_API_KEY_ID` | the 10-char Key ID |
143+
| `APP_STORE_CONNECT_API_ISSUER_ID` | the Issuer UUID |
144+
| `APP_STORE_CONNECT_API_KEY_BASE64` | base64 of the `.p8` file |
145+
146+
### 5. Set the development team in `project.yml`
147+
148+
`mobile/iosApp/project.yml` currently has `DEVELOPMENT_TEAM: ""`
149+
fill it with your 10-char Apple Team ID (Apple Developer portal →
150+
Membership). Commit that change. Without it, `xcodebuild archive`
151+
can't pick the right cert.
152+
153+
---
154+
155+
## Verifying
156+
157+
Once all the above are in place:
158+
159+
```bash
160+
git tag -a mobile-v0.1.0 -m "Phase 1 read-only MVP"
161+
git push origin mobile-v0.1.0
162+
```
163+
164+
Watch the run at **Actions → mobile-release**. Both jobs should
165+
finish green. After ~15 minutes the build appears under TestFlight →
166+
Internal Testing. The signed AAB + APK appear on the GitHub Release
167+
page automatically.
168+
169+
If a secret is missing the corresponding job logs a `::warning::`
170+
and falls back to producing an unsigned artifact instead of failing
171+
the workflow — useful for sanity-checking the pipeline before the
172+
full secret set is in place.
173+
174+
---
175+
176+
## Rotating / leaked secrets
177+
178+
- **Android upload keystore leaked**: contact Google Play support to
179+
reset the upload key. Generate a new keystore, update all four
180+
Android secrets. The app-signing key (held by Google after Play
181+
App Signing enrollment) is unaffected.
182+
- **iOS distribution cert leaked**: revoke in Apple Developer portal,
183+
generate a new one, regenerate the provisioning profile, update
184+
`IOS_DIST_CERT_*` and `IOS_PROVISIONING_PROFILE_BASE64`.
185+
- **App Store Connect API key leaked**: revoke in App Store Connect,
186+
generate a new key, update the three `APP_STORE_CONNECT_API_*`
187+
secrets. Key revocation is instant.

plans/phase-1-read-only-mvp.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,10 @@ The active instance's API client is held in an
295295

296296
`.github/workflows/mobile-release.yml` runs on tag push and produces
297297
signed Android artifacts plus a TestFlight upload. Before the first
298-
real release, populate these GitHub secrets:
298+
real release, populate these GitHub secrets — see
299+
[`mobile/RELEASE_SIGNING.md`](../mobile/RELEASE_SIGNING.md) for the
300+
step-by-step (keystore generation, cert export, App Store Connect
301+
API key, Apple team ID, encoding commands).
299302

300303
**Android**
301304
- `ANDROID_KEYSTORE_BASE64``base64 -i upload.jks`

0 commit comments

Comments
 (0)