9 Commits

Author SHA1 Message Date
ml f65b9be1df Adding publishing of artifacts
release / build (push) Successful in 4m53s
2026-07-17 20:45:04 +02:00
ml 5ac145bff6 Adding small note on new image build scripts 2026-07-04 16:25:05 +02:00
ml e07258c8bd Fix VICE cross-compile and image cache-warming for release builds
release / build (push) Successful in 5m34s
build-image/Dockerfile: add bison (VICE's configure needs a yacc-compatible
parser generator, same category as the earlier flex fix) and exclude
packageRelease/signReleaseBundle from the cache-warm stage's gradlew run,
since those always fail there for lack of a keystore that's never baked
into the image.

vice_jni.c: guard getFrameCount()/setSoundEnabled() with #ifdef
HAVE_VICE_SRC like their sibling functions already do — they referenced
globals that only exist when VICE is linked in, breaking the documented
no-VICE placeholder build. Only surfaced now because the cache-warm stage
is the first thing to ever compile this file without VICE.

dist.sh: floor the untagged dev-build's versionCode at 1, since the
0.0.0-dev+<sha> placeholder otherwise computes to 0, which Android's
Gradle plugin rejects.

run-image.sh: wipe generated build artifacts (vice-src, vice-libs,
nibtools-src/libs, app/build, .cxx) before each run so it exercises a true
from-scratch build like CI does, instead of silently reusing artifacts
left over from a previous local run. Also chown the bind-mounted repo back
to the host user on exit, since the container runs as root and was
otherwise leaving root-owned files behind.
2026-07-04 15:11:53 +02:00
ml 8881241fd8 Split the monolithic build-image.sh into three scripts: build-image.sh now only builds locally, run-image.sh
runs dist.sh inside that local image via a bind mount, andcupload-image.sh pushes the already-built image.
The Dockerfile also warms the Gradle dependency cache in a throwaway build stage, and pre-installs
the pinned CMake version, so `run-image.sh` doesn't re-download the same Maven dependencies on every run.

