A daily job keeps the F-Droid tools current and rebuilds the repository when they change
Build / Desktop, server and tools (push) Successful in 51s

- Every day the F-Droid repository's tools are checked for a newer version that has been out for at least two
  weeks. When there is one, or when the app's listing changed, the repository is rebuilt with it, and releases
  use it only once that worked, so a faulty update of the tools can never hold up a release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9uhaCTSycTh1r8At679Pz
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-31 11:03:52 +01:00
1 parent cafa40decf
commit 10a85e6bc5
3 files changed
+100 -36

No files matched your search

+62 -25
View File
@@ -1,35 +1,32 @@
#!/bin/sh
set -eu
version=$1
version=${1:-}
here=$(pwd)
root=${PACKAGES_ROOT:-/var/www/packages.arasaka.software}
keep=${FDROID_KEEP:-3}
url=https://packages.arasaka.software/fdroid/repo
fingerprint=5F52CFF4F52F4E0B6DA6CD044BFCEAF78D1D42DB24F2470602DB7F19CBFDF846
id=software.arasaka.meweb
apk=$here/releases/$version/MeWeb-$version-android.apk
test -f "$apk"
if [ -z "${MEWEB_KEYSTORE_B64:-}" ] || [ -z "${MEWEB_KEYSTORE_PASS:-}" ]; then
echo "::error::the repository secrets MEWEB_KEYSTORE_B64 and MEWEB_KEYSTORE_PASS are not set"
exit 1
fi
major=$(echo "$version" | cut -d. -f1)
minor=$(echo "$version" | cut -d. -f2)
patch=$(echo "$version" | cut -d. -f3)
code=$((major * 10000 + minor * 100 + patch))
if [ -n "$version" ]; then
apk=$here/releases/$version/MeWeb-$version-android.apk
test -f "$apk"
major=$(echo "$version" | cut -d. -f1)
minor=$(echo "$version" | cut -d. -f2)
patch=$(echo "$version" | cut -d. -f3)
code=$((major * 10000 + minor * 100 + patch))
elif [ ! -e "$root/fdroid/repo" ]; then
echo "no repository at $root/fdroid/repo yet: the first release makes it"
exit 0
fi
tools/setup.sh --group fdroid
tools=$here/tools/fdroid/linux-x64
tools=$(cd "$here/tools/fdroid/linux-x64" && pwd -P)
uv=$tools/uv-x86_64-unknown-linux-gnu/uv
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
venv=$work/venv
settled=$(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%SZ)
env -u MEWEB_KEYSTORE_B64 -u MEWEB_KEYSTORE_PASS UV_CACHE_DIR="$tools/cache" UV_PYTHON_INSTALL_DIR="$tools/python" \
"$uv" venv -q --python 3 "$venv"
env -u MEWEB_KEYSTORE_B64 -u MEWEB_KEYSTORE_PASS UV_CACHE_DIR="$tools/cache" \
"$uv" pip install -q --python "$venv/bin/python" --exclude-newer "$settled" fdroidserver
echo "fdroidserver $("$venv/bin/fdroid" --version), the newest released before $settled"
python=$(command -v python3)
JAVA_HOME=$(ls -d "$here"/tools/jdk/linux-x64/*/ | head -n 1)
ANDROID_HOME=$here/tools/android-sdk
PATH=$JAVA_HOME/bin:$PATH
@@ -37,6 +34,33 @@ export JAVA_HOME ANDROID_HOME PATH
exec 9>>"$root/.lock"
flock -w 2700 9
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
keyless() {
env -u MEWEB_KEYSTORE_B64 -u MEWEB_KEYSTORE_PASS UV_CACHE_DIR="$tools/cache" "$@"
}
if [ -n "$version" ] && "$tools/current/bin/fdroid" --version > /dev/null 2>&1; then
install=$(readlink -f "$tools/current")
else
settled=$(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%SZ)
echo fdroidserver | keyless "$uv" pip compile -q --python "$python" --exclude-newer "$settled" --generate-hashes \
--no-header --no-annotate -o "$work/lock.txt" -
install=$tools/fdroidserver-$(sha256sum < "$work/lock.txt" | cut -c1-12)
if ! "$install/bin/fdroid" --version > /dev/null 2>&1; then
rm -rf "$install"
keyless "$uv" venv -q --python "$python" "$install"
keyless "$uv" pip sync -q --python "$install/bin/python" "$work/lock.txt"
cp "$work/lock.txt" "$install/lock.txt"
fi
fi
tool=$("$install/bin/fdroid" --version)
stamp=$( { cat "$install/lock.txt"; cd packaging/fdroid && find config.yml icon.png metadata -type f | LC_ALL=C sort | xargs sha256sum; } | sha256sum | cut -d' ' -f1)
if [ -z "$version" ] && [ "$(cat "$(readlink -f "$root/fdroid/repo")/../built-from" 2>/dev/null)" = "$stamp" ]; then
echo "nothing new: the repository is already built with fdroidserver $tool and this listing"
exit 0
fi
echo "fdroidserver $tool, $(basename "$install")"
(umask 077 && printf '%s' "$MEWEB_KEYSTORE_B64" | base64 -d > "$work/meweb.keystore")
MEWEB_KEYSTORE=$work/meweb.keystore
@@ -46,13 +70,16 @@ held=$(keytool -J-Duser.language=en -list -v -keystore "$MEWEB_KEYSTORE" -storep
test "$held" = "$fingerprint"
state=$root/.fdroid
snap=$state/$(date -u +%Y%m%d%H%M%S)-$version
snap=$state/$(date -u +%Y%m%d%H%M%S)-${version:-tools}
mkdir -p "$snap/repo" "$snap/metadata" "$snap/tmp"
if [ -e "$root/fdroid/repo" ]; then
prior=$(readlink -f "$root/fdroid/repo")/..
for file in "$prior"/repo/*.apk; do
[ -e "$file" ] && ln "$file" "$snap/repo/"
done
for icons in "$prior"/repo/icons*; do
[ -d "$icons" ] && cp -a "$icons" "$snap/repo/"
done
cp -a "$prior/metadata/." "$snap/metadata/"
cp "$prior/repo/index-v2.json" "$snap/repo/"
if [ -f "$prior/tmp/apkcache.json" ]; then cp "$prior/tmp/apkcache.json" "$snap/tmp/"; fi
@@ -61,15 +88,19 @@ cp packaging/fdroid/config.yml "$snap/config.yml"
chmod 600 "$snap/config.yml"
cp packaging/fdroid/icon.png "$snap/icon.png"
cp -r packaging/fdroid/metadata/. "$snap/metadata/"
rm -f "$snap/repo/${id}_$code.apk"
cp "$apk" "$snap/repo/${id}_$code.apk"
if [ -n "$version" ]; then
rm -f "$snap/repo/${id}_$code.apk"
cp "$apk" "$snap/repo/${id}_$code.apk"
fi
(cd "$snap/repo" && ls "$id"_*.apk | sed 's/.*_\([0-9]*\)\.apk$/\1/' | sort -n | head -n "-$keep" | while read -r old; do
rm -f "${id}_$old.apk"
done)
newest=$(cd "$snap/repo" && ls "$id"_*.apk | sed 's/.*_\([0-9]*\)\.apk$/\1/' | sort -n | tail -n 1)
(cd "$snap" && "$venv/bin/fdroid" update)
(cd "$snap" && "$install/bin/fdroid" update)
rm -rf "$snap/repo/status"
chmod -R u=rwX,go=rX "$snap/repo"
echo "$stamp" > "$snap/built-from"
mkdir -p "$root/fdroid"
ln -sfn "../.fdroid/$(basename "$snap")/repo" "$root/fdroid/repo.new"
@@ -77,13 +108,19 @@ mv -T -f "$root/fdroid/repo.new" "$root/fdroid/repo"
ls -1d "$state"/*/ | sort | head -n -3 | while read -r old; do
find "$old" -maxdepth 0 -mmin +1440 -exec rm -rf {} +
done
echo "published ${id}_$code.apk in $root/fdroid/repo"
echo "published $root/fdroid/repo, newest ${id}_$newest.apk, with fdroidserver $tool"
if [ "$root" = /var/www/packages.arasaka.software ]; then
curl -fsS "$url/entry.jar" -o "$work/entry.jar"
signed=$(keytool -J-Duser.language=en -printcert -jarfile "$work/entry.jar" | sed -n 's/^.*SHA256: *//p' | head -n 1 | tr -d ':')
test "$signed" = "$fingerprint"
curl -fsS "$url/index-v2.json" | grep -Eq "\"versionCode\": ?$code\b"
curl -fsSI "$url/${id}_$code.apk" > /dev/null
echo "live at $url: $id $version ($code)"
curl -fsS "$url/index-v2.json" | grep -Eq "\"versionCode\": ?$newest\b"
curl -fsSI "$url/${id}_$newest.apk" > /dev/null
echo "live at $url: newest $id $newest"
fi
ln -sfn "$(basename "$install")" "$tools/current.new"
mv -T -f "$tools/current.new" "$tools/current"
for old in "$tools"/fdroidserver-*; do
[ "$old" = "$install" ] || rm -rf "$old"
done
+22
View File
@@ -0,0 +1,22 @@
name: F-Droid tools
on:
schedule:
- cron: '23 5 * * *'
workflow_dispatch:
jobs:
refresh:
name: The newest F-Droid tools, tried on the repository
runs-on: nuvola
steps:
- uses: actions/checkout@v4
- name: Prepare
run: .gitea/scripts/prepare.sh
- name: Rebuild the repository on packages.arasaka.software when the tools or the listing changed
env:
MEWEB_KEYSTORE_B64: ${{ secrets.MEWEB_KEYSTORE_B64 }}
MEWEB_KEYSTORE_PASS: ${{ secrets.MEWEB_KEYSTORE_PASS }}
run: .gitea/scripts/fdroid.sh
+16 -11
View File
@@ -44,7 +44,8 @@ is root-owned, so CI uses the runner user's own `~/.dotnet` (passolo's `tools/re
| `MEWEB_KEYSTORE` | release step | a temporary file in `$RUNNER_TEMP` decoded by `.gitea/scripts/android.sh`, removed on exit |
`build.yml` runs on every push to `master`; `release.yml` on a `v*` tag (publishes the Gitea release) or by hand
(`publish: false` builds every platform and deploys the server only). Both run on the Arasaka org runner `nuvola`,
(`publish: false` builds every platform and deploys the server only); `fdroid.yml` daily at 05:23 UTC or by hand
(the F-Droid tools, below). All three run on the Arasaka org runner `nuvola`,
user `gitea-runner`, home `/var/lib/gitea-runner`: `tools/sdk.sh` installs the SDK from `global.json` into its
`~/.dotnet` (the system SDK in `/opt/dotnet` is older and root-owned), and the JDK, nfpm and the Android SDK are
cached in `~/.cache/meweb`. There is deliberately no `pull_request` trigger: the runner is the production box.
@@ -176,20 +177,24 @@ The Android release key: alias `meweb`, certificate SHA-256
- **The APK reaches an F-Droid repository on packages.arasaka.software from the same job** (31 Oct 2026), after the
release is published, so the repository can lag the release page but never lead it. `.gitea/scripts/fdroid.sh`
installs fdroidserver with uv from `setup.json` (its download cache in `tools/fdroid`, cached like the JDK), because
the box has no `ensurepip` and the apt package would bring 242 others. **Nothing pins fdroidserver** (owner, 31 Oct
2026: no patching it from time to time): each run installs the newest fdroidserver, and the newest of everything it
needs, published at least 14 days before the run, so a bad upload has usually been pulled before the job sees it,
and prints the version it got. It installs into a fresh environment in a temporary folder with the signing key taken
out of its environment; nothing is kept between runs, because an environment's scripts carry the absolute path they
were made at, and 0.15.1's step failed with `fdroid: not found` on a kept one. A new fdroidserver that breaks
`fdroid update` fails this step after the release is published: the repository then lags, and the log names the
version to look at.
the box has no `ensurepip` and the apt package would bring 242 others. **Nothing pins fdroidserver; a daily job
keeps it current** (owner, 31 Oct 2026: a daily service, not patching it from time to time). `fdroid.yml` runs the
same script with no version at 05:23 UTC: it resolves the newest fdroidserver, and the newest of everything it
needs, published at least 14 days earlier (a bad upload has usually been pulled by then), against the box's own
`python3`, into a hash-locked list; a new list gets its own install, `fdroidserver-<hash of the list>` in the cache's
real path (an install's scripts carry the absolute path they were made at; 0.15.1 failed with `fdroid: not found` on
one made from a deleted folder). When that list or the listing in `packaging/fdroid/` differs from what the live
repository was built from (`built-from` beside each snapshot), it rebuilds the repository with it, and only once
that is live and verified does the install become `current` and the older ones go. A release uses `current` and
downloads nothing, so a new fdroidserver that breaks fails the daily job, never a release; the job's log names the
version. Every install runs with the signing key out of its environment.
It signs the repository with the APK's own key (`MEWEB_KEYSTORE_B64` and `MEWEB_KEYSTORE_PASS`, alias `meweb`), so
the F-Droid fingerprint is the certificate's `5F52CFF4…FDF846` and there is no second key to lose; it refuses any
other. It builds a new snapshot in
`/var/www/packages.arasaka.software/.fdroid/` from the live one (the APKs as hard links, so `rm` then `cp` as above;
the metadata, `apkcache.json` and the previous `index-v2.json`, which is where fdroidserver reads each APK's first
date), adds this version, keeps the newest three, runs `fdroid update`, deletes `repo/status/` (it names the
the metadata, the icons, `apkcache.json` and the previous `index-v2.json`, which is where fdroidserver reads each
APK's first date; without the icons a rebuild with no new APK fails, because the cache skips extracting them), adds
this version (a release) or nothing (the daily job), keeps the newest three, runs `fdroid update`, deletes `repo/status/` (it names the
build machine's paths and `/etc/issue.net`), swaps the `fdroid/repo` link and checks the live signature. The key
was already never to be regenerated; now everyone who added the repository trusts only its fingerprint too. `packaging/fdroid/` holds the
repository's settings, its icon (the Arasaka emblem) and the app's listing in English and Italian; the download