Add pinned Docker APK builds and release signing #13

Merged
justinzeus merged 2 commits from fm/wolfskin-4-build into main 2026-10-22 14:44:21 +02:00
Owner

Outcome

make apk builds clean, signed default-flavor release APKs entirely inside Docker, including the real upstream native codec extensions. No host Java/SDK is required.

Closes justinzeus/wolfskin#4

Changes

  • Add Make targets for release APKs, secret-free debug APKs, upstream JVM tests, image builds, and local release markers.
  • Pin Temurin JDK 21.0.11+10 by digest, Android command-line tools 23.0 and platform-tools 37.0.1 by archive/checksum, build-tools 36.0.0, and SDK platform 37.0 revision 2. A platform revision change fails the image build until deliberately accepted.
  • Keep Gradle caches in named volume wolfskin-gradle; run the committed wrapper with --no-daemon, host UID/GID, and four workers. Support linked worktrees by mounting Git metadata read-only.
  • Mount the separate Wolfskin signing escrow read-only and select it through upstream's local.properties / release.signing.config hook. Load the dedicated GitHub Packages token inside the container, never in Docker command arguments. Escrow location is overridable with WOLFSKIN_SECRETS_DIR.
  • Add make release-tag: create lightweight UTC pwolfskin-YYYY_MM_DD[_N] markers locally so upstream's tag-count versionCode increases. It never pushes a tag or publishes a release.
  • Document setup, credential requirements, versioning, and verification in docker/README.md. All upstream Gradle source, version catalog, and .github/workflows remain untouched.

Clean signed build proof

At pushed HEAD 3094fc8cf09e8dfa1f9d48e1524a92410bec4fe9, make apk ran clean assembleDefaultRelease successfully: 6m26s Gradle / 6m30.174s wall, all 70 tasks executed. It produced armeabi-v7a, arm64-v8a, x86_64, and universal APKs.

  • Built image index: sha256:b9d8c758e8d909734d7527c70cc208107396bac13392e1b4366a12732742a012
  • Image content manifest: sha256:c4b096368df9d8e03d9c383b5c29a72bf1a8c333a4b5ac1ca7f296481c1560c7
  • Pinned Temurin base: sha256:dbfd085220ae632a0830166e443747d1ee89e9038d92e3b48c3e5e9d8292b9a7

apksigner verify --verbose --print-certs passed on all four APKs, with v1/v2/v3 signatures and RSA 4096. Every APK certificate SHA-256 matches keytool -list -v on the escrowed wolfskin alias:

5cd708ffa94cc22fa41648377a305af3afe0f6da394c57e67024df43c1fcb5a0

The new PKCS12 key was generated inside the image, with 36,500-day validity (through 2126-09-28), independently of wolfskin-android. The escrow directory is 0700 and its keystore, password properties, token, and README are 0600. No secret is committed or included here.

aapt2 dump badging reports the following on every release APK:

versionCode='53' versionName='1.0.9-2-g3094fc8c'

The full human release name from Git is v1.0.9-2-g3094fc8c; upstream removes the leading v in the APK versionName. No project release marker was created or pushed during this task.

Native codec proof

Dependency insight on defaultReleaseRuntimeClasspath resolves wholphin-mpv, decoder-ffmpeg, and decoder-av1 to 0.2.4, exactly as the unchanged upstream catalog specifies. No stub project participates in the release assembly.

APK ZIP inspection and ELF-header checks confirm real shared objects with the correct CPU architecture in each split and in the universal APK. Uncompressed sizes:

ABI libmpv.so bytes libffmpegJNI.so bytes libavcodec.so bytes
armeabi-v7a 5,188,052 1,216,296 11,782,184
arm64-v8a 5,756,024 1,333,192 12,328,944
x86_64 5,906,128 1,399,272 16,500,368

libdav1dJNI.so is also a real ELF shared object for all three ABIs.

Additional checks and limits

  • make apk-debug passed without secrets and generated all four default debug APKs; those deliberately use upstream's stub and are not playback-complete.
  • make test passed: 382 tests, zero failures, zero skips. Test runtime 1m12.73s; Gradle 2m44s. Upstream exposes no testDefaultReleaseUnitTest; the target uses the closest existing testDefaultDebugUnitTest without changing test configuration.
  • Release-tag smoke in a temporary repository created consecutive lightweight date markers and advanced versionCode 1 → 2 → 3. The temporary repository was removed.
  • Missing release credentials fail closed before Gradle can ship a stubbed release.
  • apksigner emits upstream META-INF v1 metadata warnings; v2/v3 verification succeeds. APK byte reproducibility is not claimed because upstream embeds a build timestamp.
  • No TV installation, playback runtime check, release publication, or tag push performed. Package identity/branding remain upstream in this build-only change.
  • Root disk remains 84% used / 70 GiB free. No host /tmp scratch remains. Full logs and ELF proof are under /root/fm-home/data/wolfskin-4-build/.