dist.sh now strips the -pre+meta suffix before writing package.json's version, since Pebble's build
tooling parses it strictly as X.Y.Z integers and was rejecting suffixed versions.
2026-07-04 05:36:14 +02:00
ml ad887c8bc8 Add flex to build image and VICE preflight check
release / build (push) Failing after 1m53s
VICE's configure.ac uses AC_PROG_LEX, which needs flex — missing from
the build image caused a late, confusing "Could not find either flex
or lex!" failure deep inside configure. Add it to the Dockerfile and
to build_vice.sh's own preflight tool check so a missing flex fails
fast with a clear error. Bump build-image/VERSION to 2.
2026-07-03 06:14:15 +02:00
ml 2117043e31 More robust nibtools build
release / build (push) Failing after 2m7s
2026-07-02 14:27:55 +02:00
ml 84c47136df Setting bash as default shell for action
release / build (push) Failing after 2m3s
2026-07-02 14:13:37 +02:00
ml 23ad9ebc32 Adding container registry login for gitea action
release / build (push) Failing after 52s
2026-07-02 13:57:35 +02:00
ml f3f7442849 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.
2026-07-02 13:19:12 +02:00
15 changed files with 356 additions and 54 deletions
+32
View File
@@ -0,0 +1,32 @@
# Build context for build-image/Dockerfile. The context is the repo root
# (see build-image.sh) so the Gradle-cache-warming stage can COPY in the
# companion app's real project files — everything else is excluded to keep
# the context small and to make sure secrets never reach the Docker daemon.
.git
dist
docs
versions
SchwertUndMagieOnPebbleWatchApp
# Companion app: only the Gradle project files are needed (see the
# gradle-cache-warm stage) — not generated build output or the VICE/nibtools
# source tarballs (buildVice/buildNibtools are excluded from that stage's
# gradle invocation, so they're never unpacked there).
SchwertUndMagieOnPebbleCompanionApp/app/build
SchwertUndMagieOnPebbleCompanionApp/app/.cxx
SchwertUndMagieOnPebbleCompanionApp/app/src/main/jni/vice-src
SchwertUndMagieOnPebbleCompanionApp/app/src/main/jni/vice-libs
SchwertUndMagieOnPebbleCompanionApp/app/src/main/jni/nibtools-src
SchwertUndMagieOnPebbleCompanionApp/app/src/main/jni/nibtools-libs
SchwertUndMagieOnPebbleCompanionApp/res/*.tar.gz
SchwertUndMagieOnPebbleCompanionApp/.gradle
SchwertUndMagieOnPebbleCompanionApp/.idea
SchwertUndMagieOnPebbleCompanionApp/local.properties
SchwertUndMagieOnPebbleCompanionApp/build
SchwertUndMagieOnPebbleCompanionApp/captures
# Secrets — must never reach the Docker daemon, even unused.
registry.env
**/keystore.properties
**/*.keystore
**/*.jks
+52 -7
View File
@@ -13,13 +13,25 @@ jobs:
# regardless of this instance's default Actions permission mode.
permissions:
contents: write
# Must match the label your self-hosted act_runner registers with.
runs-on: self-hosted
# Runner defaults `run:` steps to `sh`, which doesn't understand
# `set -o pipefail` used below — force bash explicitly.
defaults:
run:
shell: bash
# 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
# Lets the runner pull a private image without a manual `docker login`
# on the runner host — see docs/publish.md §4.3.
credentials:
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
steps:
- uses: actions/checkout@v4
@@ -44,13 +56,46 @@ jobs:
set -euo pipefail
API="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}"
AUTH="Authorization: token ${{ secrets.GITEA_TOKEN }}"
TAG="${{ gitea.ref_name }}"
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)"
# Reuse an existing release for this tag instead of failing outright
# (curl -f exit 22) if a prior run already created it — e.g. a retry
# after a later step failed. (No -f here: a 404 for "no release yet"
# is expected, not an error — it just leaves .id empty below.)
RELEASE_ID="$(curl -s "$API/releases/tags/$TAG" -H "$AUTH" | jq -r '.id // empty')"
if [ -z "$RELEASE_ID" ]; then
RELEASE_ID="$(curl -sf -X POST "$API/releases" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "{\"tag_name\": \"$TAG\", \"name\": \"$TAG\"}" \
| jq -r .id)"
fi
for f in dist/*; do
curl -sf -X POST "$API/releases/$RELEASE_ID/assets?name=$(basename "$f")" \
NAME="$(basename "$f")"
# Same idempotency concern for assets: a retry re-uploading a name
# that's already attached would 409, so replace it instead.
EXISTING_ID="$(curl -sf "$API/releases/$RELEASE_ID/assets" -H "$AUTH" \
| jq -r --arg n "$NAME" '.[] | select(.name == $n) | .id')"
if [ -n "$EXISTING_ID" ]; then
curl -sf -X DELETE "$API/releases/$RELEASE_ID/assets/$EXISTING_ID" -H "$AUTH"
fi
curl -sf -X POST "$API/releases/$RELEASE_ID/assets?name=$NAME" \
-H "$AUTH" -F "attachment=@$f"
done
- name: Publish to dl.ladkau.de
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "${{ secrets.DL_SFTP_KEY }}" > ~/.ssh/dl_sftp_key
chmod 600 ~/.ssh/dl_sftp_key
BATCH="$(mktemp)"
{
echo "-mkdir files/schwert-und-magie"
for f in dist/*; do
echo "put $f files/schwert-und-magie/$(basename "$f")"
done
} > "$BATCH"
sftp -i ~/.ssh/dl_sftp_key -P 2223 \
-o StrictHostKeyChecking=accept-new \
-b "$BATCH" uploader@dl.ladkau.de
+1 -1
View File
@@ -6,7 +6,7 @@
.cxx
local.properties
.lock*
.idea
/SchwertUndMagieOnPebbleCompanionApp/local.properties
/SchwertUndMagieOnPebbleCompanionApp/.idea/caches
/SchwertUndMagieOnPebbleCompanionApp/.idea/libraries
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Matthias Ladkau
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+5 -2
View File
@@ -150,8 +150,10 @@ 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`).
1. Build the build environment image locally and push it (`./build-image.sh`
then `./upload-image.sh`, needs `registry.env` — copy from
`registry.env.example`). Use `./run-image.sh` in between to sanity-check
the image before pushing.
2. Register a self-hosted `act_runner` with a Docker executor.
3. Add repo secrets under **Settings → Actions → Secrets**:
@@ -159,6 +161,7 @@ publishes a Gitea Release with the versioned artifacts attached — see
|---|---|
| `RELEASE_KEYSTORE_B64` | `base64 -w0 SchwertUndMagieOnPebbleCompanionApp/release.keystore` |
| `RELEASE_KEYSTORE_PROPERTIES` | full contents of `SchwertUndMagieOnPebbleCompanionApp/keystore.properties` |
| `REGISTRY_USER` / `REGISTRY_PASSWORD` | same as in `registry.env`, so the runner can pull the private build image |
`GITEA_TOKEN` is injected automatically per job — nothing to add for it.
4. `git tag v1.2.3 && git push origin v1.2.3`.
@@ -42,7 +42,11 @@ fi
if [ ! -d "${SRC}" ]; then
echo "Unpacking nibtools..."
tar -xzf "${TARBALL}" -C "${SCRIPT_DIR}"
mv "${SCRIPT_DIR}"/nibtools-*/ "${SRC}"
# Hardcoded, not a glob: on a non-ephemeral runner workspace, a
# nibtools-*/ glob can also match SRC itself once it exists, or other
# stray nibtools-prefixed leftovers, and mv then fails with "target is
# not a directory" (multiple sources, no existing target dir).
mv "${SCRIPT_DIR}/nibtools-91344e0ee3" "${SRC}"
echo "Unpacked to ${SRC}"
fi
@@ -32,7 +32,7 @@ echo " TOOLCHAIN : ${TOOLCHAIN} [OK]"
# ---- Ensure required host tools are present ---------------------------------
MISSING=()
for tool in dos2unix autoconf automake pkg-config xa; do
for tool in dos2unix autoconf automake pkg-config xa flex; do
command -v "$tool" &>/dev/null || MISSING+=("$tool")
done
if [ ${#MISSING[@]} -gt 0 ]; then
@@ -807,7 +807,11 @@ JNI_FN(jboolean, getDriveLed)(JNIEnv *env, jobject obj) {
JNI_FN(jint, getFrameCount)(JNIEnv *env, jobject obj) {
(void)env; (void)obj;
#ifdef HAVE_VICE_SRC
return (jint)g_frame_count;
#else
return 0;
#endif
}
/* Schwert und Magie uploads a custom character set that redefines a handful of
@@ -895,7 +899,11 @@ JNI_FN(void, injectKey)(JNIEnv *env, jobject obj, jint keyCode, jboolean pressed
JNI_FN(void, setSoundEnabled)(JNIEnv *env, jobject obj, jboolean enabled) {
(void)env; (void)obj;
#ifdef HAVE_VICE_SRC
g_sound_enabled = enabled ? 1 : 0;
#else
(void)enabled;
#endif
}
JNI_FN(jboolean, saveState)(JNIEnv *env, jobject obj, jstring jpath) {
+9 -16
View File
@@ -1,8 +1,7 @@
#!/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.
# Builds the release build environment image (build-image/Dockerfile)
# locally, tagged with build-image/VERSION and :latest. Does not push —
# test it with ./run-image.sh first, then publish with ./upload-image.sh.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
@@ -19,27 +18,21 @@ command -v docker >/dev/null 2>&1 \
# 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 =="
# Context is the repo root (not build-image/) so the Dockerfile's
# gradle-cache-warm stage can COPY in the companion app's real Gradle
# project files — see .dockerignore for what's excluded from that context.
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"
"$ROOT"
echo "== Done =="
echo "Image: $REGISTRY_IMAGE:$VERSION"
echo "Image: $REGISTRY_IMAGE:$VERSION (and :latest)"
echo "Test it with ./run-image.sh, then publish with ./upload-image.sh"
+54 -10
View File
@@ -1,17 +1,18 @@
# 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.
# dev machine. Rebuild 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
FROM eclipse-temurin:21-jdk-jammy AS base
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 ANDROID_CMAKE=3.22.1
ARG PEBBLE_TOOL_VERSION=5.0.35
ARG PEBBLE_SDK_CORE_VERSION=4.9.169
ARG NODE_VERSION=24.16.0
@@ -24,17 +25,21 @@ ENV DEBIAN_FRONTEND=noninteractive \
# git/unzip/curl/jq: checkout, SDK downloads, and the release workflow's
# calls to the Gitea API (create release, upload assets).
# openssh-client: the release workflow's `sftp` upload to dl.ladkau.de.
# 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.
# dos2unix/autoconf/automake/pkg-config/xa65/build-essential/gettext/flex/bison:
# host tools required by the companion app's build_vice.sh (see that file's
# own preflight check, plus flex/bison for VICE's AC_PROG_LEX/AC_PROG_YACC-
# based configure) 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 \
curl ca-certificates git unzip tar xz-utils python3 python3-venv file jq openssh-client \
dos2unix autoconf automake pkg-config xa65 build-essential gettext flex bison \
&& rm -rf /var/lib/apt/lists/*
# --- Android SDK: cmdline-tools, platform, build-tools, NDK ---
# --- Android SDK: cmdline-tools, platform, build-tools, NDK, CMake ---
# CMake version must match app/build.gradle.kts's externalNativeBuild.cmake.version
# — baking it in here avoids AGP installing it on first `docker run` instead.
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" \
@@ -47,6 +52,7 @@ RUN mkdir -p "$ANDROID_HOME/cmdline-tools" \
"platforms;${ANDROID_PLATFORM}" \
"build-tools;${ANDROID_BUILD_TOOLS}" \
"ndk;${ANDROID_NDK}" \
"cmake;${ANDROID_CMAKE}" \
>/dev/null
# Node.js: `pebble sdk install` below runs `npm install` for the SDK-core's
@@ -63,4 +69,42 @@ 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}"
# --- Warm the Gradle dependency cache ---
# The companion app's real Gradle project files (not the generated build/
# output, and not the VICE/nibtools source tarballs — see .dockerignore) are
# COPYed into a throwaway location and built once here, so every actual
# `docker run` of this image (which bind-mounts a fresh checkout over
# /workspace) hits a warm ~/.gradle cache instead of re-downloading the same
# Maven dependencies from dl.google.com/mavenCentral every single run.
#
# buildVice/buildNibtools are excluded (their source tarballs aren't in the
# build context) — CMake already handles that gracefully, falling back to a
# placeholder (see vice_jni.c / build_vice.sh's own header comment) — so this
# stage only ever warms the Gradle/Maven dependency cache, never bakes in
# compiled VICE/nibtools output.
#
# packageRelease/signReleaseBundle are also excluded — they're the only tasks
# that need the release keystore, which never exists here (secrets are
# injected at job runtime, never baked into the image). Excluding them lets
# everything upstream (dependency resolution, Kotlin/native compilation,
# resource merging, dexing) still run and get cached, without two guaranteed,
# noisy "missing storeFile" failures cluttering every image build.
#
# This is a pure optimization: if the app's dependencies change after this
# image was built, Gradle just downloads the delta against the warm cache at
# `docker run` time — same as it would without this stage, just slower for
# that one run, never broken. `|| true` means a transient network failure
# here only costs a slower first `docker run`, never breaks the image build.
FROM base AS gradle-cache-warm
COPY SchwertUndMagieOnPebbleCompanionApp /tmp/warm/SchwertUndMagieOnPebbleCompanionApp
WORKDIR /tmp/warm/SchwertUndMagieOnPebbleCompanionApp
RUN chmod +x gradlew \
&& (./gradlew bundleRelease assembleRelease \
-x buildVice -x buildNibtools \
-x packageRelease -x signReleaseBundle \
--continue || true)
FROM base
COPY --from=gradle-cache-warm /root/.gradle /root/.gradle
WORKDIR /workspace
+1 -1
View File
@@ -1 +1 @@
1
4
+8 -1
View File
@@ -74,6 +74,9 @@ 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]} ))
# Android requires a positive versionCode — the untagged dev placeholder
# (0.0.0-dev+<sha>) would otherwise compute to 0 and fail Gradle configuration.
[ "$VERSION_CODE" -gt 0 ] || VERSION_CODE=1
echo "Version: $VERSION (Android versionCode $VERSION_CODE)"
# Patch versions into the tracked source files for this build only, then
@@ -87,7 +90,11 @@ 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"
# Pebble's own build tooling parses package.json's version strictly as
# X.Y.Z integers — it rejects the -pre+meta suffix dist.sh otherwise allows
# (including the default "0.0.0-dev+<sha>" placeholder), so strip it here.
PEBBLE_VERSION="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}"
sed -i -e "s/\"version\": \"[^\"]*\"/\"version\": \"$PEBBLE_VERSION\"/" "$WATCH_PKG_JSON"
mkdir -p "$DIST"
+45 -14
View File
@@ -249,11 +249,11 @@ be bumped in source files beforehand (`dist.sh` patches `versionCode`/
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.
`build-image/Dockerfile`, built locally and pushed to a container registry by
three separate scripts (below). 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
@@ -262,9 +262,14 @@ 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.
Three scripts, kept separate so a Dockerfile change can be built and tested
locally before anything is pushed to the registry:
```bash
cp registry.env.example registry.env # fill in your registry credentials
./build-image.sh # builds + pushes :latest and the pinned VERSION tag
./build-image.sh # builds :latest and the pinned VERSION tag, locally only
./run-image.sh # runs dist.sh inside that local image — sanity-check before publishing
./upload-image.sh # pushes the already-built :latest and VERSION tag to cr.ladkau.de
```
Rebuild and push whenever `build-image/Dockerfile` changes (e.g. a Pebble SDK
@@ -273,6 +278,16 @@ 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.
`run-image.sh` bind-mounts this repo straight into the container, so before
each run it wipes generated build artifacts (`vice-src`, `vice-libs`,
`nibtools-src`, `nibtools-libs`, `app/build`, `app/.cxx`, the watch app's
`build/`) — otherwise leftovers from a previous local run would let
`build_vice.sh`/`build_nibtools.sh` skip work a real fresh CI checkout always
does, hiding bugs that only show up in CI. The container also runs as root
(needed for the baked-in SDK/NDK/Gradle setup), so it chowns the whole repo
back to your host user on exit — you shouldn't ever need `sudo` to clean up
after it.
### 4.2 Runner setup (one-time)
1. Enable Actions for the repo: repo Settings → Actions → enable, if not
@@ -285,18 +300,21 @@ line in `.gitea/workflows/release.yml` to the versioned tag instead.
# 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 self-hosted:docker://node:20-bookworm
--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 (`self-hosted`) must match `runs-on:` in
`.gitea/workflows/release.yml` — edit both together if you rename it.
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`.
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.
No manual `docker login` needed on the runner host — the workflow's
`container:` block authenticates the image pull itself via the
`REGISTRY_USER`/`REGISTRY_PASSWORD` secrets (§4.3).
### 4.3 Repo secrets
@@ -306,6 +324,9 @@ Settings → Actions → Secrets, add:
|---|---|
| `RELEASE_KEYSTORE_B64` | `base64 -w0 SchwertUndMagieOnPebbleCompanionApp/release.keystore` |
| `RELEASE_KEYSTORE_PROPERTIES` | the full contents of `SchwertUndMagieOnPebbleCompanionApp/keystore.properties` (§2.2) |
| `REGISTRY_USER` | same as `REGISTRY_USER` in `registry.env` |
| `REGISTRY_PASSWORD` | same as `REGISTRY_PASSWORD` in `registry.env` |
| `DL_SFTP_KEY` | private key (PEM) for the `uploader` SFTP account on dl.ladkau.de |
`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.
@@ -321,15 +342,25 @@ 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.
under the repo's Releases page with the three versioned artifacts attached,
and the same three files are uploaded over SFTP to
`dl.ladkau.de:files/schwert-und-magie/` (using the `DL_SFTP_KEY` secret,
§4.3) for direct download outside of Gitea.
To build the same versioned artifacts locally without pushing a tag (e.g. to
test before releasing):
test before releasing), either run `dist.sh` directly with the host toolchain:
```bash
VERSION=1.2.3 ./dist.sh
```
or run it inside the build-image container (same environment the runner
uses — see §4.1):
```bash
./run-image.sh 1.2.3
```
## 5. Pebble watch app → Rebble app store / direct distribution
The official Pebble app store shut down years ago; the community-run
Executable
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Runs dist.sh inside the local build-image container, mirroring what the
# Gitea Actions release workflow does — useful for testing build-image
# changes (or dist.sh/build_vice.sh/build_nibtools.sh changes) locally
# before pushing anything to cr.ladkau.de.
#
# Usage: ./run-image.sh [VERSION]
# VERSION is passed through to dist.sh; omit it for dist.sh's own
# git-tag-based default (see dist.sh's header comment).
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_IMAGE:?registry.env must set REGISTRY_IMAGE}"
IMAGE_TAG="$(<"$ROOT/build-image/VERSION")"
[ -n "$IMAGE_TAG" ] || fail "build-image/VERSION is empty"
IMAGE="$REGISTRY_IMAGE:$IMAGE_TAG"
docker image inspect "$IMAGE" >/dev/null 2>&1 \
|| fail "$IMAGE not found locally — run ./build-image.sh first"
VERSION="${1:-${VERSION:-}}"
# CI always starts from a fresh checkout, but this script bind-mounts the live
# host repo — so build outputs left over from a previous local run (e.g. a
# vice-src/ already configured, or a libvice.a that's already built) would
# make build_vice.sh/build_nibtools.sh skip work they'd have to do on a real
# fresh checkout, silently hiding bugs (like a missing host build tool) that
# only show up in CI. Wipe them first so every run exercises a true from-
# scratch build, same as CI.
JNI="$ROOT/SchwertUndMagieOnPebbleCompanionApp/app/src/main/jni"
echo "== Cleaning generated build artifacts for a fresh build =="
rm -rf \
"$JNI/vice-src" "$JNI/vice-libs" \
"$JNI/nibtools-src" "$JNI/nibtools-libs" \
"$ROOT/SchwertUndMagieOnPebbleCompanionApp/app/build" \
"$ROOT/SchwertUndMagieOnPebbleCompanionApp/app/.cxx" \
"$ROOT/SchwertUndMagieOnPebbleWatchApp/build"
echo "== Running dist.sh inside $IMAGE =="
# The container runs as root (needed for the SDK/NDK/Gradle setup baked into
# the image), so anything it writes into this bind mount — dist/, app/build,
# .cxx, etc. — would otherwise come back owned by root, leaving the host repo
# unusable without sudo. Chown everything back to the host user on exit,
# whether dist.sh succeeds or fails.
docker run --rm \
-v "$ROOT:/workspace" \
-w /workspace \
-e VERSION="$VERSION" \
-e HOST_UID="$(id -u)" \
-e HOST_GID="$(id -g)" \
"$IMAGE" \
bash -c '
git config --global --add safe.directory /workspace
trap "chown -R \"$HOST_UID:$HOST_GID\" /workspace" EXIT
./dist.sh
'
echo "== Done — artifacts in dist/ =="
ls -la "$ROOT/dist"
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Pushes the build environment image — already built locally with
# ./build-image.sh, and ideally verified with ./run-image.sh — to
# cr.ladkau.de. The Gitea Actions release workflow pulls this image to
# run dist.sh.
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"
docker image inspect "$REGISTRY_IMAGE:$VERSION" >/dev/null 2>&1 \
|| fail "$REGISTRY_IMAGE:$VERSION not found locally — run ./build-image.sh first"
echo "== Logging in to $REGISTRY =="
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin
echo "== Pushing $REGISTRY_IMAGE:$VERSION and :latest =="
docker push "$REGISTRY_IMAGE:$VERSION"
docker push "$REGISTRY_IMAGE:latest"
echo "== Done =="
echo "Image: $REGISTRY_IMAGE:$VERSION"