Vault mirror setup — runbook
Click-by-click guide for wiring up the iCloud → GitHub vault mirror.
Why this exists: The MICHIU Obsidian vault lives in iCloud (
~/Library/Mobile Documents/iCloud~md~obsidian/Documents/MICHIU/). iCloud +git initis a known-bad combo (iCloud rewrites.git/internals). This runbook sets up a separate local mirror directory, rsynced by a LaunchAgent, that is a proper git repo. The mirror repo on GitHub (quarbstudio/michiu-brand-vault) is then the authoritative source for the webhook-triggered vault sync pipeline in Marketing OS.Two phases:
- Phase 1 (low-blast) — scripts are already written; just set up the local mirror. No GitHub repo creation, no LaunchAgent yet. Good for "I want to try a manual sync first."
- Phase 2 (high-blast) — create the private GitHub repo, clone it as the mirror dir, install the LaunchAgent for automated 15-min syncs, wire the GitHub webhook to
/api/webhooks/vault-sync.
Phase 1 — Local mirror (manual sync, no LaunchAgent)
Step 1 — Create the mirror directory
mkdir -p ~/vault-mirrors/michiu-vault
cd ~/vault-mirrors/michiu-vault
git init
git commit --allow-empty -m "init"
The mirror dir must be outside iCloud.
~/vault-mirrors/is a safe location.
Step 2 — Run a manual sync
cd /path/to/michiu-marketing
bash scripts/vault-sync.sh
The script rsyncs ~/Library/Mobile Documents/iCloud~md~obsidian/Documents/MICHIU/ → ~/vault-mirrors/michiu-vault/, then commits any changes. On the first run it will commit the entire vault (~all .md files).
Expected output:
[2026-05-12 10:00:01] vault-sync: rsync … → ~/vault-mirrors/michiu-vault
[2026-05-12 10:00:03] vault-sync: pushed (312 file(s) changed)
If you see Mirror repo not initialised at …: you skipped Step 1, or you used a different path — set MIRROR_DIR env var to point to the correct directory:
MIRROR_DIR=~/vault-mirrors/michiu-vault bash scripts/vault-sync.sh
Step 3 — Add vault:push to package.json (optional convenience alias)
The script is already invocable as bash scripts/vault-sync.sh. If you want pnpm vault:push, add to package.json scripts:
"vault:push": "bash scripts/vault-sync.sh"
Phase 2 — GitHub repo + LaunchAgent + webhook
Step 1 — Create the private GitHub repo
gh repo create quarbstudio/michiu-brand-vault \
--private \
--description "MICHIU Obsidian vault mirror (iCloud → GitHub rsync)" \
--clone=false
Step 2 — Wire the mirror dir to the GitHub remote
cd ~/vault-mirrors/michiu-vault
git remote add origin https://github.com/quarbstudio/michiu-brand-vault.git
git branch -M main
git push -u origin main
If you haven't set up a PAT yet:
- GitHub → Settings → Developer Settings → Personal Access Tokens → Fine-grained tokens → Generate new token.
- Resource owner:
quarbstudio. Repository:michiu-brand-vault. Permissions: Contents → Read and write. - Copy the token. In your terminal:
Then rungit config --global credential.helper storegit push— macOS will prompt for credentials. Enter your GitHub username and the PAT as the password. The credential gets stored for future pushes.
Step 3 — Verify a push works end-to-end
bash scripts/vault-sync.sh
# Check https://github.com/quarbstudio/michiu-brand-vault — commits should appear
Step 4 — Install the LaunchAgent
# 1. Copy the plist template
cp scripts/vault-sync.launchagent.plist \
~/Library/LaunchAgents/com.quarb.michiu-vault-sync.plist
# 2. Replace the PLACEHOLDER_USERNAME with your actual macOS username
sed -i '' "s/PLACEHOLDER_USERNAME/$(whoami)/g" \
~/Library/LaunchAgents/com.quarb.michiu-vault-sync.plist
# 3. Also update the vault-sync.sh path if your clone is not at the default location
# (open the plist in a text editor and check the ProgramArguments string)
# 4. Load the agent
launchctl load ~/Library/LaunchAgents/com.quarb.michiu-vault-sync.plist
# 5. Verify it loaded
launchctl list | grep michiu-vault-sync
Expected output from step 5:
- 0 com.quarb.michiu-vault-sync
The - in the PID column means it's not running right now (it fires on interval). 0 means last exit was clean. If you see a non-zero exit code, check the log:
tail -f ~/Library/Logs/michiu-vault-sync.log
The LaunchAgent fires every 15 minutes and on every login (RunAtLoad: true).
To unload:
launchctl unload ~/Library/LaunchAgents/com.quarb.michiu-vault-sync.plist
Step 5 — Configure the GitHub webhook
This wires GitHub → Marketing OS vault sync pipeline (Inngest vault.changed event).
- Go to
https://github.com/quarbstudio/michiu-brand-vault/settings/hooks/new. - Payload URL:
https://marketing.michiu.quarb.com/api/webhooks/vault-sync - Content type:
application/json - Secret: generate a strong random string:
Copy it — you need it in two places (GitHub and Vercel).openssl rand -hex 32 - Which events:
Just the push event. - Click Add webhook.
Then in Vercel → michiu-marketing → Settings → Environment Variables:
| Env key | Value |
|---|---|
GITHUB_VAULT_SYNC_SECRET | the string from step 4 above |
GITHUB_VAULT_REPO | quarbstudio/michiu-brand-vault |
GITHUB_VAULT_PAT | a fine-grained PAT with Contents: Read-only on michiu-brand-vault |
Trigger a manual redeploy after saving.
Step 6 — Smoke test end-to-end
- Make a trivial edit to any
.mdfile in the MICHIU Obsidian vault. - Wait up to 15 minutes for the LaunchAgent (or run
bash scripts/vault-sync.shmanually). - Confirm the commit appears in
https://github.com/quarbstudio/michiu-brand-vault. - In GitHub → the repo → Settings → Webhooks, confirm the recent delivery shows a green checkmark.
- In Vercel → Project → Functions →
/api/webhooks/vault-sync→ Logs, confirm a POST with 200 appears. - In Supabase →
vault_documentstable (or via the/admin/marketing-os/vaultpage), confirm the changed document'supdated_athas refreshed.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Mirror repo not initialised | MIRROR_DIR doesn't have .git/ | Run Phase 1 Step 1, or set MIRROR_DIR env var to the correct path |
rsync: No such file or directory for vault src | iCloud vault path differs on your machine | Set VAULT_SRC env var to the correct path; run ls ~/Library/Mobile\ Documents/ | grep obsidian to find it |
| LaunchAgent exit code non-zero | Script error | tail -f ~/Library/Logs/michiu-vault-sync.log to see the error |
| GitHub webhook 403 | GITHUB_VAULT_SYNC_SECRET mismatch | Re-copy from GitHub → Webhooks → Edit → Secret, update Vercel, redeploy |
| GitHub webhook 503 | Vercel env var not set or cold start | Check GITHUB_VAULT_SYNC_SECRET is set; trigger a redeploy |
| Vault documents not updating in Supabase | GITHUB_VAULT_PAT unset or expired | Regenerate PAT, update Vercel env var, redeploy |
| git push prompts for credentials every time | Credential helper not configured | Run git config --global credential.helper osxkeychain then push once to store |
Env vars summary
# Vault mirror → GitHub webhook → Marketing OS
GITHUB_VAULT_SYNC_SECRET= # random hex (openssl rand -hex 32) — shared with GitHub webhook
GITHUB_VAULT_REPO=quarbstudio/michiu-brand-vault
GITHUB_VAULT_PAT= # fine-grained PAT, Contents: Read-only on vault repo
These go in Vercel env vars (Production + Preview). The webhook handler reads them at runtime.