AI-assisted implementation and validation.

## Outcome `make apk` builds clean, signed default-flavor release APKs entirely inside Docker, including the real upstream native codec extensions. No host Java/SDK is required. Closes justinzeus/wolfskin#4 ## Changes - Add Make targets for release APKs, secret-free debug APKs, upstream JVM tests, image builds, and local release markers. - Pin Temurin JDK 21.0.11+10 by digest, Android command-line tools 23.0 and platform-tools 37.0.1 by archive/checksum, build-tools 36.0.0, and SDK platform 37.0 revision 2. A platform revision change fails the image build until deliberately accepted. - Keep Gradle caches in named volume `wolfskin-gradle`; run the committed wrapper with `--no-daemon`, host UID/GID, and four workers. Support linked worktrees by mounting Git metadata read-only. - Mount the separate Wolfskin signing escrow read-only and select it through upstream's `local.properties` / `release.signing.config` hook. Load the dedicated GitHub Packages token inside the container, never in Docker command arguments. Escrow location is overridable with `WOLFSKIN_SECRETS_DIR`. - Add `make release-tag`: create lightweight UTC `pwolfskin-YYYY_MM_DD[_N]` markers locally so upstream's tag-count versionCode increases. It never pushes a tag or publishes a release. - Document setup, credential requirements, versioning, and verification in `docker/README.md`. All upstream Gradle source, version catalog, and `.github/workflows` remain untouched. ## Clean signed build proof At pushed HEAD `3094fc8cf09e8dfa1f9d48e1524a92410bec4fe9`, `make apk` ran `clean assembleDefaultRelease` successfully: **6m26s Gradle / 6m30.174s wall**, all 70 tasks executed. It produced armeabi-v7a, arm64-v8a, x86_64, and universal APKs. - Built image index: `sha256:b9d8c758e8d909734d7527c70cc208107396bac13392e1b4366a12732742a012` - Image content manifest: `sha256:c4b096368df9d8e03d9c383b5c29a72bf1a8c333a4b5ac1ca7f296481c1560c7` - Pinned Temurin base: `sha256:dbfd085220ae632a0830166e443747d1ee89e9038d92e3b48c3e5e9d8292b9a7` `apksigner verify --verbose --print-certs` passed on **all four APKs**, with v1/v2/v3 signatures and RSA 4096. Every APK certificate SHA-256 matches `keytool -list -v` on the escrowed `wolfskin` alias: ```text 5cd708ffa94cc22fa41648377a305af3afe0f6da394c57e67024df43c1fcb5a0 ``` The new PKCS12 key was generated inside the image, with 36,500-day validity (through 2126-09-28), independently of wolfskin-android. The escrow directory is 0700 and its keystore, password properties, token, and README are 0600. No secret is committed or included here. `aapt2 dump badging` reports the following on every release APK: ```text versionCode='53' versionName='1.0.9-2-g3094fc8c' ``` The full human release name from Git is `v1.0.9-2-g3094fc8c`; upstream removes the leading `v` in the APK versionName. No project release marker was created or pushed during this task. ## Native codec proof Dependency insight on `defaultReleaseRuntimeClasspath` resolves `wholphin-mpv`, `decoder-ffmpeg`, and `decoder-av1` to **0.2.4**, exactly as the unchanged upstream catalog specifies. No stub project participates in the release assembly. APK ZIP inspection and ELF-header checks confirm real shared objects with the correct CPU architecture in each split and in the universal APK. Uncompressed sizes: | ABI | libmpv.so bytes | libffmpegJNI.so bytes | libavcodec.so bytes | | --- | ---: | ---: | ---: | | armeabi-v7a | 5,188,052 | 1,216,296 | 11,782,184 | | arm64-v8a | 5,756,024 | 1,333,192 | 12,328,944 | | x86_64 | 5,906,128 | 1,399,272 | 16,500,368 | `libdav1dJNI.so` is also a real ELF shared object for all three ABIs. ## Additional checks and limits - `make apk-debug` passed without secrets and generated all four default debug APKs; those deliberately use upstream's stub and are not playback-complete. - `make test` passed: **382 tests, zero failures, zero skips**. Test runtime 1m12.73s; Gradle 2m44s. Upstream exposes no `testDefaultReleaseUnitTest`; the target uses the closest existing `testDefaultDebugUnitTest` without changing test configuration. - Release-tag smoke in a temporary repository created consecutive lightweight date markers and advanced versionCode 1 → 2 → 3. The temporary repository was removed. - Missing release credentials fail closed before Gradle can ship a stubbed release. - `apksigner` emits upstream META-INF v1 metadata warnings; v2/v3 verification succeeds. APK byte reproducibility is not claimed because upstream embeds a build timestamp. - No TV installation, playback runtime check, release publication, or tag push performed. Package identity/branding remain upstream in this build-only change. - Root disk remains 84% used / 70 GiB free. No host `/tmp` scratch remains. Full logs and ELF proof are under `/root/fm-home/data/wolfskin-4-build/`. AI-assisted implementation and validation.
justinzeus added 2 commits 2026-10-22 13:13:18 +02:00
Pin platform tools and use the upstream debug unit task
Some checks failed
review/security-claude Pass at 3094fc8c; no blocking findings
review/code-claude Pass at 3094fc8c; no blocking findings
PR / pre-commit (pull_request) Has been cancelled
PR / build (pull_request) Has been cancelled
3094fc8cf0
Author
Owner

