14 KiB
description, icon
| description | icon |
|---|---|
| Release cadence, version policy, OAuth-and-installer rules. How shipping works. | ship |
Release policy: latest desktop builds and OAuth
This runbook describes how we avoid users completing OAuth (including Gmail) on outdated desktop installers while the canonical flow is the latest release.
Distribution
- GitHub Releases for tinyhumansai/openhuman are the primary source for desktop builds.
- The Tauri updater endpoint (see
scripts/prepareTauriConfig.jsand release workflows) should point users at the current release artifacts. - Retiring old stable artifacts: When dropping a release line, remove or hide obsolete installer assets on GitHub Releases, update website / CDN download links to releases/latest (or current), refresh the updater manifest (e.g. Gist /
latest.json) so it does not point users at deprecated builds, and spot-check that old direct URLs are redirected, 404, or 410 where appropriate. Verification: try known-old asset URLs from docs or bookmarks and confirm they no longer deliver primary install paths.
Minimum app version for OAuth
Production web builds embed a minimum supported app semver at build time so OAuth deep links cannot complete on deprecated binaries. Each installer carries the floor that was set when that build was produced; raising the floor for users who never upgrade requires a new release they install (or in-app update). Optional future work: enforce a moving minimum via a runtime API with the bundled value as fallback only.
| Variable | Purpose |
|---|---|
VITE_MINIMUM_SUPPORTED_APP_VERSION |
e.g. 0.51.0 - desktop app must be ≥ this to finish openhuman://oauth/success. |
VITE_LATEST_APP_DOWNLOAD_URL |
Optional; defaults to https://github.com/tinyhumansai/openhuman/releases/latest. Opened when the gate blocks OAuth. |
Configure these as GitHub Actions variables. They must be present on both the standalone pnpm build step and the tauri-apps/tauri-action step env in .github/workflows/build-desktop.yml (the reusable matrix invoked by release-production.yml / release-staging.yml) so the Vite bundle embedded in shipped installers includes the gate. Leave VITE_MINIMUM_SUPPORTED_APP_VERSION unset for local dev (gate disabled).
Implementation: app/src/utils/oauthAppVersionGate.ts, app/src/utils/desktopDeepLinkListener.ts.
Gmail / Google Cloud OAuth
- Redirect URIs in Google Cloud Console must match the current backend + tunnel callback paths.
- The desktop scheme (
openhuman://) is stable; the installed binary must meet the minimum version whenVITE_MINIMUM_SUPPORTED_APP_VERSIONis set.
Release checklist (avoid regressions)
- Bump
app/package.jsonandapp/src-tauri/tauri.conf.json(and rootCargo.toml/ core) per existing version workflows. - When dropping support for older installs, set
VITE_MINIMUM_SUPPORTED_APP_VERSIONto the new floor before or with that release (repo Actions variables + both workflow steps above). - Remove, redirect, or retire older stable installers and stale updater entries from user-facing surfaces (GitHub Release assets, website, CDN, updater feed). Confirm deprecated artifacts are not reachable from default install/update flows.
- Smoke-test Gmail connect on a fresh install from releases/latest.
- Complete the manual smoke checklist, then paste the completed sign-off block (verbatim, with every checked item left checked) into the release PR description before tagging.
Workflows: staging vs. production
Two first-class GitHub Actions workflows, one per environment. Pick by intent rather than toggling a flag.
| Workflow | Branch | Bumps | Tags pushed | Concurrency group | Use when |
|---|---|---|---|---|---|
release-staging.yml |
main |
patch only |
v<version>-staging |
release-staging |
Cutting a staging build for QA. Runs frequently; narrow semver moves. |
release-production.yml |
main |
patch / minor / major (only on main_head) |
v<version> |
release-production |
Promoting a validated staging tag, or hotfixing from main HEAD. |
The matrix build / sign / Sentry-DIF / artifact-upload pipeline used by both flows lives in .github/workflows/build-desktop.yml as a workflow_call reusable workflow. The two top-level workflows above own ref resolution, version bumping, tagging, and publish/cleanup; the build itself is shared.
Android / Google Play
Android releases are handled by the separate .github/workflows/android-compile.yml workflow, which builds a release Android App Bundle (.aab), signs it with the Play upload key, and uploads it to Google Play when publishing is enabled. The workflow keeps the unsigned and signed AABs as Actions artifacts for audit/debugging.
Manual Android uploads use the same workflow:
pnpm --dir app release:android:play -- --track internal
pnpm --dir app release:android:play -- --ref main --track production --status draft
Required GitHub Actions secrets:
| Secret | Purpose |
|---|---|
ANDROID_UPLOAD_KEYSTORE_BASE64 |
Base64-encoded Play upload keystore (.jks). Use the upload key, not the Google app signing key. |
ANDROID_UPLOAD_KEY_ALIAS |
Keystore alias for the upload key. |
ANDROID_UPLOAD_KEYSTORE_PASSWORD |
Keystore password. |
ANDROID_UPLOAD_KEY_PASSWORD |
Key password. |
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON |
Raw JSON for the Play Console service account with release permissions for com.openhuman.app. |
Optional GitHub Actions variables:
| Variable | Default | Purpose |
|---|---|---|
ANDROID_PLAY_TRACK |
internal |
Play track to upload to (internal, alpha, beta, or production). |
ANDROID_PLAY_STATUS |
completed |
Play release status (completed, draft, inProgress, halted). |
Google Play requires each upload to use a monotonically increasing Android versionCode. The release bump scripts update app/src-tauri-mobile/tauri.conf.json, app/src-tauri-mobile/Cargo.toml, and app/src-tauri-mobile/Cargo.lock alongside desktop files so the generated Android tauri.properties moves with each release.
Cutting a staging build
- Run Release (Staging) via
workflow_dispatchfrommain. - The workflow bumps
patchonmain, commitschore(staging): vX.Y.Z, pushes the branch, and creates an immutablevX.Y.Z-stagingtag at that commit. - Build matrix runs from the tag (not main HEAD), so reruns rebuild byte-identical content even if
mainhas moved on. - On failure the staging tag is auto-deleted; the bump commit on
mainstays so the next cut continues fromvX.Y.(Z+1).
There is no separate staging branch, staging cuts and production promotions both live on main. The two are distinguished only by tag suffix (-staging vs none) and by which workflow created the tag.
Promoting to production (default flow)
- Run Release Production via
workflow_dispatchwithrelease_source = staging_tag(the default). - Leave
staging_tagblank to promote the latestv*-staging, or pass an explicit tag (e.g.v1.2.4-staging) to pin. - The workflow strips
-staging, createsv<version>at the same commit, and runs the production build matrix from that tag. No further version bump, the artifact reuses what staging already validated.
Hotfix from main HEAD
- Run Release Production via
workflow_dispatchwithrelease_source = main_headand the desiredrelease_type(patch/minor/major). - The run first parks on the
review-approvaljob (environment: Release-Approval); a required reviewer must approve before anything is pushed. Only after approval doesprepare-buildrun the bump-and-tag path: bump onmain, commitchore(release): vX.Y.Z, push, tagvX.Y.Z, build. - Use this only when a production-only fix needs to ship without going through staging.
The
staging_tagpromotion path is not gated byreview-approval: it pushes only an immutablev<version>tag at an already-validated staging commit, never a commit tomain. It remains gated by the existingProductionenvironment.
Tag policy and rollback
- Naming. Staging tags use the SemVer pre-release suffix
-staging(v1.2.4-staging) so they sort before the matching production tag. Promotion to production drops the suffix verbatim; the version embedded in the bundled installer is identical between the two tags. - Collisions. Both workflows fail fast if the target tag already exists locally or on
origin. Resolve by deleting the stale tag (org maintainers only) or bumping past it. - Rollback (production). A failed build matrix triggers
cleanup-failed-release, which deletes both the draft GitHub Release and thev<version>tag. The staging tag it was promoted from is left untouched and can be re-promoted after fixing. - Rollback (staging). A failed staging build deletes the
v<version>-stagingtag. The bump commit onmainis left in place; the next staging cut continues from the new patch number rather than re-using it (we accept a small “gap” in patch numbers over racing with concurrent merges). - Who can delete tags. Same write-access as
main. Workflow-driven cleanup deletes run with the workflow's token viaactions/github-script(the GitHub App token is only used byprepare-buildfor the bump commit + tag push); manual deletes (git push --delete origin <tag>) require equivalent maintainer permissions.
Release App token: approval gate and rotation
The main_head path of release-production.yml bumps the version, commits to main, and pushes that commit + tag with a GitHub App token (secrets.XGITHUB_APP_ID / secrets.XGITHUB_APP_PRIVATE_KEY) that bypasses branch protection. A leaked private key (via a log, a compromised action, or a misconfigured runner) would let an attacker push arbitrary commits to the default branch (CWE-250: Execution with Unnecessary Privileges). Two controls bound that blast radius.
Manual approval gate
The review-approval job runs before prepare-build and parks the run on the Release-Approval GitHub environment, so a human must approve before any push happens. It is scoped to release_source == 'main_head'; the staging_tag promotion path skips it (it pushes only a tag, never a commit to main).
One-time setup (repo Settings → Environments):
- Create an environment named
Release-Approval(exact name: the workflow references it verbatim). - Under Deployment protection rules, enable Required reviewers and add the maintainers allowed to authorize a production push to
main. A reviewer cannot approve their own run unless Prevent self-review is left off; for a release gate, keeping a second approver is preferred. - (Optional) Set a short wait timer of 0. The gate is a human decision, not a delay.
When a main_head run starts it shows "Waiting" on the review-approval job; an approver opens the run and clicks Review deployments → Approve. Rejecting (or cancelling) leaves prepare-build skipped, so nothing is pushed.
Quarterly key rotation
Rotate XGITHUB_APP_PRIVATE_KEY every quarter (and immediately on any suspected exposure). Schedule: end of Mar / Jun / Sep / Dec.
- In the GitHub App settings (Org → Settings → Developer settings → GitHub Apps → the release App), under Private keys click Generate a private key. Download the new
.pem. - Update the repo secret: Settings → Secrets and variables → Actions →
XGITHUB_APP_PRIVATE_KEY→ paste the full new key (including the-----BEGIN/END-----lines).XGITHUB_APP_IDis unchanged. - Trigger a low-risk verification run (e.g. Release (Staging)) and confirm the Generate GitHub App token step succeeds and the push authenticates.
Do not use
main_headfor this check unless you intentionally want to cut a real bump commit and production tag: even withcreate_release = false,prepare-buildstill bumps the version, commits tomain, and pushes the release tag. - Back in App settings → Private keys, delete the old key so only the freshly-issued one remains valid.
- Record the rotation date (PR description, ops log, or
docs/OPERATIONS.md) so the next quarter's owner can see when it last happened.
Rotating invalidates any copy of the old key that may have leaked, capping the exposure window at one quarter.