# Baikal CalDAV Runbook

# Service: Baikal CalDAV

**Label:** Docker container `baikal` + LaunchAgent `com.john.calendar-bridge`
**Tier:** P2 (Business)
**Port:** 5232 (local), calendar.basicconsulting.no (public via Cloudflare)

## What It Does
Self-hosted CalDAV server for ALAI Business calendar. Alem syncs from iPhone/MacBook via native Calendar app. calendar-bridge.js daemon scans emails every 5min, detects meeting invites, forwards to alem@alai.no, and creates CalDAV events.

## Architecture
```
Email (john@) → email-agent.js → calendar-bridge.js → Baikal CalDAV → Alem iPhone/Mac
                                       ↓
                               mail-native.js forward → alem@alai.no
```

## Components
| Component | Location | Type |
|-----------|----------|------|
| Baikal server | ~/system/services/baikal/docker-compose.yml | Docker |
| calendar-bridge.js | ~/system/tools/calendar-bridge.js | Tool + Daemon |
| LaunchAgent | ~/Library/LaunchAgents/com.john.calendar-bridge.plist | Daemon (5min) |
| Cloudflare tunnel | calendar.basicconsulting.no → localhost:5232 | Tunnel |
| Credentials | Vaultwarden → "Baikal CalDAV" | Vault |
| Calendar | "ALAI Business" (CalDAV user: alem) | CalDAV |
| Data | ~/system/services/baikal/data/ | Persistent volume |

## Dependencies
- Docker (container: baikal)
- Cloudflare tunnel (com.john.cloudflared)
- Vaultwarden (credentials)
- mail-native.js (email forwarding)
- email-agent.js (inline meeting detection)

## Health Check
```bash
# Quick check
node ~/system/tools/calendar-bridge.js test

# Docker container
docker ps --filter name=baikal

# CalDAV endpoint
curl -s -o /dev/null -w "%{http_code}" http://localhost:5232/dav.php/

# Public URL (expect 401 = auth required = healthy)
curl -s -o /dev/null -w "%{http_code}" https://calendar.basicconsulting.no/dav.php/

# List events
node ~/system/tools/calendar-bridge.js list
```

## Common Failures & Fixes

### Failure 1: Baikal container down
**Symptoms:** calendar-bridge.js test fails, CalDAV 502/connection refused
**Fix:**
```bash
cd ~/system/services/baikal && docker compose up -d
```

### Failure 2: Cloudflare tunnel not routing
**Symptoms:** Public URL returns 404 or timeout, local URL works fine
**Fix:**
```bash
# Check config includes calendar entry
grep calendar ~/.cloudflared/config.yml
# Restart tunnel
launchctl kickstart -k gui/$(id -u)/com.john.cloudflared
```

### Failure 3: Calendar-bridge scan finds nothing
**Symptoms:** Meeting invites arrive but no events created, no forwards
**Check:**
```bash
# Check daemon is running
launchctl list | grep calendar-bridge
# Check logs
tail -50 ~/system/logs/calendar-bridge.log
# Check state file
cat ~/system/logs/calendar-bridge-state.json
# Manual scan with verbose
node ~/system/tools/calendar-bridge.js scan --verbose
```

### Failure 4: Alem can't sync from iPhone
**Symptoms:** iPhone Calendar shows error, events not showing
**Check:**
1. Verify credentials in Vault: `node ~/system/tools/vault.js get "Baikal CalDAV"`
2. Test public CalDAV endpoint (should return 401, not 502/404)
3. iPhone settings: Server = `calendar.basicconsulting.no/dav.php/principals/alem`

### Failure 5: Authentication failure
**Symptoms:** 401 with correct password
**Fix:** Password might be out of sync. Re-hash in Baikal DB:
```bash
NEW_PASS=$(bw get password "Baikal CalDAV" --session $(cat /tmp/bw-session))
DIGEST=$(printf "alem:BaikalDAV:$NEW_PASS" | md5)
docker exec baikal sqlite3 /var/www/baikal/Specific/db/db.sqlite \
  "UPDATE users SET digesta1='$DIGEST' WHERE username='alem';"
```

## Restart Procedure
```bash
# Restart Baikal
cd ~/system/services/baikal && docker compose restart

# Restart calendar-bridge daemon
launchctl kickstart -k gui/$(id -u)/com.john.calendar-bridge
```

## Backup
- SQLite DB: ~/system/services/baikal/data/Specific/db/db.sqlite
- Config: ~/system/services/baikal/data/config/baikal.yaml
- Included in daily db-backup.sh via Docker volume mount

## MC Task
Created: #3029 (Deploy), #3035 (Documentation + Watchdog)