Import of ProLibertateGames (upstream main, GPL-3.0) rebranded for Wallacenet and repointed at git.wallacenet.ca. - Identity: applicationId ca.wallacenet.pilgrimsofkatan (+ .dev for the dev channel), launcher names "Pilgrims of Katan" / "Pilgrims of Katan DEV", neutral hexagon branding replacing the Wallace tartan. Uncial Antiqua keeps its OFL licence, and the GPL-3.0 LICENSE plus upstream attribution stay. - OTA: Updater.kt now reads this repo's own Gitea releases (/api/v1/repos/devadmin/PilgrimsOfKatan/releases...) and BuildConfig. OTA_ENABLED is on; Gitea answers the same JSON shape the code already parsed. - Build numbers: versionCode = CI run number + plgVersionRunOffset (1000), with the tag and versionName derived from the same figure. The offset is permanent, because Gitea Actions restarts run numbers at 1 and a reset would otherwise make Android reject the next build as a downgrade. - CI: ci.yml publishes a dev prerelease from dev, release.yml a production release from main, both through scripts/publish-release.sh (Gitea REST API, runnable by hand until an Actions runner is registered). - Docs: docs/CI.md records the channel, numbering and runner decisions; docs/README.md seeds the per-game RULES-* conventions; CLAUDE.md carries the agent ground rules. Verified: ./gradlew assembleDebug and testDebugUnitTest green on JDK 17 / compileSdk 34, both variants stamped, installing side by side under the new identity. Co-Authored-By: Paperclip <[email protected]>
6.4 KiB
CI, releases, and OTA — decisions and how to build
Status as of the Pilgrims of Katan bootstrap (WALA-118).
Decisions
-
The release channel is this instance's own releases. The app ships from https://git.wallacenet.ca/devadmin/PilgrimsOfKatan, and the in-app updater reads that repo's releases over Gitea's REST API. GitHub is not a channel for this project; the only GitHub URLs left are the
upstreamremote and the attribution link in the README.The repo is public on purpose: the updater downloads anonymously, with no token embedded in the APK. A private repo would make every check fail (the API answers 404 to anonymous callers), not leak anything — but it would also stop updates dead.
-
CI lives in Actions-compatible workflows in
.github/workflows/. Gitea Actions runs workflows from.github/workflows/(or.gitea/workflows/), so these files serve both a GitHub-style runner and the Gitea instance. Publishing goes throughscripts/publish-release.sh— Gitea's REST API, the same routes the app's updater reads — rather than theghCLI, which a runner image need not carry.Workflow Trigger Publishes ci.ymlpush to dev, PRs intodev/main, manualdev prerelease, from devonlyrelease.ymlpush to main, manualproduction release, from mainonlyPull requests build and check but never publish. Prereleases are marked so the "latest release" endpoint — which skips them — can never hand a dev build to a production install.
-
A runner has to be registered before any of that executes. As of the bootstrap the instance has no Actions runner, so the workflows are correct but idle: a Gitea runner (Docker or host) must be registered against the instance and able to resolve
actions/checkout,actions/setup-java,android-actions/setup-androidandactions/upload-artifact. Until then the manual build below and the manual publish script are the working paths. -
The local build is the guaranteed path. Workflows need a runner that may not exist yet;
./gradlew assembleDebug+./gradlew testDebugUnitTestwork on any machine with JDK 17 and the Android SDK (compileSdk 34), and CI's green means exactly these two commands plus lint.
Build numbers, tags, and why updates never downgrade
An APK is <series>.<build number>, the release tag is v<series>.<build number>, and the build number is what Android sees as versionCode:
build number = CI run number + plgVersionRunOffset (gradle.properties)
plgVersionSeries(currently1.4) is the human-facing series, set by hand ingradle.properties.plgVersionRunOffset(currently1000) is a permanent floor, also set by hand ingradle.properties, set once at bootstrap and never changed.
Why the offset exists: Gitea Actions restarts run numbers at 1 for each
workflow, so without it the first release of a fresh instance would be build
1 — the number an unstamped local build already carries — and any reset of
the counter would hand Android a lower versionCode, which it refuses as a
downgrade. With the offset, every stamped build starts at 1001 and only ever
climbs, because the run number is monotonic and the offset never moves. The
gradle build and both workflows read the same two properties, so the tag and
the APK inside it cannot name different versions.
The series is a name for people: bumping it changes nothing about ordering,
because versionCode never includes the series.
Unstamped builds (local, or a PR build) are versionCode 1, below every
release — the honest answer for a build nobody stamped.
Manual build (works today, no runner needed)
# JDK 17, Android SDK with platform 34 + build-tools 34
export JAVA_HOME=... # a JDK 17 install
export ANDROID_HOME=... # SDK root (or set sdk.dir in local.properties)
./gradlew assembleDebug # dev-channel app (default: ca.wallacenet.pilgrimsofkatan.dev)
PLG_CHANNEL=production ./gradlew assembleDebug # production app (ca.wallacenet.pilgrimsofkatan)
./gradlew testDebugUnitTest # JVM unit tests
./gradlew lintDebug # Android lint (CI runs this too)
The two variants install side by side: different applicationId, different
launcher name (Pilgrims of Katan DEV vs Pilgrims of Katan), different brand
colour.
Manual release (works today, no runner needed)
GITEA_TOKEN=… ./scripts/publish-release.sh v1.4.1001 app/build/outputs/apk/debug/app-debug.apk release "$(git rev-parse HEAD)"
The script takes <tag> <apk> <prerelease|release> [target-sha], creates the
release through POST /api/v1/repos/{owner}/{repo}/releases, and attaches the
APK through POST …/releases/{id}/assets — which is the upload route Gitea's
Swagger declares for this instance (1.27.3). If the tag already exists it
publishes into the existing release instead of failing, so a retry is safe.
What the in-app updater reads
app/src/main/java/org/prolibertate/games/update/Updater.kt:
| Channel | Endpoint |
|---|---|
| production | GET /api/v1/repos/devadmin/PilgrimsOfKatan/releases/latest |
| dev | GET /api/v1/repos/devadmin/PilgrimsOfKatan/releases?limit=30 |
/releases/latestis documented by Gitea as the most recent non-prerelease, non-draft release — the same rule GitHub implements, so the production channel keeps skipping dev builds.- The dev path takes the highest
versionCodeamong the prereleases rather than whatever came first on the page, so it does not depend on server ordering. - The JSON shape is the one the code already parsed from GitHub:
tag_name,prerelease,draft, and assets carryingbrowser_download_url. Gitea sayslimitwhere GitHub saysper_page. - Each asset must be named so it contains its tag —
PilgrimsOfKatan-v1.4.1001.apkfor tagv1.4.1001— because the updater picks the versioned APK out of the release's assets by that rule.
BuildConfig.OTA_ENABLED is a kill switch, not a channel decision: it is
true, and setting it false makes every check report that and fetch nothing.
UpdatePolicy.kt — channel rules, tag parsing, verdicts — is pure and stays
under test either way.
Keystore
app/debug.keystore is committed deliberately and must never be rotated:
every build — local, CI, release — signs with it, so an OTA update installs
over the previous build instead of being rejected for a signature mismatch.