# Deploying Mozart on DigitalOcean

Run Mozart on a $12/mo DigitalOcean Droplet with password-gated access.

## Architecture

```
Browser → Caddy (basic auth + optional TLS) → Mozart (:4141, localhost only)
                                                 ├── systemd (process management)
                                                 ├── Podman rootless (agent containers)
                                                 └── ~/.mozart/ (env, logs, agent data)
```

## Prerequisites

- A DigitalOcean account
- An OpenRouter API key
- Your SSH public key
- A domain name (optional -- can use the Droplet's IP for HTTP-only access)
- A GitHub Personal Access Token if the repo is private (see [Private Repo](#private-repo) below)

## 1. Create a Droplet

- **Image:** Ubuntu 24.04 LTS
- **Size:** $12/mo (2 GB RAM, 1 vCPU, 50 GB SSD) — handles up to ~5 agents
- **Region:** closest to your users
- **Authentication:** SSH key

For 10+ agents, use the $24/mo plan (4 GB RAM, 2 vCPU).

## 2. Run Bootstrap

SSH in as root. Clone the repo first (the bootstrap script is inside it), then run it.

**Quick start (IP only, HTTP):**

```bash
git clone https://github.com/seanli/mozart.git
cd mozart
bash deploy/bootstrap.sh \
  --repo https://github.com/seanli/mozart.git \
  --key sk-or-your-openrouter-key
```

The script auto-detects the Droplet's public IP and configures Caddy to serve over HTTP with basic auth.

**With a domain (HTTPS):**

```bash
git clone https://github.com/seanli/mozart.git
cd mozart
bash deploy/bootstrap.sh \
  --repo https://github.com/seanli/mozart.git \
  --key sk-or-your-openrouter-key \
  --domain mozart.example.com
```

Point your domain's DNS A record to the Droplet's IP first. Caddy auto-provisions the TLS certificate.

Both modes take ~5 minutes and handle everything: user creation, Podman, Bun, firewall, swap, build, systemd, and Caddy.

### Private Repo

The repo is private, so the Droplet needs credentials to clone and pull.

**Option A: Personal Access Token (simplest)**

1. GitHub → Settings → Developer Settings → Personal Access Tokens → Fine-grained tokens
2. Create a token scoped to `seanli/mozart` with `Contents: Read-only`
3. Include the token in the URL for both clone and bootstrap:

```bash
git clone https://YOUR_TOKEN@github.com/seanli/mozart.git
cd mozart
bash deploy/bootstrap.sh \
  --repo https://YOUR_TOKEN@github.com/seanli/mozart.git \
  --key sk-or-your-openrouter-key
```

The token is stored in the git remote, so `deploy.sh` (which runs `git pull`) keeps working.

**Option B: SSH deploy key (more locked down)**

1. Bootstrap first with Option A, then SSH in as `mozart`
2. Generate a key and print it:

```bash
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
cat ~/.ssh/id_ed25519.pub
```

3. Add the public key in GitHub: repo → Settings → Deploy keys → Add (read-only)
4. Switch the remote to SSH:

```bash
cd ~/mozart
git remote set-url origin git@github.com:seanli/mozart.git
git pull  # verify it works
```

## 3. Set the Caddy Password

The bootstrap creates a placeholder. Generate a real password hash and configure it:

```bash
# Generate the bcrypt hash
caddy hash-password --plaintext 'your-strong-password'

# Edit the env file — replace PLACEHOLDER with the hash output,
# and set your username
sudo nano /etc/caddy/env
```

The env file looks like this (IP mode):

```
MOZART_DOMAIN=http://123.45.67.89
MOZART_USER=admin
MOZART_HASH=$2a$14$xYz...your-hash-here
```

Or with a domain:

```
MOZART_DOMAIN=mozart.example.com
MOZART_USER=admin
MOZART_HASH=$2a$14$xYz...your-hash-here
```

Then restart Caddy:

```bash
sudo systemctl restart caddy
```

## 4. Verify

```bash
# On the server
curl -sf localhost:4141/health
# → {"status":"ok","agents":1,"ready":true}

# From your browser — use your IP or domain
# http://123.45.67.89  (IP mode)
# https://mozart.example.com  (domain mode)
# Enter the username/password you configured
# The Mozart UI should load
```

### Upgrading from IP to domain later

When you're ready to add HTTPS:

1. Point your domain's DNS A record to the Droplet's IP
2. Edit `/etc/caddy/env` -- change `MOZART_DOMAIN=http://1.2.3.4` to `MOZART_DOMAIN=yourdomain.com`
3. `sudo systemctl restart caddy` -- Caddy auto-provisions the TLS cert

## Updating

SSH in as the `mozart` user and run:

```bash
~/mozart/deploy/deploy.sh
```

This pulls latest code, rebuilds everything (UI, binary, Podman images), and restarts the daemon. Takes ~2 minutes.

## Files Reference

| File | Purpose |
|------|---------|
| `deploy/bootstrap.sh` | One-time server setup (run as root) |
| `deploy/mozart.service` | systemd unit file |
| `deploy/Caddyfile` | Caddy reverse proxy config |
| `deploy/deploy.sh` | Update/redeploy script |
| `deploy/health-check.sh` | Cron watchdog (restarts on failure) |

### System locations after bootstrap

| Path | Contents |
|------|----------|
| `/home/mozart/mozart/` | Repo clone + compiled binary |
| `/home/mozart/.mozart/env` | `OPENROUTER_API_KEY` (mode 600) |
| `/etc/systemd/system/mozart.service` | systemd unit |
| `/etc/caddy/Caddyfile` | Caddy site config |
| `/etc/caddy/env` | Caddy credentials (domain, user, hash) |

## Monitoring

- **systemd** auto-restarts on crash (`Restart=on-failure`)
- **Cron watchdog** checks `/health` every 5 minutes, restarts if down
- **DigitalOcean monitoring** — enable in the Droplet dashboard for CPU/RAM/disk graphs
- **Logs:** `sudo journalctl -u mozart -f`

## Troubleshooting

### Mozart won't start

```bash
sudo systemctl status mozart
sudo journalctl -u mozart --no-pager -n 50
```

Common causes:
- Missing API key in `~/.mozart/env`
- Podman not running — check `podman info` as the `mozart` user
- Port conflict — another process on 4141

### Caddy shows 502

Mozart isn't ready yet. Check `curl localhost:4141/health` — if it returns `"ready":false`, agents are still restoring. Wait 10-15 seconds.

### Agents use too much memory

Edit agent `.soul` files to reduce memory limits (default is 256m). The `MEMORY` directive controls this. With 2 GB Droplet + 1 GB swap, stay under ~1.5 GB total for containers.

### SSH locked out

Use the DigitalOcean console (Droplet dashboard → Access → Launch Droplet Console) to regain access. Bootstrap disables password auth and root login for security.

## Cost

| Item | Cost |
|------|------|
| Droplet (2 GB) | $12/mo |
| Caddy + TLS | Free (Let's Encrypt) |
| Domain (optional) | ~$10/yr |
| Total | **$12/mo** |
