Releases
This guide covers how a new Patrimoine version gets from a git tag to a published image, and how the changelog and in-app version display are derived.
Release Flow
Section titled “Release Flow”-
A maintainer bumps the version and tags the commit:
Terminal window git tag vX.Y.Z && git push --tags -
The tag push triggers
.forgejo/workflows/backend.yml,.forgejo/workflows/frontend.ymland.forgejo/workflows/web.yml(all match onpush: tags: v*). Each first calls its matchingci-*.yml(see CI/CD Pipeline) as a pre-publish gate — a tag can never publish an image that skips a check normal CI would have caught. -
Buildah builds and pushes semver-tagged images to the registry — see Docker Images for the build mechanics.
-
An ntfy notification fires on success, failure, or cancellation via
niniyas/ntfy-action.
Android is a separate release track
Section titled “Android is a separate release track”Android ships on its own schedule, unrelated to the v* tag above (backend/frontend/web deploy together; Android ships to app stores/sideload gated by different things — review, signing — entirely).
Run android/scripts/release.sh X.Y.Z to cut a release. It sets versionName and versionCode in android/app/build.gradle.kts, commits the change as chore(android): bump version to X.Y.Z (signed) and creates the signed tag android-vX.Y.Z. versionCode goes up by one unless you pass --code N, and --dry-run only prints the plan. The script never pushes: push the commit, then the tag, to trigger .forgejo/workflows/android.yml:
git push origin HEADgit push origin android-vX.Y.ZThe workflow:
- First runs
android/scripts/check-release-version.sh, which fails the run when the tag does not matchversionName, or whenversionCodeis not higher than on every earlierandroid-v*tag. - Also calls
ci-android.yml. - Builds a signed release APK.
- Generates a CycloneDX SBOM with Syft.
- Generates release notes with
android/scripts/release-notes.sh: the breaking changes, features and fixes (conventional commits) that touchedandroid/since the previousandroid-v*tag.chore,test,docsandrefactorcommits are left out, and the first release reads “Initial release.”. - Publishes the Forgejo release for the tag with
forgejo-release, attaching the APK and the SBOM and using those notes. A re-run replaces the existing release. This needs aFORGEJO_TOKENrepo secret with thewrite:repositoryscope.
No container image is built for Android; there is nothing to push to the registry.
Android release signing
Section titled “Android release signing”The release APK is signed with a keystore that you create once, with keytool:
keytool -genkeypair -v \ -keystore ~/patrimoine-release.keystore \ -alias patrimoine \ -keyalg RSA -keysize 4096 -validity 10000keytool creates a PKCS12 keystore. It has a single password for the store and the key, so keytool asks for only one. Use that same password for storePassword and keyPassword below.
Keep the keystore file outside the repository (the repository ignores *.keystore and keystore.properties) and back it up first. Android refuses an update signed with a different key, and a lost key cannot be recovered.
For a local release build, create android/keystore.properties:
keyAlias=patrimoinekeyPassword=<password>storeFile=/home/you/patrimoine-release.keystorestorePassword=<password>Then run cd android && ./gradlew :app:assembleRelease. The APK is app/build/outputs/apk/release/app-release.apk. Without keystore.properties, a local release build falls back to debug signing.
For releases built by the workflow, add four repository secrets (Actions secrets in the repository settings):
| Secret | Value |
|---|---|
ANDROID_KEYSTORE_BASE64 |
the output of base64 -w0 ~/patrimoine-release.keystore |
ANDROID_KEYSTORE_PASSWORD |
the keystore password |
ANDROID_KEY_ALIAS |
patrimoine |
ANDROID_KEY_PASSWORD |
the same password as ANDROID_KEYSTORE_PASSWORD |
Check the key with keytool -list -v -keystore ~/patrimoine-release.keystore. The certificate fingerprint it prints is the app’s signing identity.
Changelog
Section titled “Changelog”CHANGELOG.md is auto-generated by git-cliff from Conventional Commits, per cliff.toml. Changelog content is never hand-written — it is generated, not described here; do not expect this page to reproduce specific entries.
Notable cliff.toml behaviors relevant to an admin reading release history:
- Only tags matching
v[0-9]*(tag_pattern) count as releases — a stray non-version tag does not shift the release boundary. cliff.tomlkeepsbreaking_always_bump_major = false, which only affects 0.x versions; from 1.0.0 on (the project is at 8.x) a breaking commit (!orBREAKING CHANGE:) bumps the major version.
Version Display
Section titled “Version Display”frontend/scripts/generate-version.mjs populates the in-app version display by writing src/generated/version.json. It resolves the version in this priority order:
- The
PATRIMOINE_VERSIONenv var — set by the Forgejo build workflow from the pushed tag. git describe --tags --exact-match HEAD— for local builds sitting exactly on a tag.- A
"dev"fallback otherwise.
The docs site shows the latest release in its header, linking to that release in the repository. web/documentation/scripts/generate-version.mjs writes src/generated/version.json for it, using the PATRIMOINE_VERSION env var when set (the Forgejo web workflow sets it from the pushed tag), otherwise the most recent tag reachable from HEAD (git describe --tags --abbrev=0). Inside the image build, where git is unavailable, it keeps the file podman.sh generated on the host; with no tag at all, the header shows the repository link without a version.
Two Separate Version Numbers
Section titled “Two Separate Version Numbers”This repository tracks two independent version numbers — do not conflate them:
| Version | Where it lives | What bumps it |
|---|---|---|
| App-wide release version | Git tags (vX.Y.Z) + CHANGELOG.md |
Managed by git-cliff from Conventional Commits, cut by a maintainer pushing a tag |
| Backend package version | backend/pyproject.toml’s version field |
Bumped only on commits that touch backend/app/routers/*.py, using the same feat=minor/fix=patch/BREAKING=major mapping, scoped to the API surface only — a router change bumps it; a service-layer refactor or test-only change does not (see Git Commit Conventions) |
The backend package version is not published as a container tag and does not drive the release flow above — it exists purely to version the API surface independently of when a release is actually cut.