dist.sh builds versioned, signed artifacts for both apps from a git tag
release / build (push) Failing after 0s

(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.
This commit is contained in:
ml
2026-07-02 13:07:52 +02:00
parent 73c6c90c8d
commit f3f7442849
9 changed files with 469 additions and 22 deletions
+58
View File
@@ -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
+3
View File
@@ -32,3 +32,6 @@ local.properties
*.jks *.jks
*.keystore *.keystore
keystore.properties keystore.properties
# Container registry push credentials for build-image.sh. See docs/publish.md.
registry.env
+32 -1
View File
@@ -38,9 +38,18 @@ SchwertUndMagieOnPebbleWatchApp/ Pebble watchapp (C + PebbleKit JS)
SchwertUndMagieOnPebbleCompanionApp/ Android companion app (Kotlin + NDK) SchwertUndMagieOnPebbleCompanionApp/ Android companion app (Kotlin + NDK)
versions/ Original .d64 disk images (4 disks) versions/ Original .d64 disk images (4 disks)
docs/ Architecture notes and diagrams 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-<version>.{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 ## How it works
Three processes cooperate across two devices: 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, See [`docs/publish.md`](docs/publish.md) for keystore setup, Google Play,
F-Droid, and Rebble submission. 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 ## License
The watch app and companion app source code in this repository are released The watch app and companion app source code in this repository are released
Executable
+45
View File
@@ -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"
+66
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
1
Executable
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
# Builds release artifacts for both apps and drops them in dist/, named
# schwert-und-magie-<version>.{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+<sha> 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"
+139 -21
View File
@@ -89,14 +89,30 @@ the whole Gradle configuration.
### 2.4 Build the release bundle ### 2.4 Build the release bundle
```bash ```bash
cd SchwertUndMagieOnPebbleCompanionApp ./dist.sh # builds both apps, copies signed artifacts into dist/
./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
``` ```
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`/ 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) ### 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 ### 2.6 Versioning for future releases
Bump both fields in `app/build.gradle.kts` before every release build: 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
```kotlin `X*10000 + Y*100 + Z`, which strictly increases as long as X.Y.Z itself does)
versionCode = 2 // must strictly increase on every Play Store upload and `versionName` in `app/build.gradle.kts` at build time, then reverts them —
versionName = "1.1" // user-visible, free-form no manual edit needed.
```
## 3. Android companion app → F-Droid ## 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 ### 3.5 Versioning for F-Droid updates
Each new release requires a new `Builds` entry in the metadata file with an Each new release requires a new `Builds` entry in the metadata file with an
incremented `versionCode` and the corresponding git commit or tag. `versionCode` incremented `versionCode` and the corresponding git commit or tag.
must match the value in `app/build.gradle.kts`. `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 <your gitea URL> --token <token> \
--name <runner-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 <registry>` 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 ## 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 ### 5.1 Build the artifact
```bash
./dist.sh # also builds the Android side; see §2.4
```
or, to build just the watch app:
```bash ```bash
cd SchwertUndMagieOnPebbleWatchApp cd SchwertUndMagieOnPebbleWatchApp
pebble build # produces build/SchwertUndMagieOnPebbleWatchApp.pbw 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 (`aplite`/`basalt`/`chalk`/`diorite`/`emery`/`flint`/`gabbro`) declared in
`package.json`. There is no signing step analogous to Android; Pebble apps `package.json`. There is no signing step analogous to Android; Pebble apps
are not cryptographically signed by the developer. are not cryptographically signed by the developer.
@@ -267,19 +383,21 @@ pebble screenshot --vnc --no-open docs/screenshots/emery.png
### 5.4 Versioning ### 5.4 Versioning
Bump `version` in `SchwertUndMagieOnPebbleWatchApp/package.json` before each Comes from the git tag automatically — see §4. `dist.sh` patches
release build. `SchwertUndMagieOnPebbleWatchApp/package.json`'s `version` field at build
time and reverts it afterward; no manual edit needed.
## 6. Pre-publish checklist ## 6. Pre-publish checklist
- [ ] Release keystore generated, passwords saved in a password manager, both - [ ] Release keystore generated, passwords saved in a password manager, both
gitignored (§1, §2.1) gitignored (§1, §2.1)
- [ ] `keystore.properties` exists locally and is **not** tracked by git - [ ] `keystore.properties` exists locally and is **not** tracked by git
- [ ] `./gradlew bundleRelease` succeeds and installs/runs on a real device - [ ] `./dist.sh` (or a tag push, §4) succeeds and produces
from the resulting AAB (test via `bundletool` or Play internal testing) `dist/schwert-und-magie-<version>.{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, - [ ] Play Console store listing content complete (icon, screenshots,
privacy policy, content rating, data safety form) privacy policy, content rating, data safety form)
- [ ] F-Droid metadata YAML created and `fdroid build` passes locally (§3) - [ ] F-Droid metadata YAML created and `fdroid build` passes locally (§3)
- [ ] `pebble build` succeeds for all target platforms; `.pbw` sideloads and - [ ] `.pbw` sideloads and runs correctly against the signed companion app
runs correctly against the signed companion app build build
- [ ] `versionCode`/`versionName` (Android) and `version` (Pebble) bumped
+6
View File
@@ -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=