Files
Waters ChurchandClaude Opus 5 469db5bc60 Bring the checklist app under version control
The project has lived only as a folder on the Mac Studio and, briefly, as a
transfer tarball. This is the first commit: the Express server and its web
front end, the Capacitor iOS and Android wrappers, and the release and backup
scripts.

The repo is the source, not a backup of the machine. Left out on purpose, by
the .gitignore files that were already in the tree: node_modules, build output,
mobile/www (regenerated from server/public on every build), and server/data,
which holds the live checklists and belongs on the NAS rather than in git.

Every credential is excluded too — server/.env, the Play upload keystore and
its properties file, the App Store Connect key and ios-signing.config. The one
secret that was not in an ignored file was the NAS SSH password, hard-coded in
build-deploy.sh and grepped back out of it by backup-to-drive.sh. Both now read
it from the login keychain (service checklist-nas, account Christian), falling
back to NAS_PASS in the environment, so rotating it is one keychain entry
rather than a history rewrite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 13:00:57 -04:00

100 lines
4.4 KiB
Bash
Executable File

#!/bin/bash
# verify-asc-key.sh — confirm the App Store Connect API key actually works.
#
# Run this once after creating the key, before trusting release.sh to upload:
#
# ./mobile/scripts/verify-asc-key.sh
#
# It calls `altool --list-providers`, which authenticates and returns the teams the key can
# see. Nothing is uploaded, nothing is created, no build number is consumed — so it is safe
# to run as often as you like. A bad key fails here in seconds instead of fifty seconds into
# an upload with an opaque error.
set -uo pipefail
MOBILE="$(cd "$(dirname "$0")/.." && pwd)"
CFG="$MOBILE/ios-signing.config"
G=$'\033[32m'; Y=$'\033[33m'; R=$'\033[31m'; DIM=$'\033[2m'; N=$'\033[0m'
ok() { echo " ${G}✓${N} $*"; }
warn() { echo " ${Y}!${N} $*"; }
bad() { echo " ${R}✗${N} $*" >&2; }
[ -f "$CFG" ] || { bad "no $CFG — see mobile/README.md → iOS one-time setup"; exit 1; }
# shellcheck source=/dev/null
. "$CFG"
if [ -z "${ASC_KEY_ID:-}" ] || [ -z "${ASC_ISSUER_ID:-}" ]; then
warn "ASC_KEY_ID / ASC_ISSUER_ID not set in ios-signing.config"
echo " ${DIM}Uploads stay manual (Xcode ▸ Organizer). Everything else still works.${N}"
exit 0
fi
# altool locates the key by NAME, not path — AuthKey_<KEYID>.p8 in one of its search dirs.
KEYFILE="$HOME/.appstoreconnect/private_keys/AuthKey_${ASC_KEY_ID}.p8"
echo " key id $ASC_KEY_ID"
echo " issuer $ASC_ISSUER_ID"
echo " key file $KEYFILE"
if [ ! -f "$KEYFILE" ]; then
bad "that file does not exist"
echo " ${DIM}The filename must match the key id EXACTLY, including the AuthKey_ prefix${N}"
echo " ${DIM}and the .p8 extension. Apple's download is already named correctly — move it:${N}"
echo " ${DIM} mv ~/Downloads/AuthKey_${ASC_KEY_ID}.p8 ~/.appstoreconnect/private_keys/${N}"
exit 1
fi
ok "key file present ($(wc -c < "$KEYFILE" | tr -d ' ') bytes)"
# A .p8 that got mangled (opened and re-saved, or downloaded as HTML) fails auth with a
# generic error. Checking the PEM header names the real problem instead.
head -1 "$KEYFILE" | grep -q "BEGIN PRIVATE KEY" \
&& ok "looks like a valid PEM private key" \
|| { bad "not a PEM private key — re-download it from App Store Connect"; exit 1; }
# Tighten permissions: this file can upload builds as your team.
chmod 600 "$KEYFILE" 2>/dev/null
# NOT --list-providers: it rejects API-key auth outright ("list-providers does not support
# APIKey authentication"), so it can never verify a key like this one. --validate-app is the
# real check — it authenticates AND runs Apple's acceptance checks on the binary, without
# uploading anything or consuming the build number.
IPA="$(ls -t "$MOBILE/../dist-release/"*.ipa 2>/dev/null | head -1)"
if [ -z "$IPA" ]; then
warn "no .ipa in dist-release/ to test with — run ./release.sh --ios first"
exit 0
fi
echo
echo " authenticating with $(basename "$IPA")…"
OUT="$(xcrun altool --validate-app -f "$IPA" -t ios --api-key "$ASC_KEY_ID" --api-issuer "$ASC_ISSUER_ID" 2>&1)"
RC=$?
if [ $RC -eq 0 ]; then
ok "authenticated, and the build passed Apple's validation"
echo
ok "release.sh will now upload iOS builds automatically"
exit 0
fi
# Authentication and validation fail the same way at the shell level, so separate them —
# "your key is wrong" and "your binary is wrong" need completely different fixes.
if echo "$OUT" | grep -qiE "Unable to authenticate|Authentication failed|NOT_AUTHORIZED|invalid.*(key|issuer)"; then
bad "authentication FAILED — the key or issuer is wrong"
echo "$OUT" | grep -iE "error|message" | head -6 | sed 's/^/ /'
echo " ${DIM}· Issuer ID is the UUID above the key TABLE, not the key's own id${N}"
echo " ${DIM}· Key needs App Manager or Admin access${N}"
exit 1
fi
# "Cannot determine the Apple ID from Bundle ID" is what Apple actually says when the key
# authenticated fine but no app record exists yet — it reads like a failure and is not one.
if echo "$OUT" | grep -qiE "Cannot determine the Apple ID|no.*app.*with.*bundle|cannot be found|No suitable application"; then
ok "authentication WORKED — the key is good"
warn "but this app does not exist in App Store Connect yet"
echo " ${DIM}Create it: App Store Connect → Apps → + → New App${N}"
echo " ${DIM}Bundle ID: $(python3 -c "import json;print(json.load(open('$MOBILE/capacitor.config.json'))['appId'])" 2>/dev/null)${N}"
exit 0
fi
bad "validation failed"
echo "$OUT" | grep -iE "error|message|warning" | head -10 | sed 's/^/ /'
exit 1