f3f7442849
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.
404 lines
16 KiB
Markdown
404 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 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
|
||
|
||
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
|