utils/scripts/provision_firebase_android.sh
Krzysztof kuhy Rudnicki 7096c102cd
Some checks are pending
crdt_sync_dart tests / test (push) Waiting to run
crdt_sync_flutter tests / test (push) Waiting to run
crdt-sync pre-commit / pre-commit (push) Waiting to run
crdt-sync tests / test (push) Waiting to run
dependency freshness / dependencies on newest stable (push) Waiting to run
design_system tests / test (push) Waiting to run
file length / file-length (push) Waiting to run
freedays pre-commit / pre-commit (push) Waiting to run
freedays tests / test (push) Waiting to run
gatelock pre-commit / pre-commit (push) Waiting to run
gatelock tests / test (push) Waiting to run
github_device_auth tests / test (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
music_theory tests / test (push) Waiting to run
palette drift / palette (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
sync_settings_ui tests / test (push) Waiting to run
web_ui tests / test (push) Waiting to run
Re-point paths for the ~ reorganisation (home-tidy)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
2026-09-11 18:45:55 +02:00

191 lines
6.4 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# Register every com.kuhy.* / dev.kuhy.* Android app in the kuhy-syncs Firebase
# project and attach both signing fingerprints to each.
#
# Google sign-in on Android needs an Android OAuth client whose (package name,
# SHA-1) pair matches the installed APK. Getting either wrong surfaces as
# `ApiException: 10 / DEVELOPER_ERROR`, which names neither -- so this does it
# from one verified list instead of five console visits.
#
# Both fingerprints go on every app: the release key signs what CI publishes,
# the debug key signs a local `flutter build apk`. Omitting the debug one means
# sign-in works from CI builds and mysteriously fails from your own.
#
# Idempotent: existing apps are reused and existing hashes skipped, so this is
# safe to re-run after adding an app or rotating a key.
#
# Requires: firebase-tools, and `firebase login` already done (browser flow).
# ============================================================================
set -euo pipefail
readonly PROJECT="kuhy-syncs"
# Fingerprints, read from the keystores rather than pasted, so a rotated key
# cannot silently leave this script registering a stale hash.
readonly DEBUG_KEYSTORE="$HOME/.android/debug.keystore"
readonly RELEASE_KEYSTORE="$HOME/.android/release/kuhy-release.jks"
# "<display name>:<package name>" -- the package must match applicationId in
# each app's build.gradle exactly. Note todo is dev.kuhy.*, the rest com.kuhy.*.
readonly APPS=(
"todo:dev.kuhy.todo"
"workout_app:com.kuhy.workout_app"
"diet_guard_app:com.kuhy.diet_guard_app"
"home_inventory:com.kuhy.home_inventory"
"wake_alarm_sync:com.kuhy.wake_alarm_sync"
)
usage() {
echo "Usage: $(basename "$0") [--dry-run]"
echo " Registers the Android apps and their SHA-1s in $PROJECT."
echo " Run 'firebase login' first."
exit 0
}
DRY_RUN=0
require_login() {
if ! firebase login:list 2>&1 | grep -qi 'logged in'; then
echo "Error: not logged in. Run: firebase login" >&2
echo " (browser flow -- this script cannot do it for you)" >&2
exit 1
fi
}
# Prints the SHA-1 of a keystore alias, or exits with a message naming the file.
sha1_of() {
local keystore="$1" alias="$2" storepass="$3"
if [[ ! -f "$keystore" ]]; then
echo "Error: keystore not found: $keystore" >&2
exit 1
fi
local out
out="$(keytool -list -v -keystore "$keystore" -alias "$alias" \
-storepass "$storepass" 2>/dev/null | grep -m1 'SHA1:' || true)"
if [[ -z "$out" ]]; then
echo "Error: no SHA1 for alias '$alias' in $keystore" >&2
exit 1
fi
# " SHA1: AA:BB:..." -> "AA:BB:..."
echo "${out##*SHA1: }"
}
# Looks up an appId by package name, via --json.
#
# Deliberately not parsing `apps:list`'s default output: that is a rendered
# table carrying ANSI colour codes, and it shows the *display name* rather than
# the package -- so a name/package mismatch silently found nothing. The JSON
# form carries packageName explicitly.
app_id_for() {
local package="$1"
firebase apps:list ANDROID --project "$PROJECT" --json 2>/dev/null \
| python3 -c "
import json, sys
package = sys.argv[1]
try:
apps = json.load(sys.stdin).get('result') or []
except ValueError:
sys.exit(0)
for app in apps:
if app.get('packageName') == package:
print(app.get('appId', ''))
break
" "$package"
}
# Returns the appId for a package name, creating the app when absent.
ensure_app() {
local display="$1" package="$2" existing
existing="$(app_id_for "$package")"
if [[ -n "$existing" ]]; then
echo "$existing"
return
fi
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "WOULD-CREATE"
return
fi
firebase apps:create ANDROID "$display" \
--package-name "$package" --project "$PROJECT" >&2
app_id_for "$package"
}
# Adds a fingerprint unless the app already carries it.
ensure_sha() {
local app_id="$1" sha="$2" label="$3"
if [[ "$app_id" == "WOULD-CREATE" ]]; then
echo " would add $label SHA-1"
return
fi
# Compare without separators and case-insensitively: the API echoes hashes
# unseparated and lowercased, while keytool prints AA:BB:CC uppercase, so a
# literal match would re-add an existing hash on every run.
local normalized="${sha//:/}"
if firebase apps:android:sha:list "$app_id" --project "$PROJECT" --json 2>/dev/null \
| tr -d '\n' | grep -qi "${normalized}"; then
echo " $label SHA-1 already present"
return
fi
if [[ "$DRY_RUN" -eq 1 ]]; then
echo " would add $label SHA-1"
return
fi
firebase apps:android:sha:create "$app_id" "$sha" --project "$PROJECT" >/dev/null
echo " added $label SHA-1"
}
main() {
local debug_sha release_sha
debug_sha="$(sha1_of "$DEBUG_KEYSTORE" androiddebugkey android)"
# The release store password lives in a key.properties committed nowhere;
# any app's copy will do since they share one keystore.
local props="$HOME/src/home_inventory/android/key.properties"
if [[ ! -f "$props" ]]; then
echo "Error: $props not found; needed for the release keystore password" >&2
exit 1
fi
local storepass alias
storepass="$(grep '^storePassword=' "$props" | cut -d= -f2-)"
alias="$(grep '^keyAlias=' "$props" | cut -d= -f2-)"
release_sha="$(sha1_of "$RELEASE_KEYSTORE" "$alias" "$storepass")"
echo "Project: $PROJECT"
echo "release: $release_sha"
echo "debug: $debug_sha"
echo
[[ "$DRY_RUN" -eq 0 ]] && require_login
for entry in "${APPS[@]}"; do
local display="${entry%%:*}" package="${entry#*:}"
echo "$display ($package)"
local app_id
app_id="$(ensure_app "$display" "$package")"
if [[ -z "$app_id" ]]; then
echo " Error: could not resolve an appId" >&2
exit 1
fi
echo " appId: $app_id"
ensure_sha "$app_id" "$release_sha" release
ensure_sha "$app_id" "$debug_sha" debug
done
echo
echo "Done. Next: enable Google sign-in, then read the Web client id with"
echo " firebase apps:sdkconfig WEB --project $PROJECT"
}
while [[ $# -gt 0 ]]; do
case $1 in
--dry-run) DRY_RUN=1; shift ;;
-h|--help) usage ;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
main