Build the shared F-Droid publishing tooling: sign, index, publish

Adds sign-apk.sh (align-check + apksigner), fdroid-update.sh (fdroid
update over a binary repo, signing the index with its own separate
key), publish.sh (S3-compatible upload) and the composite action
tying them together, matching weblib-ci's shape.

zipalign is not a standalone nixpkgs attribute; resolved by measurement
rather than assumption that Gradle/AGP already aligns its output (Android's
own docs, corroborated by fdroidserver never shelling out to zipalign) and
added check_zip_alignment.py as the safety net, since apksigner itself does
not check alignment at all. nixpkgs is pinned via a committed nixpkgs-pin
revision, since this repo has no flake.lock of its own for with-nixpkgs.sh
to read (fdroid-repo-ci#1, weblib-ci#9) -- a missing or malformed pin fails
loudly rather than degrading to an unpinned nix shell.

No consumer repo, bucket, hostname or channel name is hard-coded: which
repo holds the keys and runs the publish has already changed once during
this issue and may again, so every example uses a placeholder.

42 offline/local-only test assertions across five suites (run_tests.sh),
including fault injection on the alignment check, the password/argument
validation, and the with-nixpkgs.sh fail-loud paths.

Co-authored-by: bit <bit@das-labor.org>
This commit is contained in:
2026-09-09 06:34:36 +00:00
co-authored by bit
parent f1dcedd3f7
commit 3f14c01b00
15 changed files with 1292 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
#
# Run a command with one or more nixpkgs packages on PATH, pinned to the
# revision recorded in `nixpkgs-pin`, next to this script.
#
# bash with-nixpkgs.sh apksigner apksigner verify --print-certs app.apk
# bash with-nixpkgs.sh "fdroidserver jdk21 apksigner" fdroid update
#
# A space-separated list of attributes puts all of them on PATH at once, via
# multiple `nix shell` installables in one call -- `fdroid update` shells out
# to `jarsigner`/`keytool` (JDK) and `apksigner` itself, so a single package
# is not always enough.
#
# **Call it as `bash <script>`, not `<script>`.** The runner has no
# `/usr/bin/env`, so the shebang cannot be relied on there -- the same trap
# weblib-ci's with-nixpkgs.sh documents.
#
# ## Why this is not a copy of weblib-ci's with-nixpkgs.sh
#
# That script reads *the consuming repo's* `./flake.lock`, relative to the
# working directory, so the pin travels with whatever project vendors it.
# This repo has no flake.lock of its own -- deliberately; see weblib-ci#9,
# which is the same tension one repo over: a `flake.lock` would exist only to
# hold one 40-character string, for a repo whose README says it is *run*, not
# built. And per fdroid-repo-ci#1, the repo that consumes this one holds
# signing keys and is not guaranteed to be a flake at all, so reading a
# caller's flake.lock the way weblib-ci's script does would not even find one
# to read here.
#
# So the pin travels with *this* repo instead, in a file named `nixpkgs-pin`
# on purpose: it is the exact fallback weblib-ci#9 proposes adding to the
# shared script (`./flake.lock` present -> today's behaviour there; else
# `./nixpkgs-pin` -> a bare rev; else fail loudly). If that lands, this repo
# already speaks its format and nothing here needs to change.
#
# `fdroidserver`'s output feeds a *signed index* (fdroid-repo-ci#1), so a
# drifting nixpkgs is a worse trade here than for a disposable test job --
# unlike weblib-ci#9's stdlib-only 1.2s suite, this pin is load-bearing. A
# missing or malformed pin is therefore a hard failure, never a silent fall
# back to `nix shell nixpkgs#foo`, which would re-resolve the registry and
# fetch whatever the channel currently points at -- measured to cost an
# unpacked channel tarball in weblib-ci's own job log.
set -euo pipefail
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
pin="$here/nixpkgs-pin"
if [ "$#" -lt 2 ]; then
echo "usage: $0 <nixpkgs attribute(s), space-separated> <command> [args...]" >&2
exit 2
fi
attrs=$1
shift
if [ ! -f "$pin" ]; then
echo "$0: no $pin -- refusing to fall back to an unpinned nix shell nixpkgs#<attr>" >&2
exit 1
fi
rev=$(tr -d ' \t\n' < "$pin")
if ! [[ "$rev" =~ ^[0-9a-f]{40}$ ]]; then
echo "$0: $pin does not hold a 40-character git revision (got '$rev')" >&2
exit 1
fi
refs=()
for attr in $attrs; do
refs+=("github:nixos/nixpkgs/${rev}#${attr}")
done
exec nix shell "${refs[@]}" --command "$@"