7de675866d
release / build (push) Failing after 1m46s
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.
419 lines
16 KiB
Markdown
419 lines
16 KiB
Markdown
# Publishing guide
|
||
|
||
Steps to generate signing credentials and publish both apps. See
|
||
`docs/architecture.md` for what each app does and `CLAUDE.md` for build
|
||
commands.
|
||
|
||
## 1. .gitignore
|
||
|
||
Keystore files and credential properties must never be committed. Already
|
||
added to `.gitignore`:
|
||
|
||
```
|
||
*.jks
|
||
*.keystore
|
||
keystore.properties
|
||
```
|
||
|
||
If you generate a keystore with a different name/extension, add that
|
||
specific path too — don't rely on a broad glob you might forget to check.
|
||
|
||
## 2. Android companion app → Google Play
|
||
|
||
### 2.1 Generate an upload keystore
|
||
|
||
```bash
|
||
keytool -genkeypair -v \
|
||
-keystore SchwertUndMagieOnPebbleCompanionApp/release.keystore \
|
||
-alias sum-release \
|
||
-keyalg RSA -keysize 2048 -validity 10000
|
||
```
|
||
|
||
`keytool` will prompt for a keystore password, a key password (can be the
|
||
same), and your name/org details for the certificate (these become public
|
||
metadata in the signed APK, not secret). Store the keystore file and both
|
||
passwords in a password manager — **losing this keystore means you can never
|
||
publish an update to the same Play Store listing again** under the same app;
|
||
Google cannot recover or reset it for you.
|
||
|
||
### 2.2 Store credentials in a gitignored properties file
|
||
|
||
Create `SchwertUndMagieOnPebbleCompanionApp/keystore.properties` (already
|
||
gitignored, never commit it):
|
||
|
||
```properties
|
||
storeFile=release.keystore
|
||
storePassword=<keystore password>
|
||
keyAlias=sum-release
|
||
keyPassword=<key password>
|
||
```
|
||
|
||
### 2.3 Wire the signing config
|
||
|
||
Add to `SchwertUndMagieOnPebbleCompanionApp/app/build.gradle.kts`, near the
|
||
top:
|
||
|
||
```kotlin
|
||
import java.util.Properties
|
||
|
||
val keystoreProps = Properties().apply {
|
||
rootProject.file("keystore.properties").takeIf { it.exists() }
|
||
?.reader()?.use { load(it) }
|
||
}
|
||
```
|
||
|
||
Inside the `android { }` block:
|
||
|
||
```kotlin
|
||
signingConfigs {
|
||
create("release") {
|
||
keystoreProps["storeFile"]?.let { storeFile = file(it as String) }
|
||
storePassword = keystoreProps["storePassword"] as String?
|
||
keyAlias = keystoreProps["keyAlias"] as String?
|
||
keyPassword = keystoreProps["keyPassword"] as String?
|
||
}
|
||
}
|
||
|
||
buildTypes {
|
||
release {
|
||
signingConfig = signingConfigs.getByName("release")
|
||
// ...existing isMinifyEnabled / proguardFiles
|
||
}
|
||
}
|
||
```
|
||
|
||
This degrades gracefully (null signing config values) on any machine without
|
||
`keystore.properties` — CI or a contributor's checkout — rather than failing
|
||
the whole Gradle configuration.
|
||
|
||
### 2.4 Build the release bundle
|
||
|
||
```bash
|
||
./dist.sh # builds both apps, copies signed artifacts into dist/
|
||
```
|
||
|
||
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). 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)
|
||
|
||
1. Enroll in the Google Play Developer program (one-time account fee).
|
||
2. Create the app in Play Console, set its package name
|
||
(`de.ladkau.schwertundmagieonpebblecompanionapp`) — this is permanent.
|
||
3. **App content**: privacy policy URL, content rating questionnaire, data
|
||
safety form (declare what data the app collects — this app's only network
|
||
activity is the loopback HTTP server talking to Core for Pebble, so
|
||
"no data collected/shared" likely applies, but fill out the form yourself).
|
||
4. **Store listing**: title, short/full description, icon (512×512 PNG),
|
||
feature graphic (1024×500), phone screenshots (min 2, current device's
|
||
actual aspect ratio).
|
||
5. Enroll in **Play App Signing** when prompted on first upload — Google
|
||
re-signs your AAB with its own key for distribution; your upload keystore
|
||
(§2.1) only needs to be kept for *future uploads to this listing*, not for
|
||
the keys end users' devices actually trust.
|
||
6. Upload the AAB to an **internal testing** track first, verify the install
|
||
works on a real device, then promote to closed/open testing or production.
|
||
|
||
### 2.6 Versioning for future releases
|
||
|
||
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
|
||
|
||
F-Droid is a community-run FOSS app catalogue that builds apps from source on
|
||
its own infrastructure and signs them with its own key. The companion app is a
|
||
good fit: it is open source, ships no proprietary binaries in the repo, and the
|
||
copyrighted ROM and disk files are handled entirely at runtime by the user.
|
||
|
||
### 3.1 Inclusion policy check
|
||
|
||
F-Droid's key requirement is that everything needed to build the app is in the
|
||
repo and is free/open-source. Verify:
|
||
|
||
- VICE 3.8 source is GPLv2 (tarball in `res/`). ✓
|
||
- nibtools source is GPLv2 (tarball in `res/`). ✓
|
||
- No prebuilt binaries committed (`.a`/`.so` files are build outputs in
|
||
`.gitignore`). ✓
|
||
- No non-free SDKs or closed-source dependencies in `build.gradle.kts`. ✓
|
||
- C64 ROMs and game disks are not bundled — user-supplied at runtime. ✓
|
||
|
||
### 3.2 Prepare the fdroiddata metadata file
|
||
|
||
F-Droid apps are registered by adding a YAML file to the
|
||
[fdroiddata](https://gitlab.com/fdroid/fdroiddata) repository. Fork it, then
|
||
create `metadata/de.ladkau.schwertundmagieonpebblecompanionapp.yml`:
|
||
|
||
```yaml
|
||
Categories:
|
||
- Games
|
||
License: GPL-2.0-or-later
|
||
AuthorName: Matthias Ladkau
|
||
SourceCode: https://github.com/<your-repo>
|
||
IssueTracker: https://github.com/<your-repo>/issues
|
||
|
||
AutoName: Schwert und Magie on Pebble
|
||
|
||
RepoType: git
|
||
Repo: https://github.com/<your-repo>
|
||
|
||
Builds:
|
||
- versionName: '1.0'
|
||
versionCode: 1
|
||
commit: <git-tag-or-sha>
|
||
subdir: SchwertUndMagieOnPebbleCompanionApp
|
||
gradle:
|
||
- release
|
||
ndk: 30.0.14904198
|
||
prebuild:
|
||
- bash app/src/main/jni/build_vice.sh
|
||
- bash app/src/main/jni/build_nibtools.sh
|
||
|
||
AutoUpdateMode: None
|
||
UpdateCheckMode: None
|
||
```
|
||
|
||
Key points:
|
||
|
||
- `subdir` points to the Gradle project root (where `gradlew` lives).
|
||
- `gradle: [release]` tells F-Droid to run `./gradlew assembleRelease`.
|
||
- `ndk` must match the `ndkVersion` in `app/build.gradle.kts`
|
||
(`30.0.14904198`). F-Droid's build server installs the requested NDK version
|
||
automatically.
|
||
- `prebuild` runs the VICE and nibtools cross-compilation before Gradle
|
||
invokes CMake. The `NDK` environment variable is set by F-Droid's build
|
||
server, matching what `build_vice.sh` and `build_nibtools.sh` expect.
|
||
- F-Droid signs with its own key, so `keystore.properties` is not needed on
|
||
the build server. The signing config in `build.gradle.kts` degrades
|
||
gracefully when the file is absent.
|
||
|
||
### 3.3 Test the build locally with fdroid-server
|
||
|
||
Before submitting, reproduce the F-Droid build environment locally:
|
||
|
||
```bash
|
||
# Install fdroid-server (Debian/Ubuntu)
|
||
sudo apt install fdroidserver
|
||
|
||
# In the fdroiddata checkout:
|
||
fdroid build de.ladkau.schwertundmagieonpebblecompanionapp --latest
|
||
```
|
||
|
||
This runs the build inside a Docker container that mirrors the F-Droid build
|
||
server. A successful local build means the merge request is very likely to
|
||
pass.
|
||
|
||
### 3.4 Submit the merge request
|
||
|
||
Push your `metadata/` addition to your fdroiddata fork and open a merge
|
||
request against `gitlab.com/fdroid/fdroiddata`. The F-Droid team reviews the
|
||
metadata and, if the build passes on their infrastructure, merges it and
|
||
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 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 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
|
||
|
||
`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.
|
||
|
||
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 :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
|
||
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.
|
||
|
||
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
|
||
|
||
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) |
|
||
| `REGISTRY_USER` | same as `REGISTRY_USER` in `registry.env` |
|
||
| `REGISTRY_PASSWORD` | same as `REGISTRY_PASSWORD` in `registry.env` |
|
||
|
||
`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), 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
|
||
**Rebble** store is the closest equivalent today, alongside the 2025 Core
|
||
Devices relaunch of Pebble hardware. Check Rebble's current developer portal
|
||
directly for their exact submission flow and requirements — that's outside
|
||
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
|
||
```
|
||
|
||
`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.
|
||
|
||
### 5.2 Direct distribution (no store)
|
||
|
||
Anyone with the `.pbw` file and Core for Pebble installed can sideload it —
|
||
this is the lowest-friction path and doesn't depend on any third party's
|
||
store being operational. This watch app is tightly coupled to the companion
|
||
app's HTTP bridge (see `docs/architecture.md` §1), so it's not really
|
||
meaningful as a standalone listing anyway — distribute both together.
|
||
|
||
### 5.3 Store submission (if Rebble's process applies)
|
||
|
||
Expect to need: an app icon resource (not yet configured in `package.json` —
|
||
there's no `icon`/menu-icon entry currently, only the in-app `IMAGE_SPLASH`
|
||
bitmap), a short description, and screenshots per platform. Generate
|
||
screenshots the same way used during development:
|
||
|
||
```bash
|
||
pebble install --emulator emery --vnc
|
||
pebble screenshot --vnc --no-open docs/screenshots/emery.png
|
||
```
|
||
|
||
(repeat per target platform you want a store screenshot for).
|
||
|
||
### 5.4 Versioning
|
||
|
||
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
|
||
- [ ] `./dist.sh` (or a tag push, §4) succeeds and produces
|
||
`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,
|
||
privacy policy, content rating, data safety form)
|
||
- [ ] F-Droid metadata YAML created and `fdroid build` passes locally (§3)
|
||
- [ ] `.pbw` sideloads and runs correctly against the signed companion app
|
||
build
|