macsync/docs/dev-setup.md
Natalie 576496ca3e feat(deploy): video-projects FUSE mount over DO Spaces
Generalize the photos-originals rclone-mount pattern to a video-projects
prefix so the video studio (and imajin ETL, per storage-portability-plan
§2.3) can read/write multi-GB project sources/renders as local files while
only hot data stays resident on plum (bounded VFS LRU cache). Lets a
small-disk laptop work with large footage without filling APFS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:10:13 -04:00

5.5 KiB

Dev setup

How to get @mac-sync running on a development machine.

Prerequisites

  • macOS 13+ (matches Package.swift:7 platform requirement)
  • Xcode command-line tools (swift --version >= 5.9, per Package.swift:1)
  • Bun — server and web runtime (src/server/package.json:7-10, web/package.json:7-10)
  • PostgreSQL 14+ with gen_random_uuid() available (used in SendQueueRepo.ts:152)
  • For the Linux server target: systemd (app.manifest.yaml:42)

Clone and resolve packages

cd ~/Code/@applications
git clone <repo> @mac-sync
cd @mac-sync
swift package resolve   # pulls GRDB, Alamofire, SwiftyJSON, Swifter

Local SwiftPM dependencies (Package.swift:9-15) live in ~/Code/@packages/@swift/:

  • agent-core, menu-bar, sync-framework, logging, settings
  • @tray/tray-resources

If swift package resolve complains about a missing local package, clone the corresponding repo into ~/Code/@packages/@swift/<scope>/<name>/.

Build the Mac client

make build       # Makefile:59-61 -> swift build --product MacSyncApp
make run         # Makefile:78-79 -> .build/debug/MacSyncApp

make build produces .build/debug/MacSyncApp. The app is a menu-bar executable; it stays in the dock-less tray once running. Look for the MacSyncShared/Storage/ActivityLog entries in stderr to confirm modules are ticking.

Tests:

make test        # Makefile:71-73 -> swift test (all test targets in Package.swift:113-157)

Boot the server

cd src/server
cp .env.example .env       # if present; otherwise set vars manually
bun install
bun run dev                # src/server/package.json:7

Required environment (read by src/server/src/app/config.ts):

  • QUINN_MACSYNC_DB_URL=postgres://… — Postgres connection string
  • SERVICE_TOKEN — bearer for /my/* and /admin/* (src/server/src/app/server.ts:136,140)
  • SSO_VALIDATE_URL — endpoint for the alternative ssoRequired gate on /my/*
  • MODEL_BOSS_EMBED_URL — embedding inference endpoint (required, no default — fails fast rather than dialing a baked LAN host)

Object storage (photo/attachment blobs), selected by STORAGE_BACKEND:

  • STORAGE_BACKEND=local (dev default) → blobs under STORAGE_LOCAL_PATH (./data/blobs). No other vars needed.
  • STORAGE_BACKEND=s3 → any S3-compatible store (MinIO / DO Spaces / AWS / R2), always signed (SigV4). Set S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET, S3_REGION (default us-east-1), S3_FORCE_PATH_STYLE (true for MinIO and the lilith-quinn-media Spaces bucket), S3_PRESIGN_TTL_SECONDS (default 900). Dev MinIO is provided by docker-compose.dev.yml.

The server listens on the port from app.manifest.yaml:44 (3201 in prod). First boot runs all migrations in order: src/server/src/app/server.ts:36-52.

Health checks:

curl http://localhost:3201/health         # server.ts:56
curl http://localhost:3201/health/deep    # server.ts:57-66 — also pings the DB

Boot the web SPA

cd web
bun install
bun run dev       # vite --port 5200 (web/package.json:7)

The dev server hot-reloads against the API on localhost:3201. For production the SPA is built (bun run build) into web/dist/ and that directory is shipped inside the Mac app bundle (webapp/ subresource) or served by the server.

Wire the Mac to the server

LocalWebServer (@packages/shared/Sources/MacSyncShared/WebServer/LocalWebServer.swift:42-110) exposes the dev SPA on http://localhost:8765 and an editable settings endpoint:

curl http://localhost:8765/api/settings
# {"serverURL":"http://localhost:3201","webServerPort":8765, …}

curl -X PUT http://localhost:8765/api/settings \
  -H 'content-type: application/json' \
  -d '{"serverURL":"http://localhost:3201"}'

For local end-to-end work, set serverURL to http://localhost:3201 and restart MacSyncApp so DeviceRegistration re-registers against the local backend.

Permissions to grant on first run

Each module needs an OS permission. Approve when prompted:

  • iMessage: Full Disk Access (to read ~/Library/Messages/chat.db). Module surfaces .fullDiskAccessRequired if missing (@packages/imessage/Sources/IMessageSync/SyncManager.swift:36).
  • iPhoto: Photos library access (PHPhotoLibrary). Modal handled by IPhotoSync.requestAuthorization().
  • iMail: Automation > Mail.
  • iCal: Calendars (EKEventStore.requestFullAccessToEvents).
  • iReminders: Reminders (EKEventStore.requestFullAccessToReminders).
  • iNotes: Automation > Notes (@packages/inotes/Sources/INoteSync/SyncManager.swift:96-100).

If you decline, the module sets its error state (e.g. .calendarAccessRequired) and skips cycles until you re-grant. Click the menu-bar status row to deep-link into System Settings.

Running a single test target

swift test --filter MacSyncSharedTests
swift test --filter IMessageSyncTests
swift test --filter ICalSyncTests

All test targets are declared in Package.swift:113-157.

Common dev problems

  • make build fails with "no such package": the local SwiftPM siblings under ~/Code/@packages/@swift/ are missing. Clone them, then swift package resolve again.
  • bun run dev (server) fails on gen_random_uuid(): enable the pgcrypto extension on the Postgres database (or use Postgres 13+).
  • The Mac app starts but the menu icon never appears: check stderr for LocalWebServer: webapp directory not found (LocalWebServer.swift:151). In dev, the resolver expects <repo>/web/dist/; run cd web && bun run build once.