From f3f74428497c55d064f162ce0bd07c186543b324 Mon Sep 17 00:00:00 2001 From: ml Date: Thu, 2 Jul 2026 13:07:52 +0200 Subject: [PATCH] dist.sh builds versioned, signed artifacts for both apps from a git tag (semver drives Android versionCode/versionName and package.json). The build-image/ Dockerfile pins the same toolchain for a portable, containerized Gitea Actions runner (build-image.sh builds and pushes it) so releases don't depend on any one machine's local setup. Pushing vX.Y.Z now builds and publishes a Gitea Release automatically. --- .gitea/workflows/release.yml | 58 +++++++++++++ .gitignore | 3 + README.md | 33 +++++++- build-image.sh | 45 ++++++++++ build-image/Dockerfile | 66 +++++++++++++++ build-image/VERSION | 1 + dist.sh | 119 ++++++++++++++++++++++++++ docs/publish.md | 160 ++++++++++++++++++++++++++++++----- registry.env.example | 6 ++ 9 files changed, 469 insertions(+), 22 deletions(-) create mode 100644 .gitea/workflows/release.yml create mode 100755 build-image.sh create mode 100644 build-image/Dockerfile create mode 100644 build-image/VERSION create mode 100755 dist.sh create mode 100644 registry.env.example diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..e9a11a5 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,58 @@ +name: release + +# Push a tag matching vX.Y.Z (e.g. v1.2.3) to build and publish a release. +# The tag drives the version — nothing to bump in source files beforehand. +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + +jobs: + build: + # Needed to create the release and upload assets with GITEA_TOKEN below, + # regardless of this instance's default Actions permission mode. + permissions: + contents: write + # Must match a label your act_runner is registered with. The runner's + # own default label-image is irrelevant here since `container:` below + # overrides the actual build image per-job. + runs-on: ubuntu-latest + container: + # :latest — always the most recently pushed build-image.sh output. + # Pin to a specific tag here (see build-image/VERSION) if you need a + # release build to be reproducible against an exact toolchain image. + image: cr.ladkau.de/schwert-und-magie/builder:latest + + steps: + - uses: actions/checkout@v4 + + - name: Write release signing credentials + run: | + set -euo pipefail + echo "${{ secrets.RELEASE_KEYSTORE_B64 }}" | base64 -d \ + > SchwertUndMagieOnPebbleCompanionApp/release.keystore + printf '%s\n' "${{ secrets.RELEASE_KEYSTORE_PROPERTIES }}" \ + > SchwertUndMagieOnPebbleCompanionApp/keystore.properties + + - name: Build artifacts + run: | + set -euo pipefail + VERSION="${{ gitea.ref_name }}" + VERSION="${VERSION#v}" + VERSION="$VERSION" ./dist.sh + + - name: Create release and upload artifacts + run: | + set -euo pipefail + API="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}" + AUTH="Authorization: token ${{ secrets.GITEA_TOKEN }}" + + RELEASE_ID="$(curl -sf -X POST "$API/releases" \ + -H "$AUTH" -H "Content-Type: application/json" \ + -d "{\"tag_name\": \"${{ gitea.ref_name }}\", \"name\": \"${{ gitea.ref_name }}\"}" \ + | jq -r .id)" + + for f in dist/*; do + curl -sf -X POST "$API/releases/$RELEASE_ID/assets?name=$(basename "$f")" \ + -H "$AUTH" -F "attachment=@$f" + done diff --git a/.gitignore b/.gitignore index 6b1f306..c77237b 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ local.properties *.jks *.keystore keystore.properties + +# Container registry push credentials for build-image.sh. See docs/publish.md. +registry.env diff --git a/README.md b/README.md index 3c7f2e9..dc5d361 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,18 @@ SchwertUndMagieOnPebbleWatchApp/ Pebble watchapp (C + PebbleKit JS) SchwertUndMagieOnPebbleCompanionApp/ Android companion app (Kotlin + NDK) versions/ Original .d64 disk images (4 disks) docs/ Architecture notes and diagrams -dist/ Built APKs (gitignored) +build-image/ Dockerfile for the release build environment +dist/ Build artifacts (gitignored) — see dist.sh ``` +Run `./dist.sh` to build release artifacts for both apps in one step: +`dist/schwert-und-magie-.{aab,apk,pbw}` — the signed Android AAB +(Play Store), APK (sideload), and Pebble `.pbw` (Rebble / direct install). +Requires the Android release keystore to already be configured +(`docs/publish.md` §2.1-2.3). The version comes from the current git tag by +default (`git tag v1.2.3`); see `docs/publish.md` §4 for pushing that tag to +trigger an automated, containerized build via Gitea Actions instead. + ## How it works Three processes cooperate across two devices: @@ -135,6 +144,28 @@ ensure register consistency — see `docs/architecture.md §4.4`. See [`docs/publish.md`](docs/publish.md) for keystore setup, Google Play, F-Droid, and Rebble submission. +### Automated releases (Gitea Actions) + +Pushing a tag `vX.Y.Z` builds both apps in a containerized runner and +publishes a Gitea Release with the versioned artifacts attached — see +`docs/publish.md` §4. One-time setup: + +1. Build and push the build environment image (`./build-image.sh`, needs + `registry.env` — copy from `registry.env.example`). +2. Register a self-hosted `act_runner` with a Docker executor. +3. Add repo secrets under **Settings → Actions → Secrets**: + + | Secret | Value | + |---|---| + | `RELEASE_KEYSTORE_B64` | `base64 -w0 SchwertUndMagieOnPebbleCompanionApp/release.keystore` | + | `RELEASE_KEYSTORE_PROPERTIES` | full contents of `SchwertUndMagieOnPebbleCompanionApp/keystore.properties` | + + `GITEA_TOKEN` is injected automatically per job — nothing to add for it. +4. `git tag v1.2.3 && git push origin v1.2.3`. + +Full details, including runner registration commands, are in +`docs/publish.md` §4. + ## License The watch app and companion app source code in this repository are released diff --git a/build-image.sh b/build-image.sh new file mode 100755 index 0000000..083d91b --- /dev/null +++ b/build-image.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Builds and pushes the release build environment image (build-image/Dockerfile) +# to the container registry. The Gitea Actions release workflow pulls this +# image to run dist.sh. Bump build-image/VERSION whenever the Dockerfile +# changes so the workflow can pin a stable tag. +set -euo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")" +ROOT="$(pwd)" + +fail() { echo "PREFLIGHT FAIL: $*" >&2; exit 1; } + +command -v docker >/dev/null 2>&1 \ + || fail "docker not found in PATH" + +[ -f "$ROOT/registry.env" ] \ + || fail "registry.env not found — copy registry.env.example to registry.env and fill in your cr.ladkau.de credentials" + +# shellcheck disable=SC1091 +source "$ROOT/registry.env" + +: "${REGISTRY:?registry.env must set REGISTRY}" +: "${REGISTRY_IMAGE:?registry.env must set REGISTRY_IMAGE}" +: "${REGISTRY_USER:?registry.env must set REGISTRY_USER}" +: "${REGISTRY_PASSWORD:?registry.env must set REGISTRY_PASSWORD}" + +VERSION="$(<"$ROOT/build-image/VERSION")" +[ -n "$VERSION" ] || fail "build-image/VERSION is empty" + +echo "== Logging in to $REGISTRY ==" +echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin + +echo "== Building $REGISTRY_IMAGE:$VERSION ==" +docker build \ + -t "$REGISTRY_IMAGE:$VERSION" \ + -t "$REGISTRY_IMAGE:latest" \ + -f "$ROOT/build-image/Dockerfile" \ + "$ROOT/build-image" + +echo "== Pushing $REGISTRY_IMAGE:$VERSION and :latest ==" +docker push "$REGISTRY_IMAGE:$VERSION" +docker push "$REGISTRY_IMAGE:latest" + +echo "== Done ==" +echo "Image: $REGISTRY_IMAGE:$VERSION" diff --git a/build-image/Dockerfile b/build-image/Dockerfile new file mode 100644 index 0000000..5ef052f --- /dev/null +++ b/build-image/Dockerfile @@ -0,0 +1,66 @@ +# Build environment for schwert_und_magie_on_pebble release artifacts: +# Android SDK/NDK (companion app) + Pebble SDK (watch app), matching the +# versions pinned in app/build.gradle.kts and validated on the maintainer's +# dev machine. Rebuild and push with ../build-image.sh whenever a version +# below, or the companion app's ndkVersion/compileSdk, changes. +# +# Does NOT contain the release keystore or any secrets — those are injected +# at job runtime from Gitea Actions secrets, never baked into this image. +FROM eclipse-temurin:21-jdk-jammy + +ARG ANDROID_CMDLINE_TOOLS_VERSION=11076708 +ARG ANDROID_PLATFORM=android-36 +ARG ANDROID_BUILD_TOOLS=36.1.0 +ARG ANDROID_NDK=30.0.14904198 +ARG PEBBLE_TOOL_VERSION=5.0.35 +ARG PEBBLE_SDK_CORE_VERSION=4.9.169 +ARG NODE_VERSION=24.16.0 + +ENV DEBIAN_FRONTEND=noninteractive \ + ANDROID_HOME=/opt/android-sdk \ + ANDROID_SDK_ROOT=/opt/android-sdk \ + ANDROID_NDK_HOME=/opt/android-sdk/ndk/30.0.14904198 \ + PATH=/root/.local/bin:/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools:${PATH} + +# git/unzip/curl/jq: checkout, SDK downloads, and the release workflow's +# calls to the Gitea API (create release, upload assets). +# python3-venv: pebble-tool's `sdk install` creates a venv per SDK version. +# dos2unix/autoconf/automake/pkg-config/xa65/build-essential/gettext: host +# tools required by the companion app's build_vice.sh (see that file's own +# preflight check) to cross-compile VICE via autotools before NDK clang +# takes over for the actual target compilation. +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl ca-certificates git unzip tar xz-utils python3 python3-venv file jq \ + dos2unix autoconf automake pkg-config xa65 build-essential gettext \ + && rm -rf /var/lib/apt/lists/* + +# --- Android SDK: cmdline-tools, platform, build-tools, NDK --- +RUN mkdir -p "$ANDROID_HOME/cmdline-tools" \ + && curl -sSL -o /tmp/cmdline-tools.zip \ + "https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS_VERSION}_latest.zip" \ + && unzip -q /tmp/cmdline-tools.zip -d "$ANDROID_HOME/cmdline-tools" \ + && mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest" \ + && rm /tmp/cmdline-tools.zip \ + && yes | sdkmanager --licenses >/dev/null \ + && sdkmanager --install \ + "platform-tools" \ + "platforms;${ANDROID_PLATFORM}" \ + "build-tools;${ANDROID_BUILD_TOOLS}" \ + "ndk;${ANDROID_NDK}" \ + >/dev/null + +# Node.js: `pebble sdk install` below runs `npm install` for the SDK-core's +# bundled webpack tooling — it does not bring its own node/npm, only the JS +# deps themselves. Version matches what's validated on the maintainer's +# machine; sdk-core's own bundled arm-none-eabi toolchain needs nothing extra. +RUN curl -sSL -o /tmp/node.tar.xz \ + "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \ + && tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 \ + && rm /tmp/node.tar.xz + +# --- Pebble SDK: pebble-tool + sdk-core --- +RUN curl -LsSf https://astral.sh/uv/install.sh | sh \ + && /root/.local/bin/uv tool install "pebble-tool==${PEBBLE_TOOL_VERSION}" \ + && /root/.local/bin/pebble sdk install "${PEBBLE_SDK_CORE_VERSION}" + +WORKDIR /workspace diff --git a/build-image/VERSION b/build-image/VERSION new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/build-image/VERSION @@ -0,0 +1 @@ +1 diff --git a/dist.sh b/dist.sh new file mode 100755 index 0000000..4630a65 --- /dev/null +++ b/dist.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# Builds release artifacts for both apps and drops them in dist/, named +# schwert-und-magie-.{aab,apk,pbw}. +# +# Version comes from the current git tag (vX.Y.Z) by default — push a tag to +# drive a release. Override with VERSION=1.2.3 ./dist.sh, or just run it +# untagged for a local dev build (gets a 0.0.0-dev+ placeholder version). +# +# Android output requires a release signing config — see docs/publish.md §2.1-2.3 +# (keystore.properties + release.keystore in SchwertUndMagieOnPebbleCompanionApp/). +# Without it, Gradle still produces an unsigned/debug-signed build; that's not +# fatal (useful for local testing) so it's a warning, not a hard stop. +set -euo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")" +ROOT="$(pwd)" +DIST="$ROOT/dist" +COMPANION="$ROOT/SchwertUndMagieOnPebbleCompanionApp" +WATCH="$ROOT/SchwertUndMagieOnPebbleWatchApp" +GRADLE_KTS="$COMPANION/app/build.gradle.kts" +WATCH_PKG_JSON="$WATCH/package.json" + +fail() { echo "PREFLIGHT FAIL: $*" >&2; exit 1; } +warn() { echo "WARNING: $*" >&2; } + +echo "== Preflight checks ==" + +command -v pebble >/dev/null 2>&1 \ + || fail "pebble CLI not found in PATH (needed to build the watch app)" + +[ -x "$COMPANION/gradlew" ] \ + || fail "$COMPANION/gradlew missing or not executable" + +SDK_DIR="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}" +if [ -f "$COMPANION/local.properties" ]; then + LOCAL_SDK="$(sed -n 's/^sdk\.dir=//p' "$COMPANION/local.properties")" + [ -n "$LOCAL_SDK" ] && SDK_DIR="$LOCAL_SDK" +fi +[ -n "$SDK_DIR" ] && [ -d "$SDK_DIR" ] \ + || fail "Android SDK not found (checked local.properties sdk.dir, \$ANDROID_SDK_ROOT, \$ANDROID_HOME)" + +NDK_VERSION="$(sed -n 's/.*ndkVersion *= *"\(.*\)".*/\1/p' "$GRADLE_KTS")" +[ -n "$NDK_VERSION" ] \ + || fail "could not read ndkVersion from app/build.gradle.kts" +[ -d "$SDK_DIR/ndk/$NDK_VERSION" ] \ + || fail "NDK $NDK_VERSION not installed under $SDK_DIR/ndk (Android Studio > SDK Manager > SDK Tools > NDK side by side)" + +[ -f "$COMPANION/res/vice-3.8.tar.gz" ] \ + || fail "$COMPANION/res/vice-3.8.tar.gz missing (VICE source tarball required by the buildVice Gradle task)" +compgen -G "$COMPANION/res/nibtools-*.tar.gz" >/dev/null \ + || fail "$COMPANION/res/nibtools-*.tar.gz missing (nibtools source tarball required by the buildNibtools Gradle task)" + +# Signing config: missing/broken keystore.properties still builds (unsigned), +# so warn here and rely on the post-build apksigner check for the real answer. +if [ ! -f "$COMPANION/keystore.properties" ]; then + warn "keystore.properties not found — release build will likely be unsigned (see docs/publish.md §2.1-2.3)" +else + STORE_FILE="$(sed -n 's/^storeFile=//p' "$COMPANION/keystore.properties")" + if [ -z "$STORE_FILE" ] || [ ! -f "$COMPANION/$STORE_FILE" ]; then + warn "keystore.properties found but its storeFile ('$STORE_FILE') does not exist — release build will likely be unsigned" + fi +fi + +echo "== Resolving version ==" + +if [ -z "${VERSION:-}" ]; then + if TAG="$(git describe --tags --exact-match --match 'v[0-9]*.[0-9]*.[0-9]*' 2>/dev/null)"; then + VERSION="${TAG#v}" + else + VERSION="0.0.0-dev+$(git rev-parse --short HEAD)" + warn "HEAD is not on a vX.Y.Z tag — building placeholder version $VERSION (push a tag to drive a real release version)" + fi +fi +[[ "$VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]] \ + || fail "VERSION '$VERSION' is not a semantic version (expected X.Y.Z, optionally with a -pre+meta suffix)" +VERSION_CODE=$(( ${BASH_REMATCH[1]} * 10000 + ${BASH_REMATCH[2]} * 100 + ${BASH_REMATCH[3]} )) +echo "Version: $VERSION (Android versionCode $VERSION_CODE)" + +# Patch versions into the tracked source files for this build only, then +# restore them — dist.sh must never leave the working tree dirty. +restore_version_files() { + git -C "$ROOT" checkout -- "$GRADLE_KTS" "$WATCH_PKG_JSON" 2>/dev/null || true +} +trap restore_version_files EXIT + +sed -i \ + -e "s/versionCode = [0-9]\+/versionCode = $VERSION_CODE/" \ + -e "s/versionName = \"[^\"]*\"/versionName = \"$VERSION\"/" \ + "$GRADLE_KTS" +sed -i -e "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" "$WATCH_PKG_JSON" + +mkdir -p "$DIST" + +echo "== Android companion app ==" +cd "$COMPANION" +./gradlew bundleRelease assembleRelease + +cp -f app/build/outputs/bundle/release/app-release.aab "$DIST/schwert-und-magie-$VERSION.aab" +cp -f app/build/outputs/apk/release/app-release.apk "$DIST/schwert-und-magie-$VERSION.apk" + +echo "== Pebble watch app ==" +cd "$WATCH" +pebble build + +cp -f build/SchwertUndMagieOnPebbleWatchApp.pbw "$DIST/schwert-und-magie-$VERSION.pbw" + +echo "== Verifying APK signature ==" +APK="$DIST/schwert-und-magie-$VERSION.apk" +APKSIGNER="$(compgen -G "$SDK_DIR/build-tools/*/apksigner" | sort -V | tail -1 || true)" +if [ -z "$APKSIGNER" ]; then + warn "apksigner not found under $SDK_DIR/build-tools — could not verify APK signature" +elif ! "$APKSIGNER" verify "$APK" >/dev/null 2>&1; then + warn "$APK is UNSIGNED (failed apksigner verification) — not installable/publishable as-is" +elif "$APKSIGNER" verify --print-certs "$APK" 2>/dev/null | grep -qi "CN=Android Debug"; then + warn "$APK is signed with the Android debug cert, not the release keystore" +fi + +echo "== Done ==" +ls -la "$DIST" diff --git a/docs/publish.md b/docs/publish.md index 1101a55..0705afa 100644 --- a/docs/publish.md +++ b/docs/publish.md @@ -89,14 +89,30 @@ the whole Gradle configuration. ### 2.4 Build the release bundle ```bash -cd SchwertUndMagieOnPebbleCompanionApp -./gradlew bundleRelease # produces app/build/outputs/bundle/release/app-release.aab -./gradlew assembleRelease # produces app/build/outputs/apk/release/app-release.apk, for sideload testing +./dist.sh # builds both apps, copies signed artifacts into dist/ ``` -Play Store requires the **AAB** (`bundleRelease` output), not the APK — Play +This runs `./gradlew bundleRelease assembleRelease` in +`SchwertUndMagieOnPebbleCompanionApp/` and `pebble build` in +`SchwertUndMagieOnPebbleWatchApp/`, then copies the outputs to +`dist/schwert-und-magie.{aab,apk,pbw}`. + +Before building, the script hard-stops (nonzero exit, nothing written to +`dist/`) if the `pebble` CLI, `gradlew`, the Android SDK, the NDK version +pinned in `app/build.gradle.kts`, or the VICE/nibtools source tarballs are +missing — these are required for the build to succeed at all. It warns but +still builds if `keystore.properties` (§2.1-2.3) is missing or points at a +nonexistent keystore file, and checks the resulting APK's signature with +`apksigner` afterward, warning if it's unsigned or debug-signed. In practice +an incomplete signing config also makes Gradle's own `signReleaseBundle` task +fail outright, so `dist/` won't be overwritten with an unusable artifact +either way — but don't rely on that as the primary check; heed the warning. + +Play Store requires the **AAB** (`schwert-und-magie.aab`), not the APK — Play re-packages per-device APKs from it (including ABI splits, so `arm64-v8a`/ -`x86_64` native VICE libraries each ship only to matching devices). +`x86_64` native VICE libraries each ship only to matching devices). The APK +(`schwert-und-magie.apk`) is for direct sideload distribution (§2.6 below) +and F-Droid-style testing, not Play Store upload. ### 2.5 Play Console setup (one-time, per app listing) @@ -119,12 +135,11 @@ re-packages per-device APKs from it (including ABI splits, so `arm64-v8a`/ ### 2.6 Versioning for future releases -Bump both fields in `app/build.gradle.kts` before every release build: - -```kotlin -versionCode = 2 // must strictly increase on every Play Store upload -versionName = "1.1" // user-visible, free-form -``` +Comes from the git tag automatically — see §4. Pushing `vX.Y.Z` (or running +`VERSION=X.Y.Z ./dist.sh` locally) patches `versionCode` (derived as +`X*10000 + Y*100 + Z`, which strictly increases as long as X.Y.Z itself does) +and `versionName` in `app/build.gradle.kts` at build time, then reverts them — +no manual edit needed. ## 3. Android companion app → F-Droid @@ -220,8 +235,102 @@ includes the app in the next index update (published roughly weekly). ### 3.5 Versioning for F-Droid updates Each new release requires a new `Builds` entry in the metadata file with an -incremented `versionCode` and the corresponding git commit or tag. `versionCode` -must match the value in `app/build.gradle.kts`. +incremented `versionCode` and the corresponding git commit or tag. +`versionCode` must match what that tag produces — `X*10000 + Y*100 + Z` for +tag `vX.Y.Z` (§2.6, §4). + +## 4. Automated releases via Gitea Actions + +Pushing a tag `vX.Y.Z` builds both apps and publishes a Gitea Release with +`schwert-und-magie-X.Y.Z.{aab,apk,pbw}` attached — no local `./dist.sh` run +needed. The tag is the only source of truth for the version; nothing needs to +be bumped in source files beforehand (`dist.sh` patches `versionCode`/ +`versionName`/`package.json` at build time and reverts them — see §4.4 for +running the same thing locally). + +The whole toolchain (Android SDK/NDK, Pebble SDK) lives in +`build-image/Dockerfile`, built and pushed to a container registry by +`build-image.sh`. This keeps the setup portable: the runner just needs Docker +and pulls that image, so it isn't tied to any one machine's local toolchain +install and can be moved or re-registered elsewhere without touching this +repo's build scripts. + +### 4.1 Build environment image + +`build-image/Dockerfile` bakes in the Android SDK/NDK and Pebble SDK versions +pinned in `app/build.gradle.kts`, matching what's validated for local builds. +It does **not** contain the release keystore — that's injected at job runtime +from Actions secrets (§4.3), never baked into the image. + +```bash +cp registry.env.example registry.env # fill in your registry credentials +./build-image.sh # builds + pushes :latest and the pinned VERSION tag +``` + +Rebuild and push whenever `build-image/Dockerfile` changes (e.g. a Pebble SDK +or Android NDK version bump) — bump `build-image/VERSION` first so the tag is +meaningful. The workflow (§4.2) pulls `:latest` by default; if you need a +release to be reproducible against an exact toolchain image, pin the `image:` +line in `.gitea/workflows/release.yml` to the versioned tag instead. + +### 4.2 Runner setup (one-time) + +1. Enable Actions for the repo: repo Settings → Actions → enable, if not + already on by default for this Gitea instance. +2. Generate a runner registration token: Site Admin → Actions → Runners (or + the repo/org-scoped equivalent) → "Create new runner". +3. On any machine with Docker that can reach both your Gitea instance and + your container registry: + ```bash + # https://gitea.com/gitea/act_runner — grab the latest release binary + ./act_runner register --no-interactive \ + --instance --token \ + --name --labels ubuntu-latest:docker://node:20-bookworm + ./act_runner daemon + ``` + The image after `docker://` in `--labels` is only a fallback for jobs that + don't specify their own `container:` — irrelevant here since + `.gitea/workflows/release.yml` always pins its own image, but the runner + still needs a Docker-executor label registered to use that executor at + all. The label name itself (`ubuntu-latest` above) must match `runs-on:` + in `.gitea/workflows/release.yml` — edit both together if you rename it, + or reuse a label an existing runner already advertises (check Site Admin → + Actions → Runners) to skip registering a new one entirely. +4. The runner's Docker daemon needs pull access to the registry — run + `docker login ` once on that machine with the same credentials + as `registry.env`. + +### 4.3 Repo secrets + +Settings → Actions → Secrets, add: + +| Secret | Value | +|---|---| +| `RELEASE_KEYSTORE_B64` | `base64 -w0 SchwertUndMagieOnPebbleCompanionApp/release.keystore` | +| `RELEASE_KEYSTORE_PROPERTIES` | the full contents of `SchwertUndMagieOnPebbleCompanionApp/keystore.properties` (§2.2) | + +`secrets.GITEA_TOKEN` (used to create the release and upload assets) is +Gitea's own auto-generated per-job token — nothing to create or add yourself. +`.gitea/workflows/release.yml` requests `contents: write` explicitly so +release creation works regardless of this instance's default Actions +permission mode. + +### 4.4 Cutting a release + +```bash +git tag v1.2.3 +git push origin v1.2.3 +``` + +Watch the run under the repo's Actions tab. On success, the release appears +under the repo's Releases page with the three versioned artifacts attached. + +To build the same versioned artifacts locally without pushing a tag (e.g. to +test before releasing): + +```bash +VERSION=1.2.3 ./dist.sh +``` ## 5. Pebble watch app → Rebble app store / direct distribution @@ -233,12 +342,19 @@ this repo and changes independently of it. ### 5.1 Build the artifact +```bash +./dist.sh # also builds the Android side; see §2.4 +``` + +or, to build just the watch app: + ```bash cd SchwertUndMagieOnPebbleWatchApp pebble build # produces build/SchwertUndMagieOnPebbleWatchApp.pbw ``` -The `.pbw` is the complete distributable — it bundles all target platforms +`dist.sh` copies the result to `dist/schwert-und-magie.pbw`. The `.pbw` is +the complete distributable — it bundles all target platforms (`aplite`/`basalt`/`chalk`/`diorite`/`emery`/`flint`/`gabbro`) declared in `package.json`. There is no signing step analogous to Android; Pebble apps are not cryptographically signed by the developer. @@ -267,19 +383,21 @@ pebble screenshot --vnc --no-open docs/screenshots/emery.png ### 5.4 Versioning -Bump `version` in `SchwertUndMagieOnPebbleWatchApp/package.json` before each -release build. +Comes from the git tag automatically — see §4. `dist.sh` patches +`SchwertUndMagieOnPebbleWatchApp/package.json`'s `version` field at build +time and reverts it afterward; no manual edit needed. ## 6. Pre-publish checklist - [ ] Release keystore generated, passwords saved in a password manager, both gitignored (§1, §2.1) - [ ] `keystore.properties` exists locally and is **not** tracked by git -- [ ] `./gradlew bundleRelease` succeeds and installs/runs on a real device - from the resulting AAB (test via `bundletool` or Play internal testing) +- [ ] `./dist.sh` (or a tag push, §4) succeeds and produces + `dist/schwert-und-magie-.{aab,apk,pbw}` +- [ ] AAB installs/runs on a real device (test via `bundletool` or Play + internal testing) - [ ] Play Console store listing content complete (icon, screenshots, privacy policy, content rating, data safety form) - [ ] F-Droid metadata YAML created and `fdroid build` passes locally (§3) -- [ ] `pebble build` succeeds for all target platforms; `.pbw` sideloads and - runs correctly against the signed companion app build -- [ ] `versionCode`/`versionName` (Android) and `version` (Pebble) bumped +- [ ] `.pbw` sideloads and runs correctly against the signed companion app + build diff --git a/registry.env.example b/registry.env.example new file mode 100644 index 0000000..4da921c --- /dev/null +++ b/registry.env.example @@ -0,0 +1,6 @@ +# Copy to registry.env (gitignored, never commit the real values) and fill in. +# Used by build-image.sh to push the build environment image to cr.ladkau.de. +REGISTRY=cr.ladkau.de +REGISTRY_IMAGE=cr.ladkau.de/schwert-und-magie/builder +REGISTRY_USER= +REGISTRY_PASSWORD=