halkyon/tool/with_signing_keys.sh
Tizian 「ティツィアーン」 7aa8ec7304
All checks were successful
CI / check (push) Successful in 38m54s
CI / deploy (push) Successful in 56s
feat(release): the keystores' passwords from Proton Pass too, and F-Droid's tools checked first
tool/with_signing_keys.sh reads each keystore's password from its item's
hidden field "Password" and hands it to the release tool in its
environment (<keystore variable>_PASSWORD), but for a dry run, which asks
for no secret; the release tool takes a secret from there where it is
set, else asks as before (the Forgejo token from HALKYON_FORGEJO_TOKEN).
Typing them guarded nothing the Pass session didn't already open.

The release tool checks fdroid, rsync, curl and jq with the other tools,
so a dry run says when one is missing, rather than the F-Droid script
after the passwords.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 20:51:21 +01:00

95 lines
3.9 KiB
Shell
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Runs a release with its signing keys fetched from Proton Pass, where alone they are
# kept (vault "Infra · Signing"): the app's Android keystore and the F-Droid index's,
# each a note's attachment, downloaded into a folder in memory ($XDG_RUNTIME_DIR, a
# tmpfs only the own user reads), handed to the release tool by the variables it reads
# their paths from, and removed however the run ends. Their passwords, each item's hidden
# field "Password", go to it in its environment (<keystore variable>_PASSWORD), so it
# asks for none; a dry run asks for no secret, and gets none.
#
# tool/with_signing_keys.sh <app> [release.dart's arguments]
# tool/with_signing_keys.sh train minor --dry-run
#
# Needs pass-cli, logged in (pass-cli login), and jq. Reads of the items take their IDs,
# their attachments' names and, but for a dry run, the field "Password", which goes
# nowhere but into the release tool's environment.
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
readonly vault='Infra · Signing'
fail() {
echo "✗ $*" >&2
exit 1
}
[ $# -ge 1 ] || fail 'Usage: tool/with_signing_keys.sh <app> [release arguments]'
app="$1"
shift
# Each app released (tool/src/release/apps.dart): the variable its keystore's path goes
# in (its AndroidSigning's keystoreVariable) and the item keeping it.
case "$app" in
learn) app_variable=HALKYON_ANDROID_KEYSTORE app_item='Halkyon Learn – Android release keystore' ;;
train) app_variable=HALKYON_TRAIN_ANDROID_KEYSTORE app_item='Halkyon Train – Android release keystore' ;;
*) fail "No signing key known for $app." ;;
esac
# The F-Droid repository's index key, the same for every app listed there.
readonly fdroid_item='Halkyon Learn – F-Droid index keystore'
for tool in pass-cli jq; do
command -v "$tool" >/dev/null || fail "$tool is missing."
done
[ -n "${XDG_RUNTIME_DIR:-}" ] && [ -d "$XDG_RUNTIME_DIR" ] ||
fail 'No $XDG_RUNTIME_DIR to keep the keys in memory.'
keys=$(mktemp -d -p "$XDG_RUNTIME_DIR")
trap 'rm -rf "$keys"' EXIT
trap 'exit 130' INT TERM HUP
# Downloads the attachment named $2 of the item titled $1 to $3.
fetch() {
local item="$1" name="$2" to="$3" ids
ids=$(pass-cli item view --vault-name "$vault" --item-title "$item" --output json |
jq -r --arg name "$name" \
'[.item.share_id, .item.id, (.attachments[] | select(.content.name == $name) | .id)]
| select(length == 3) | @tsv') ||
fail "Can't read the item \"$item\" in $vault (pass-cli login?)."
[ -n "$ids" ] || fail "The item \"$item\" in $vault has no attachment $name."
local share item_id attachment
IFS=$'\t' read -r share item_id attachment <<<"$ids"
pass-cli item attachment download --share-id "$share" --item-id "$item_id" \
--attachment-id "$attachment" --output "$to" >/dev/null
[ -s "$to" ] || fail "The attachment $name of \"$item\" came empty."
}
# The hidden field "Password" of the item titled $1.
password() {
local item="$1" value
value=$(pass-cli item view --vault-name "$vault" --item-title "$item" --output json |
jq -r '.item.content.extra_fields[]? | select(.name == "Password") | .content.Hidden // empty') ||
fail "Can't read the item \"$item\" in $vault (pass-cli login?)."
[ -n "$value" ] || fail "The item \"$item\" in $vault has no field Password."
printf '%s' "$value"
}
echo "▸ Fetching the signing keys from $vault"
fetch "$app_item" android-release.p12 "$keys/android-release.p12"
fetch "$fdroid_item" fdroid-index.p12 "$keys/fdroid-index.p12"
export "$app_variable=$keys/android-release.p12"
export HALKYON_FDROID_KEYSTORE="$keys/fdroid-index.p12"
dry_run=false
for argument in "$@"; do
[ "$argument" = --dry-run ] && dry_run=true
done
if ! $dry_run; then
app_password=$(password "$app_item")
fdroid_password=$(password "$fdroid_item")
export "${app_variable}_PASSWORD=$app_password"
export HALKYON_FDROID_KEYSTORE_PASSWORD="$fdroid_password"
unset app_password fdroid_password
fi
status=0
dart run tool/release.dart "$app" "$@" || status=$?
exit "$status"