|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Cortex — one-shot encrypted iCloud sync setup for a new device (macOS). |
| 3 | +# |
| 4 | +# What it does: |
| 5 | +# 1. Installs cortex-mcp-server (builds from this repo if not already installed). |
| 6 | +# 2. Waits for the iCloud `cortex-sync` folder to come down from iCloud Drive. |
| 7 | +# 3. Joins the encrypted sync with your passphrase — full restore happens on join, |
| 8 | +# the passphrase goes into this device's login Keychain, and sync auto-resumes |
| 9 | +# on every restart from then on. |
| 10 | +# |
| 11 | +# Usage: |
| 12 | +# ./scripts/setup-device-sync.sh |
| 13 | +# CORTEX_SYNC_PASSPHRASE=... ./scripts/setup-device-sync.sh # non-interactive |
| 14 | +# |
| 15 | +# Privacy notes: only memories you explicitly mark `shared` ever leave a device; |
| 16 | +# everything is AES-256-GCM encrypted client-side before it touches iCloud. |
| 17 | + |
| 18 | +set -euo pipefail |
| 19 | + |
| 20 | +BOLD=$'\033[1m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; RESET=$'\033[0m' |
| 21 | +say() { printf '%s\n' "${BOLD}${1}${RESET}"; } |
| 22 | +ok() { printf '%s\n' "${GREEN}✓ ${1}${RESET}"; } |
| 23 | +warn() { printf '%s\n' "${YELLOW}! ${1}${RESET}"; } |
| 24 | +die() { printf '%s\n' "${YELLOW}✗ ${1}${RESET}" >&2; exit 1; } |
| 25 | + |
| 26 | +[ "$(uname)" = "Darwin" ] || die "This script targets macOS (iCloud Drive). For Linux, use a synced folder + 'sync enable' manually." |
| 27 | + |
| 28 | +DB_PATH="${CORTEX_DB_PATH:-$HOME/.cortex/memory.db}" |
| 29 | +BIN="$HOME/.local/bin/cortex-mcp-server" |
| 30 | +ICLOUD_DIR="$HOME/Library/Mobile Documents/com~apple~CloudDocs" |
| 31 | +SYNC_DIR="$ICLOUD_DIR/cortex-sync" |
| 32 | + |
| 33 | +# ── 1. Binary ──────────────────────────────────────────────────────────────── |
| 34 | +say "[1/3] cortex-mcp-server binary" |
| 35 | +if command -v cortex-mcp-server >/dev/null 2>&1; then |
| 36 | + BIN="$(command -v cortex-mcp-server)" |
| 37 | + ok "found: $BIN ($($BIN --version))" |
| 38 | +elif [ -x "$BIN" ]; then |
| 39 | + ok "found: $BIN ($($BIN --version))" |
| 40 | +else |
| 41 | + REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" |
| 42 | + [ -f "$REPO_DIR/Cargo.toml" ] || die "binary not installed and no repo found — clone github.com/gambletan/cortex first" |
| 43 | + command -v cargo >/dev/null 2>&1 || die "cargo not found — install Rust (https://rustup.rs) and re-run" |
| 44 | + say " building from $REPO_DIR (one-time, ~2 min)..." |
| 45 | + (cd "$REPO_DIR" && cargo build --release -p cortex-mcp-server) |
| 46 | + mkdir -p "$HOME/.local/bin" |
| 47 | + rm -f "$BIN" # fresh inode: avoids macOS code-sign cache SIGKILL on overwrite |
| 48 | + cp "$REPO_DIR/target/release/cortex-mcp-server" "$BIN" |
| 49 | + ok "installed: $BIN ($($BIN --version))" |
| 50 | +fi |
| 51 | + |
| 52 | +# ── 2. iCloud sync folder ──────────────────────────────────────────────────── |
| 53 | +say "[2/3] waiting for iCloud Drive to deliver cortex-sync" |
| 54 | +[ -d "$ICLOUD_DIR" ] || die "iCloud Drive folder not found — sign into iCloud and enable iCloud Drive first" |
| 55 | + |
| 56 | +WAIT=120 |
| 57 | +while [ ! -f "$SYNC_DIR/manifest.json" ] && [ "$WAIT" -gt 0 ]; do |
| 58 | + # Nudge iCloud to download if it is still a placeholder |
| 59 | + brctl download "$SYNC_DIR" >/dev/null 2>&1 || true |
| 60 | + sleep 3; WAIT=$((WAIT - 3)) |
| 61 | + printf '.' |
| 62 | +done |
| 63 | +printf '\n' |
| 64 | +[ -f "$SYNC_DIR/manifest.json" ] || die "cortex-sync/manifest.json never appeared. Is sync enabled on your other device, and has iCloud finished syncing?" |
| 65 | +ok "sync folder present: $SYNC_DIR" |
| 66 | + |
| 67 | +# ── 3. Join (full restore) ─────────────────────────────────────────────────── |
| 68 | +say "[3/3] joining encrypted sync" |
| 69 | +PASS="${CORTEX_SYNC_PASSPHRASE:-}" |
| 70 | +if [ -z "$PASS" ]; then |
| 71 | + printf 'Sync passphrase (input hidden): ' |
| 72 | + read -rs PASS; printf '\n' |
| 73 | +fi |
| 74 | +[ -n "$PASS" ] || die "empty passphrase" |
| 75 | + |
| 76 | +mkdir -p "$(dirname "$DB_PATH")" |
| 77 | +"$BIN" "$DB_PATH" sync enable --provider icloud --passphrase "$PASS" |
| 78 | + |
| 79 | +# Deny-by-default capability policy (read+write+sync) if none exists yet |
| 80 | +CAPS="$(dirname "$DB_PATH")/capabilities.json" |
| 81 | +if [ ! -f "$CAPS" ]; then |
| 82 | + printf '{\n "version": 1,\n "grants": ["read", "write", "sync"]\n}\n' > "$CAPS" |
| 83 | + ok "capability policy created: $CAPS" |
| 84 | +fi |
| 85 | + |
| 86 | +echo |
| 87 | +"$BIN" "$DB_PATH" stats |
| 88 | +echo |
| 89 | +ok "Done. Shared memories now flow both ways automatically (~30s)." |
| 90 | +ok "Passphrase is in this device's login Keychain — sync auto-resumes on restart." |
| 91 | +echo |
| 92 | +say "To use with Claude Code, register the MCP server:" |
| 93 | +printf ' claude mcp add cortex-memory -- %s %s\n' "$BIN" "$DB_PATH" |
0 commit comments