Releasing¶
How Sevak versions are published, how to publish them more carefully, and how to take one back. For the build, signing key and package-manager secrets see Development; this page is the maintainer's runbook.
The two flows¶
| Auto (default) | Staged (opt-in) | |
|---|---|---|
A merge to main publishes |
a stable release vX.Y.Z |
a beta pre-release vX.Y.Z-beta.N |
latest.json (stable users) |
updated at once | untouched until you promote |
latest-beta.json (beta users) |
not touched | updated |
| winget, Scoop, Homebrew, AUR | updated | updated only when you promote |
| Stable users get it | within about a day | after you run Promote |
Nothing changes until you switch: with no setup, every merge publishes a stable release exactly as before.
Auto flow¶
.github/workflows/release.yml runs on every push to main that changes more
than docs: it works out the version, builds on Windows, macOS and Linux, signs the
installers for the updater, writes latest.json and SHA256SUMS.txt, publishes
the release and then updates the package managers. It also calls
sbom.yml and attest.yml, which attach the SBOMs and the build-provenance
attestations to the published release. Details are in
Development and
Supply chain.
Staged flow¶
- Switch it on (once): set the repository variable
RELEASE_MODEtostaged(Settings, Secrets and variables, Actions, Variables, orgh variable set RELEASE_MODE --body staged). Setting it toauto, or deleting it, goes back to the default. Any other value is ignored with a warning and releases stable. - Merges publish betas. The same workflow now publishes
vX.Y.Z-beta.Nas a GitHub pre-release withlatest-beta.jsonandSHA256SUMS.txt.X.Y.Zis the version the next stable release would get;Ncounts up from 1 for thatX.Y.Z. People on the beta channel (Settings, General, Update channel) are offered it. Stable users,latest.jsonand the package managers are not touched. - Soak it. Install the beta yourself, run the checklist, and give beta users some time. A sensible default is two days with no regression reports.
- Promote. Actions, Promote, enter the beta tag (for example
v1.3.0-beta.2). The workflow downloads the release's assets, verifiesSHA256SUMS.txtand the signatures, writeslatest.jsonfrom them, makes the release the newest stable one, updates the package managers and attaches the SBOMs and attestations (betas get neither before this). Nothing is rebuilt: the files stable users get are the files beta users tested.
You can also cut a beta without switching modes: Actions, Release, Run workflow,
channel = beta (builds from main), or channel = stable to cut a stable
release while RELEASE_MODE is staged. channel = default follows the variable.
A promoted build keeps its beta version
The installers are not rebuilt, so the version inside them stays
1.3.0-beta.2 and stable users see that string in "About" and in update
prompts. It is a valid, higher version than any earlier stable (1.2.9 <
1.3.0-beta.2), and the next real 1.3.0 or 1.3.1 is higher again, so
updates keep working. If you want a plain 1.3.0 instead, run Release with
channel = stable on the same commit: it rebuilds and ships v1.3.0 through
the normal path, at the cost of shipping bits that were not the ones tested.
How the beta channel is wired¶
GitHub's releases/latest/download/ shortcut, which stable installs use, skips
pre-releases. So a pre-release can't be found that way. Each beta publish also
uploads latest-beta.json to one fixed pre-release tagged channel-beta (titled
"Beta channel (do not download)", created on first use). Beta installs read
https://github.com/ninad-k/Sevak/releases/download/channel-beta/latest-beta.json
and also the stable latest.json, and offer the newer of the two. That is why a
stable release that follows the last beta still reaches beta users, and why a beta
user who switches back to Stable is never moved to an older version: the updater
only offers a version higher than the one installed.
Version numbers and commit types¶
The version comes from the Conventional Commit prefixes in the pull request title
(and commit messages) since the newest stable vX.Y.Z tag
(scripts/release-version.mjs):
| Prefix | Bump |
|---|---|
feat!:, fix!: or a BREAKING CHANGE: footer |
major (minor while on 0.x) |
feat: |
minor |
fix:, docs:, chore:, ci:, refactor:, test:, perf: |
patch |
Betas use the version the next stable release would get plus -beta.N. Beta tags
never move the baseline for stable versions. A pull-request title also sets a
label (.github/workflows/pr-labels.yml) that groups the generated release notes
(.github/release.yml) into Breaking changes, New features, Fixes, Performance,
Documentation and Maintenance; label a PR skip-changelog to leave it out.
Preview locally: node scripts/release-version.mjs next (stable),
... next-beta, ... compare 1.3.0-beta.2 1.3.0.
Release notes¶
The release starts as a draft with generated notes plus a Known issues and
upgrade notes section ("None known for this release."). The draft exists while the
installers build, so you can edit that section then; the workflow copies the notes
into the update manifest when it publishes. In the staged flow the beta stays a
published pre-release until you promote it: edit its notes before you run Promote,
which copies them into latest.json as the update notes (editing later changes the
release page but not what the updater shows).
Security hardening. The first release that contains the security hardening
changes (approvals bound to contents, scrubbed script environment, encrypted
clipboard history in the local folder, tag-pinned galleries and the rest) changes
behaviour you will want users to hear about: scripts and paste workflows ask for
approval once more, remapped shortcuts need accept_injected_hotkeys, snippets
stop expanding in browsers, and network paths are off. Paste the bullets of
Hardening: release notes into Known
issues and upgrade notes for that release.
Before promoting a beta¶
Tick these before you run Promote (or, in the auto flow, before merging anything that touches the risk areas below). The detailed manual cases are in Testing; use the risk-area list here to choose the checks that apply to the release.
Always
- CI is green on
mainon Windows, macOS and Linux (lint, tests, bundle build). - The release run finished: all three build jobs succeeded and the release
has installers,
.sigfiles,latest-beta.jsonandSHA256SUMS.txt. - Beta notes read well; fill in Known issues and upgrade notes.
By risk area (do the ones your changes touch)
| Area | Check |
|---|---|
| Installer and upgrade | Install the new build over the previous stable on Windows (NSIS and, if changed, MSI), macOS (drag to Applications) and Linux (.deb/AppImage). Config and data survive; the old version is replaced, not duplicated. |
| Hotkey | The global hotkey opens Sevak after a fresh install and after an upgrade; the Universal Actions hotkey works; Wayland and GNOME paths if touched. |
| Updater | From the previous stable, switch Update channel to Beta and use the tray's Check for updates: the beta is offered, the signature verifies, install and restart work, the new version number shows. Then switch back to Stable and check no downgrade is offered. Also check a plain stable-to-stable update on the previous release. |
| Signature | The .sig files exist for every installer, latest-beta.json lists all platforms with signatures, and node scripts/verify-release.mjs <dir> <version> latest-beta.json passes on the downloaded assets (Promote runs this too). |
| Config and migrations | Start the new build with the previous release's config.toml; nothing is reset and no warning appears. |
| Platform integration | Autostart, tray, single-instance, clipboard and file search on each OS you changed. |
| Packages | If packaging changed, render the manifests: node scripts/package-manifests.mjs <version> SHA256SUMS.txt out. |
After a release¶
- Download. Fetch one installer per OS from the release page and run it.
- Checksum.
sha256sum -c SHA256SUMS.txt(orshasum -a 256 -c) in the download folder reports every file OK. - Manifest. Open
https://github.com/ninad-k/Sevak/releases/latest/download/latest.json(stable) or.../releases/download/channel-beta/latest-beta.json(beta). Theversionis the one you released, every platform has aurlunder the new tag and asignature. Promote and Rollback print this check at the end of the run; GitHub can take a minute to serve a new file. - Updater. On a machine with the previous version, Check for updates offers the new one and installing it works.
- Package managers (stable releases): the winget pull request, the Scoop and Homebrew commits and the AUR push appear; each is skipped while its secret is unset.
- SBOMs and attestations (stable releases): the release has
sevak-sbom-rust.cdx.json,sevak-sbom-npm.cdx.jsonandSBOM-SHA256SUMS.txt, andgh attestation verify <installer> --repo ninad-k/Sevaksucceeds. If theSBOMorAttestjob failed, the release itself is fine: re-run the job, or run the workflow from the Actions tab with the tag.
Rolling back¶
.github/workflows/rollback.yml (Actions, Rollback). Inputs: tag (a
known-good earlier release to serve as stable again), bad_tag (the release to
withdraw) and a one-line reason.
What it does:
- Downloads the earlier release and verifies it: checksums, a signature next to
every installer, and a
latest.jsonthat agrees with them. It refuses if any of that is missing. - Makes that release the newest stable release again, so
releases/latest/download/latest.jsonserves it. - Marks the bad release as a pre-release with a "withdrawn" note on top (its files stay for investigation).
- If the beta channel was serving the bad version, points it at the earlier release too.
What rollback cannot do¶
Installed copies of the bad version are not moved back. The updater only offers
a version higher than the installed one, on purpose: a downgrade switch would let
anyone who can serve a crafted manifest push people to an old, genuinely signed but
vulnerable build, because the manifest itself is not signed (only the installer is).
Sevak keeps that guard, and there is no downgrade flag in latest.json. So:
- People who have not updated yet stay on their good version: they are no longer offered the bad one. This is what rollback is for, and why you do it fast.
- People who already updated to the bad version stay on it until a newer
version exists. The real fix is to publish a fixed newer version quickly: merge
a
fix:(a patch bump above the bad one, even though the bad tag is now a pre-release, since version numbers never go down). - Package managers are not touched by the workflow. If winget, Scoop, Homebrew or
the AUR already carry the bad version, revert the Scoop and Homebrew commits by
hand, push the earlier
PKGBUILDto the AUR, and close or amend the winget pull request. The next release overwrites them anyway. - In an emergency, someone can still reinstall the earlier version by hand from its release page. Say so in the withdrawn note.
Procedure¶
- Decide fast: is the problem in the update path itself, data loss, or a crash on start? Roll back first, investigate second.
- Run Rollback with the last good tag and the bad tag.
- Check
latest.jsonserves the good version (After a release). - Merge the fix (see Hotfix). In the staged flow the fix goes out as a
beta first; for an urgent fix, run Release with
channel=stable. - Write down what happened in the withdrawn release's notes and, if useful, an issue.
Hotfix¶
- Branch from
main(the bad code is onmain, so the fix goes on top; there are no release branches). - Pull request titled
fix: ...with a test where possible; CI must pass. - Merge. Auto flow: it is published as the next patch release and reaches
everyone within the day, including people who took the bad version.
Staged flow: it is published as a beta; to ship it straight away run
Release with
channel=stable(it builds and ships the stable release frommainimmediately) rather than waiting for a soak. - Verify as in After a release.
Who does what¶
| Task | Who |
|---|---|
| Review and merge pull requests, choose the title prefix that sets the bump | Maintainer |
Switch RELEASE_MODE, run Release, Promote and Rollback |
Maintainer (needs write access to the repository) |
| Soak a beta and run the checklist | Maintainer, optionally testers on the beta channel |
Signing key, PACKAGING_TOKEN, WINGET_TOKEN, AUR_SSH_PRIVATE_KEY |
Repository secrets; see Development |
What the scripts and workflows are¶
| File | Purpose |
|---|---|
.github/workflows/release.yml |
Plan, build, publish, distribute; stable or beta |
.github/workflows/promote.yml |
Publish an existing release as stable, without rebuilding |
.github/workflows/sbom.yml, .github/workflows/attest.yml |
SBOMs and build-provenance attestations; called by Release and Promote for stable releases, or run by hand for a tag |
.github/workflows/rollback.yml |
Serve an earlier release as stable again |
.github/workflows/pr-labels.yml, .github/release.yml |
Group the generated release notes by commit type |
.github/workflows/release-checks.yml |
Tests the scripts below and parses the workflows on pull requests |
scripts/release-version.mjs |
next, next-beta, previous-tag, compare, set |
scripts/updater-manifest.mjs |
Writes latest.json / latest-beta.json |
scripts/verify-release.mjs |
Checks checksums, signatures and the manifest of a downloaded release |
Run the script tests with npm run test:scripts.