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
|
2026-10-31 21:53:08 +01:00
|
|
|
|
# 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.
|
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-11-03 12:11:30 +01:00
|
|
|
|
# tool/with_signing_keys.sh train:minor learn several, one after another
|
fix(release): F-Droid's upload resumable alone, by content, and Forgejo asked again
Train's first release (31 Oct 2026) stopped twice after its tag was pushed:
- Forgejo answered 500 (a unique constraint) to the release: it enters a
pushed tag in its releases itself, a moment after the push. Asked again a
few seconds later, it makes the release of that entry.
- The F-Droid upload filled the halkyon.app server's disk: rsync compared by
time, and this laptop's working folder, restored from the repository, had
every APK anew, so all 1.9 GB went again. It compares by content now.
tool/with_signing_keys.sh fdroid [APK] runs tool/fdroid_repo.sh alone with
the index's key, for an upload that failed after its release was made, and
the script skips an APK the working folder holds already rather than adding
a second file of the same build, which fdroid refuses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 23:28:49 +01:00
|
|
|
|
# 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>).
|
2026-10-30 16:54:57 +01:00
|
|
|
|
#
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 12:11:30 +01:00
|
|
|
|
[ $# -ge 1 ] || fail 'Usage: tool/with_signing_keys.sh <app[:version] … | fdroid> [arguments]'
|
2026-10-30 16:54:57 +01:00
|
|
|
|
|
|
|
|
|
|
# Each app released (tool/src/release/apps.dart): the variable its keystore's path goes
|
2026-11-03 12:11:30 +01:00
|
|
|
|
# in (its AndroidSigning's keystoreVariable) and the item keeping it, set for the app $1.
|
|
|
|
|
|
key_of() {
|
|
|
|
|
|
case "$1" in
|
2026-10-30 16:54:57 +01:00
|
|
|
|
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' ;;
|
build(todo): Todo deployable beside Learn's server, and released as an APK, on F-Droid and as a snap (ADR 0033, step 5)
deploy/compose.todo.yaml adds Todo's server at todo.halkyon.eu, a member
of Learn's register, its database halkyon_todo in the instance's
PostgreSQL, its image built as Train's. redeploy.sh and backup.sh go over
every app server .env switches on (train, todo). Each compose file names
its server to the register in a variable of its own
(INSTANCE_MEMBER_TRAIN, INSTANCE_MEMBER_TODO), which halkyon_base adds to
INSTANCE_MEMBERS, each address once, as a second compose file's default
would have replaced Train's.
apps/todo/release.dart configures the release tool for Todo's APK and
snap: a key of its own (pinned once made; until then nothing is
released), the F-Droid listing (Time, NonFreeNet), and the snap from
apps/todo/snap/, which the tool builds in a clone of the repository at the
tag with that folder in place of snap/, as snapcraft builds one recipe a
folder and the root's is Learn's. What's new for 0.1.0 in English and
German, now shown. Android's backup and transfer are off, as Train's
(D53): the reminders scheduled keep their tasks' titles unsealed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-02 15:35:40 +01:00
|
|
|
|
todo) app_variable=HALKYON_TODO_ANDROID_KEYSTORE app_item='Halkyon Todo – Android release keystore' ;;
|
2026-11-03 12:11:30 +01:00
|
|
|
|
*) 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
|
2026-10-30 16:54:57 +01:00
|
|
|
|
# The F-Droid repository's index key, the same for every app listed there.
|
|
|
|
|
|
readonly fdroid_item='Halkyon Learn – F-Droid index keystore'
|
2026-10-31 21:53:08 +01:00
|
|
|
|
# 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)'
|
2026-10-30 16:54:57 +01:00
|
|
|
|
|
|
|
|
|
|
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-31 21:53:08 +01:00
|
|
|
|
# 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"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-03 12:11:30 +01:00
|
|
|
|
if [ "$1" = fdroid ]; then
|
|
|
|
|
|
shift
|
fix(release): F-Droid's upload resumable alone, by content, and Forgejo asked again
Train's first release (31 Oct 2026) stopped twice after its tag was pushed:
- Forgejo answered 500 (a unique constraint) to the release: it enters a
pushed tag in its releases itself, a moment after the push. Asked again a
few seconds later, it makes the release of that entry.
- The F-Droid upload filled the halkyon.app server's disk: rsync compared by
time, and this laptop's working folder, restored from the repository, had
every APK anew, so all 1.9 GB went again. It compares by content now.
tool/with_signing_keys.sh fdroid [APK] runs tool/fdroid_repo.sh alone with
the index's key, for an upload that failed after its release was made, and
the script skips an APK the working folder holds already rather than adding
a second file of the same build, which fdroid refuses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 23:28:49 +01:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-10-31 20:51:21 +01:00
|
|
|
|
dry_run=false
|
|
|
|
|
|
for argument in "$@"; do
|
|
|
|
|
|
[ "$argument" = --dry-run ] && dry_run=true
|
|
|
|
|
|
done
|
2026-11-03 12:11:30 +01:00
|
|
|
|
|
|
|
|
|
|
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"
|
2026-10-31 20:51:21 +01:00
|
|
|
|
if ! $dry_run; then
|
|
|
|
|
|
fdroid_password=$(password "$fdroid_item")
|
|
|
|
|
|
export HALKYON_FDROID_KEYSTORE_PASSWORD="$fdroid_password"
|
2026-10-31 21:53:08 +01:00
|
|
|
|
token=$(forgejo_token)
|
|
|
|
|
|
export HALKYON_FORGEJO_TOKEN="$token"
|
2026-11-03 12:11:30 +01:00
|
|
|
|
unset fdroid_password token
|
2026-10-31 20:51:21 +01:00
|
|
|
|
fi
|
2026-10-30 16:54:57 +01:00
|
|
|
|
status=0
|
2026-11-03 12:11:30 +01:00
|
|
|
|
dart run tool/release.dart "$@" || status=$?
|
2026-10-30 16:54:57 +01:00
|
|
|
|
exit "$status"
|