Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions desktop/RELEASE.md
Original file line number Diff line number Diff line change
@@ -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 <version>` does this cleanly)

The publish script reads the version from `tauri.conf.json` and tags `v<version>`.

### 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_<version>_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<version>` 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`.
2 changes: 1 addition & 1 deletion desktop/app/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion desktop/app/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "orcabot-desktop"
version = "0.4.0"
version = "0.5.0"
edition = "2021"
description = "Orcabot Desktop shell"
license = "UNLICENSED"
Expand Down
2 changes: 1 addition & 1 deletion desktop/app/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -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\"",
Expand Down
4 changes: 4 additions & 0 deletions desktop/scripts/publish-vm-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
91 changes: 91 additions & 0 deletions desktop/scripts/release.sh
Original file line number Diff line number Diff line change
@@ -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