# Baikal — Client Setup

Companion to [`baikal.md`](baikal.md) (auto-generated from compose). This file is hand-maintained and covers how to connect CalDAV/CardDAV clients to the Baikal server on Atlantis.

## Server Reference

| Field | Value |
|-------|-------|
| Host | Atlantis (Synology NAS) |
| Container | `baikal` (image `ckulka/baikal`) |
| Port | `12852` → container `80` |
| Auth | HTTP **Digest** (not Basic) |
| DAV root | `/dav.php/` |
| Admin UI | `http://<host>:12852/admin/` |

### Endpoints (all equivalent for authed CalDAV)

| URL | When to use |
|-----|-------------|
| `http://192.168.0.200:12852/` | LAN-only clients (dashboard, LAN appliances) |
| `http://atlantis:12852/` | Nodes with Tailscale MagicDNS |
| `http://atlantis.tail.vish.gg:12852/` | **Preferred for portable clients** (laptops that move on/off LAN) — stable DNS, works everywhere on the tailnet |
| `http://100.83.230.112:12852/` | Raw Tailscale IP — fallback if DNS misbehaves |

### URL patterns

- Principal: `/dav.php/principals/<user>/` — point auto-discovering clients (iOS, macOS, DAVx⁵) here.
- Calendar: `/dav.php/calendars/<user>/<calendar-uri>/` — explicit calendar URL for clients that need it (Thunderbird with "Locate calendar" off).
- Address book: `/dav.php/addressbooks/<user>/<addressbook-uri>/`.

## Credentials

- Baikal users live in the `users` table of `/volume1/docker/baikal/Specific/db/db.sqlite`. Passwords are stored as MD5 digesta1 (`MD5(user:BaikalDAV:password)`) — not reversible.
- The `vish` user password is referenced by the dashboard's upcoming-events widget (see `dashboard/api/routers/overview.py`, `BAIKAL_PASS` constant). Pull it from there — do **not** paste it into docs or client configs that get committed.
- Reset or create a user from the admin UI at `/admin/` (login with the admin password from compose env).

## Inspecting the database

No `sqlite3` binary in the container — use PHP PDO:

```bash
ssh atlantis "sudo /usr/local/bin/docker exec baikal php -r '
\$db=new PDO(\"sqlite:/var/www/baikal/Specific/db/db.sqlite\");
foreach(\$db->query(\"SELECT id,username FROM users\") as \$r){
  echo \$r[\"id\"].\" \".\$r[\"username\"].PHP_EOL;
}'"
```

Useful tables: `users`, `principals`, `calendars`, `calendarinstances` (displayname, ACL, share state), `calendarobjects`, `addressbooks`, `cards`.

## Thunderbird (GUI path)

1. **File → New → Calendar → On the Network → Next**.
2. **Username**: Baikal user (e.g. `vish`).
3. **Location**: either
   - `http://atlantis.tail.vish.gg:12852/dav.php/principals/vish/` — lets TB auto-discover all calendars for that user, or
   - a specific calendar URL, e.g. `http://atlantis.tail.vish.gg:12852/dav.php/calendars/vish/default/`.
4. **Find Calendars** → select the one(s) to add → **Subscribe**.
5. Password prompt appears on first sync; tick **Use Password Manager** to save.

## Thunderbird (headless / prefs.js path)

Use when the client machine is remote and you want to drop a calendar in without touching the GUI. Procedure:

1. **Close Thunderbird.** `prefs.js` is rewritten on exit — any edits made while it is running get clobbered.
2. Clear stale lock files if present: `rm -f ~/.thunderbird/<profile>/lock ~/.thunderbird/<profile>/.parentlock`.
3. Back up: `cp prefs.js prefs.js.bak-$(date +%Y%m%d-%H%M%S)`.
4. Generate a UUID: `cat /proc/sys/kernel/random/uuid`.
5. Append the registry block below (replace `<UUID>` and tweak `name`, `color`, `uri`, `username`).
6. Update the `calendar.list.sortOrder` line — append the new UUID (space-separated) so the calendar actually shows up.

```ini
user_pref("calendar.registry.<UUID>.cache.enabled", true);
user_pref("calendar.registry.<UUID>.calendar-main-in-composite", true);
user_pref("calendar.registry.<UUID>.color", "#3aa57c");
user_pref("calendar.registry.<UUID>.disabled", false);
user_pref("calendar.registry.<UUID>.forceEmailScheduling", false);
user_pref("calendar.registry.<UUID>.imip.identity.key", "id1");
user_pref("calendar.registry.<UUID>.name", "vish (Baikal)");
user_pref("calendar.registry.<UUID>.notifications.times", "");
user_pref("calendar.registry.<UUID>.readOnly", false);
user_pref("calendar.registry.<UUID>.refreshInterval", "30");
user_pref("calendar.registry.<UUID>.suppressAlarms", false);
user_pref("calendar.registry.<UUID>.type", "caldav");
user_pref("calendar.registry.<UUID>.uri", "http://atlantis.tail.vish.gg:12852/dav.php/calendars/vish/default/");
user_pref("calendar.registry.<UUID>.username", "vish");
```

**Password handling**: do *not* attempt to pre-populate the login store — it is NSS-encrypted and requires live TB APIs to write correctly. Leave the password out of `prefs.js` entirely; on first sync Thunderbird will prompt, and the user ticks **Use Password Manager** once.

**Verification (from client machine, before TB relaunch)**: confirm the client can reach the URL with the credentials:

```bash
curl -s -o /dev/null -w 'HTTP %{http_code}\n' \
  --digest -u '<user>:<password>' \
  http://atlantis.tail.vish.gg:12852/dav.php/calendars/<user>/default/
```

Expect `HTTP 200`. If `401`, the password is wrong or the realm name changed. If no response, check Tailscale reachability (`tailscale status | grep atlantis`) before blaming Baikal.

## Deployed clients (as of 2026-04-19)

| Client | Calendar | URL | Auth flavor |
|--------|----------|-----|-------------|
| Dashboard widget (homelab-vm) | `vish/default` | `http://192.168.0.200:12852/dav.php/calendars/vish/default/` | Digest via `httpx.DigestAuth`, creds in `dashboard/api/routers/overview.py` |
| Thunderbird on moon laptop | `vish/default` | `http://atlantis.tail.vish.gg:12852/dav.php/calendars/vish/default/` | Digest, password in TB password manager (user-entered on first sync) |

## Troubleshooting

- **`401 Unauthorized`**: Baikal speaks Digest, not Basic. Most `curl` tests need `--digest`. Thunderbird handles this automatically.
- **Calendar appears but never refreshes**: check `calendar.registry.<UUID>.cache.enabled` is `true`; the local-cache is what TB displays between network syncs.
- **Events not showing on the dashboard**: the widget only queries `vish/default`. Other calendars need a separate `calendar-query` REPORT against their URL.
- **Admin UI locked out**: `INSTALL_DISABLED` is touched in `/var/www/baikal/Specific/` after first setup. Delete that file in the container to re-enable the install wizard if you need to reset the admin password.

## Related

- [`baikal.md`](baikal.md) — auto-generated compose reference.
- `hosts/synology/atlantis/baikal/baikal.yaml` — stack definition.
- `dashboard/api/routers/overview.py` — dashboard CalDAV consumer (reference implementation for Digest queries).
