309 lines
14 KiB
Markdown
309 lines
14 KiB
Markdown
# CTMS Web and Desktop Release Guide
|
|
|
|
## Release Unit
|
|
|
|
CTMS uses one repository, one promotion path, and one product version. Web and
|
|
Desktop are build targets from the same source commit, not separately versioned
|
|
products.
|
|
|
|
The release identity consists of:
|
|
|
|
- semantic version, for example `1.8.0`
|
|
- Git tag, for example `v1.8.0`
|
|
- full Git commit SHA
|
|
- build channel: `dev`, `main`, or `release`
|
|
- client type: `web` or `desktop`
|
|
|
|
## Runtime Boundary
|
|
|
|
Shared Vue business code lives under `frontend/src/`. Platform decisions are
|
|
exposed through `frontend/src/runtime/index.ts`.
|
|
|
|
The runtime contract currently provides:
|
|
|
|
- API base URL resolution
|
|
- Web, macOS, Windows, and Linux runtime identification
|
|
- app version, source commit, build channel, and client type metadata
|
|
- explicit capability flags
|
|
- Desktop server address configuration
|
|
- secure session storage
|
|
- file picker/save/open adapters
|
|
- desktop system notification adapters
|
|
- desktop updater adapters
|
|
- native desktop application menu and shortcut synchronization
|
|
- desktop window lifecycle and system appearance synchronization
|
|
|
|
Business modules must use this public runtime entry point. They must not inspect
|
|
Tauri globals or import Tauri packages directly. Native files, notifications,
|
|
secure session storage, and automatic updates must remain behind
|
|
`frontend/src/runtime/` and explicit capability flags.
|
|
|
|
Desktop menu accelerators and window appearance are synchronized through narrow
|
|
Tauri commands owned by `frontend/src/runtime/`. macOS close/reopen behavior is
|
|
handled by the Tauri event loop so a genuinely closed main window can be rebuilt from the Dock;
|
|
these concerns must not move into Vue business modules.
|
|
|
|
## Version Change
|
|
|
|
Update every client manifest with one command:
|
|
|
|
```bash
|
|
cd frontend
|
|
npm run version:set -- 1.8.0
|
|
npm run version:check
|
|
```
|
|
|
|
This synchronizes:
|
|
|
|
- `frontend/package.json`
|
|
- `frontend/package-lock.json`
|
|
- `frontend/src-tauri/tauri.conf.json`
|
|
- `frontend/src-tauri/Cargo.toml`
|
|
- `frontend/src-tauri/Cargo.lock`
|
|
|
|
Manual edits that leave these files inconsistent fail CI.
|
|
|
|
## Stabilization Gates
|
|
|
|
Client builds require Node.js 22.13.0 or newer. The development frontend container,
|
|
Web CI, macOS and Windows release candidates, Windows internal validation, and the
|
|
production Web image use the same Node 22.13 baseline.
|
|
|
|
Client release candidates must pass the shared Web checks and the Desktop
|
|
release/security gate before promotion:
|
|
|
|
```bash
|
|
cd frontend
|
|
npm run version:check
|
|
npm run release:env:check
|
|
npm run runtime:check
|
|
npm run desktop:release:check
|
|
npm run ui:contract
|
|
npm run type-check
|
|
npm run test:unit
|
|
npm run build
|
|
npm run desktop:build:app
|
|
```
|
|
|
|
`release:env:check` verifies build channel and commit metadata, and becomes
|
|
platform-strict with `REQUIRE_DESKTOP_SIGNING=true` on macOS or
|
|
`REQUIRE_WINDOWS_SIGNING=true` on Windows. Formal native jobs also set
|
|
`REQUIRE_UPDATER_SIGNING=true`, `DESKTOP_RELEASE_PLATFORM`, and the
|
|
version-derived `DESKTOP_PLATFORM_SIGNING_MODE`. The exact-version policy in
|
|
`frontend/desktop-release-policy.json` is checked with
|
|
`npm run desktop:release-policy:check`.
|
|
`desktop:release:check` statically verifies the Tauri bundle, updater public
|
|
key, CSP, capability scopes, command allowlist, query-token ban, generic system
|
|
notification boundary, CI gate coverage, and secure session token boundary. The
|
|
full manual release, security, regression, and Desktop UX checklist lives in
|
|
`docs/audits/desktop-release-stabilization-checklist.md`.
|
|
|
|
## Promotion
|
|
|
|
1. Merge feature branches into `dev`.
|
|
2. Require the shared client/Web and macOS/Windows Desktop CI jobs to pass.
|
|
3. Promote the accepted scope from `dev` to `main`.
|
|
4. Set the release version and complete regression testing on `main`.
|
|
5. Promote `main` to `release`.
|
|
6. Create the matching `vX.Y.Z` tag on the accepted `release` commit.
|
|
7. Build both Web and Desktop artifacts from that exact tag.
|
|
|
|
Build metadata is injected by CI:
|
|
|
|
```bash
|
|
VITE_BUILD_CHANNEL=release
|
|
VITE_BUILD_COMMIT="$(git rev-parse HEAD)"
|
|
```
|
|
|
|
These values support diagnostics but do not replace the semantic version.
|
|
|
|
## Desktop Updater
|
|
|
|
Formal Desktop release builds must use Tauri updater signatures. The application
|
|
embeds the updater public key in `frontend/src-tauri/tauri.conf.json`; the
|
|
private key must live only in the organization key vault or CI secret store.
|
|
|
|
Runtime update checks derive the feed from the currently configured CTMS origin:
|
|
|
|
```text
|
|
/desktop-updates/stable/latest.json
|
|
```
|
|
|
|
Production update feeds must use HTTPS. Local update testing may use HTTP only
|
|
for `localhost`, `127.0.0.1`, or `::1`.
|
|
|
|
The release pipeline must:
|
|
|
|
1. build from the accepted release tag and commit;
|
|
2. build the Universal macOS app/DMG and Windows x64 NSIS installer from that tag;
|
|
3. use Apple signing/notarization and Windows Authenticode/RFC 3161 signing by default, or use a checked-in exact-version exception that constrains macOS to ad-hoc signing and Windows to Authenticode-unsigned output;
|
|
4. produce macOS `.app.tar.gz` and Windows `.nsis.zip` updater artifacts plus their `.sig` files with the shared updater private key;
|
|
5. generate `DESKTOP-RELEASE-PROVENANCE.json`, one combined `latest.json`, and a checksum manifest;
|
|
6. verify all macOS and Windows entries with `npm run desktop:update-feed:check -- --feed <release-dir>/latest.json --artifacts-dir <release-dir> --require-platform windows-x86_64 --require-provenance`;
|
|
7. upload immutable artifacts first;
|
|
8. atomically replace `latest.json` last.
|
|
|
|
For Universal macOS artifacts, `latest.json` must provide both
|
|
`darwin-aarch64` and `darwin-x86_64` entries pointing at the same Universal
|
|
update package.
|
|
|
|
The same feed must also contain `windows-x86_64`, pointing to the updater-signed
|
|
NSIS `.nsis.zip` package. The Windows `.exe` installer is distributed next to
|
|
the updater package but is not used as the updater URL. Tauri updater signing is
|
|
mandatory in both platform-signed and platform-signing-exception modes.
|
|
|
|
The formal release candidate workflow lives at
|
|
`.github/workflows/desktop-release-candidate.yml`. It must be run from a
|
|
matching `vX.Y.Z` tag and produces a verified release directory as a GitHub
|
|
artifact. That artifact is still only a release candidate; the release owner
|
|
must upload immutable files to the production download origin and replace
|
|
`latest.json` atomically after validation.
|
|
|
|
Platform signing defaults to `signed`. An exception is allowed only when
|
|
`frontend/desktop-release-policy.json` names the exact product version and
|
|
records release-owner approval. The current v0.1.0 exception uses macOS ad-hoc
|
|
signing and unsigned Windows application/installer binaries. Its artifact names
|
|
and verified updater release directory contain `UNSIGNED-PLATFORM`; the private
|
|
evidence/update directory must include `UNSIGNED-PLATFORM-RELEASE.txt`,
|
|
`DESKTOP-RELEASE-PROVENANCE.json`, and the full `SHA256SUMS.txt`. The
|
|
user-facing GitHub Release may use the exact-version
|
|
`installer-and-checksum-only` profile, with platform warnings in Release Notes
|
|
and a separate checksum manifest covering only the displayed installer. This
|
|
does not establish Apple or Microsoft publisher trust, and Gatekeeper or
|
|
SmartScreen warnings are expected. Later versions return to the signed default
|
|
unless separately approved.
|
|
|
|
### v0.1.0 staged macOS contingency
|
|
|
|
The release owner approved one additional v0.1.0 contingency for unavailable
|
|
hosted Actions capacity. A controlled local macOS host may build the macOS
|
|
ad-hoc artifacts from the immutable final `v0.1.0` tag and publish them first.
|
|
That initial user-facing GitHub Release contains only the DMG and a
|
|
`SHA256SUMS.txt` that covers the DMG, in addition to GitHub's automatic source
|
|
archives. The Release Notes must state that macOS is ad-hoc/not notarized and
|
|
that Windows remains pending. The macOS updater package and `.sig`, full
|
|
checksum manifest, provenance, unsigned-platform warning, and Windows-pending
|
|
notice must be copied to a permission-restricted, Git-ignored private release
|
|
directory before any visible asset is removed. Windows must later be built from
|
|
the same tag and SHA.
|
|
|
|
The staged macOS release is an installer distribution, not an activated
|
|
cross-platform updater release. Do not upload or replace production
|
|
`latest.json` until the Windows `NotSigned` installer/updater has been built and
|
|
the combined macOS/Windows feed passes full verification. The combined updater
|
|
artifacts, signatures, provenance, full checksum manifest, and `latest.json`
|
|
must use a separately configured HTTPS update origin that anonymous production
|
|
clients can read; a private GitHub Release is not a valid production updater
|
|
origin. This fallback is limited to v0.1.0 and does not authorize local formal
|
|
builds for later versions.
|
|
|
|
## Windows Release and Internal Validation
|
|
|
|
Windows x64 NSIS is an approved formal Desktop target. Formal Windows builds
|
|
run in `.github/workflows/desktop-release-candidate.yml` from the same exact
|
|
`vX.Y.Z` tag and SHA as Web and macOS. The default signed path must:
|
|
|
|
- import a Base64-encoded PFX from `WINDOWS_CERTIFICATE` using
|
|
`WINDOWS_CERTIFICATE_PASSWORD`;
|
|
- configure the imported certificate thumbprint, SHA-256 digest,
|
|
`WINDOWS_TIMESTAMP_URL`, and RFC 3161 timestamp mode dynamically in CI;
|
|
- set `REQUIRE_WINDOWS_SIGNING=true` and use the updater signing secrets;
|
|
- verify the application and installer with `Get-AuthenticodeSignature`,
|
|
including signer and timestamp certificates;
|
|
- publish the signed NSIS `.exe`, `.nsis.zip`, and `.nsis.zip.sig` into the
|
|
combined verified Desktop release directory.
|
|
|
|
For an approved exact-version unsigned-platform exception, the Windows job
|
|
must instead leave the application and installer Authenticode-unsigned, require
|
|
`Get-AuthenticodeSignature` to return `NotSigned`, append `_UNSIGNED` to the
|
|
installer and updater artifact names, and still create and verify the updater
|
|
`.sig`. A certificate secret and timestamp URL are not required in this mode.
|
|
|
|
Windows release validation also covers:
|
|
|
|
- WebView2 runtime prerequisite behavior;
|
|
- user-level installer assumptions;
|
|
- Windows Credential Manager session storage;
|
|
- path handling and temporary file cleanup;
|
|
- notification and updater compilation;
|
|
- code-signing trust, timestamp validity, and updater signature verification.
|
|
|
|
The manual internal Windows validation workflow lives at
|
|
`.github/workflows/desktop-windows-internal.yml`. It is `workflow_dispatch`
|
|
only, runs on `windows-latest`, injects `VITE_BUILD_CHANNEL` and
|
|
`VITE_BUILD_COMMIT` from the selected branch context, executes the shared client
|
|
and Desktop safety gates, builds an unsigned NSIS installer with updater
|
|
artifacts disabled, and uploads the `.exe` plus `SHA256SUMS.txt` as a GitHub
|
|
Actions artifact. This workflow is for internal compatibility verification
|
|
only; it must not generate `latest.json`, update feeds, verified release
|
|
directories, or formal Windows release artifacts. A successful internal build
|
|
does not substitute for the tag-only formal workflow, including when that
|
|
formal workflow uses an approved platform-signing exception.
|
|
|
|
The formal workflow always requires these organization settings:
|
|
|
|
- secrets: `TAURI_SIGNING_PRIVATE_KEY` and
|
|
`TAURI_SIGNING_PRIVATE_KEY_PASSWORD`;
|
|
- client default CTMS origin: repository variable `VITE_DESKTOP_SERVER_URL`;
|
|
- shared versioned HTTPS artifact prefix: `DESKTOP_UPDATE_BASE_URL` or the
|
|
manual workflow input.
|
|
|
|
`VITE_DESKTOP_SERVER_URL` must be an HTTPS origin without credentials, path,
|
|
query, or fragment. Vite embeds it as the Desktop client's first-run default;
|
|
the runtime source must not contain the production hostname. A user can still
|
|
override this default in Desktop server settings, where the persisted manual
|
|
value takes precedence and switching origins clears the existing session and
|
|
server-scoped client state. Do not reuse this value as
|
|
`DESKTOP_UPDATE_BASE_URL`: the latter points to immutable updater artifacts,
|
|
not the CTMS business API.
|
|
|
|
The default signed path additionally requires `WINDOWS_CERTIFICATE`,
|
|
`WINDOWS_CERTIFICATE_PASSWORD`, and `WINDOWS_TIMESTAMP_URL`, plus the Apple
|
|
credentials documented by the release owner. The v0.1.0 exception does not
|
|
require those platform certificate settings.
|
|
|
|
The checked-in workflow implements the exportable PFX path. Confirm that the
|
|
organization's certificate policy permits an exportable CI certificate before
|
|
provisioning it. If the selected CA provides only hardware- or cloud-backed
|
|
keys, replace the PFX import with a reviewed Tauri `signCommand` integration
|
|
(for example, the organization's Azure signing provider) while retaining the
|
|
same Authenticode and timestamp verification gates.
|
|
|
|
## Required Checks
|
|
|
|
```bash
|
|
cd frontend
|
|
npm ci
|
|
npm run version:check
|
|
export VITE_BUILD_CHANNEL=release
|
|
export VITE_BUILD_COMMIT="$(git rev-parse HEAD)"
|
|
export VITE_DESKTOP_SERVER_URL="https://ctms.example.com"
|
|
npm run release:env:check
|
|
npm run runtime:check
|
|
npm run desktop:release:check
|
|
npm run ui:contract
|
|
npm run type-check
|
|
npm run test:unit
|
|
npm run build
|
|
npm run desktop:build:app
|
|
export TAURI_SIGNING_PRIVATE_KEY="$UPDATER_PRIVATE_KEY"
|
|
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$UPDATER_PRIVATE_KEY_PASSWORD"
|
|
export REQUIRE_UPDATER_SIGNING=true
|
|
export DESKTOP_RELEASE_PLATFORM=macos
|
|
export DESKTOP_PLATFORM_SIGNING_MODE=unsigned-exception
|
|
export ALLOW_UNSIGNED_PLATFORM_RELEASE=true
|
|
npm run release:env:check
|
|
npm run desktop:release-readiness:check
|
|
npm run desktop:build:macos-unsigned-release -- --ci
|
|
npm run desktop:update-feed:create -- --artifact <CTMS.app.tar.gz> --platform-artifact windows-x86_64=<CTMS.nsis.zip> --include <CTMS.dmg> --include <CTMS-installer.exe> --base-url <versioned-https-artifact-prefix> --output-dir <release-dir>
|
|
npm run desktop:update-feed:check -- --feed <release-dir>/latest.json --artifacts-dir <release-dir> --require-platform windows-x86_64 --require-provenance
|
|
```
|
|
|
|
The macOS and Windows builds run in their native CI jobs and always use the same
|
|
updater signing key. In the default path, macOS requires Apple
|
|
signing/notarization credentials and Windows requires the PFX/password and
|
|
timestamp URL. In the approved v0.1.0 exception, macOS must verify
|
|
`Signature=adhoc`, Windows must verify `NotSigned`, and both must carry explicit
|
|
platform-trust warnings. Native artifacts are aggregated only after both jobs
|
|
and the combined updater feed pass.
|