From 0dfaeef7bffe853a51407ba63660147b14dc7a84 Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Sat, 18 Apr 2026 22:00:56 -0700 Subject: [PATCH] Require dedicated release signing for APK builds --- .codex/skills/keepassgo-apk-test/SKILL.md | 12 +++++++++--- .codex/skills/keepassgo-ship-it/SKILL.md | 8 +++++++- .gitea/workflows/ci.yml | 9 ++++++--- APK.md | 21 ++++++++++++++++++++ Makefile | 24 +++++++++++++++++++++-- README.md | 11 +++++++++++ 6 files changed, 76 insertions(+), 9 deletions(-) diff --git a/.codex/skills/keepassgo-apk-test/SKILL.md b/.codex/skills/keepassgo-apk-test/SKILL.md index b6e4e73..8dc4fd9 100644 --- a/.codex/skills/keepassgo-apk-test/SKILL.md +++ b/.codex/skills/keepassgo-apk-test/SKILL.md @@ -45,8 +45,8 @@ Use this skill together with the installed `android-emulator-debug` skill. That ## Build Workflow 1. Verify the JDK/SDK paths match the known working environment. -2. Build with `make apk`. -3. If `make apk` fails, inspect the effective `JAVA_HOME`, `ANDROID_SDK_ROOT`, and `ANDROID_NDK_ROOT` before changing code. +2. Build with `make apk` for debug validation, or `make apk-release` when validating production signing behavior. +3. If the build fails, inspect the effective `JAVA_HOME`, `ANDROID_SDK_ROOT`, and `ANDROID_NDK_ROOT` before changing code. 4. If the problem is Android-only, avoid desktop-only conclusions from `go test ./...`. Typical local build: @@ -55,6 +55,12 @@ Typical local build: JAVA_HOME=/usr/lib/jvm/java-25-openjdk make apk ``` +Typical local release build: + +```sh +JAVA_HOME=/usr/lib/jvm/java-25-openjdk make apk-release +``` + ## Emulator Workflow 1. Reuse an existing emulator session if one is already running. @@ -79,7 +85,7 @@ adb shell dumpsys window | rg 'mCurrentFocus|mFocusedApp' ## Validation Checklist -- APK builds successfully with `make apk`. +- APK builds successfully with the intended target: `make apk` for debug validation or `make apk-release` for release-signing validation. - App launches to `org.julianfamily.keepassgo/org.gioui.GioActivity`. - Screenshot shows the expected screen, not just a black frame. - `logcat` shows no app crash or Android runtime fatal error. diff --git a/.codex/skills/keepassgo-ship-it/SKILL.md b/.codex/skills/keepassgo-ship-it/SKILL.md index 81f2768..f819b22 100644 --- a/.codex/skills/keepassgo-ship-it/SKILL.md +++ b/.codex/skills/keepassgo-ship-it/SKILL.md @@ -52,11 +52,17 @@ The installed package version must correspond to the committed source, not a dir Use the repo's known-good local JDK unless the environment already proves otherwise: ```sh -JAVA_HOME=/usr/lib/jvm/java-25-openjdk make apk +JAVA_HOME=/usr/lib/jvm/java-25-openjdk make apk-release ``` If that JDK is unavailable on the current host, use the working replacement already established for the machine and say so in the closeout. +- `ship it` must use the dedicated release keystore flow, not Gio's implicit debug or temporary signing path. +- The default local release-signing paths are: + `~/.config/keepassgo/android-release.keystore` + `~/.config/keepassgo/android-release.pass` +- If those files are unavailable, stop and fix signing instead of shipping a differently signed APK. + ### 4. Zip The APK - Create the ZIP under the globally required temporary secret-safe directory. diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 1f6e61d..b4e8c49 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -135,11 +135,14 @@ jobs: shell: bash run: | set -euo pipefail - signkey_path="$(mktemp)" - trap 'rm -f -- "$signkey_path"' EXIT + mkdir -p build/ci-signing + signkey_path="$(pwd)/build/ci-signing/android-release.keystore" + signpass_path="$(pwd)/build/ci-signing/android-release.pass" + trap 'rm -f -- "$signkey_path" "$signpass_path"' EXIT printf '%s' '${{ secrets.APK_SIGNKEY_B64 }}' | base64 -d > "$signkey_path" + printf '%s' '${{ secrets.APK_SIGNPASS }}' > "$signpass_path" export APP_VERSION="$(git describe --tags --always --dirty)" - make apk SIGNKEY="$signkey_path" SIGNPASS='${{ secrets.APK_SIGNPASS }}' + make apk-release RELEASE_SIGNKEY="$signkey_path" RELEASE_SIGNPASS_FILE="$signpass_path" cp build/keepassgo.apk "${DIST_DIR}/keepassgo.apk" - name: Upload CI artifacts diff --git a/APK.md b/APK.md index 71af8c6..5899548 100644 --- a/APK.md +++ b/APK.md @@ -6,11 +6,22 @@ Build the APK with: make apk ``` +Build the release-signed APK with: + +```sh +make apk-release +``` + `make apk` uses a local Java 25 install when `JAVA_HOME` points to one. If the host does not have a working Java 25 install, it falls back to the repo-managed Docker image in `packaging/docker/android-apk/`, which also builds with Java 25. +`make apk` remains a developer build path and may use Gio's default debug or +ephemeral signing behavior if no explicit signing key is provided. +`make apk-release` is the production-signing path and fails unless a dedicated +release keystore and password file are present. + Environment: - `ANDROID_SDK_ROOT` defaults to `/opt/android-sdk`. @@ -23,6 +34,13 @@ Environment: - `APK_VERSION` overrides the packaged app version. - `ANDROID_MIN_SDK` overrides the minimum supported Android SDK. - `ANDROID_TARGET_SDK` overrides the target Android SDK. +- `RELEASE_SIGNKEY` overrides the release keystore path used by `make apk-release`. +- `RELEASE_SIGNPASS_FILE` overrides the password file path used by `make apk-release`. + +Default release-signing paths: + +- `~/.config/keepassgo/android-release.keystore` +- `~/.config/keepassgo/android-release.pass` Installed machine prerequisites expected by this repo: @@ -38,6 +56,9 @@ The repo tracks `gogio` as a Go tool, and the local build runs through: go tool gogio -target android ./cmd/keepassgo ... ``` +The release target wraps `make apk` and injects explicit signing credentials so +local release builds and CI use the same stable key. + The Android build uses the branded icon asset at: - `internal/assets/keepassgo-icon.png` diff --git a/Makefile b/Makefile index cf5eae4..8f549d2 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,8 @@ ANDROID_MIN_SDK ?= 28 ANDROID_TARGET_SDK ?= 35 SIGNKEY ?= SIGNPASS ?= +RELEASE_SIGNKEY ?= $(HOME)/.config/keepassgo/android-release.keystore +RELEASE_SIGNPASS_FILE ?= $(HOME)/.config/keepassgo/android-release.pass ARCH_PKG_DIR ?= packaging/archlinux/keepassgo-git ARCH_PKG_TMPL ?= $(ARCH_PKG_DIR)/PKGBUILD.tmpl ARCH_PKGBUILD ?= $(ARCH_PKG_DIR)/PKGBUILD @@ -26,7 +28,17 @@ ifneq ($(strip $(SIGNPASS)),) GOGIO_SIGN_FLAGS += -signpass $(SIGNPASS) endif -.PHONY: apk apk-local apk-container apk-container-image archlinux-pkgbuild browser-bridge browser-extension-validate +CONTAINER_SIGNKEY_MOUNT := +CONTAINER_SIGN_ARGS := +ifneq ($(strip $(SIGNKEY)),) +CONTAINER_SIGNKEY_MOUNT += -v "$(dir $(abspath $(SIGNKEY))):$(dir $(abspath $(SIGNKEY))):ro" +CONTAINER_SIGN_ARGS += SIGNKEY="$(abspath $(SIGNKEY))" +endif +ifneq ($(strip $(SIGNPASS)),) +CONTAINER_SIGN_ARGS += SIGNPASS="$(SIGNPASS)" +endif + +.PHONY: apk apk-local apk-release apk-container apk-container-image archlinux-pkgbuild browser-bridge browser-extension-validate apk: @if [ -x "$(JAVA_HOME)/bin/java" ] && "$(JAVA_HOME)/bin/java" -version 2>&1 | grep -q 'version "25'; then \ $(MAKE) apk-local JAVA_HOME="$(JAVA_HOME)"; \ @@ -59,6 +71,13 @@ apk-local: android/keepassgo-android.jar -icon internal/assets/keepassgo-icon.png \ ./cmd/keepassgo +apk-release: + @test -f "$(RELEASE_SIGNKEY)" || { echo "Release signing key not found at $(RELEASE_SIGNKEY)"; exit 1; } + @test -f "$(RELEASE_SIGNPASS_FILE)" || { echo "Release signing password file not found at $(RELEASE_SIGNPASS_FILE)"; exit 1; } + @signpass="$$(tr -d '\r\n' < "$(RELEASE_SIGNPASS_FILE)")"; \ + test -n "$$signpass" || { echo "Release signing password file is empty"; exit 1; }; \ + $(MAKE) apk SIGNKEY="$(abspath $(RELEASE_SIGNKEY))" SIGNPASS="$$signpass" + apk-container: apk-container-image @command -v docker >/dev/null 2>&1 || { echo "docker is required for apk-container"; exit 1; } @test -d "$(ANDROID_SDK_ROOT)" || { echo "ANDROID_SDK_ROOT must point to an Android SDK install"; exit 1; } @@ -69,11 +88,12 @@ apk-container: apk-container-image -w "$(CURDIR)" \ -v "$(ANDROID_SDK_ROOT):$(ANDROID_SDK_ROOT)" \ -v "$(ANDROID_NDK_ROOT):$(ANDROID_NDK_ROOT)" \ + $(CONTAINER_SIGNKEY_MOUNT) \ -e ANDROID_SDK_ROOT="$(ANDROID_SDK_ROOT)" \ -e ANDROID_NDK_ROOT="$(ANDROID_NDK_ROOT)" \ -e JAVA_HOME=/opt/java/openjdk \ $(APK_BUILD_IMAGE) \ - make apk-local JAVA_HOME=/opt/java/openjdk + make apk-local JAVA_HOME=/opt/java/openjdk $(CONTAINER_SIGN_ARGS) apk-container-image: @command -v docker >/dev/null 2>&1 || { echo "docker is required for apk-container-image"; exit 1; } diff --git a/README.md b/README.md index 16197cb..fb720d3 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,17 @@ available, it falls back to the repo-managed Docker build image, which also uses Java 25. You still need the Android SDK and NDK installed and configured for real device or release packaging. +Release package: + +```bash +make apk-release +``` + +`make apk-release` is the production-signing path. It requires a dedicated +release keystore at `~/.config/keepassgo/android-release.keystore` and a +password file at `~/.config/keepassgo/android-release.pass`, unless you +override `RELEASE_SIGNKEY` and `RELEASE_SIGNPASS_FILE`. + ## Automation Desktop automation is resolved through the secure gRPC API rather than synthetic auto-type.