From 994aaa88d16262130d722e91a6c42a68368f3d13 Mon Sep 17 00:00:00 2001 From: Hank Date: Wed, 8 Jul 2026 04:16:37 +0000 Subject: [PATCH] Move APK runner to a separate host; openssl-based keystore - gen-keystore.sh now builds a PKCS12 keystore with openssl (no JDK/keytool), and signing.gradle declares storeType PKCS12. - Runner is meant to run on a roomy server (the app host lacks disk for the Android SDK); added runner/README.md with deployment steps. Removed the runner that was registered on the app host. Co-Authored-By: Claude Fable 5 --- README.md | 11 +++++------ ci/signing.gradle | 1 + runner/README.md | 39 +++++++++++++++++++++++++++++++++++++++ scripts/gen-keystore.sh | 34 +++++++++++++++++++++++----------- 4 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 runner/README.md diff --git a/README.md b/README.md index 75ff7ed..0f2ee50 100644 --- a/README.md +++ b/README.md @@ -174,17 +174,16 @@ On success the buyer receives the QR email. Re-sends of the same submission are ## APK builds (Forgejo CI) -The runner in `runner/` is registered against `git.mowden.top` and builds a signed APK whenever a `vX.Y.Z` tag is pushed. +A Forgejo Actions runner builds a signed APK whenever a `vX.Y.Z` tag is pushed. **Run the runner on the roomy server, not the app host** — the Android SDK needs several GB the app host doesn't have. See [`runner/README.md`](./runner/README.md) for deploying it. **One-time setup:** -1. Generate a release keystore and print the secrets: `bash scripts/gen-keystore.sh` +1. Generate a release keystore (PKCS12, uses openssl — no JDK needed) and print the secrets: `bash scripts/gen-keystore.sh` 2. In Forgejo → CampgroundTickets → *Settings → Actions → Secrets*, add: - `ANDROID_KEYSTORE_B64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD`. + `ANDROID_KEYSTORE_B64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD` + (for PKCS12 the keystore and key passwords are the same value). 3. Keep the keystore file safe forever — Obtainium updates require the same signing key on every release. -**Release:** `git tag v0.1.0 && git push origin v0.1.0` → the workflow builds `camp-scan-v0.1.0.apk` and attaches it to the Forgejo release. Obtainium picks it up (see INSTALL.md). - -The runner runs jobs in a `node:22-bookworm` container and installs the Android SDK itself. To (re)start it: `cd runner && cp .env.example .env` (set `REGISTRATION_TOKEN`, `DOCKER_GID`) `&& docker compose up -d`. +**Release:** `git tag v0.1.0 && git push origin v0.1.0` → the workflow builds `camp-scan-v0.1.0.apk` and attaches it to the Forgejo release. Obtainium picks it up (see INSTALL.md). The build runs jobs in a `node:22-bookworm` container and installs the Android SDK itself; the first run may need an SDK/Gradle tweak, so shake it out with a throwaway `v0.0.1` tag first. ## API reference (staff endpoints require `Authorization: Bearer `) diff --git a/ci/signing.gradle b/ci/signing.gradle index c4e85c6..8e78c33 100644 --- a/ci/signing.gradle +++ b/ci/signing.gradle @@ -13,6 +13,7 @@ gradle.projectsLoaded { signingConfigs { campscanRelease { storeFile file(storeFilePath) + storeType 'PKCS12' storePassword System.getenv('CAMPSCAN_STORE_PASSWORD') keyAlias System.getenv('CAMPSCAN_KEY_ALIAS') keyPassword System.getenv('CAMPSCAN_KEY_PASSWORD') diff --git a/runner/README.md b/runner/README.md new file mode 100644 index 0000000..5113651 --- /dev/null +++ b/runner/README.md @@ -0,0 +1,39 @@ +# Forgejo Actions runner (APK builds) + +Run this **on the roomy server** (where Forgejo lives / where there's disk for +the ~3–4 GB Android SDK), **not** on the app host — the Android build needs +several GB of scratch space that the small app host doesn't have. + +## Deploy + +1. Copy this `runner/` directory to the roomy server (or clone the repo there). +2. In Forgejo → `Beartaria/CampgroundTickets` → **Settings → Actions → Runners → Create new runner**, copy the **registration token**. +3. Create `.env` from the example and fill it in: + ```bash + cp .env.example .env + # REGISTRATION_TOKEN= + # DOCKER_GID= + ``` +4. Start it: + ```bash + mkdir -p data && sudo chown -R 1000:1000 data + docker compose up -d + docker compose logs -f # expect "declared successfully" then "poller launched" + ``` + +The runner advertises the `docker` label; the build workflow (`.forgejo/workflows/build-apk.yml`) runs jobs in a `node:22-bookworm` container and installs the Android SDK itself. + +## Build an APK + +1. One-time: generate a keystore with `../scripts/gen-keystore.sh` and add the four + `ANDROID_*` secrets to the repo (see the main README → *APK builds*). +2. Push a tag: `git tag v0.1.0 && git push origin v0.1.0`. +3. The runner builds `camp-scan-v0.1.0.apk` and attaches it to a Forgejo release; Obtainium picks it up. + +## Notes + +- The runner I initially registered on the app host has been removed. If Forgejo + still lists an **offline** `camptickets-runner`, delete it from + *Settings → Actions → Runners*. +- `DOCKER_GID` must match the roomy server's docker socket group, or the runner + can't reach the Docker daemon. diff --git a/scripts/gen-keystore.sh b/scripts/gen-keystore.sh index 9e698e5..cdf7623 100644 --- a/scripts/gen-keystore.sh +++ b/scripts/gen-keystore.sh @@ -1,8 +1,11 @@ #!/usr/bin/env bash -# Generate a release signing keystore for the Android APK (run ONCE), then print -# the base64 + values to paste into Forgejo repo secrets. Keep the keystore file -# safe and constant forever — losing it or changing it breaks Obtainium updates -# (a differently-signed APK will not install over the old one). +# Generate a PKCS12 release signing keystore for the Android APK (run ONCE), +# using openssl (no JDK/keytool required). Then print the base64 + values to +# paste into Forgejo repo secrets. +# +# Keep the keystore file safe and constant forever — losing it or changing it +# breaks Obtainium updates (a differently-signed APK won't install over the old +# one). PKCS12 uses ONE password for both the store and the key. set -euo pipefail KEYSTORE="${1:-campscan-release.keystore}" @@ -12,17 +15,26 @@ if [ -f "$KEYSTORE" ]; then echo "Refusing to overwrite existing $KEYSTORE" >&2 exit 1 fi +command -v openssl >/dev/null || { echo "openssl not found" >&2; exit 1; } read -r -s -p "Choose a keystore password: " STOREPASS; echo read -r -s -p "Confirm keystore password: " STOREPASS2; echo [ "$STOREPASS" = "$STOREPASS2" ] || { echo "Passwords do not match" >&2; exit 1; } +[ ${#STOREPASS} -ge 6 ] || { echo "Use at least 6 characters" >&2; exit 1; } -keytool -genkeypair -v \ - -keystore "$KEYSTORE" \ - -alias "$ALIAS" \ - -keyalg RSA -keysize 2048 -validity 10000 \ - -storepass "$STOREPASS" -keypass "$STOREPASS" \ - -dname "CN=Beartaria Campgrounds, OU=Gate, O=Beartaria, L=, ST=, C=US" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +# Self-signed cert + key, valid ~27 years, then bundle into a PKCS12 keystore. +openssl req -x509 -newkey rsa:2048 -sha256 -days 10000 -nodes \ + -keyout "$TMP/key.pem" -out "$TMP/cert.pem" \ + -subj "/CN=Beartaria Campgrounds/OU=Gate/O=Beartaria/C=US" 2>/dev/null + +openssl pkcs12 -export \ + -inkey "$TMP/key.pem" -in "$TMP/cert.pem" \ + -name "$ALIAS" \ + -out "$KEYSTORE" \ + -passout pass:"$STOREPASS" echo echo "==================== Forgejo repo secrets ====================" @@ -30,7 +42,7 @@ echo "Set these under: git.mowden.top -> CampgroundTickets -> Settings -> Action echo echo "ANDROID_KEY_ALIAS = $ALIAS" echo "ANDROID_KEYSTORE_PASSWORD = (the password you just entered)" -echo "ANDROID_KEY_PASSWORD = (the same password)" +echo "ANDROID_KEY_PASSWORD = (the SAME password — PKCS12 uses one)" echo "ANDROID_KEYSTORE_B64 = (paste the block below, single line)" echo base64 -w0 "$KEYSTORE"; echo