halkyon/tool/fdroid_repo.sh
Tizian 「ティツィアーン」 f749acb9ec
build(learn): the F-Droid repository keeps the kingfisher as its icon
The repository is all of Halkyon's apps', so its icon stays Halkyon's
emblem (ADR 0031, D3), kept in tool/release/fdroid/ now that the snap's
icon is Learn's graduation cap. Learn's listing keeps the snap's.

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

112 lines
4.8 KiB
Shell
Executable file

#!/usr/bin/env bash
# Updates the F-Droid repository at https://halkyon.app/fdroid/repo and
# uploads it: adds an APK if one is given, signs the index with the repository's
# key, and uploads everything to the halkyon.app server as the user fdroid, whose key can only
# write into the folder Caddy serves (rrsync). tool/release.dart runs it with each
# release's APK; run alone, it publishes the repository as it is, e.g. after its
# settings changed.
#
# tool/fdroid_repo.sh [APK]
# tool/fdroid_repo.sh --check (the tools, the key and its password only)
#
# The repository's working folder, outside Git, keeps every APK released so far:
# $HALKYON_FDROID_DIR (default below). Its settings and the apps' listings come
# from tool/release/fdroid/, copied in at every run. The index key is a PKCS #12
# keystore at $HALKYON_FDROID_KEYSTORE, alias halkyon-fdroid, whose certificate's
# SHA-256 is pinned in tool/release/fdroid-index-certificate.sha256: an index
# signed otherwise would make the F-Droid app refuse the repository. Its password
# comes from $HALKYON_FDROID_KEYSTORE_PASSWORD, or is asked for; it's never
# written down. Needs fdroidserver v2.4.5 (pipx), rsync, a JDK, the Android SDK,
# and the ssh host ramiel-fdroid.
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
readonly working="${HALKYON_FDROID_DIR:-$HOME/.local/share/halkyon-learn/fdroid}"
keystore="${HALKYON_FDROID_KEYSTORE:-$HOME/.local/share/halkyon-learn/fdroid-index.p12}"
readonly key_alias='halkyon-fdroid'
readonly pinned_file='tool/release/fdroid-index-certificate.sha256'
readonly upload_host='ramiel-fdroid'
readonly repo_url='https://halkyon.app/fdroid/repo'
fail() {
echo "✗ $*" >&2
exit 1
}
step() { echo "▸ $*"; }
[ $# -le 1 ] || fail 'Usage: tool/fdroid_repo.sh [APK | --check]'
check_only=false
apk=''
case "${1:-}" in
--check) check_only=true ;;
'') ;;
*) apk="$1" ;;
esac
for tool in fdroid rsync; do
command -v "$tool" >/dev/null || fail "$tool is missing."
done
# fdroid takes jarsigner and keytool from the PATH, apksigner from the SDK.
jdk_bin=$(dirname "$(command -v java || echo /snap/android-studio/current/jbr/bin/java)")
[ -x "$jdk_bin/jarsigner" ] && [ -x "$jdk_bin/keytool" ] ||
fail 'No JDK found (jarsigner, keytool).'
export PATH="$jdk_bin:$PATH"
export ANDROID_HOME="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}}"
[ -f "$keystore" ] || fail "No keystore at $keystore (HALKYON_FDROID_KEYSTORE)."
keystore=$(realpath "$keystore")
export HALKYON_FDROID_KEYSTORE="$keystore"
[ -f "$pinned_file" ] || fail "$pinned_file is missing."
pinned=$(grep -v '^#' "$pinned_file" | tr -d ' :\n' | tr 'a-f' 'A-F' || true)
[[ "$pinned" =~ ^[0-9A-F]{64}$ ]] || fail "$pinned_file holds no SHA-256."
if [ -z "${HALKYON_FDROID_KEYSTORE_PASSWORD:-}" ]; then
read -rsp "Password of $keystore: " HALKYON_FDROID_KEYSTORE_PASSWORD
echo
fi
export HALKYON_FDROID_KEYSTORE_PASSWORD
# The F-Droid app knows the repository by this fingerprint: the certificate's
# SHA-256 in capitals, without colons.
fingerprint=$(keytool -list -v -keystore "$keystore" -alias "$key_alias" \
-storepass:env HALKYON_FDROID_KEYSTORE_PASSWORD 2>/dev/null |
sed -n 's/^.*SHA256: *//p' | head -n 1 | tr -d ':' | tr 'a-f' 'A-F' || true)
[ -n "$fingerprint" ] || fail "Wrong password, or no key $key_alias in $keystore."
[ "$fingerprint" = "$pinned" ] ||
fail "The key $key_alias in $keystore isn't the one in $pinned_file."
if $check_only; then
exit 0
fi
step 'Updating the F-Droid repository'
mkdir -p "$working/repo"
cp -r tool/release/fdroid/. "$working/"
# fdroid refuses a config.yml others can read.
chmod 600 "$working/config.yml"
# Learn's own icon in the F-Droid app, the snap's (the repository's is the
# kingfisher, halkyon.png in tool/release/fdroid/): fdroid can't find the launcher
# icon in a release APK, whose build shortens the resources' paths (res/9w.png
# rather than res/mipmap-hdpi/ic_launcher.png; fdroidserver issue 885).
mkdir -p "$working/metadata/app.halkyon.learn/en-US"
cp snap/gui/halkyon-learn.png "$working/metadata/app.halkyon.learn/en-US/icon.png"
if [ -n "$apk" ]; then
[ -f "$apk" ] || fail "No APK at $apk."
cp "$apk" "$working/repo/"
fi
# Names every APK as F-Droid does (<app id>_<version code>.apk) and signs the
# indexes with the repository's key.
(cd "$working" && fdroid update --rename-apks)
step "Uploading it to $upload_host"
# The APKs and icons first, the indexes last, so the F-Droid app never reads an
# index naming a file that isn't there yet.
for dir in repo archive; do
[ -d "$working/$dir" ] || continue
rsync -a --chmod=D755,F644 --exclude='index*' --exclude='entry.*' \
"$working/$dir/" "$upload_host:$dir/"
done
for dir in repo archive; do
[ -d "$working/$dir" ] || continue
rsync -a --chmod=D755,F644 --delete-after "$working/$dir/" "$upload_host:$dir/"
done
echo " $repo_url?fingerprint=$fingerprint"