Skip to content

chore(signing): bootstrap android keystore and play credentials from 1password - #567

Open
RonenMars wants to merge 2 commits into
mainfrom
chore/local-signing-op-android
Open

chore(signing): bootstrap android keystore and play credentials from 1password#567
RonenMars wants to merge 2 commits into
mainfrom
chore/local-signing-op-android

Conversation

@RonenMars

@RonenMars RonenMars commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Why

scripts/bootstrap-local-signing-op.sh pulled every iOS credential from 1Password, but had no Android half.
The upload keystore and the Play publisher service account had to be rebuilt by hand on every machine, and PLAY_SA_JSON_B64 is the one credential with no local fallback — fetch-play-credentials.sh hard-requires it, so npm run ship:android aborted at its first step on any machine where ~/.config/threadbase/play-console-sa.json was absent.

What changed

bootstrap-local-signing-op.sh gains a --platform ios|android|all flag (default all) and an Android section that reads the four ANDROID_* keystore fields plus PLAY_SA_JSON_B64 from two more 1Password items.
It hands them to the existing bootstrap-android-signing.sh and fetch-play-credentials.sh rather than duplicating their work — those already validate the keystore with keytool -list and the service-account JSON with a jq shape check.
require_field now takes an optional vault/item so the Android item can be read without disturbing any existing call site, and OP_VAULT/OP_ITEM are required only when iOS is in scope.
The whole Android half degrades to a skip-with-warning when OP_ANDROID_* is unset, matching how the existing Sentry block behaves.

ship-android.sh gains one self-bootstrap hook, placed before the promote fast path so it covers both that and the build path.

Why the hook is inert in CI

Two independent conditions, either sufficient on its own.

scripts/.env.signing-op is gitignored, so it never exists on a runner and the -f test fails.

deploy.yml materializes both artifacts before ship-android.sh runs — the Play credential cache in the Fetch Play credentials step and .env.signing.android in the Bootstrap Android signing step — so the missing-artifact test fails too.

Worth stating explicitly because it is easy to assume otherwise: deploy.yml does not export PLAY_SA_JSON_B64 into ship-android.sh.
That variable is scoped to the Fetch Play credentials step, and Actions env: is per-step, so it is unset by the time Ship Android runs.
The PLAY_SA_JSON_B64 check in the guard is therefore for a local shell that exports it directly, not for CI.

On a machine with neither 1Password nor the artifacts, the fall-through is today's behavior: fetch-play-credentials.sh's own hard error.

Verification

bash -n clean on both scripts; --help, --platform bogus (exit 2) and the missing-config path all behave.

--platform ios --dry-run is unchanged against a real vault: all ASC fields OK, both provisioning profiles resolved with app-groups present, Sentry OK, Distribution cert found.

--platform android --dry-run resolves all five Android/Play fields.

A live --platform android run wrote a keystore byte-identical to the pre-existing one (same file SHA-256, same certificate fingerprint, alias upload), a valid play-console-sa.json at mode 600, and an .env.signing.android carrying all four TB_MOBILE_UPLOAD_*.

No secrets in the diff: only the gitignored scripts/.env.signing-op holds real vault and item names, and the committed .example carries placeholders only.

Note that CI cannot exercise the new op path by design, so the evidence above is from local runs.

Follow-up, not in this PR

The Android 1Password item can carry a sha256_fingerprint field that bootstrap-android-signing.sh does not check.
Wiring it in would catch a wrong-keystore rotation at bootstrap time rather than at Play upload time.

…1password

scripts/bootstrap-local-signing-op.sh only covered iOS, so the Android upload keystore and the Play publisher service account had to be reconstructed by hand on every machine.
PLAY_SA_JSON_B64 in particular has no local fallback — ship-android.sh aborts at its first step without it.
Adds a --platform ios|android|all flag and an Android section that pulls the four ANDROID_* keystore fields plus PLAY_SA_JSON_B64 from two more 1Password items, then hands them to the existing bootstrap-android-signing.sh and fetch-play-credentials.sh, which already validate them with keytool and jq.
ship-android.sh self-bootstraps the same way ship-ios.sh does, and stays inert in CI because scripts/.env.signing-op is gitignored and deploy.yml materializes both artifacts in earlier steps.
@RonenMars
RonenMars force-pushed the chore/local-signing-op-android branch from b370d57 to cb6f2b9 Compare August 8, 2026 20:05
The 1Password bootstrap was undocumented — deployment.md still told you to export ANDROID_* and PLAY_SA_JSON_B64 by hand, and nothing explained why a fresh worktree needs a bootstrap run when the keystore and .p8 are already on the machine.
Adds a Local credentials section covering the machine-global versus per-checkout split, the per-checkout command, and the everyday ship commands.
Records the asymmetry that ship-android.sh calls the 1Password wrapper itself while ship-ios.sh only skips its bootstrap when .env.signing already exists, so iOS fails with ASC_KEY_ID must be set in a fresh worktree.
Also records why none of this reaches CI, including the easy-to-miss detail that deploy.yml does not export PLAY_SA_JSON_B64 into ship-android.sh because Actions env is scoped per step.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant