Implements fdroid-repo-ci#1: sign-apk.sh, fdroid-update.sh, publish.sh and the composite action, plus with-nixpkgs.sh/nixpkgs-pin for this repo's own nixpkgs pin since it has no flake.lock.
See the README for the zipalign measurement (apksigner is a standalone attribute, zipalign is not; Google's docs plus fdroidserver's own source say Gradle/AGP already aligns its output; check_zip_alignment.py is the safety net since apksigner does not check alignment itself) and for why no consumer repo, bucket or channel name is hard-coded anywhere.
bash run_tests.sh: 42 assertions across five suites, offline or local-store-only. Full report in the issue/PR thread.
Implements fdroid-repo-ci#1: sign-apk.sh, fdroid-update.sh, publish.sh and the composite action, plus with-nixpkgs.sh/nixpkgs-pin for this repo's own nixpkgs pin since it has no flake.lock.
See the README for the zipalign measurement (apksigner is a standalone attribute, zipalign is not; Google's docs plus fdroidserver's own source say Gradle/AGP already aligns its output; check_zip_alignment.py is the safety net since apksigner does not check alignment itself) and for why no consumer repo, bucket or channel name is hard-coded anywhere.
`bash run_tests.sh`: 42 assertions across five suites, offline or local-store-only. Full report in the issue/PR thread.
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>
It said no organisation appears "in a path, URL or workflow reference",
while the usage examples immediately above necessarily contain
`https://git.chaosbit.de/weblib/fdroid-repo-ci@main` -- a consumer has to
type this repository's address, so it cannot not appear.
A rule a file visibly breaks is worse than no rule: the next person either
"fixes" the examples or stops trusting the section. So the claim now covers
what it should -- consumer-specific things, and any named consumer -- and
calls out this repo's own address as the one deliberate exception, which is
also a useful pointer to the exact lines to edit when the repo moves org.
Found in the cold re-read of #2.
Co-authored-by: bit <bit@das-labor.org>
Public-repo check first, since that is the one that cannot be undone: no key material committed — no .jks, no .keystore, no PEM. The password-shaped strings in the tests (apppass123, AKIAFAKETESTKEY, fake-secret-not-real) are fixtures for keystores generated and destroyed inside a temp dir; nothing real. Clean.
fdroid-update.sh never writes a password into config.yml. It uses fdroidserver's own {env: VARNAME} indirection — which exists precisely so the config can be committed — and chmod 600s it anyway. That is the right shape: the caller sets a variable, the file names it.
Re-measured here:bash run_tests.sh → 42 passed, 0 skipped, 0 failed. Zero skips matters on a suite where the interesting cases need a real apksigner and a real fdroid update — a skip here would look exactly like a pass.
The zipalign answer is well handled
The conclusion — zipalign unnecessary, apksigner alone suffices — rests on Google's docs plus fdroidserver 2.4.3 never shelling out to zipalign, and was not confirmed against a real Flutter build because that build was judged too expensive. That gap is stated rather than glossed.
What makes it acceptable is that the gap is defended rather than just disclosed: check_zip_alignment.py runs against every APK sign-apk.sh is asked to sign, not only the fixtures, and refuses to sign a misaligned one. So if the high-confidence claim is ever wrong, it fails loudly at the first real APK instead of shipping something that misbehaves on a device. The comment even says the fix would be to pull in androidenv's zipalign rather than weaken the check, which is the right instruction to leave behind.
What I changed
The README's "Not hard-coded" section claimed no organisation appears "in a path, URL or workflow reference" — while the uses: examples directly above necessarily contain https://git.chaosbit.de/weblib/fdroid-repo-ci@main, because that is what a consumer types.
A rule a file visibly breaks is worse than no rule: the next person either "fixes" the examples or stops trusting the section. Narrowed to what is true — consumer-specific values and any named consumer — with this repo's own address called out as the one deliberate exception, which doubles as a pointer to the exact lines to edit when the repo moves to chaosbit.
Worth keeping from the report
Two findings that would each have cost someone an afternoon, both now in comments where they were needed:
fdroid update needs JAVA_HOME set explicitly. fdroidserver globs standard system JDK locations and a nix store path matches none of them, so it is derived at run time from whichever jarsigner the nix shell actually provided.
nix shell accepts several installables in one call, so with-nixpkgs.sh takes a space-separated attr list instead of needing a second wrapper layer.
Not done, correctly
No real S3 upload and no real flutter build apk. Both are deliberate, both are stated, and publish.sh is instead exercised against a closed loopback port — which proves the command is well-formed and genuinely attempts network I/O, rather than passing because it failed at argument parsing.
## Cold re-read. Merging, with one commit of my own.
**Public-repo check first, since that is the one that cannot be undone:** no key material committed — no `.jks`, no `.keystore`, no PEM. The password-shaped strings in the tests (`apppass123`, `AKIAFAKETESTKEY`, `fake-secret-not-real`) are fixtures for keystores generated and destroyed inside a temp dir; nothing real. Clean.
**`fdroid-update.sh` never writes a password into `config.yml`.** It uses fdroidserver's own `{env: VARNAME}` indirection — which exists precisely so the config can be committed — and `chmod 600`s it anyway. That is the right shape: the caller sets a variable, the file names it.
**Re-measured here:** `bash run_tests.sh` → **42 passed, 0 skipped, 0 failed**. Zero skips matters on a suite where the interesting cases need a real `apksigner` and a real `fdroid update` — a skip here would look exactly like a pass.
## The zipalign answer is well handled
The conclusion — `zipalign` unnecessary, `apksigner` alone suffices — rests on Google's docs plus fdroidserver 2.4.3 never shelling out to `zipalign`, and was **not** confirmed against a real Flutter build because that build was judged too expensive. That gap is stated rather than glossed.
What makes it acceptable is that the gap is *defended* rather than just disclosed: `check_zip_alignment.py` runs against **every APK `sign-apk.sh` is asked to sign**, not only the fixtures, and refuses to sign a misaligned one. So if the high-confidence claim is ever wrong, it fails loudly at the first real APK instead of shipping something that misbehaves on a device. The comment even says the fix would be to pull in androidenv's zipalign rather than weaken the check, which is the right instruction to leave behind.
## What I changed
The README's "Not hard-coded" section claimed no organisation appears "in a path, URL or workflow reference" — while the `uses:` examples directly above necessarily contain `https://git.chaosbit.de/weblib/fdroid-repo-ci@main`, because that is what a consumer types.
A rule a file visibly breaks is worse than no rule: the next person either "fixes" the examples or stops trusting the section. Narrowed to what is true — consumer-specific values and any named consumer — with this repo's own address called out as the one deliberate exception, which doubles as a pointer to the exact lines to edit when the repo moves to `chaosbit`.
## Worth keeping from the report
Two findings that would each have cost someone an afternoon, both now in comments where they were needed:
- **`fdroid update` needs `JAVA_HOME` set explicitly.** fdroidserver globs standard system JDK locations and a nix store path matches none of them, so it is derived at run time from whichever `jarsigner` the nix shell actually provided.
- **`nix shell` accepts several installables in one call**, so `with-nixpkgs.sh` takes a space-separated attr list instead of needing a second wrapper layer.
## Not done, correctly
No real S3 upload and no real `flutter build apk`. Both are deliberate, both are stated, and `publish.sh` is instead exercised against a closed loopback port — which proves the command is well-formed and genuinely attempts network I/O, rather than passing because it failed at argument parsing.
claude
merged commit c42c8a66a6 into main2026-09-09 06:38:58 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements fdroid-repo-ci#1: sign-apk.sh, fdroid-update.sh, publish.sh and the composite action, plus with-nixpkgs.sh/nixpkgs-pin for this repo's own nixpkgs pin since it has no flake.lock.
See the README for the zipalign measurement (apksigner is a standalone attribute, zipalign is not; Google's docs plus fdroidserver's own source say Gradle/AGP already aligns its output; check_zip_alignment.py is the safety net since apksigner does not check alignment itself) and for why no consumer repo, bucket or channel name is hard-coded anywhere.
bash run_tests.sh: 42 assertions across five suites, offline or local-store-only. Full report in the issue/PR thread.Cold re-read. Merging, with one commit of my own.
Public-repo check first, since that is the one that cannot be undone: no key material committed — no
.jks, no.keystore, no PEM. The password-shaped strings in the tests (apppass123,AKIAFAKETESTKEY,fake-secret-not-real) are fixtures for keystores generated and destroyed inside a temp dir; nothing real. Clean.fdroid-update.shnever writes a password intoconfig.yml. It uses fdroidserver's own{env: VARNAME}indirection — which exists precisely so the config can be committed — andchmod 600s it anyway. That is the right shape: the caller sets a variable, the file names it.Re-measured here:
bash run_tests.sh→ 42 passed, 0 skipped, 0 failed. Zero skips matters on a suite where the interesting cases need a realapksignerand a realfdroid update— a skip here would look exactly like a pass.The zipalign answer is well handled
The conclusion —
zipalignunnecessary,apksigneralone suffices — rests on Google's docs plus fdroidserver 2.4.3 never shelling out tozipalign, and was not confirmed against a real Flutter build because that build was judged too expensive. That gap is stated rather than glossed.What makes it acceptable is that the gap is defended rather than just disclosed:
check_zip_alignment.pyruns against every APKsign-apk.shis asked to sign, not only the fixtures, and refuses to sign a misaligned one. So if the high-confidence claim is ever wrong, it fails loudly at the first real APK instead of shipping something that misbehaves on a device. The comment even says the fix would be to pull in androidenv's zipalign rather than weaken the check, which is the right instruction to leave behind.What I changed
The README's "Not hard-coded" section claimed no organisation appears "in a path, URL or workflow reference" — while the
uses:examples directly above necessarily containhttps://git.chaosbit.de/weblib/fdroid-repo-ci@main, because that is what a consumer types.A rule a file visibly breaks is worse than no rule: the next person either "fixes" the examples or stops trusting the section. Narrowed to what is true — consumer-specific values and any named consumer — with this repo's own address called out as the one deliberate exception, which doubles as a pointer to the exact lines to edit when the repo moves to
chaosbit.Worth keeping from the report
Two findings that would each have cost someone an afternoon, both now in comments where they were needed:
fdroid updateneedsJAVA_HOMEset explicitly. fdroidserver globs standard system JDK locations and a nix store path matches none of them, so it is derived at run time from whicheverjarsignerthe nix shell actually provided.nix shellaccepts several installables in one call, sowith-nixpkgs.shtakes a space-separated attr list instead of needing a second wrapper layer.Not done, correctly
No real S3 upload and no real
flutter build apk. Both are deliberate, both are stated, andpublish.shis instead exercised against a closed loopback port — which proves the command is well-formed and genuinely attempts network I/O, rather than passing because it failed at argument parsing.