Files
fdroid-repo-ci/publish.sh
T
claudeandbit 3f14c01b00 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>
2026-09-09 06:34:36 +00:00

96 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Upload a repo/ tree -- signed APKs plus the signed index -- to an
# S3-compatible bucket.
#
# AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... \
# bash publish.sh <repo-root>/repo <bucket> <endpoint-url> [<prefix>]
#
# Bucket, endpoint and prefix are all arguments, never baked in here: this
# repo does not know, and per fdroid-repo-ci#1 must never come to know, which
# bucket or host any consumer publishes to. Credentials come from the
# standard AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKEN
# environment variables that `aws` itself reads -- nothing here parses or
# forwards them as arguments.
#
# Region is required by the `aws` CLI even against a non-AWS S3-compatible
# backend that ignores it; set AWS_DEFAULT_REGION if the endpoint cares,
# otherwise the placeholder below is never actually consulted.
#
# Uses `aws s3 sync`, not `cp --recursive`: re-publishing re-uploads only
# what changed. Does **not** pass `--delete` by default -- an F-Droid repo
# normally keeps every APK it has ever served (older index entries can still
# point at them), so silently pruning the remote on every publish is the
# wrong default. Pass `--delete` as an extra argument if a caller genuinely
# wants the remote pruned to match local.
#
# `fdroid update` needs no network; this is the one script in this repo that
# does (fdroid-repo-ci#1), and is accordingly the one script whose happy path
# the test suite cannot exercise against a real bucket. `--dry-run` prints
# the exact command this script would run -- including the `with-nixpkgs.sh`
# pin -- and exits without touching the network, which is what the test
# suite asserts against. (A PATH-stubbed `aws` cannot do this job: `nix
# shell` puts the packages it was asked for at the *front* of PATH, ahead of
# anything already there, so a stub earlier on the caller's PATH would simply
# be shadowed by the real `aws` from awscli2.) Separately, the test suite
# also runs this for real against a closed local port, to confirm the
# constructed command is well-formed and genuinely attempts a network call
# rather than failing on argument parsing.
#
# Pins awscli2 via with-nixpkgs.sh/nixpkgs-pin, next to this script.
set -euo pipefail
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
if [ "$#" -lt 3 ]; then
cat >&2 <<'USAGE'
usage: publish.sh [--dry-run] <local-repo-dir> <bucket> <endpoint-url> [<prefix>] [extra aws s3 sync args...]
Credentials: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (and optionally
AWS_SESSION_TOKEN) must already be exported -- never passed as arguments.
USAGE
exit 2
fi
dry_run=false
if [ "$1" = "--dry-run" ]; then
dry_run=true
shift
fi
local_dir=$1
bucket=$2
endpoint=$3
shift 3
prefix=""
if [ "$#" -gt 0 ] && [[ $1 != -* ]]; then
prefix=$1
shift
fi
extra_args=("$@")
[ -d "$local_dir" ] || { echo "publish.sh: no such directory: $local_dir" >&2; exit 2; }
: "${AWS_ACCESS_KEY_ID:?publish.sh: AWS_ACCESS_KEY_ID is not set}"
: "${AWS_SECRET_ACCESS_KEY:?publish.sh: AWS_SECRET_ACCESS_KEY is not set}"
dest="s3://$bucket"
if [ -n "$prefix" ]; then
dest="$dest/${prefix#/}"
fi
cmd=(bash "$here/with-nixpkgs.sh" awscli2 aws s3 sync "$local_dir" "$dest"
--endpoint-url "$endpoint"
--region "${AWS_DEFAULT_REGION:-us-east-1}"
"${extra_args[@]}")
if [ "$dry_run" = true ]; then
printf '%q ' "${cmd[@]}"
printf '\n'
exit 0
fi
echo "publish.sh: syncing $local_dir -> $dest (endpoint $endpoint)"
exec "${cmd[@]}"