2026-10-30 16:54:57 +01:00
|
|
|
|
#!/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
|
2026-10-31 20:51:21 +01:00
|
|
|
|
# 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.
|
2026-10-30 16:54:57 +01:00
|
|
|
|
#
|
|
|
|
|
|
# tool/with_signing_keys.sh <app> [release.dart's arguments]
|
|
|
|
|
|
# tool/with_signing_keys.sh train minor --dry-run
|
|
|
|
|
|
#
|
2026-10-31 20:51:21 +01:00
|
|
|
|
# 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.
|
2026-10-30 16:54:57 +01:00
|
|
|
|
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."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-31 20:51:21 +01:00
|
|
|
|
# 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"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-30 16:54:57 +01:00
|
|
|
|
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"
|
2026-10-31 20:51:21 +01:00
|
|
|
|
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
|
2026-10-30 16:54:57 +01:00
|
|
|
|
status=0
|
|
|
|
|
|
dart run tool/release.dart "$app" "$@" || status=$?
|
|
|
|
|
|
exit "$status"
|