Files
fdroid-repo-ci/action.yml
T
claudeandbit 2906f00564 fdroid-update.sh: carry each build's "added" date across a rebuild
weblib/weblib-viewer#95: every version in the published index claimed it
was added at the moment of the *last* publish, so an F-Droid client's
version list showed five separate builds all added within seconds of each
other. Measured on the live dev repo: index-v2.json gave versionCodes
365139/365516/365666/365710/366099 the added dates 05:50:06/08/10/12/13 on
2026-09-12, while the bucket's own Last-Modified for those objects says
they were actually published on 2026-09-11 at 13:53, 20:09, 22:37, 23:23
and 2026-09-12 05:50.

It is not an mtime problem, and --use-date-from-apk does not fix it.
fdroidserver takes the date from common.KnownApks, whose __init__ loads it
out of an already existing repo/index-v2.json (and archive/index-v2.json),
and whose recordapk falls back to datetime.now() for any filename it has
not seen; --use-date-from-apk only changes that fallback to the local
file's mtime, which for a re-downloaded APK is when it was downloaded.
KnownApks' own docstring says there is nowhere else to look: the index is
"currently the only way to permanently store the 'updated' date of APKs".
A CI job that rebuilds repo/ on a fresh runner -- which is exactly what
weblib-viewer#32's retention scheme does when it re-downloads the previous
APKs -- therefore starts with an empty KnownApks, and every build in it
counts as new.

So hand fdroid-update.sh the previously published index, optionally, as
FDROID_PREVIOUS_INDEX -- a local path, because fetching it needs to know
the bucket and per fdroid-repo-ci#1 that stays on the consumer's side;
this script still needs no network. preserve_added.py stages it where
KnownApks looks, and afterwards *checks* that every build present in both
indexes really did keep its date, failing the run if not. That is not
belt-and-braces: fdroidserver's own read_added_date_from_all_apks carries
a TODO about reading these dates differently, so a future version could
drop the behaviour this depends on, and a silent return to flattened
history is the failure mode that took a human noticing to catch the first
time.

Set-but-missing is a hard failure rather than a shrug, and so is a file
that is not a parseable index-v2.json (a truncated download, an S3
NoSuchKey error document): KnownApks has no error handling of its own and
would abort mid-library with a traceback that never names the file, and a
consumer whose fetch silently produced nothing would otherwise flatten its
whole history on every publish while looking healthy. A first-ever publish
expresses "there is no previous index" by leaving the variable unset.

Verified end to end without publishing anything, against the five real
APKs and a copy of the live dev index-v2.json pulled over HTTPS:

  - no previous index (today's behaviour), all five re-stamped:
    366099 06:23:19, 365710 06:23:18, 365666 06:23:17, 365516 06:23:15,
    365139 06:23:14 -- a five-second spread, all of it "now".
  - previous index minus 366099, i.e. the real "four retained plus one
    genuinely new" case: 365139/365516/365666/365710 keep 05:50:06/08/10/12
    exactly, 366099 gets 06:24:01, and lastUpdated follows the new build.
  - a second run on a fresh repo/ with the APK mtimes deliberately touched
    to now: all five unchanged, "5 already-published build(s) kept their
    original 'added' timestamp, 0 new".

test_preserve_added.py (12 assertions, stdlib only) covers the drift
detection in both directions, including the negative control that a
drifted date fails; test_fdroid_update.sh covers the shell contract.
run_tests.sh: 123 passed, 4 skipped, 0 failed.

A weblib-viewer change is needed too -- its workflows have to fetch last
publish's index and set FDROID_PREVIOUS_INDEX -- and is a separate PR in
that repo.

Co-authored-by: bit <bit@das-labor.org>
2026-09-12 06:29:20 +00:00

55 lines
2.5 KiB
YAML

# Makes sign-apk.sh, fdroid-update.sh, publish.sh, next-version-code.sh and
# with-nixpkgs.sh available to a workflow and reports where they are.
#
# Consume it with the **full URL**, the same reason recorded in weblib-ci's
# README: `uses: owner/repo@main` resolves against the instance's default
# actions URL rather than this host, and only the full URL form works while
# the repository being referenced is private -- and the repository holding
# the signing keys that consumes this one is private on purpose.
#
# - uses: actions/checkout@v4
# - id: ci
# uses: https://git.chaosbit.de/weblib/fdroid-repo-ci@main
# - run: bash "${{ steps.ci.outputs.path }}/fdroid-update.sh" ...
# - run: bash "${{ steps.ci.outputs.path }}/publish.sh" ...
#
# sign-apk.sh is deliberately not in that default flow: the builder signs
# the APK before it ever reaches this action (weblib-viewer#19), so this
# repo's normal job is to index and publish an already-signed artifact.
# sign-apk.sh stays available for the exceptional consumer whose build
# genuinely produces an unsigned APK -- see the README.
#
# Nothing here reads a secret or names a bucket, a keystore or a consumer
# repo -- those all stay on the caller's side of this action's output path.
# This action is also never handed an *app* signing key -- only the repo
# index key that fdroid-update.sh signs the index with (see README).
name: F-Droid repo publishing tools
description: >-
Key-free tooling to index an already-signed F-Droid binary repo (`fdroid
update`), compute the next monotonic versionCode for a commit's committer
date, and publish the result to an S3-compatible bucket. Also includes
sign-apk.sh for the exceptional consumer whose build does not already
produce a signed APK. The caller supplies the keys, the bucket, the APK
and the commit being built; this action supplies the pinned toolchain and
the scripts.
outputs:
path:
description: >-
Directory holding with-nixpkgs.sh, nixpkgs-pin, sign-apk.sh,
fdroid-update.sh, next-version-code.sh, next_version_code.py,
preserve_added.py and publish.sh.
value: ${{ github.action_path }}
runs:
using: composite
steps:
- shell: bash
run: |
echo "fdroid-repo-ci tools at ${{ github.action_path }}"
for f in with-nixpkgs.sh nixpkgs-pin sign-apk.sh fdroid-update.sh \
next-version-code.sh next_version_code.py preserve_added.py \
publish.sh; do
test -f "${{ github.action_path }}/$f"
done