mirror of
https://github.com/priyanshujain/margin-docs.git
synced 2026-10-02 19:17:05 +00:00
118 lines
7.5 KiB
Markdown
118 lines
7.5 KiB
Markdown
# Releasing
|
|
|
|
## Installing locally
|
|
|
|
`just install` builds the app for whatever machine you are sitting at and installs it where that
|
|
machine expects to find applications: `/Applications` (or `~/Applications` when `/Applications`
|
|
is not writable) on macOS, the package manager or an AppImage plus a desktop entry on Linux. It is
|
|
the same command whether or not the app is already installed, so it doubles as the update. On
|
|
macOS it asks a running copy to quit first, because replacing a bundle under a live process leaves
|
|
it half old and half new. `just uninstall` reverses it and leaves the data directory alone.
|
|
|
|
The local build skips the dmg and builds only the `.app` on macOS, or the `.deb` and the AppImage
|
|
on Linux, since nothing about copying a bundle into place needs a disk image and building one is
|
|
the slowest part of a mac bundle. That makes a locally installed app slightly different from a
|
|
released one: it is ad hoc signed and carries no updater artifacts, so it will not update itself.
|
|
Rerun `just install`.
|
|
|
|
## Cutting a release
|
|
|
|
Releases are manual: run the Release workflow from the Actions tab. Leave the version empty to
|
|
bump the patch number, or give one to set it. The workflow bumps `tauri.conf.json`, `package.json`
|
|
and `src-tauri/Cargo.toml` together, commits that to main, tags it, and builds the tag rather than
|
|
whatever main happens to be by then.
|
|
|
|
It builds one universal macOS bundle and publishes nothing until it has landed. The last job
|
|
downloads `latest.json` and refuses to take the release out of draft unless three things hold: the
|
|
version in the manifest is the version in the tag, `darwin-aarch64` and `darwin-x86_64` are both
|
|
present, and each of them has a signature as well as a url. One universal build writes both of
|
|
those keys, pointing them at the same archive and the same signature, because an installed copy
|
|
asks the manifest for the architecture it is running on and never for a universal one. A
|
|
half-populated manifest is worse than no release at all: the updater would offer an update to the
|
|
platforms that made it and error on the ones that did not. A manifest carrying the version before
|
|
this one is worse again, because it tells everybody the copy they are already running is the newest
|
|
there is, and an entry with an empty signature is an update every installed copy downloads and then
|
|
refuses.
|
|
|
|
Linux is not built. It was until recently, because this pipeline was copied from margin-calendar,
|
|
which ships on Linux. This app does not, so the row and the manifest key it required are gone and
|
|
the release runs on one macOS runner.
|
|
|
|
Windows is not built. Nothing in `tauri.conf.json` targets it and the app has never claimed it.
|
|
|
|
## What the build needs
|
|
|
|
Two repository secrets sign the updater artifacts, so that an installed copy can tell a real update
|
|
from anything else offered at the same URL: `TAURI_SIGNING_PRIVATE_KEY` and
|
|
`TAURI_SIGNING_PRIVATE_KEY_PASSWORD`. The public half lives in
|
|
`src-tauri/tauri.release.conf.json`, baked into every build, so the private half can never be
|
|
rotated without stranding everyone who has not updated yet; back up the key and its password
|
|
somewhere that is not the machine that generated them.
|
|
|
|
That file currently carries the placeholder `REPLACE_WITH_TAURI_SIGNER_PUBKEY`, and the keypair
|
|
behind it does not exist yet. Making it is a one-time step, run once by whoever owns the repository
|
|
and never again:
|
|
|
|
```
|
|
pnpm tauri signer generate -w ~/.tauri/margin-docs.key
|
|
```
|
|
|
|
It asks for a password, writes the private key to that path and the public key beside it as
|
|
`margin-docs.key.pub`, and prints both. The public one replaces the placeholder in
|
|
`tauri.release.conf.json` and is committed. The private one is the contents of
|
|
`~/.tauri/margin-docs.key`, pasted into `TAURI_SIGNING_PRIVATE_KEY`, with its password in
|
|
`TAURI_SIGNING_PRIVATE_KEY_PASSWORD`. Neither the key file nor the password goes into the
|
|
repository.
|
|
|
|
Six more secrets codesign and notarize the bundle, and unlike the two above they are optional as
|
|
far as the workflow is concerned: with none of them set the build still runs, still publishes, and
|
|
produces an ad hoc signed app. `APPLE_CERTIFICATE` is the Developer ID Application certificate
|
|
exported from Keychain Access as a `.p12` and base64 encoded (`base64 -i certificate.p12`), with
|
|
the password it was exported under in `APPLE_CERTIFICATE_PASSWORD`. `APPLE_SIGNING_IDENTITY` is the
|
|
certificate's full name, which looks like `Developer ID Application: Your Name (TEAMID)`. The other
|
|
three are for notarization: `APPLE_ID` is the Apple account's email address, `APPLE_PASSWORD` is an
|
|
app-specific password made at appleid.apple.com rather than the account password itself, and
|
|
`APPLE_TEAM_ID` is the ten character team identifier.
|
|
|
|
Those six are all or nothing. A repository with a certificate but no notarization credentials fails
|
|
the build rather than warning, because a signed bundle that has not been notarized is one Gatekeeper
|
|
refuses on any machine that has not seen it before, and finding that out from a user is worse than
|
|
finding it out from a red run.
|
|
|
|
The bundle asks for the hardened runtime, which notarization requires, and for no entitlements at
|
|
all. That is worth a sentence because it nearly went the other way. Deleting a document goes to the
|
|
Trash through the `trash` crate, whose macOS default is to ask Finder over an Apple event, and the
|
|
hardened runtime blocks an Apple event unless the bundle carries
|
|
`com.apple.security.automation.apple-events` and the user agrees to a permission prompt about
|
|
controlling Finder. `src-tauri/src/fs.rs` asks for `trashItemAtURL:` instead, which needs none of
|
|
that. The cost is Put Back, which the Finder method leaves on the file and this one does not; the
|
|
file is still in the Trash and can still be dragged out of it.
|
|
|
|
Beyond signing there is nothing to provision. Margin Docs talks to no external API and holds no
|
|
OAuth client, unlike margin-calendar, so there are no other repository secrets and nothing
|
|
equivalent to a `google-credentials.json` to embed at build time.
|
|
|
|
## Updates
|
|
|
|
Installed copies check
|
|
`https://github.com/priyanshujain/margin-docs/releases/latest/download/latest.json` and update
|
|
themselves from it. `--latest` on the publish step is what moves that pointer, so a release that
|
|
fails the manifest check stays a draft and no one is offered a broken update. The app checks that
|
|
URL once a day in the background and on the Check for Updates menu item, and either way what
|
|
happens next is a dialog rather than an install: the version, the release notes, the download with
|
|
a progress bar, and Later as a real answer.
|
|
|
|
Self-update does not work yet, and it will not until there is an Apple Developer ID certificate.
|
|
This is not a gap in the workflow, which is wired for one, or in the app, which is wired for the
|
|
whole flow. It is macOS: an update replaces the installed bundle with the downloaded one, and the
|
|
system will not let an ad hoc signed bundle take the place of a signed one, nor run a replacement
|
|
whose signature does not match what was there before. Every build this repository can currently
|
|
produce is ad hoc signed, so every self-update attempt ends in a bundle the system refuses to
|
|
launch. Until the certificate exists, updates are something to install by hand, and the honest
|
|
version of the feature is that the app will find the update, show it, download it, and fail on the
|
|
last step.
|
|
|
|
There is a second thing gated behind the same certificate. The build is not notarized either, so a
|
|
copy downloaded from the releases page is quarantined by the browser and refused on first launch
|
|
with the message about an unidentified developer. Both problems have the one fix.
|