Security and code review: pass (reviewed SHA 3094fc8cf09e8dfa1f9d48e1524a92410bec4fe9)

Security: pass. Code: pass. No blocking findings.

Evidence from a fresh clean make apk on this head (BUILD SUCCESSFUL in 5m09s, signer SHA-256 5cd708ff…c1fcb5a0 on all four APKs, versionCode 53 / versionName 1.0.9-2-g3094fc8c, real libmpv/libffmpegJNI/libdav1dJNI in the splits):

  • The GitHub token and the keystore passwords were read from escrow into shell variables and grepped for, together with the ghp_ prefix. Zero matches in the build log, the whole worktree including app/build and the APKs, the wolfskin-gradle volume, the committed tree, and docker save of the build image. docker history and the image env are clean.
  • During the build, the token appeared in no ps argv and not in the container .Config.Env or .Args. The escrow, local.properties and the Git common dir are mounted rw=false.
  • make apk with a missing escrow fails before docker run. make apk-debug succeeds with WOLFSKIN_SECRETS_DIR pointed at a nonexistent path.
  • release-tag.sh only creates a new local tag (no -f, no push or remote path). A scratch-repo test confirmed it skips existing tags (_1, _2) and refuses a dirty tree.
  • Only Makefile and docker/* are added; no upstream source, catalog or workflow changes.

Non-blocking notes:

  1. Docker leaves an empty local.properties mountpoint in the checkout after a release build (gitignored, no secret).
  2. The token is in the Gradle JVMs' /proc/<pid>/environ, which only the building user (root on tank) can read. This is upstream's hook.
  3. export X=$(cat …) masks a cat failure (docker/gradle.sh:28). The host-side -s check covers this.
  4. The platform-revision loop passes if Pkg.Revision is absent (docker/Dockerfile:15-17).
  5. Unversioned apt git/unzip. Upstream's wrapper has no distributionSha256Sum. Both are inherited or minor.
## Security and code review: pass (reviewed SHA `3094fc8cf09e8dfa1f9d48e1524a92410bec4fe9`) **Security: pass. Code: pass. No blocking findings.** Evidence from a fresh clean `make apk` on this head (BUILD SUCCESSFUL in 5m09s, signer SHA-256 `5cd708ff…c1fcb5a0` on all four APKs, versionCode 53 / versionName `1.0.9-2-g3094fc8c`, real `libmpv`/`libffmpegJNI`/`libdav1dJNI` in the splits): - The GitHub token and the keystore passwords were read from escrow into shell variables and grepped for, together with the `ghp_` prefix. Zero matches in the build log, the whole worktree including `app/build` and the APKs, the `wolfskin-gradle` volume, the committed tree, and `docker save` of the build image. `docker history` and the image env are clean. - During the build, the token appeared in no `ps` argv and not in the container `.Config.Env` or `.Args`. The escrow, `local.properties` and the Git common dir are mounted `rw=false`. - `make apk` with a missing escrow fails before `docker run`. `make apk-debug` succeeds with `WOLFSKIN_SECRETS_DIR` pointed at a nonexistent path. - `release-tag.sh` only creates a new local tag (no `-f`, no push or remote path). A scratch-repo test confirmed it skips existing tags (`_1`, `_2`) and refuses a dirty tree. - Only `Makefile` and `docker/*` are added; no upstream source, catalog or workflow changes. Non-blocking notes: 1. Docker leaves an empty `local.properties` mountpoint in the checkout after a release build (gitignored, no secret). 2. The token is in the Gradle JVMs' `/proc/<pid>/environ`, which only the building user (root on tank) can read. This is upstream's hook. 3. `export X=$(cat …)` masks a `cat` failure (`docker/gradle.sh:28`). The host-side `-s` check covers this. 4. The platform-revision loop passes if `Pkg.Revision` is absent (`docker/Dockerfile:15-17`). 5. Unversioned apt `git`/`unzip`. Upstream's wrapper has no `distributionSha256Sum`. Both are inherited or minor.
justinzeus force-pushed fm/wolfskin-4-build from 3094fc8cf0 to 19adf54c23 2026-10-22 14:43:08 +02:00 Compare
justinzeus merged commit f312138a75 into main 2026-10-22 14:44:21 +02:00
Sign in to join this conversation.
No reviewers
No labels
exploring
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: justinzeus/wolfskin#13
No description provided.