macsync/docs/release.md
Natalie ad8e126dd1 docs(mac-sync): outbox/read architecture, handoffs, module docs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:35:13 -04:00

4.1 KiB

Release

@mac-sync ships three artefacts: the Mac client, the server, and the web SPA. They are deployed independently.

Version source

VERSION.json at the repo root is the canonical version. app.manifest.yaml:5 duplicates it for the manage-apps CLI. Bump both together.

Mac client — MacSyncApp

Build (release):

make build-release   # Makefile:63-65 -> swift build -c release --product MacSyncApp

Output: .build/release/MacSyncApp. The binary is a menu-bar executable; for distribution wrap it as a .app bundle including:

  • The binary itself
  • A webapp/ resource directory built from web/dist/ (LocalWebServer.swift:138-141 looks here first)
  • Info.plist with LSUIElement=true (menu-bar only)
  • Privacy usage strings: NSCalendarsFullAccessUsageDescription, NSRemindersFullAccessUsageDescription, NSAppleEventsUsageDescription, NSPhotoLibraryUsageDescription, plus Full Disk Access via signed entitlement

The user instals through deploy/install.sh and a LaunchAgent (app.manifest.yaml:20-22):

ssh plum 'bash -s' < deploy/install.sh

Or via the manage-apps CLI (app.manifest.yaml:24-30):

manage-apps start mac-sync plum    # runs deploy/deploy-remote.sh
manage-apps stop mac-sync plum     # launchctl unload
manage-apps status mac-sync        # ssh plum 'pgrep -x MacSyncApp'
manage-apps logs mac-sync          # tail of ~/Library/Application Support/MacSync/stderr.log

Server — mac-sync-server

Build is implicit (Bun runs TypeScript directly). The systemd unit lives in deploy/systemd/ and is referenced by app.manifest.yaml:42-44.

manage-apps start mac-sync backend-droplet     # deploy/deploy-server.sh
manage-apps status mac-sync backend-droplet    # curl http://localhost:3201/health
manage-apps logs mac-sync backend-droplet      # journalctl -u mac-sync-server
manage-apps stop mac-sync backend-droplet      # sudo systemctl stop mac-sync-server

Migrations apply automatically on boot (src/server/src/app/server.ts:36-52). To verify a fresh deploy:

curl http://209.38.51.98:3201/health/deep   # DO backend droplet (was black 10.0.0.11)
# {"ok":true,"checks":{"db":"ok"}}

Environment is loaded by loadConfig() (src/server/src/app/config.ts); required vars are documented in dev-setup.

Web SPA

Two distribution paths:

  1. Bundled with the Mac client. Run cd web && bun run build before make build-release; copy web/dist/ into the .app bundle's webapp/ resource directory. Served at http://localhost:8765/ by LocalWebServer (@packages/shared/Sources/MacSyncShared/WebServer/LocalWebServer.swift:94-109).

  2. Hosted by the server. The nginx config in deploy/nginx/ (referenced from the deploy scripts) can serve web/dist/ from the same origin as the API.

cd web
bun install
bun run build           # tsc --noEmit && vite build  (web/package.json:8)

Output goes to web/dist/. Verify with bun run preview.

Rollback

  • Mac client: previous .app bundle is preserved alongside the new one on plum; relaunch via launchctl load.
  • Server: sudo systemctl restart mac-sync-server after reverting the source tree. Migrations are forward-only — see Caveats below.
  • Web: redeploy the previous web/dist/. The bundle is fingerprinted by Vite, so cache invalidation is automatic.

Caveats

  • Migrations are forward-only. runMigrations(db, [...]) (src/server/src/app/server.ts:36-52) appends; there is no down script. A schema-breaking change requires manual SQL on the database.
  • iMessage icloud.send_queue is on a bespoke schema. Versioning it is independent of the generic per-module tables. Don't drop and recreate it on a release — outgoing messages will be lost.
  • The Mac client retains UserDefaults watermarks across releases. If a release changes the inbound payload shape, bump SyncManager.syncSchemaVersion (@packages/imessage/Sources/IMessageSync/SyncManager.swift:47 for iMessage) so the bootstrap resets the watermark.
  • Don't commit from a release script. External tooling owns commits (CLAUDE.md:46).