release candidates: every push to main runs the full CI, the acceptance matrix and the Linux / Windows installer builds in parallel (ci.yml) and records the green result per commit as the draft rc-<sha> (green.json last); a version-only bump commit (proven file by file with the version writers) reuses its parent's CI and matrix and rebuilds only the installers; a v* tag only promotes the candidate (release.yml), fails fast on a red or missing commit; acceptance runs carry a CLI key; tools/release.ts does the whole release on the Mac (bump, CHANGELOG cut, fast gate, push, tag, Mac build + notarize in parallel with Forgejo, cached macOS acceptance, add, verify, publish with one upload of the Mac files, /Applications update, npm commands) and --rehearsal; DMG notarization runs while the app and updater bundle are checked; rehearsal channel on the VDS; docs
This commit is contained in:
parent
89d94d6993
commit
c3b1e8c370
14 changed files with 1438 additions and 526 deletions
|
|
@ -31,55 +31,116 @@ in `~/Desktop/storebound-dist/desktop-0.1.0` was never published, so no installe
|
|||
`tauri.conf.json`, `Cargo.toml` and the app's entry in `Cargo.lock`. CI runs this check; `src-tauri/tests/d5.rs`
|
||||
checks the app's copies from the Rust side.
|
||||
- `bun tools/desktop-version.ts --set 0.8.0` writes a new version into package.json and all of them.
|
||||
- **Release order** (one commit, one version): see "The release on git.crt.fyi" below. The app goes first
|
||||
- **Release order** (one commit, one version): see "The release in one command" below. The app goes first
|
||||
(`publish` refuses a folder whose version is not package.json's), then `npm publish` of the same version
|
||||
(CONTRIBUTING.md), so `npx storebound desktop` of the new CLI finds its own version.
|
||||
|
||||
## The release on git.crt.fyi
|
||||
## The release in one command
|
||||
|
||||
One version tag, one Forgejo release: git.crt.fyi/CRT/storebound/releases keeps only the newest entry (older entries
|
||||
are deleted after a successful release; their git tags stay). Notes are short: the CHANGELOG section's summary
|
||||
paragraph (at most 4 lines), the install line and links, never the whole section. So the section's first paragraph
|
||||
is the release summary: write it for that.
|
||||
```sh
|
||||
bun tools/release.ts 0.8.5 # checks and the plan
|
||||
bun tools/release.ts 0.8.5 --yes # the release; then the owner's npm publish
|
||||
```
|
||||
|
||||
1. **Version and changelog** on main: `bun tools/desktop-version.ts --set 0.8.0`, a `## 0.8.0 · <date>` section
|
||||
that starts with a summary paragraph, `bun run ci`, push.
|
||||
2. **Tag push:** `git tag v0.8.0 && git push origin v0.8.0`. `.forgejo/workflows/release.yml` then:
|
||||
- checks that the tag is `v` + package.json's version, every version copy matches (tools/desktop-version.ts) and
|
||||
the CHANGELOG has the section;
|
||||
- runs the full CI (`bun run ci`, the same steps as ci.yml: `.forgejo/actions/linux-setup` and `ci-run`);
|
||||
- builds the AppImage + deb (Linux runner) and the NSIS installer (Windows runner), with the checks described
|
||||
under "Windows and Linux" below;
|
||||
- scans every artifact with the private terms (secret `FORBIDDEN_TERMS`), writes SHA256SUMS;
|
||||
- creates or updates the release "storebound 0.8.0" (`tools/forgejo-release.ts upsert`: found by tag, assets of
|
||||
the same name replaced, SHA256SUMS merged, read back), then deletes the older release entries
|
||||
(`forgejo-release.ts prune --keep v0.8.0`).
|
||||
3. **macOS on the Mac** (the DMG cannot be built or notarized on these runners): `bun tools/desktop-release.ts mac`,
|
||||
then `add` of the runners' Linux / Windows files (signs them for the updater, see below), then
|
||||
`bun tools/desktop-release.ts upload-forgejo --out ~/.storebound-releases/desktop/0.8.0 --yes`: the signed DMG and
|
||||
the updater bundle with their `.sig` join the same release, SHA256SUMS is merged. It refuses an unsigned DMG and
|
||||
a release the workflow has not made yet. Token: `FORGEJO_TOKEN`, else the git credential helper of git.crt.fyi.
|
||||
4. **storebound.dev:** `bun tools/desktop-release.ts publish --out ... --yes` (below).
|
||||
5. **npm:** `npm publish` of the same version: the owner's step (CONTRIBUTING.md).
|
||||
Option, not set up: an npm automation token as the Actions secret `NPM_TOKEN` and a manual `npm` job in
|
||||
release.yml (`needs: release`, workflow_dispatch only, `npm publish` after the Mac's upload) would let the owner
|
||||
publish with one click in Forgejo instead of a terminal. The token can publish any version of the package, so it
|
||||
stays off until the owner wants it.
|
||||
Target: tag push to everything published (the Forgejo release with every installer, storebound.dev's latest.json,
|
||||
the app in /Applications) in under 10 minutes, with every gate still in place. It works because nothing is built or
|
||||
tested twice: every push to main is already a release candidate.
|
||||
|
||||
**Dry run** (no version, nothing public): Actions > release > Run workflow with `dry` ticked (the default), or
|
||||
`POST /api/v1/repos/CRT/storebound/actions/workflows/release.yml/dispatches` with
|
||||
`{"ref":"main","inputs":{"dry":"on","windows":"on"}}` (over the API a boolean input is "on"; "true" counts as off).
|
||||
The same jobs run on that commit; the release job makes a DRAFT `test-<sha>` with the real files, reads it back,
|
||||
prints what `prune` would delete, and deletes the draft at the end (also when a step failed). `dry` off needs a `tag`
|
||||
input and remakes that tag's release.
|
||||
### Release candidates (every push to main)
|
||||
|
||||
`.forgejo/workflows/ci.yml` runs on every push to main, in parallel:
|
||||
|
||||
- `ci`: the full `bun run ci` (Rust tests and clippy included);
|
||||
- `acceptance`: the release acceptance matrix, Linux and Windows x Node 20 / Node 22 / Bun (docs/acceptance.md);
|
||||
- `linux`: the AppImage and the deb, self-tests, both started under Xvfb, privacy scan (the release target folder is
|
||||
cached, so only changed crates compile);
|
||||
- `windows`: the NSIS installer, silent per-user install, self-test, uninstall, the Node prompt smoke, privacy scan.
|
||||
|
||||
When all are green, the `record` job scans the installers with the private terms (secret `FORBIDDEN_TERMS`), builds
|
||||
the 6-run acceptance matrix, and stores everything as the **draft release `rc-<full sha>`** on git.crt.fyi:
|
||||
installers, `SHA256SUMS`, the acceptance run files, `storebound_<version>_acceptance.md` and `green.json` (sha,
|
||||
version, CLI key, run link, every file's SHA-256). `green.json` is uploaded last: a candidate without it is not
|
||||
complete. The newest 12 candidates are kept (`tools/release-candidate.ts prune`); release pruning never touches them.
|
||||
`bun tools/release-candidate.ts status --sha <sha>` says green (with the record), red (with the failed run's link),
|
||||
pending or none.
|
||||
|
||||
**Version-only commits.** The release's bump commit changes only the version. ci.yml's first job, `gate`, proves it
|
||||
file by file: every changed file must be a version copy of `tools/desktop-version.ts` whose new text is exactly the
|
||||
parent's text with the new version written in by that same writer, or `CHANGELOG.md` / `docs/acceptance*`. Then the
|
||||
parent's full CI and acceptance matrix count for the bump commit (the parent's run is waited for if it is still
|
||||
running; a red parent fails the gate), and only the installers are built again, because their version is compiled in
|
||||
(Cargo, tauri.conf.json, the bundled CLI, the file names). That is simpler and safer than making the installers
|
||||
version-independent. The acceptance runs carry a **CLI key** (`tools/cli-key.ts`: a hash of everything the packed
|
||||
CLI is made of, with the version copies normalized), and `acceptance.ts report --strict` accepts a run of another
|
||||
version only when its key is the checkout's.
|
||||
|
||||
### The tag only promotes
|
||||
|
||||
`.forgejo/workflows/release.yml` on a `v*` tag checks the tag against package.json, every copy and the CHANGELOG
|
||||
section, then `release-candidate.ts promote`: it waits for the tagged commit's candidate (heartbeat; fails at once
|
||||
when the commit is red or has no ci run, with the run's link; 40 min timeout), checks its version, and makes the
|
||||
release "storebound <version>" from the candidate's files (no rebuild, no retest). Stable versions then delete the
|
||||
older release entries (tags stay). Versions with a "-" are prereleases: never pruning, never the stable release.
|
||||
Run it by hand from Actions > release with a `tag` (dry by default: only the plan).
|
||||
|
||||
### What `tools/release.ts` does on the Mac
|
||||
|
||||
1. **Preflight:** a clean checkout equal to origin/main whose candidate is green or still running, the updater key,
|
||||
the publish host (`STOREBOUND_PUBLISH_HOST` or `~/.storebound-keys/publish-host`). The macOS acceptance matrix
|
||||
(Bun, Node 20, Node 22 from fnm) starts in the background unless its results for this CLI key are cached in
|
||||
`~/.storebound-releases/acceptance-cache/<key>/` (a version bump keeps the key; `bun tools/release.ts
|
||||
mac-acceptance` fills the cache ahead of time).
|
||||
2. **Bump:** the version in package.json and every copy, the CHANGELOG's `## Unreleased` cut into
|
||||
`## <version> · <date>` (its first paragraph is the release summary).
|
||||
3. **Fast gate, commit, push:** `bun run ci:quick`, the commit as CRT, push to main, push the tag.
|
||||
4. **In parallel:** `desktop-release.ts mac` (universal build, sign; Tauri notarizes the app while the boot check runs;
|
||||
the DMG is notarized while the app and the updater bundle are checked, scanned and signed), Forgejo's ci run of the
|
||||
bump commit (installers only) and release.yml's promotion. Meanwhile the candidate's Linux / Windows installers
|
||||
and acceptance runs are downloaded.
|
||||
5. **add, matrix, verify:** updater signatures of the Linux / Windows files, the 9-run acceptance matrix (strict) to
|
||||
the release, `verify`.
|
||||
6. **publish** with `--forgejo-tag`: the VDS fetches the Linux / Windows files from the Forgejo release over its own
|
||||
network, rsync sends only the Mac's DMG and updater bundle, the VDS attaches those to the Forgejo release from its
|
||||
copy, then the publish script swaps latest.json. The owner's uplink (about 0.5 to 1 MB/s) carries each Mac file
|
||||
once instead of three times.
|
||||
7. **/Applications/storebound.app:** the running app is asked to quit (an AppleEvent), then its exact PID gets
|
||||
SIGTERM / SIGKILL if needed; the new app is copied from the DMG, verified (`codesign --verify --deep --strict`)
|
||||
and reopened in the background (`open -g`).
|
||||
8. **Prints** `npm publish` and `npm deprecate storebound@"<version>" ...` (the owner's step) and the time of every
|
||||
phase (also in `~/.storebound-releases/timings/`). Logs: `~/.storebound-releases/logs/<version>/`.
|
||||
|
||||
Every wait has a timeout and a heartbeat; child processes are killed by PID when it exits.
|
||||
|
||||
**Notarization in parallel, where possible.** Apple notarizes the app (inside `tauri bundle`) and then the DMG. The
|
||||
DMG cannot go first or at the same time: it must contain the app with its stapled ticket (the gate checks
|
||||
`stapler validate` on the app inside the mounted DMG). So each wait for Apple runs next to other work instead: the
|
||||
boot check during the app's notarization; every app and updater-bundle check, their privacy scan and the updater
|
||||
signature during the DMG's.
|
||||
|
||||
**Rehearsal:** `bun tools/release.ts --rehearsal [<n>] --yes` runs all of it for version `0.0.0-rehearsal.<n>` on the
|
||||
branch `rehearsal/<n>` (ci.yml runs there too): a prerelease on git.crt.fyi, the `rehearsal` channel on
|
||||
storebound.dev (`/desktop/rehearsal/latest.json`, which no app reads; the publish script refuses rehearsal versions
|
||||
on stable / beta and other versions on rehearsal), the app copied to a scratch folder (nothing quit or opened), never
|
||||
npm. At the end it deletes the release and tag, the candidate, the branch, the version's files on storebound.dev and
|
||||
the local folders, and prints the times.
|
||||
|
||||
### By hand (what release.ts runs)
|
||||
|
||||
- `bun tools/desktop-version.ts --set 0.8.5` and the CHANGELOG section, `bun run ci:quick`, commit, push, tag, push
|
||||
the tag (release.yml promotes the candidate).
|
||||
- `bun tools/desktop-release.ts mac --out ~/.storebound-releases/desktop/0.8.5`.
|
||||
- `bun tools/release-candidate.ts fetch --sha <sha> --dir <dir>`, then `bun tools/desktop-release.ts add --out ...
|
||||
--commit <sha> --run <run> <AppImage> <deb> <setup.exe>`.
|
||||
- `bun tools/desktop-release.ts publish --out ... --forgejo-tag v0.8.5 --yes` (or `upload-forgejo`, then `publish`).
|
||||
- `npm publish` of the same version: the owner's step (CONTRIBUTING.md).
|
||||
|
||||
**Secrets** (repo Settings > Actions > Secrets): `RELEASE_TOKEN`, a token of CRT with scope `write:repository`
|
||||
(name `storebound-release-workflow`; releases, assets, the dry run's tag) and `FORBIDDEN_TERMS`, the contents of
|
||||
`forbidden-terms.txt` (never printed: hits show `x***`), and `FORGEJO_ORIGIN`, the server's IP: git.crt.fyi is
|
||||
behind Cloudflare, which refuses uploads over 100 MB (the AppImage, the DMG) with a 502, so `forgejo-release.ts`
|
||||
uploads assets to the origin directly (`curl --resolve`, TLS still checked for git.crt.fyi). On the Mac the same IP
|
||||
is in `~/.storebound-keys/forgejo-origin`. Rotate the token: create a new one under CRT's
|
||||
Settings > Applications with that scope, put it into the secret, delete the old one.
|
||||
(name `storebound-release-workflow`; releases, assets, candidates: ci.yml's gate and record jobs and release.yml use
|
||||
it), `FORBIDDEN_TERMS`, the contents of `forbidden-terms.txt` (never printed: hits show `x***`), and
|
||||
`FORGEJO_ORIGIN`, the server's IP: git.crt.fyi is behind Cloudflare, which refuses uploads over 100 MB (the AppImage,
|
||||
the DMG) with a 502, so `forgejo-release.ts` uploads assets to the origin directly (`curl --resolve`, TLS still
|
||||
checked for git.crt.fyi). On the Mac the same IP is in `~/.storebound-keys/forgejo-origin`. Rotate the token: create
|
||||
a new one under CRT's Settings > Applications with that scope, put it into the secret, delete the old one.
|
||||
|
||||
## Keys and private files (outside every repository)
|
||||
|
||||
|
|
@ -197,8 +258,7 @@ and choose Open Anyway (once).
|
|||
|
||||
## Windows and Linux (Forgejo Actions)
|
||||
|
||||
The `linux` and `windows` jobs of `.forgejo/workflows/release.yml` (a `v*` tag push, or a dry run by hand; untick
|
||||
"windows" in a dry run to build Linux only).
|
||||
The `linux` and `windows` jobs of `.forgejo/workflows/ci.yml`, on every push to main (the release candidate).
|
||||
|
||||
- **Linux** (`runs-on: ubuntu-22.04`, a container): AppImage and .deb built with the path-free environment, then:
|
||||
- self-tests of the extracted AppImage and the unpacked .deb;
|
||||
|
|
@ -233,9 +293,9 @@ installer compare with `Get-FileHash` of `C:\storebound-target\release\bundle\ns
|
|||
|
||||
### Runner setup
|
||||
|
||||
Forgejo runs in Docker on the VDS. `.forgejo/workflows/ci.yml` (`bun run ci`, Rust tests and clippy included) runs on
|
||||
every push and pull request to main; `release.yml` as above makes the Forgejo release (Jenkins still publishes
|
||||
site/, and `publish` storebound.dev/desktop). Workflow artifacts of this public repository can be downloaded by anyone for 14 days.
|
||||
Forgejo runs in Docker on the VDS. `.forgejo/workflows/ci.yml` (the release candidate: `bun run ci`, the acceptance
|
||||
matrix, the installers) runs on every push to main and `ci` + `acceptance` on pull requests; `release.yml` promotes a
|
||||
candidate on a tag (Jenkins still publishes site/, and `publish` storebound.dev/desktop). Workflow artifacts of this public repository can be downloaded by anyone for 14 days.
|
||||
|
||||
1. **Actions in Forgejo (done).** `FORGEJO__actions__ENABLED=true` and
|
||||
`FORGEJO__actions__DEFAULT_ACTIONS_URL=https://data.forgejo.org` in the compose file; the Actions unit is on for
|
||||
|
|
@ -292,7 +352,6 @@ site/, and `publish` storebound.dev/desktop). Workflow artifacts of this public
|
|||
- **Shared runner:** any workflow of any CRT repository with `runs-on: windows` runs here, as `runner`, with
|
||||
internet access. Keep workflows of fork pull requests off it (as ci.yml does for Linux).
|
||||
|
||||
The workflow's Windows job runs only when "windows" is ticked (from the API, `"inputs": {"windows": "on"}`).
|
||||
4. **Windows code signing (later).** Buy a code-signing certificate, then sign the installer before `add` and pass
|
||||
`--code-signed`.
|
||||
|
||||
|
|
@ -301,7 +360,7 @@ site/, and `publish` storebound.dev/desktop). Workflow artifacts of this public
|
|||
| URL | served from | how it gets there |
|
||||
|---|---|---|
|
||||
| /#download (the downloads, a section of the home page; /desktop/ redirects there) | /var/www/storebound.dev/index.html, /var/www/storebound.dev/desktop/index.html | `site/index.html`, `site/desktop/index.html`, with the site (Jenkins + /usr/local/sbin/storebound-publish) |
|
||||
| /desktop/latest.json, /desktop/beta/latest.json | /var/www/storebound-desktop/ | storebound-desktop-publish |
|
||||
| /desktop/latest.json, /desktop/beta/latest.json, /desktop/rehearsal/latest.json | /var/www/storebound-desktop/ | storebound-desktop-publish |
|
||||
| /desktop/dl/<version>/<files> | /var/www/storebound-desktop/dl/ | storebound-desktop-publish |
|
||||
|
||||
**Why the binaries live outside the site's document root.** The site's publish script rsyncs `site/` with
|
||||
|
|
@ -313,7 +372,7 @@ Apache maps the three paths with `Include /etc/apache2/storebound-desktop.conf`
|
|||
- version folders are sent `immutable`;
|
||||
- no directory listings.
|
||||
|
||||
**`/usr/local/sbin/storebound-desktop-publish <version> [stable|beta]`** (`tools/vds/storebound-desktop-publish`,
|
||||
**`/usr/local/sbin/storebound-desktop-publish <version> [stable|beta|rehearsal]`** (`tools/vds/storebound-desktop-publish`,
|
||||
root:root 755) takes `/var/lib/storebound-desktop/incoming/<version>/` (root only, mode 700) and checks it:
|
||||
|
||||
- only plain files with plain names;
|
||||
|
|
@ -358,7 +417,9 @@ bun tools/desktop-release.ts publish --out ~/.storebound-releases/desktop/0.1.0
|
|||
3. **Publishes:** runs the publish script.
|
||||
4. **Confirms:** reads the live latest.json back.
|
||||
|
||||
`--channel beta` publishes to /desktop/beta/latest.json, which apps on the Beta channel read.
|
||||
`--channel beta` publishes to /desktop/beta/latest.json, which apps on the Beta channel read. `--channel rehearsal`
|
||||
(release rehearsals only) to /desktop/rehearsal/latest.json, which no app reads. `--forgejo-tag v<version>`: the
|
||||
server fetches the files the Forgejo release already has and attaches the Mac's files to it (see above).
|
||||
|
||||
**Rollback.** Tauri updates only to a higher version, so a bad release is replaced by a fixed higher version. Its
|
||||
files stay where they are, because links and checksums must not change.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue