scripts/rpm-publish.sh builds the RPM, signs it and the repo metadata with a dedicated GPG key, and adds it to a dnf repo in a given directory, keeping the newest three versions for downgrades. The directory holds nothing secret, so it can be served as is. Both publish scripts now share the version scheme in scripts/lib/release-version.sh: <major>.<minor>.<commit count>, plain enough for RPM, MSI and semver alike. Android's versionCode stays the commit count. Fixes the publish scripts failing under set -u when the repo URL variable isn't set, which is every run after the first. tauri.conf.json now declares the license and homepage for the package metadata.
238 lines
8.3 KiB
Bash
Executable File
238 lines
8.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# fdroid-publish.sh — build a signed release APK and add it to an F-Droid repo.
|
|
#
|
|
# Usage:
|
|
# FDROID_REPO_URL=https://fdroid.example.com/repo \
|
|
# ./scripts/fdroid-publish.sh --init-keystore ~/fdroid-mtgc # first time
|
|
# ./scripts/fdroid-publish.sh ~/fdroid-mtgc # every release after
|
|
#
|
|
# Or through npm: `npm run android:publish -- ~/fdroid-mtgc`.
|
|
#
|
|
# Options:
|
|
# --init-keystore generate the app signing key (once, ever) and keystore.properties
|
|
# --allow-dirty publish uncommitted changes; the version gets a -dirty suffix
|
|
#
|
|
# Environment:
|
|
# MTGC_FDROID_DIR repo directory, if not given as an argument
|
|
# FDROID_REPO_URL public address of <dir>/repo, ending in /repo; needed the first time,
|
|
# and changes the repo's address if set later
|
|
# MTGC_ANDROID_KEYSTORE where --init-keystore puts the key (default ~/.local/share/mtgc/android-release.jks)
|
|
#
|
|
# Serve <dir>/repo (and <dir>/archive if you like), never <dir> itself: it holds
|
|
# the repo signing key and its password. Needs fdroidserver (`pipx install
|
|
# fdroidserver`) and the Android setup from ./scripts/android-setup.sh.
|
|
#
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
sed -n '3,24p' "$0" | sed 's/^# \{0,1\}//'
|
|
exit "${1:-0}"
|
|
}
|
|
|
|
info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
|
|
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*"; }
|
|
err() { printf '\033[1;31m[err]\033[0m %s\n' "$*" >&2; }
|
|
|
|
APP_ID="com.garrettmills.mtgc"
|
|
APP_KEYSTORE="${MTGC_ANDROID_KEYSTORE:-$HOME/.local/share/mtgc/android-release.jks}"
|
|
APP_KEY_ALIAS="mtgc"
|
|
|
|
init_keystore=false
|
|
allow_dirty=false
|
|
dir="${MTGC_FDROID_DIR:-}"
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--init-keystore) init_keystore=true ;;
|
|
--allow-dirty) allow_dirty=true ;;
|
|
-h | --help) usage ;;
|
|
-*) usage 1 ;;
|
|
*) dir="$1" ;;
|
|
esac
|
|
shift
|
|
done
|
|
[ -n "$dir" ] || usage 1
|
|
mkdir -p "$dir"
|
|
dir="$(cd "$dir" && pwd)"
|
|
|
|
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$repo_root"
|
|
key_props="src-tauri/gen/android/keystore.properties"
|
|
|
|
# --- 1. Tools ---------------------------------------------------------------
|
|
|
|
source scripts/android-env.sh >/dev/null
|
|
|
|
if ! command -v fdroid >/dev/null 2>&1; then
|
|
err "fdroid not found. Install fdroidserver: pipx install fdroidserver"
|
|
exit 1
|
|
fi
|
|
build_tools="$(find "$ANDROID_HOME/build-tools" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1)"
|
|
apksigner="$build_tools/apksigner"
|
|
if [ ! -x "$apksigner" ]; then
|
|
err "apksigner not found under $ANDROID_HOME/build-tools (install a build-tools package)"
|
|
exit 1
|
|
fi
|
|
|
|
# --- 2. App signing key -----------------------------------------------------
|
|
#
|
|
# Every update must be signed with the same key as the installed app, so this
|
|
# key is made once and kept forever. Lose it, and each phone has to uninstall
|
|
# the app before it will take a newer build.
|
|
|
|
if $init_keystore; then
|
|
if [ -e "$APP_KEYSTORE" ] || [ -e "$key_props" ]; then
|
|
err "a signing key already exists ($APP_KEYSTORE or $key_props); not overwriting it"
|
|
exit 1
|
|
fi
|
|
info "generating the app signing key at $APP_KEYSTORE"
|
|
mkdir -p "$(dirname "$APP_KEYSTORE")"
|
|
password="$(openssl rand -hex 24)"
|
|
keytool -genkeypair -keystore "$APP_KEYSTORE" -storetype PKCS12 \
|
|
-storepass "$password" -alias "$APP_KEY_ALIAS" \
|
|
-keyalg RSA -keysize 4096 -validity 10000 -dname "CN=mtgc" >/dev/null
|
|
chmod 600 "$APP_KEYSTORE"
|
|
(
|
|
umask 077
|
|
printf 'storeFile=%s\npassword=%s\nkeyAlias=%s\n' "$APP_KEYSTORE" "$password" "$APP_KEY_ALIAS" >"$key_props"
|
|
)
|
|
warn "Back up $APP_KEYSTORE and $key_props (it holds the password)."
|
|
warn "Without them, phones can't update the app without uninstalling it first."
|
|
elif [ ! -f "$key_props" ]; then
|
|
err "no app signing key ($key_props). Run once with --init-keystore."
|
|
exit 1
|
|
fi
|
|
|
|
# --- 3. Version -------------------------------------------------------------
|
|
#
|
|
# The versionCode is the commit count, so every commit on main can ship with no
|
|
# manual bump and F-Droid sees each one as newer than the last.
|
|
|
|
source scripts/lib/release-version.sh
|
|
version_code="$release_count"
|
|
version_name="$release_version"
|
|
if $release_dirty; then
|
|
if ! $allow_dirty; then
|
|
err "the working tree has uncommitted changes; commit them or pass --allow-dirty"
|
|
exit 1
|
|
fi
|
|
version_name="$version_name-dirty"
|
|
fi
|
|
|
|
# --- 4. The repo, the first time --------------------------------------------
|
|
|
|
repo_url="${FDROID_REPO_URL:-}"
|
|
repo_url="${repo_url%/}"
|
|
case "$repo_url" in
|
|
"" | */repo) ;;
|
|
*) err "FDROID_REPO_URL must end in /repo (fdroidserver requires it), e.g. $repo_url/repo"; exit 1 ;;
|
|
esac
|
|
|
|
if [ ! -f "$dir/config.yml" ]; then
|
|
if [ -z "$repo_url" ]; then
|
|
err "no F-Droid repo in $dir yet; set FDROID_REPO_URL to the address $dir/repo will be served at"
|
|
exit 1
|
|
fi
|
|
info "creating the F-Droid repo in $dir"
|
|
(cd "$dir" && fdroid init --android-home "$ANDROID_HOME" --no-prompt -q)
|
|
cat >>"$dir/config.yml" <<EOF
|
|
|
|
repo_url: $repo_url
|
|
repo_name: mtgc
|
|
repo_description: >-
|
|
Releases of mtgc, a personal Magic: The Gathering library catalog.
|
|
archive_older: 3
|
|
EOF
|
|
# `fdroid update` copies this into repo/icons/ itself.
|
|
cp src-tauri/icons/icon.png "$dir/icon.png"
|
|
fi
|
|
# A later FDROID_REPO_URL moves the repo to a new address.
|
|
if [ -n "$repo_url" ] && ! grep -qxF "repo_url: $repo_url" "$dir/config.yml"; then
|
|
info "setting repo_url to $repo_url"
|
|
sed -i "s|^repo_url:.*|repo_url: $repo_url|" "$dir/config.yml"
|
|
fi
|
|
|
|
if [ ! -f "$dir/metadata/$APP_ID.yml" ]; then
|
|
mkdir -p "$dir/metadata"
|
|
cat >"$dir/metadata/$APP_ID.yml" <<'EOF'
|
|
Categories:
|
|
- Games
|
|
License: MIT
|
|
AuthorName: Garrett Mills
|
|
SourceCode: https://code.garrettmills.dev/garrettmills/mtgc
|
|
|
|
Name: mtgc
|
|
Summary: 'Personal Magic: The Gathering library catalog'
|
|
Description: |-
|
|
Catalog your Magic: The Gathering collection: rapid fuzzy card entry, rich
|
|
search (colors, commander color identity, mana value, type, price, format
|
|
legality) and deck organization. Works offline, and syncs between devices
|
|
through an mtgc sync server.
|
|
EOF
|
|
fi
|
|
# Release builds shorten resource paths, so `fdroid update` can't find the
|
|
# launcher icon inside the APK; hand it over as metadata instead.
|
|
if [ ! -f "$dir/metadata/$APP_ID/en-US/icon.png" ]; then
|
|
mkdir -p "$dir/metadata/$APP_ID/en-US"
|
|
cp src-tauri/icons/icon.png "$dir/metadata/$APP_ID/en-US/icon.png"
|
|
fi
|
|
|
|
# --- 5. Only ever move forward ----------------------------------------------
|
|
|
|
newest=0
|
|
for apk in "$dir"/repo/"${APP_ID}"_*.apk "$dir"/archive/"${APP_ID}"_*.apk; do
|
|
[ -e "$apk" ] || continue
|
|
code="${apk##*_}"
|
|
code="${code%.apk}"
|
|
[ "$code" -gt "$newest" ] && newest="$code"
|
|
done
|
|
if [ "$version_code" -le "$newest" ]; then
|
|
err "versionCode $version_code is not newer than $newest, already in the repo; commit something first"
|
|
exit 1
|
|
fi
|
|
|
|
# --- 6. Build ---------------------------------------------------------------
|
|
|
|
info "building $version_name (versionCode $version_code)"
|
|
outputs="src-tauri/gen/android/app/build/outputs/apk/universal/release"
|
|
rm -rf "$outputs"
|
|
cargo tauri android build --apk --target aarch64 --ci \
|
|
--config "{\"version\":\"$version_name\",\"bundle\":{\"android\":{\"versionCode\":$version_code}}}"
|
|
|
|
apk="$(find "$outputs" -name '*.apk' | head -1)"
|
|
if [ -z "$apk" ]; then
|
|
err "the build produced no APK under $outputs"
|
|
exit 1
|
|
fi
|
|
case "$apk" in
|
|
*unsigned*) err "the APK came out unsigned; check $key_props"; exit 1 ;;
|
|
esac
|
|
"$apksigner" verify "$apk"
|
|
|
|
# --- 7. Publish -------------------------------------------------------------
|
|
|
|
published="$dir/repo/${APP_ID}_${version_code}.apk"
|
|
cp "$apk" "$published"
|
|
info "updating the repo index"
|
|
if ! (cd "$dir" && fdroid update -q); then
|
|
# Take the APK back out, or the next run would think it's already published.
|
|
rm -f "$published"
|
|
err "fdroid update failed; nothing was published"
|
|
exit 1
|
|
fi
|
|
|
|
# --- 8. Where to point F-Droid ----------------------------------------------
|
|
|
|
cfg() { sed -n "s/^$1: *//p" "$dir/config.yml" | head -1; }
|
|
fingerprint="$(keytool -list -v -keystore "$dir/$(cfg keystore)" -storepass "$(cfg keystorepass)" \
|
|
-alias "$(cfg repo_keyalias)" | sed -n 's/^[[:space:]]*SHA256: *//p' | tr -d ':')"
|
|
|
|
info "published $version_name"
|
|
cat <<EOF
|
|
|
|
Serve $dir/repo at $(cfg repo_url)
|
|
(not $dir itself — it holds the repo signing key). Add this to F-Droid:
|
|
|
|
$(cfg repo_url)?fingerprint=$fingerprint
|
|
EOF
|