diff --git a/desktop/RELEASE.md b/desktop/RELEASE.md new file mode 100644 index 00000000..bcb07f74 --- /dev/null +++ b/desktop/RELEASE.md @@ -0,0 +1,158 @@ +# Releasing the Orcabot Desktop app + +The desktop app auto-updates from **GitHub Releases**. The app polls +`https://github.com/Hyper-Int/OrcaBot/releases/latest/download/latest.json` +(configured in `app/src-tauri/tauri.conf.json` → `plugins.updater`), so a release +must be **published (not draft)** and must be the repo's **"latest"** release to +reach existing installs. + +There are two independent artifacts, released on their own cadence: + +| Artifact | When to release | Script | +| --- | --- | --- | +| **App** (DMG + updater tarball) | every version | `scripts/publish-release.sh` | +| **VM image** (sandbox rootfs) | only when the VM image content changes (rare) | `scripts/publish-vm-image.sh` | + +The VM image is **not** bundled in the app (it would bloat every ~40 MB auto-update +to ~1 GB). The app downloads it on demand, once per image version, and verifies it +against a SHA-256 baked into the notarized binary via `app/src-tauri/vm-image.json`. +**Most releases don't touch it** — only republish when you rebuilt the sandbox +image (`BUILD_VM=force`), and always bump `VM_IMAGE_VERSION`. + +--- + +## One-shot + +```sh +# Signing + OAuth secrets live OUTSIDE the repo (never committed). Point the +# release script at your env file (defaults to ~/.orcabot-release.env): +ORCABOT_RELEASE_ENV=~/.orcabot-release.env sh desktop/scripts/release.sh 0.5.0 +``` + +`release.sh` bumps the version, builds resources (bakes OAuth creds), runs the +signed+notarized `cargo tauri build`, then runs the preflight gate and publishes. +It stops before anything irreversible if a step fails. See "What the wrapper does" +below for the exact sequence, and run the steps by hand if you prefer. + +--- + +## Prerequisites (one-time) + +- **`gh` CLI** authenticated: `gh auth login`. +- **Apple Developer ID** signing + notarization credentials, and the **Tauri + updater minisign key**. Keep these in a gitignored env file OUTSIDE the repo + (e.g. `~/.orcabot-release.env`) and `source` it before building: + + ```sh + # Apple code-signing + notarization + export APPLE_SIGNING_IDENTITY="Developer ID Application: Robert Macrae (3927MKQNPA)" + export APPLE_API_ISSUER="…" # App Store Connect API issuer UUID + export APPLE_API_KEY="…" # key id, e.g. K7A56UPB7U + export APPLE_API_KEY_PATH="…/AuthKey_K7A56UPB7U.p8" + + # Tauri updater signing (minisign). Pubkey is committed in tauri.conf.json; + # the PRIVATE key is NOT. + export TAURI_SIGNING_PRIVATE_KEY="…" # key contents or path + export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="…" + + # OAuth client creds baked into the app at build time (public IDs + Google's + # non-confidential desktop secret only). Unset = those integrations ship off. + export GOOGLE_CLIENT_ID="…" + export GOOGLE_CLIENT_SECRET="…" # Google "Desktop app" secret (non-confidential) + export GOOGLE_API_KEY="…" + export GITHUB_CLIENT_ID="…" + export MICROSOFT_CLIENT_ID="…" + export ONEDRIVE_CLIENT_ID="…" + ``` + +> Do **not** commit any of the above. The bake step rewrites only the *staged* +> copy of `workerd.desktop.capnp`; the source stays clean. + +--- + +## Release steps (manual) + +### 0. Land your changes on `main` +The publish script builds from the working tree, so merge everything first and +release from an up-to-date `main`. + +### 1. Bump the version (PR — never push to `main` directly) +Bump all three to the new version, then open a PR and merge it: +- `app/src-tauri/tauri.conf.json` → `"version"` +- `app/src-tauri/Cargo.toml` → `version` +- `app/src-tauri/Cargo.lock` → the `orcabot-desktop` entry + (`cargo update -p orcabot-desktop --precise ` does this cleanly) + +The publish script reads the version from `tauri.conf.json` and tags `v`. + +### 2. Source your signing/OAuth env +```sh +source ~/.orcabot-release.env +``` + +### 3. Build bundled resources (frontend + control-plane workerd, bakes OAuth) +```sh +sh desktop/scripts/build-desktop-resources.sh +``` +- Add `BUILD_VM=force` **only** if the VM image changed (rare — see §6). +- Watch for `baked OAuth binding:` lines confirming each secret you set was + embedded. A "set but no binding found" warning means the bake silently failed — + fix it before shipping. + +### 4. Signed + notarized app build +```sh +cd desktop/app/src-tauri && cargo tauri build && cd - +``` +Produces, under `app/src-tauri/target/release/bundle/`: +- `dmg/Orcabot__aarch64.dmg` — fresh-install download +- `macos/Orcabot.app.tar.gz` (+ `.sig`) — updater artifacts + +### 5. Publish the app +```sh +sh desktop/scripts/publish-release.sh +``` +- Runs a **preflight gate** (boots the built stack, checks dashboards load + + dev-auth surface-token + CORS). Publishing aborts if it fails. Override only + with `SKIP_PREFLIGHT=1` if you know what you're doing. +- Creates/updates the `v` GitHub release, uploads the DMG, tarball, + `.sig`, and `latest.json`, and makes it the repo's "latest". + +Verify: +```sh +curl -sL https://github.com/Hyper-Int/OrcaBot/releases/latest/download/latest.json +# → should show the new version +``` + +### 6. (Rare) Publish a new VM image +Only if you rebuilt the sandbox rootfs (its content changed). Bump the version tag: +```sh +VM_IMAGE_VERSION=v3 sh desktop/scripts/publish-vm-image.sh path/to/sandbox.img +``` +This updates `app/src-tauri/vm-image.json` (version + checksum) — **commit that via +a PR**, then rebuild + re-publish the app (§3–5) so the new checksum is baked into +the notarized binary. The VM-image release is created with `--latest=false` so it +never shadows the app release the updater polls. + +--- + +## Gotchas + +- **`latest.json` "Not Found" after release** — something other than the app + release is the repo's "latest". `gh release create` marks new releases "latest" + by default; the VM-image publish now passes `--latest=false` to avoid this. + Re-running the app publish reclaims "latest". +- **OAuth didn't take in the shipped app** — the baked value contained a + non-ASCII char (e.g. a `…` from a truncated copy-paste), so the bake failed. + Re-copy the full value, re-`source`, rebuild, and confirm the `baked OAuth + binding:` lines. (`grep -n '[^ -~]'` on the value catches stray chars.) +- **Wrong repo** — set `ORCABOT_RELEASE_REPO` if publishing anywhere other than + the default `Hyper-Int/OrcaBot`. + +--- + +## Full CI automation (not yet done) + +The heavy blocker is that signing + notarization need the Apple `.p8` / Developer +ID cert and the minisign key as **GitHub Actions secrets**, plus a macOS runner. +Once those are in place a tag-triggered workflow (`on: push: tags: 'v*'`) could run +§3–5. Until then, releases are driven locally via `release.sh`. diff --git a/desktop/app/src-tauri/Cargo.lock b/desktop/app/src-tauri/Cargo.lock index 62c1f6a2..b9d0da53 100644 --- a/desktop/app/src-tauri/Cargo.lock +++ b/desktop/app/src-tauri/Cargo.lock @@ -2305,7 +2305,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orcabot-desktop" -version = "0.4.0" +version = "0.5.0" dependencies = [ "ctrlc", "filetime", diff --git a/desktop/app/src-tauri/Cargo.toml b/desktop/app/src-tauri/Cargo.toml index 858892df..a5f94716 100644 --- a/desktop/app/src-tauri/Cargo.toml +++ b/desktop/app/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orcabot-desktop" -version = "0.4.0" +version = "0.5.0" edition = "2021" description = "Orcabot Desktop shell" license = "UNLICENSED" diff --git a/desktop/app/src-tauri/tauri.conf.json b/desktop/app/src-tauri/tauri.conf.json index 8eeb8eff..dbc13f45 100644 --- a/desktop/app/src-tauri/tauri.conf.json +++ b/desktop/app/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Orcabot", - "version": "0.4.0", + "version": "0.5.0", "identifier": "com.orcabot.desktop", "build": { "beforeDevCommand": "sh -c \"cd ../../frontend && NEXT_PUBLIC_API_URL=http://localhost:8787 NEXT_PUBLIC_SITE_URL=http://localhost:8788 NEXT_PUBLIC_DEV_MODE_ENABLED=true NEXT_PUBLIC_DESKTOP_MODE=true npx wrangler dev -c wrangler.toml --port 8788\"", diff --git a/desktop/scripts/publish-vm-image.sh b/desktop/scripts/publish-vm-image.sh index d60609bf..112a0ed4 100755 --- a/desktop/scripts/publish-vm-image.sh +++ b/desktop/scripts/publish-vm-image.sh @@ -48,7 +48,11 @@ echo "Publishing $GZ to release $TAG..." if gh release view "$TAG" --repo "$REPO" >/dev/null 2>&1; then gh release upload "$TAG" --repo "$REPO" --clobber "$GZ" else + # --latest=false: a VM-image release must NEVER become the repo's "latest" + # release, or it shadows the app release that the updater polls at + # /releases/latest/download/latest.json (breaking auto-update with "Not Found"). gh release create "$TAG" --repo "$REPO" \ + --latest=false \ --title "VM image $VER" \ --notes "Sandbox VM disk image ($VER). Downloaded on demand + checksum-verified by the desktop app; not bundled in the app itself." \ "$GZ" diff --git a/desktop/scripts/release.sh b/desktop/scripts/release.sh new file mode 100755 index 00000000..142c1929 --- /dev/null +++ b/desktop/scripts/release.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env sh +set -eu +# One-shot desktop release: bump (optional) -> build resources (bakes OAuth) -> +# signed+notarized `cargo tauri build` -> preflight gate + publish to GitHub. +# +# This is a thin orchestrator over the individual scripts; see desktop/RELEASE.md +# for the manual sequence and prerequisites. +# +# Usage: +# [ORCABOT_RELEASE_ENV=~/.orcabot-release.env] sh desktop/scripts/release.sh [VERSION] +# +# VERSION optional (e.g. 0.5.0). If given and different from the current +# tauri.conf.json version, the three version files are bumped IN THE +# WORKING TREE (not committed — open a bump PR separately; we never +# push to main). If omitted, the current version is released as-is. +# +# Env: +# ORCABOT_RELEASE_ENV path to a file (sourced) exporting the Apple signing + +# notarization creds, the Tauri minisign key, and the +# OAuth client IDs/secrets. Default: ~/.orcabot-release.env +# ALLOW_UNSIGNED=1 skip the signing-identity check (produces an UNSHIPPABLE +# build — updater/notarization will be wrong; dev only). +# SKIP_PREFLIGHT=1 passed through to publish-release.sh (not recommended). +# BUILD_VM=force passed through to build-desktop-resources.sh (only when +# the VM image actually changed; see RELEASE.md §6). + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +SRC_TAURI="$SCRIPT_DIR/../app/src-tauri" +CONF="$SRC_TAURI/tauri.conf.json" + +read_version() { + grep -m1 '"version"' "$CONF" | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/' +} + +# --- 1. Source signing/OAuth secrets -------------------------------------- +ENV_FILE="${ORCABOT_RELEASE_ENV:-$HOME/.orcabot-release.env}" +if [ -f "$ENV_FILE" ]; then + echo "== sourcing release env: $ENV_FILE" + # shellcheck disable=SC1090 + . "$ENV_FILE" +else + echo "== no env file at $ENV_FILE — assuming signing/OAuth vars are already exported" +fi + +if [ -z "${APPLE_SIGNING_IDENTITY:-}" ] && [ "${ALLOW_UNSIGNED:-0}" != "1" ]; then + echo "ERROR: APPLE_SIGNING_IDENTITY is not set — the build wouldn't be signable/notarizable." >&2 + echo " Point ORCABOT_RELEASE_ENV at your secrets file, or set ALLOW_UNSIGNED=1 for a throwaway build." >&2 + exit 1 +fi + +# --- 2. Optional version bump (working tree only) ------------------------- +CURRENT=$(read_version) +WANT="${1:-$CURRENT}" +if [ "$WANT" != "$CURRENT" ]; then + echo "== bumping version $CURRENT -> $WANT (working tree only; open a bump PR — do not push to main)" + # tauri.conf.json + Cargo.toml + if [ "$(uname)" = "Darwin" ]; then + sed -i '' "s/\"version\": \"$CURRENT\"/\"version\": \"$WANT\"/" "$CONF" + sed -i '' "s/^version = \"$CURRENT\"/version = \"$WANT\"/" "$SRC_TAURI/Cargo.toml" + else + sed -i "s/\"version\": \"$CURRENT\"/\"version\": \"$WANT\"/" "$CONF" + sed -i "s/^version = \"$CURRENT\"/version = \"$WANT\"/" "$SRC_TAURI/Cargo.toml" + fi + # Cargo.lock (keeps the workspace lock in sync) + ( cd "$SRC_TAURI" && cargo update -p orcabot-desktop --precise "$WANT" >/dev/null 2>&1 ) || true + echo " bumped: tauri.conf.json, Cargo.toml, Cargo.lock" +else + echo "== releasing current version: $CURRENT" +fi + +VERSION=$(read_version) +echo "== target release: v$VERSION" + +# --- 3. Build bundled resources (frontend + workerd; bakes OAuth) --------- +echo "== building desktop resources (frontend + control-plane workerd)…" +sh "$SCRIPT_DIR/build-desktop-resources.sh" + +# --- 4. Signed + notarized app build -------------------------------------- +echo "== cargo tauri build (signed + notarized; this takes a while)…" +( cd "$SRC_TAURI" && cargo tauri build ) + +# --- 5. Preflight gate + publish ------------------------------------------ +echo "== publishing v$VERSION…" +sh "$SCRIPT_DIR/publish-release.sh" + +echo +echo "== release v$VERSION done." +echo " Verify: curl -sL https://github.com/${ORCABOT_RELEASE_REPO:-Hyper-Int/OrcaBot}/releases/latest/download/latest.json" +if [ "$WANT" != "$CURRENT" ]; then + echo " Reminder: commit the version bump via a PR (tauri.conf.json, Cargo.toml, Cargo.lock)." +fi