Cut a release
A Primico release is a GitHub release carrying two APKs and the desktop packages. The same script
builds them on a laptop and on a runner, so you can publish from either. This guide is the human
summary of the releasing skill
(.claude/skills/releasing/SKILL.md). Every task and
workflow input is in the build and CI reference.
Prerequisites
Section titled “Prerequisites”- You’re on
main, up to date, with a clean working tree. A release is always cut frommain. - JDK 17+ and the Android SDK (for the APKs);
fakerootfor a.debandrpmbuildfor an.rpmon Linux. gh, authenticated against the repository, for publishing.- For an official build: the release keystore and its passwords, and the two
CADENCE_NEON_*sync endpoints. Without the keystore the release APK falls back to the debug key, and without the endpoints the build has no sync. Both are fine for a test build and wrong for a public one.
How the version is decided
Section titled “How the version is decided”You never edit a version. .github/scripts/next-version.sh reads the Conventional Commit subjects
since the last v* tag and bumps:
| Commits since the last tag contain… | Bump |
|---|---|
a ! before the colon (feat!:, fix(sync)!:) or a BREAKING CHANGE: footer |
major |
any feat: |
minor |
| anything else | patch |
versionCode is major × 10000 + minor × 100 + patch. Preview what releasing now would publish:
bash .github/scripts/next-version.shskip=falseversion=4.1.0tag=v4.1.0version_code=40100bump=minorprevious_tag=v4.0.0notes<<NOTES_EOF### Features…NOTES_EOFThe notes block is the release notes, rendered from the same commits. With no commits since the
last tag it prints skip=true instead, and release.yml refuses to run.
Option A: release from a laptop
Section titled “Option A: release from a laptop”build.sh is the whole build: it derives the version, runs one Gradle invocation for every
artefact, stages the files in dist/ under the names the download links use, and checks the
APK signatures.
-
See the plan. Nothing is built:
Terminal window bash .github/scripts/build.sh apk deb --dry-run==> Preflightjava 17, linuxversion 4.1.0 (40100) — derived from commits==> Plantargets: apk debversion: 4.1.0 (40100)gradle: ./gradlew :core:jvmTest :ui:jvmTest :app-desktop:test :ui:compileKotlinJvm :core:testDebugUnitTest :ui:testDebugUnitTest assembleDebug assembleRelease :app-desktop:packageDeb --console=plain --stacktraceoutput: dist/apk debis what a release ships today. With no targets it buildsall: the APKs plus every desktop format this OS can package (deb rpm taron Linux,dmgon macOS,msion Windows). A format whose tool is missing is skipped with a warning when an alias implied it, and is a hard error when you named it. -
Build with the release key and the sync endpoints:
Terminal window export CADENCE_KEYSTORE_PASSWORD=… CADENCE_KEY_ALIAS=… CADENCE_KEY_PASSWORD=…export CADENCE_NEON_DATA_API_URL='https://<endpoint>.apirest.<region>.aws.neon.tech/neondb/rest/v1'export CADENCE_NEON_AUTH_URL='https://<endpoint>.neonauth.<region>.aws.neon.tech/neondb/auth'bash .github/scripts/build.sh apk deb --clean \--keystore ~/.cadence-release/cadence-release.jksIt runs the tests first (
--skip-testsskips them), then stagesdist/primico-debug.apk,dist/primico-release.apkand the.debunder its jpackage name, then runscheck-signing.sh. It ends by printing the publish command. -
Publish:
Terminal window gh release create v4.1.0 dist/* --title "Primico 4.1.0" --generate-notesUse the exact line
build.shprinted. The tag is created on the default branch, which is why you release from an up-to-datemain. The permanent linkreleases/latest/download/primico-release.apknow serves the new build.
Option B: release from GitHub Actions
Section titled “Option B: release from GitHub Actions”release.yml runs the same script on runners, and it’s the
only workflow that publishes. It refuses to run from any branch but main, and refuses when
there are no commits since the last tag.
# build and test everything, publish nothinggh workflow run release.yml --ref main -f targets=android+deb -f dry-run=true
# the real thinggh workflow run release.yml --ref main -f targets=android+debtargets |
Builds | Runners |
|---|---|---|
android+deb (default) |
both APKs and the Linux .deb, which is a normal release |
two Linux |
android |
the APKs only | one Linux |
deb |
the .deb only |
one Linux |
all |
plus .rpm, the tarball, .dmg and .msi |
adds a macOS (10× minutes) and a Windows (2×) runner |
The release body’s download list is generated from the files that actually landed, so a run
without an .msi publishes no link to one.
Signing: two keys, two rules
Section titled “Signing: two keys, two rules”- The debug key is committed (
app-android/debug.keystore) and must stay committed. Android only installs a build over another one signed with the same certificate, and without a pinned key every CI run would invent a new one. Up to v1.1.2 that happened, and phones said “App not installed”.check-signing.shfails the build if the debug APK stops matching the committed keystore. - The release key never enters a checkout. It lives outside the repository
(
~/.cadence-release/by default) and reaches CI through four secrets:CADENCE_KEYSTORE_BASE64,CADENCE_KEYSTORE_PASSWORD,CADENCE_KEY_ALIASandCADENCE_KEY_PASSWORD. WhenCADENCE_KEYSTOREis set but the release APK still carries the debug certificate,check-signing.shfails, because a release built that way could update nobody.
To mint the keystore, or to (re)set all six secrets (the four signing ones plus
CADENCE_NEON_DATA_API_URL and CADENCE_NEON_AUTH_URL), run the wizard. It needs keytool, an
authenticated gh and base64:
bash tools/release-signing-wizard.shRe-running is safe: it reuses an existing keystore and never overwrites one. Passphrases are never written anywhere.
Verify
Section titled “Verify”gh release view v4.1.0lists the assets, and the release is marked Latest.- On a phone with the previous release installed, open
https://github.com/viberfasend/primico/releases/latest/download/primico-release.apk. It should install over the old version and keep the tasks. - In the new build’s Settings, the Sync section shows a sign-in form. If it says the build has no
sync server configured, the
CADENCE_NEON_*variables were missing at build time.
Related
Section titled “Related”- Build and CI reference: every workflow, input and
build.shflag. - Configuration reference: every
CADENCE_*variable. - Self-hosting sync: the endpoints a fork’s release is built against.