ced537ecf220a5c547deba8cbc747309b2cf8509
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
01706c6053 |
ci(android): say why the keystore did not open
"the keystore did not open — is ANDROID_KEYSTORE_PASSWORD right?" is a guess, and there are three quite different reasons behind it. The step distinguishes them now. **A secret pasted into a web form very often carries a trailing newline**, and a password is compared byte for byte, so the run failed with a password that was correct. Reproduced exactly: keytool rejects `Correct123\n` against a keystore whose password is `Correct123`. CR and LF are stripped from the password, the alias and the key password now, and the step says when that mattered. **A wrong alias failed a minute later, inside Gradle.** It defaults to `yellowjacket`, so any keystore created with another alias got there. The alias is checked up front and the failure lists the aliases the keystore actually holds. **And a truncated or mis-pasted base64 is a different problem from a bad password**, so the artifact is described before it is opened: size and its first four bytes, named as PKCS12 or legacy JKS, with a warning when the header is neither. A truncation shows up as 300 bytes against 2564. Verified against real keystores for all five cases: correct, trailing newline, wrong password, wrong alias, truncated base64. Decode and build are one step now. Splitting them would mean either handing the password to a later step through $GITHUB_ENV -- where the env dump is only masked for values that are verbatim a secret, so a trimmed one could print in clear -- or repeating the trimming in both. The failure message also prints the password's length, which is the one thing that distinguishes "wrong value" from "invisible whitespace", and only on failure. |
||
|
|
0c6ca72cf1 |
ci(android): publish a signed APK on every version tag
Builds the fat APK and puts it in Gitea's *generic* package registry, which unlike the repository is readable without credentials -- the reason an Obtainium client can poll a plain URL with no token and no public mirror of the source. A versioned copy for history, a fixed `latest` URL to watch. **Its own workflow, not a job in ci.yml.** That workflow runs on every branch push and is the one that gates; this takes tens of minutes on a cold cache and the runner has capacity 1, so hanging it off the gate would put every push behind an SDK download. **Keyed on the tag.** The ljos pipeline this is modelled on computes a version in CI and cuts the release itself, then gates its Android job on needs.release.outputs.version with an always() whose absence silently kills the manual path. This repo has no release automation -- tags are pushed by hand and homebrew-formula.yml already keys on v* -- so the tag is the version and none of that machinery, or its failure modes, is needed. **No continue-on-error**, which that pipeline does carry: there the Android job shares a workflow with a server deploy that must never go red over a phone build. Here it is standalone and can neither delay nor redden anything, so a release step that fails silently would be strictly worse than one that fails visibly. Four gates before anything is published, each checked against a real APK: a non-empty artifact, both ABIs present, a versionCode equal to the one derived from the tag, and -- verified by pointing it at a deliberately debug-signed build, which it refused -- **not signed with the debug key**. Android refuses to update an app whose signing certificate changed and the only remedy is an uninstall that takes the user's library with it, so the job also refuses to *build* without the keystore secret rather than falling through to Gradle's debug default. The keystore is opened with `keytool -list` before Gradle runs, because Gradle only notices a bad password at :app:validateSigningRelease, a minute of build time in, and reports it as a missing file. And nothing pipes into `head`: under pipefail it exits after one line, the producer takes SIGPIPE and the step fails with 141 having already printed a perfectly good APK. Two secrets, not four. keytool has produced PKCS12 by default since JDK 9 regardless of the .jks extension, and PKCS12 cannot hold a key password distinct from the store password -- given one it says so and ignores it. So ANDROID_KEY_PASSWORD defaults to the store password and the alias to a documented default. The Wails CLI needs no caching hack here: it is a vendored `go tool` and the runner already bind-mounts GOCACHE for every job, so it is warm from ci.yml's own bindings-check. A fourth cache volume for GRADLE_USER_HOME saves ~700MB a run. |