Files called the servers by their own host names, which are the operator's and say nothing a reader needs. They now say which domain a server serves: learn.halkyon.eu's server (the instance and its CI runner), the halkyon.app server (Forgejo and the F-Droid repository). Two names are settings rather than words. CI's runner label becomes `instance` (`runs-on: instance`, `labels: ["instance:host"]`): the runner reads its installed copy of deploy/runner/config.yml, so jobs wait in the queue until that copy is updated and the runner restarted. The SSH aliases the scripts and docs use become `learn-deploy` and `fdroid-upload`. AGENTS.md says to keep it so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
112 lines
4.8 KiB
Shell
Executable file
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.sh 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 fdroid-upload.
|
|
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='fdroid-upload'
|
|
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"
|
|
cp snap/gui/halkyon-learn.png "$working/halkyon.png"
|
|
# The app's own icon in the F-Droid app, the same: 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"
|