Add pinned Docker APK builds and release signing #13
7 changed files with 214 additions and 0 deletions
21
Makefile
Normal file
21
Makefile
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
.DEFAULT_GOAL := apk
|
||||
WOLFSKIN_BUILD_IMAGE ?= wolfskin-build:sdk37-jdk21
|
||||
WOLFSKIN_GRADLE_VOLUME ?= wolfskin-gradle
|
||||
WOLFSKIN_SECRETS_DIR ?= /opt/stacks/ventoy/machine-parity/manifest-tank/secrets/wolfskin
|
||||
export WOLFSKIN_BUILD_IMAGE WOLFSKIN_GRADLE_VOLUME WOLFSKIN_SECRETS_DIR
|
||||
|
||||
.PHONY: build-image apk apk-debug test release-tag
|
||||
build-image:
|
||||
docker build --platform linux/amd64 --tag "$(WOLFSKIN_BUILD_IMAGE)" docker
|
||||
|
||||
apk: build-image
|
||||
@bash docker/gradle.sh release clean assembleDefaultRelease
|
||||
|
||||
apk-debug: build-image
|
||||
@bash docker/gradle.sh debug assembleDefaultDebug
|
||||
|
||||
test: build-image
|
||||
@bash docker/gradle.sh debug testDefaultDebugUnitTest
|
||||
|
||||
release-tag:
|
||||
@bash docker/release-tag.sh
|
||||
24
docker/Dockerfile
Normal file
24
docker/Dockerfile
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
FROM eclipse-temurin:21.0.11_10-jdk-jammy@sha256:dbfd085220ae632a0830166e443747d1ee89e9038d92e3b48c3e5e9d8292b9a7
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends git unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
ENV ANDROID_HOME=/opt/android-sdk ANDROID_SDK_ROOT=/opt/android-sdk
|
||||
ENV PATH="/opt/android-sdk/cmdline-tools/23.0/bin:/opt/android-sdk/build-tools/36.0.0:${PATH}"
|
||||
RUN curl -fsSL https://dl.google.com/android/repository/commandlinetools-linux-16111833_latest.zip -o /tmp/tools.zip \
|
||||
&& echo 'e025545c62a8e64c7559119566a569fb1dec5f60 /tmp/tools.zip' | sha1sum -c - \
|
||||
&& mkdir -p "$ANDROID_HOME/cmdline-tools" \
|
||||
&& unzip -q /tmp/tools.zip -d /tmp/android-tools \
|
||||
&& mv /tmp/android-tools/cmdline-tools "$ANDROID_HOME/cmdline-tools/23.0" \
|
||||
&& rm -rf /tmp/tools.zip /tmp/android-tools \
|
||||
&& yes | sdkmanager --licenses >/dev/null \
|
||||
&& sdkmanager 'platforms;android-37.0' 'build-tools;36.0.0' \
|
||||
&& while IFS='=' read -r name value; do \
|
||||
if [ "$name" = Pkg.Revision ]; then test "$value" = 2 || exit 1; fi; \
|
||||
done < "$ANDROID_HOME/platforms/android-37.0/source.properties" \
|
||||
&& curl -fsSL https://dl.google.com/android/repository/platform-tools_r37.0.1-linux.zip -o /tmp/platform-tools.zip \
|
||||
&& echo '477254aa5f903c15cf51001717bdf347fb6b53e0 /tmp/platform-tools.zip' | sha1sum -c - \
|
||||
&& unzip -q /tmp/platform-tools.zip -d "$ANDROID_HOME" \
|
||||
&& rm /tmp/platform-tools.zip \
|
||||
&& mkdir -m 1777 /gradle
|
||||
ENV GRADLE_USER_HOME=/gradle HOME=/tmp
|
||||
COPY signing.init.gradle /opt/wolfskin/signing.init.gradle
|
||||
100
docker/README.md
Normal file
100
docker/README.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# Wolfskin TV container builds
|
||||
|
||||
Run from a full Git checkout with the upstream `v*` and `p*` tags available. The
|
||||
host needs Docker, GNU Make, Bash, and Git, not Java or the Android SDK.
|
||||
|
||||
```sh
|
||||
git fetch origin --tags
|
||||
make apk # clean, signed default release: three ABI splits + universal
|
||||
make test # upstream testDefaultDebugUnitTest in the same image, no secrets
|
||||
make apk-debug # default debug, no secrets; MPV stub and no extra codec decoders
|
||||
```
|
||||
|
||||
APKs remain under `app/build/outputs/apk/default/{release,debug}/`. Debug builds
|
||||
are not playback-complete and must not be distributed as releases. Upstream's
|
||||
`.github/workflows` and dependency version catalog are unchanged; release builds
|
||||
use the catalog's exact Wholphin extension version (currently `0.2.4`). Do not
|
||||
place local extension AARs in `app/libs`: upstream prefers them over the pinned
|
||||
GitHub Packages dependencies.
|
||||
|
||||
Upstream enables JVM unit tests only for debug variants; there is no
|
||||
`testDefaultReleaseUnitTest` task. `make test` uses the closest default-flavor
|
||||
task without changing upstream's test configuration.
|
||||
|
||||
## Toolchain and disk use
|
||||
|
||||
`Dockerfile` pins Temurin JDK `21.0.11+10` by image digest, command-line tools
|
||||
`23.0` and platform-tools `37.0.1` by archive and checksum, and build-tools
|
||||
`36.0.0` by SDK package version. The SDK manager registers platform `37.0` and
|
||||
its licenses; the build rejects any platform revision other than 2. An SDK
|
||||
revision upgrade therefore requires a deliberate Dockerfile update.
|
||||
Gradle comes from the committed wrapper. The image is built for `linux/amd64`;
|
||||
the default local image tag is `wolfskin-build:sdk37-jdk21`.
|
||||
Upstream's build timestamp means APKs are not byte-for-byte reproducible.
|
||||
|
||||
Gradle downloads and caches live in the named volume `wolfskin-gradle`, not
|
||||
`/tmp`. Module outputs stay in the checkout. Builds run with the host UID/GID,
|
||||
`--no-daemon`, and at most four workers. The repository's Git metadata is mounted
|
||||
read-only, including when the checkout is a linked worktree.
|
||||
|
||||
Override `WOLFSKIN_BUILD_IMAGE` to change the locally built image tag and
|
||||
`WOLFSKIN_GRADLE_VOLUME` to isolate a cache. `make build-image` builds only the
|
||||
image. Do not delete shared caches during another build.
|
||||
|
||||
## Release credentials
|
||||
|
||||
The default escrow is
|
||||
`/opt/stacks/ventoy/machine-parity/manifest-tank/secrets/wolfskin/`. Check
|
||||
`mountpoint -q /mnt/ventoy` before using that path. Set `WOLFSKIN_SECRETS_DIR`
|
||||
to use another existing directory. Its required files are:
|
||||
|
||||
| File | Contents |
|
||||
| --- | --- |
|
||||
| `release.jks` | Separate Wolfskin PKCS12 keystore, RSA 4096, alias `wolfskin` |
|
||||
| `release.properties` | Java properties `storePassword` and `keyPassword` |
|
||||
| `github-packages-token` | GitHub token with `read:packages`, username `JustinZeus` |
|
||||
|
||||
The directory must be mode `0700`, each file mode `0600`, owned by the building
|
||||
user. Never commit them or put their contents in logs. Back up the keystore and
|
||||
passwords together: losing the release key forces a reinstall. Do not reuse
|
||||
wolfskin-android's key or the gh CLI login token.
|
||||
|
||||
Release builds mount escrow read-only. The token is loaded inside the container
|
||||
into upstream's `ORG_GRADLE_PROJECT_WholphinExtensionsUsername` and
|
||||
`ORG_GRADLE_PROJECT_WholphinExtensionsPassword` environment hooks, never a Docker
|
||||
command argument. The signing init script registers `wolfskin`; the nonsecret
|
||||
`release-signing.properties` is mounted as `local.properties` so upstream's
|
||||
`release.signing.config` hook selects it. Any host `local.properties` is hidden
|
||||
only for the duration of this container. Keystore and passwords stay outside the
|
||||
checkout and image. Missing credentials fail the release rather than silently
|
||||
shipping the MPV stub.
|
||||
|
||||
## Versioning and local release markers
|
||||
|
||||
Upstream computes `versionName` from
|
||||
`git describe --tags --long --match='v*'`, dropping the leading `v`, and computes
|
||||
`versionCode` from the count of all local `v*` and `p*` tags. Preserve and fetch
|
||||
those tags; a shallow or incomplete checkout can give an incorrect version.
|
||||
Use the full `git describe` output as the human release name.
|
||||
|
||||
After committing the intended release, with a clean checkout and fetched tags:
|
||||
|
||||
```sh
|
||||
make release-tag
|
||||
make apk
|
||||
```
|
||||
|
||||
The helper creates one lightweight `pwolfskin-YYYY_MM_DD` tag in UTC, appending
|
||||
`_1`, `_2`, etc. on collision. Each marker adds one to `versionCode` without
|
||||
changing upstream's `v*`-based version name. It never pushes tags, publishes a
|
||||
release, or installs an APK. Do not delete published release markers. Tag
|
||||
creation and publication must be serialized between release operators.
|
||||
|
||||
## Inspecting release artifacts
|
||||
|
||||
Use `apksigner verify --verbose --print-certs APK` and
|
||||
`aapt2 dump badging APK` inside the build image. Compare the signer SHA-256 with
|
||||
`keytool -list -v -alias wolfskin -keystore /path/to/release.jks` (enter its
|
||||
password privately). Inspect APK ZIP entries for real native MPV and ffmpeg
|
||||
libraries for each shipped ABI; a successful Java compilation alone does not
|
||||
prove that codec extensions are included.
|
||||
33
docker/gradle.sh
Executable file
33
docker/gradle.sh
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env bash
|
||||
# Run the upstream wrapper with a persistent cache and read-only build credentials.
|
||||
set -euo pipefail
|
||||
mode=$1
|
||||
shift
|
||||
root=$(git rev-parse --show-toplevel)
|
||||
git_common=$(git rev-parse --path-format=absolute --git-common-dir)
|
||||
image=${WOLFSKIN_BUILD_IMAGE:-wolfskin-build:sdk37-jdk21}
|
||||
cache=${WOLFSKIN_GRADLE_VOLUME:-wolfskin-gradle}
|
||||
secrets=${WOLFSKIN_SECRETS_DIR:-/opt/stacks/ventoy/machine-parity/manifest-tank/secrets/wolfskin}
|
||||
arguments=(--rm --platform linux/amd64 --user "$(id -u):$(id -g)"
|
||||
--mount "type=bind,src=$root,dst=$root" --workdir "$root"
|
||||
--mount "type=volume,src=$cache,dst=/gradle"
|
||||
--mount "type=bind,src=$git_common,dst=$git_common,readonly")
|
||||
gradle_arguments=(--no-daemon --console=plain --max-workers=4)
|
||||
if [[ $mode == release ]]; then
|
||||
for name in release.jks release.properties github-packages-token; do
|
||||
if [[ ! -s $secrets/$name ]]; then
|
||||
printf 'Missing build credential: %s/%s\n' "$secrets" "$name" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
arguments+=(--mount "type=bind,src=$secrets,dst=/run/wolfskin,readonly"
|
||||
--mount "type=bind,src=$root/docker/release-signing.properties,dst=$root/local.properties,readonly")
|
||||
gradle_arguments+=(--init-script /opt/wolfskin/signing.init.gradle)
|
||||
docker run "${arguments[@]}" "$image" bash -euc '
|
||||
export ORG_GRADLE_PROJECT_WholphinExtensionsUsername=JustinZeus
|
||||
export ORG_GRADLE_PROJECT_WholphinExtensionsPassword=$(cat /run/wolfskin/github-packages-token)
|
||||
exec ./gradlew "$@"
|
||||
' bash "${gradle_arguments[@]}" "$@"
|
||||
else
|
||||
docker run "${arguments[@]}" "$image" ./gradlew "${gradle_arguments[@]}" "$@"
|
||||
fi
|
||||
1
docker/release-signing.properties
Normal file
1
docker/release-signing.properties
Normal file
|
|
@ -0,0 +1 @@
|
|||
release.signing.config=wolfskin
|
||||
18
docker/release-tag.sh
Executable file
18
docker/release-tag.sh
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
# Add one lightweight release marker without publishing it.
|
||||
set -euo pipefail
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
printf 'Commit or discard checkout changes before tagging a release.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
base="pwolfskin-$(date -u +%Y_%m_%d)"
|
||||
tag=$base
|
||||
sequence=1
|
||||
while git show-ref --verify --quiet "refs/tags/$tag"; do
|
||||
tag="${base}_${sequence}"
|
||||
sequence=$((sequence + 1))
|
||||
done
|
||||
git tag "$tag"
|
||||
printf 'Created %s locally; no tag was pushed.\n' "$tag"
|
||||
printf 'versionName: %s\n' "$(git describe --tags --long --match='v*' | cut -c 2-)"
|
||||
printf 'versionCode: %s\n' "$(git tag --list 'v*' 'p*' | wc -l)"
|
||||
17
docker/signing.init.gradle
Normal file
17
docker/signing.init.gradle
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Keep signing material outside the checkout; upstream selects this configuration via local.properties.
|
||||
allprojects {
|
||||
pluginManager.withPlugin('com.android.application') {
|
||||
def passwords = new Properties()
|
||||
file('/run/wolfskin/release.properties').withInputStream { passwords.load(it) }
|
||||
android.signingConfigs.create('wolfskin') {
|
||||
storeFile = file('/run/wolfskin/release.jks')
|
||||
storePassword = passwords.getProperty('storePassword')
|
||||
keyPassword = passwords.getProperty('keyPassword')
|
||||
keyAlias = 'wolfskin'
|
||||
enableV1Signing = true
|
||||
enableV2Signing = true
|
||||
enableV3Signing = true
|
||||
enableV4Signing = true
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue