101 lines
12 KiB
Markdown
101 lines
12 KiB
Markdown
# Packaging and releases
|
||
|
||
Capsule uses electron-builder 26.15.3 and electron-updater 6.8.9. The first distribution target is Windows x64 with a per-user NSIS installer. Build on Windows with Node 22.12+ and `npm ci`; the lockfile fixes the resolved dependency versions. No PHP server is needed to build the client.
|
||
|
||
## 1. Package the application
|
||
|
||
Run `npm run dist:win`. The installer and blockmap are written to `dist/local/`; `npm run pack:win` produces only `dist/local/win-unpacked/`.
|
||
|
||
Local builds are unsigned, named **Capsule Local**, and have application ID `com.thetempusproject.capsule.local`. They store sessions in `%APPDATA%/capsule-local`. Updates are deliberately disabled, and these installers must not be distributed as the public product. Windows may display an unknown-publisher warning.
|
||
|
||
Public builds use the permanent ID `com.thetempusproject.capsule`, executable `Capsule.exe`, and `%APPDATA%/capsule/session.json`, preserving the original app's session location. Both installers run per user and preserve app data on uninstall. Do not change the public ID, package name, install scope, or data path after shipping without an explicit migration. Windows builds use `build/icon.ico`, which must contain a 256×256 image. A packaged update replaces the application icon without uninstalling; Windows may temporarily retain a cached shortcut or taskbar icon.
|
||
|
||
## Free automatic-update testing
|
||
|
||
The original Capsule Local 0.1.0 installer cannot check for updates. Install the update-enabled bootstrap once to test an update to the current version:
|
||
|
||
```powershell
|
||
$env:CAPSULE_TEST_UPDATE_URL = 'https://ttp.joeykimsey.com/capsule/testfeed/'
|
||
npm run dist:test-bootstrap
|
||
npm run dist:test
|
||
```
|
||
|
||
`dist/test-bootstrap/Capsule-0.1.0-x64-Setup.exe` installs as Capsule Local and preserves its existing session location. Close the existing app before installing it. `dist/test/Capsule-0.1.2-x64-Setup.exe` is the current update target; do not manually install that target when testing the updater. Both builds use the same Local identity. The dashboard message is hidden in the bootstrap and shows the installed version after updating.
|
||
|
||
Test builds explicitly omit Authenticode verification; they rely on HTTPS, the publisher-controlled test feed, and metadata checksums. This is for trusted testers, not public signed releases. Windows can warn or block unsigned executables. The app labels this mode “Unsigned test channel.” Public release builds still require signing and verify publisher signatures. Normal `dist:win` builds still have updates disabled.
|
||
|
||
The TTP plugin must include `/capsule/testfeed/` and `/capsule/testupload/` support before publication. Those endpoints use separate storage from the public release channel, with the existing dedicated publisher token. After the operator deploys that code, set `CAPSULE_UPLOAD_URL` to `https://ttp.joeykimsey.com/capsule/testupload/` and load `CAPSULE_UPLOAD_TOKEN` securely, then run:
|
||
|
||
```powershell
|
||
npm run publish:release -- --test-updates --upload
|
||
```
|
||
|
||
The command publishes only `dist/test`, with metadata last. It never publishes the bootstrap or changes the public feed. Open the installed bootstrap, choose **Check for updates**, wait for **Restart to update**, then verify version 0.1.2, the custom icon, the dashboard message, and the preserved connection. Signed production upgrade acceptance remains a separate test.
|
||
|
||
## 2. Configure the release feed
|
||
|
||
Set `CAPSULE_UPDATE_URL` in the build environment to a permanent public HTTPS directory ending in `/`. The TTP Capsule plugin is installed on the LAN demo: use `https://ttp.joeykimsey.com/capsule/feed/` for updates and `https://ttp.joeykimsey.com/capsule/upload/` for uploads. These are environment settings, not hardcoded defaults. See the TTP checkout's `docs/capsule-hosting.md` for provisioning and token handoff. The URL must not contain login credentials, a query, or a fragment. The generated `app-update.yml` is baked into the signed application; site selection, login, and IPC cannot change it.
|
||
|
||
The host serves these files at that URL:
|
||
|
||
- `Capsule-<version>-x64-Setup.exe`
|
||
- `Capsule-<version>-x64-Setup.exe.blockmap`
|
||
- `latest.yml`, generated by electron-builder
|
||
|
||
Use immutable versioned filenames. Retain old release artifacts so downloads already in progress keep working. Serve the installer and blockmap as binary files, allow range requests for differential downloads, and avoid authentication/HTML challenge pages or response transformations. The plugin serves metadata and unpublished artifacts with `Cache-Control: no-store`; published versioned artifacts receive long immutable cache lifetimes. The generic provider disables multiple-range requests to match the host's single-range support.
|
||
|
||
Publishing `latest.yml` announces a release. Upload and verify both artifacts first. A running Capsule checks 15 seconds after launch and four hours after each scheduled check finishes. A manual check uses the same updater. No push service or background Windows service is installed. Immediate remote notifications can later request a check, but must never supply executable URLs or commands.
|
||
|
||
## 3. Update behavior
|
||
|
||
`src/main/updateController.mjs` owns the state machine. `updates.js` wires it into Electron, and `updateIpc.mjs` permits only the known top-level app window. The preload bridge exposes status, check, install, and a status subscription. The renderer receives no signing credentials, release tokens, site tokens, or updater configuration.
|
||
|
||
The updater automatically downloads a newer stable version. It validates checksums and the configured Windows publisher signature before reporting readiness. The user chooses **Restart to update** after saving work. Normal close, Windows shutdown, and logoff do not intentionally launch an update installer (`autoInstallOnAppQuit = false`). Reopening checks again and can reuse a valid cached download. No downgrade or prerelease is accepted. If a release is faulty, publish a higher version containing the reverted code; do not overwrite an existing installer or assume automatic rollback.
|
||
|
||
Network, metadata, signature, and download failures show a retryable error while leaving the installed app usable. The update panel is available on the login screen, so a broken site connection does not prevent a client update. Multiple app instances are prevented to avoid competing installs. Local and development builds never contact an update feed.
|
||
|
||
## 4. Sign a public release
|
||
|
||
Set these variables in a secure release environment:
|
||
|
||
- `CAPSULE_UPDATE_URL`: the public read-only feed directory.
|
||
- `CAPSULE_PUBLISHER_NAME`: the exact common name on the signing certificate.
|
||
- `CSC_LINK`: a supported certificate location or base64 PFX, with `CSC_KEY_PASSWORD` when required; **or** `CSC_NAME`: the subject name of a signing identity already in the Windows certificate store, including a configured hardware-backed identity.
|
||
|
||
The certificate provider determines how its private key is accessed; not all certificates can be exported to a PFX. Store credentials in the CI secret store or machine certificate provider. Do not commit keys, `.env` files, or release credentials. This project does not load `.env` automatically.
|
||
|
||
Run `npm run release:win`. This invalidates any prior ready marker, runs the tests, compiles the client, requires code signing, and builds to `dist/release/`. Missing URL, missing publisher, missing signing identity, or a signing failure prevents a publishable build. Signature verification stays enabled. Signing identity configuration must be consistent across releases; plan certificate/publisher changes before the old certificate expires.
|
||
|
||
The output includes `release-ready.json`, a local record of the exact artifact hashes and feed. Before recording hashes, the build rewrites `latest.yml` using JSON syntax, which is valid YAML and is the plugin's accepted metadata format. It is not served to clients and is not a substitute for Authenticode. Only files referenced by `latest.yml` plus the matching blockmap are publishable. Check both the installed executable and installer using `Get-AuthenticodeSignature` before the first public release.
|
||
|
||
## 5. Automate build and publication
|
||
|
||
`scripts/release.ps1` is the CI-independent Windows entry point. It runs `npm ci`, tests, the signed build, and release verification. Without `-Publish` it performs no upload. Keep build jobs serialized for each feed; concurrent publishers can otherwise move `latest.yml` backward. No CI service is configured because this checkout has no remote and no runner was selected.
|
||
|
||
`npm run publish:release` verifies the build and prints its upload order without uploading. The included upload adapter targets the TTP Capsule plugin's authenticated HTTPS PUT endpoint. It needs:
|
||
|
||
- `CAPSULE_UPLOAD_URL`: HTTPS upload directory, ending in `/`, mapped by the host to the public feed.
|
||
- `CAPSULE_UPLOAD_TOKEN`: a bearer token with upload access, available only on the release machine.
|
||
|
||
The host must support authenticated PUT, conditional creation with `If-None-Match: *`, and the plugin's contiguous 512 KiB chunk protocol with `Content-Range` and a shared upload ID. Plain WebDAV and S3 do not assemble this protocol. The plugin verifies artifact hashes and atomically promotes metadata, rejecting downgrades and conflicting versions. Interrupted uploads restart with a new upload ID; abandoned parts are removed after 24 hours on a subsequent authenticated upload.
|
||
|
||
Run `npm run publish:release -- --upload`, or `powershell -File scripts/release.ps1 -Publish` for the complete pipeline. It verifies local checksums, uploads the installer and blockmap, verifies their publicly served bytes without credentials, and only then publishes `latest.yml`. Redirects are refused. Existing versioned objects are reused only if their public content matches. A failure stops the pipeline. If verification fails after `latest.yml` was uploaded, inspect and restore the feed pointer before retrying; the script does not claim a rollback.
|
||
|
||
Never publish `dist/local/`, `win-unpacked/`, build debug files, certificate files, or `release-ready.json`. Increment `package.json` and the lockfile version before each release (`npm version patch --no-git-tag-version` is one option). Release metadata accepts stable `major.minor.patch` versions only. A separate beta feed/channel can be added later.
|
||
|
||
## Acceptance checks before public distribution
|
||
|
||
The automated tests exercise updater state transitions, duplicate checks, retryable failures, IPC boundaries, signing configuration, and artifact integrity. After `npm run build`, `npm run smoke` opens the compiled app hidden with an isolated `dist/smoke-profile` data directory, verifies the real preload/status bridge and a synthetic ready prompt, and captures `dist/local/smoke.png` and `smoke-ready.png`. It does not sign in or install anything. These checks do not substitute for an installed upgrade.
|
||
|
||
On a disposable Windows account or VM with the real signing identity and a staging HTTPS feed:
|
||
|
||
1. Build and install signed `0.1.0`; confirm the correct publisher, per-user installation, and launch. Save a site/session and preferences.
|
||
2. Build signed `0.1.1` against the same staging feed and publish it. Open `0.1.0`, confirm the download and restart prompt, then restart and verify the running version and preserved session/preferences.
|
||
3. Close without accepting the update: it must not install at quit. Reopen and check that the cached update can become ready again.
|
||
4. Test offline checks and an interrupted download, restore connectivity, and retry. Try an installer signed by a different publisher and a tampered file on the isolated feed; neither may become installable.
|
||
5. Test a second launch, a normal uninstall (data retained), and reinstall. Confirm no unexpected elevation prompt. Verify the local build remains separate from the public product.
|
||
|
||
These installed/signed checks remain pending until a signing identity is provided. The LAN demo host is online, with no public release announced. The local build scripts do not provision signing accounts or servers.
|
||
|
||
References: [electron-builder auto updates](https://www.electron.build/docs/features/auto-update/), [Windows configuration](https://www.electron.build/docs/win/). Consult the installed package APIs when newer online documentation describes features from a later major version.
|