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
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:
1 parent
cafa40decf
commit
10a85e6bc5
3 files changed
+100
-36
No files matched your search
+62
-25
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user