halkyon/tool/with_signing_keys.sh

155 lines
6.3 KiB
Shell
Raw Normal View History

#!/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, nor for the Forgejo token, a login's password in the vault Infra
# (HALKYON_FORGEJO_TOKEN); 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
# tool/with_signing_keys.sh train:minor learn several, one after another
# tool/with_signing_keys.sh fdroid [tool/fdroid_repo.sh's arguments]
#
# The last runs tool/fdroid_repo.sh alone, with the F-Droid index's key and its password
# only: to publish the repository again, or a release's APK whose upload failed after its
# release was made (build/release/<tag>/<apk>).
#
# 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[:version] … | fdroid> [arguments]'
# 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, set for the app $1.
key_of() {
case "$1" 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' ;;
todo) app_variable=HALKYON_TODO_ANDROID_KEYSTORE app_item='Halkyon Todo – Android release keystore' ;;
*) fail "No signing key known for $1." ;;
esac
}
# The apps the arguments name (`train:minor learn`, `train minor --dry-run`): every
# word but the options and a version on its own, without a version after a colon.
apps=()
if [ "$1" != fdroid ]; then
for word in "$@"; do
case "$word" in
-* | major | minor | patch) ;;
*) apps+=("${word%%:*}") ;;
esac
done
[ ${#apps[@]} -ge 1 ] || fail 'No app named.'
for app in "${apps[@]}"; do
key_of "$app"
done
fi
# The F-Droid repository's index key, the same for every app listed there.
readonly fdroid_item='Halkyon Learn – F-Droid index keystore'
# The Forgejo token releases publish with (write:repository), a login's password in
# the vault Infra (its folder API Tokens), made for this laptop.
readonly token_vault='Infra'
readonly token_item='Forgejo Token halkyon_learn release signing script (melchior)'
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"
}
# The Forgejo token: the password of the login $token_item in $token_vault.
forgejo_token() {
local value
value=$(pass-cli item view --vault-name "$token_vault" --item-title "$token_item" --output json |
jq -r '.item.content.content.Login.password // empty') ||
fail "Can't read the item \"$token_item\" in $token_vault (pass-cli login?)."
[ -n "$value" ] || fail "The item \"$token_item\" in $token_vault has no password."
printf '%s' "$value"
}
if [ "$1" = fdroid ]; then
shift
echo "▸ Fetching the F-Droid index's key from $vault"
fetch "$fdroid_item" fdroid-index.p12 "$keys/fdroid-index.p12"
export HALKYON_FDROID_KEYSTORE="$keys/fdroid-index.p12"
fdroid_password=$(password "$fdroid_item")
export HALKYON_FDROID_KEYSTORE_PASSWORD="$fdroid_password"
unset fdroid_password
status=0
tool/fdroid_repo.sh "$@" || status=$?
exit "$status"
fi
dry_run=false
for argument in "$@"; do
[ "$argument" = --dry-run ] && dry_run=true
done
echo "▸ Fetching the signing keys from $vault"
# Each app's keystore under a name of its own, as each has a variable of its own.
for app in "${apps[@]}"; do
key_of "$app"
fetch "$app_item" android-release.p12 "$keys/$app-android-release.p12"
export "$app_variable=$keys/$app-android-release.p12"
if ! $dry_run; then
app_password=$(password "$app_item")
export "${app_variable}_PASSWORD=$app_password"
unset app_password
fi
done
fetch "$fdroid_item" fdroid-index.p12 "$keys/fdroid-index.p12"
export HALKYON_FDROID_KEYSTORE="$keys/fdroid-index.p12"
if ! $dry_run; then
fdroid_password=$(password "$fdroid_item")
export HALKYON_FDROID_KEYSTORE_PASSWORD="$fdroid_password"
token=$(forgejo_token)
export HALKYON_FORGEJO_TOKEN="$token"
unset fdroid_password token
fi
status=0
dart run tool/release.dart "$@" || status=$?
exit "$status"