Online updates are the release vehicle: every platform must be able to check, download, verify and install a new version without the user hunting for download pages. The behavior (triggers, UX, strings, failure handling) is identical on all platforms; the transport and install mechanism is native to each platform, like every other part of the Taskly architecture.
- Triggers: one silent check shortly after launch (throttled: at most
once per 4 hours, persisted as config key
last-update-check, unix epoch seconds), plus a manualSettings ▸ 检查更新… / Check for Updates…menu item that bypasses the throttle. - Silent mode: network failure / rate limit / not-installed → silently ignore (never nag). Manual mode reports every outcome.
- Newer version found (semver compare, downgrades ignored) → dialog
with the new version:
立即更新 / Update(install + relaunch) or取消 / Cancel. Up-to-date →当前已是最新版本 / Up to date. - Install failure → non-crashing error with the platform message; the running version keeps working (same rule as notifications).
- CLI parity:
--jsonoutput and exit codes are version-stable; an update must never change golden outputs within a major version. - i18n keys are shared (
shared/i18n):menuCheckUpdates,updateUpToDate,updateAvailableTitle,updateAvailableBody,updateRestart,updateCheckFailed,updatePortable, plus macOS/ Linux-only keys below.
The release pipeline signs and uploads update-manifest.json +
manifest.sig for every release (see release.yml), so the feed exists
on all platforms from day one. Host-side updaters land per platform:
| Platform | Feed | Install | Integrity | Status |
|---|---|---|---|---|
| macOS | update-manifest.json (this contract) |
zip → sha256 → ditto → atomic swap in place | Ed25519 manifest signature (CryptoKit) + artifact sha256 | shipped (macos-host/Sources/RivetHost/UpdateService.swift) |
| Windows | update-manifest.json (this contract, windows entry) |
manual download until the host updater lands | manifest sha256 | host updater not built yet |
| Linux | update-manifest.json (this contract, linux entry) |
manual download until the host updater lands | manifest sha256 | host updater not built yet |
(The frozen v1 line used Velopack on Windows and a tarball replacer on Linux; those mechanisms died with that line — the Rivet hosts start from the manifest feed above.)
Generated by scripts/make-update-manifest.sh from the release
artifacts and uploaded as a release asset. Exact bytes of the file are
signed; the signature ships alongside as manifest.sig.
{
"version": "1.1.0",
"notesUrl": "https://github.com/turinglambdaai/taskly/releases/download/v1.1.0",
"platforms": {
"macos": { "url": "https://…/taskly-1.1.0-macos-arm64.zip",
"sha256": "<hex of the zip>", "size": 12345678 },
"windows": { "url": "https://…/taskly-1.1.0-windows-x64.zip",
"sha256": "<hex of the zip>", "size": 23456789 },
"linux": { "url": "https://…/taskly-1.1.0-linux-x64.tar.gz",
"sha256": "<hex of the tarball>", "size": 34567890 }
}
}version: the release version without thevprefix, must equal the rootVERSIONfile.- Clients resolve the latest release via
https://api.github.com/repos/turinglambdaai/taskly/releases/latestand download theupdate-manifest.json+manifest.sigassets from it (no separate metadata host).
- Key type: Ed25519. Private key lives ONLY in the GitHub secret
UPDATE_ED25519_PRIVATE_KEY(PEM) and in the release manager's local backup — never in the repository. - Generate / inspect:
scripts/update-keys.sh(openssl, one-time). - Sign: `openssl pkeyutl -sign -inkey key.pem -rawin -in update-manifest.json
manifest.sig
(-rawin` is mandatory for Ed25519). - Public key: 32 raw bytes, base64, embedded as
UpdateService.publicKeyBase64in the macOS app. Rotate = ship a new public key one release before retiring the old private key. - Verification: exact manifest bytes →
isValidSignature; any mismatch aborts the update silently in silent mode, with an error in manual mode.
- Downgrades are never auto-installed; semver compare is numeric per segment.
- The updater must never delete or touch
~/.taskly/(DB + config) — it swaps application bundles/prefixes only. - A failed swap must leave the previous install runnable (macOS keeps a
.oldcopy until the next successful launch; future host updaters follow the same rule — replace only after a complete, checksummed extraction).