# Deployment Guide

## Requirements

- Docker 24.0+
- Docker Compose 2.20+
- 1GB RAM minimum (2GB recommended)
- 10GB disk space
- TLS certificate (Let's Encrypt or custom)

## Quick Start

```bash
# Clone repository
git clone https://github.com/your-org/cairnquire.git
cd cairnquire

# Copy and edit configuration
cp .env.example .env
# Edit .env with your domain, email, Postmark API key

# Start services
docker-compose up -d

# Verify health
curl https://your-domain.com/health
```

## Configuration

### Environment Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `DOMAIN` | Yes | — | Public domain name |
| `DB_PATH` | No | `/data/db.sqlite` | Database file path |
| `FILESTORE_PATH` | No | `/data/files` | Content-addressed file storage |
| `NOTES_PATH` | No | `/notes` | Watched markdown directory |
| `POSTMARK_API_KEY` | Yes | — | Postmark server API token |
| `POSTMARK_FROM` | No | `notifications@DOMAIN` | From email address |
| `SESSION_SECRET` | Yes | — | 32-byte hex for session signing |
| `ENV` | No | `production` | `development` or `production` |

### Docker Compose

```yaml
version: "3.8"

services:
  app:
    build: .
    ports:
      - "8080:8080"
    volumes:
      - ./data:/data
      - ./notes:/notes:ro
    environment:
      - DOMAIN=${DOMAIN}
      - DB_PATH=/data/db.sqlite
      - FILESTORE_PATH=/data/files
      - NOTES_PATH=/notes
      - POSTMARK_API_KEY=${POSTMARK_API_KEY}
      - SESSION_SECRET=${SESSION_SECRET}
    env_file:
      - .env
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 10s
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp:noexec,nosuid,size=100m

  # Optional: Traefik for auto-HTTPS
  traefik:
    image: traefik:v3.0
    command:
      - "--api.insecure=false"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=${EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    labels:
      - "traefik.enable=true"
    restart: unless-stopped
```

## Production Checklist

### Pre-Deployment
- [ ] Change default `SESSION_SECRET` (generate: `openssl rand -hex 32`)
- [ ] Configure `POSTMARK_API_KEY` with valid token
- [ ] Set `DOMAIN` to public hostname
- [ ] Configure DNS A/AAAA records
- [ ] Set up SPF and DKIM for email domain
- [ ] Review `.env` file — no default secrets

### Security
- [ ] TLS 1.3 enforced (Traefik or reverse proxy)
- [ ] HSTS header configured
- [ ] Firewall rules: only 80/443 open
- [ ] Fail2ban or rate limiting on SSH
- [ ] Automatic security updates enabled
- [ ] Backup encryption key stored securely

### Monitoring
- [ ] Health check endpoint accessible
- [ ] Log rotation configured
- [ ] Disk space alerting (>80%)
- [ ] Memory usage alerting
- [ ] Uptime monitoring (external)

## Backup Procedures

### Automated Backup

```bash
#!/bin/bash
# backup.sh

BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/md-hub-$TIMESTAMP.tar.gz"

# Create backup
tar czf "$BACKUP_FILE" \
  -C /data \
  db.sqlite \
  files/

# Encrypt (optional)
gpg --symmetric --cipher-algo AES256 "$BACKUP_FILE"
rm "$BACKUP_FILE"

# Keep only last 30 days
find "$BACKUP_DIR" -name "md-hub-*.tar.gz.gpg" -mtime +30 -delete
```

**Cron**: `0 2 * * * /opt/cairnquire/backup.sh`

### Restore from Backup

```bash
# Stop application
docker-compose stop app

# Extract backup
tar xzf md-hub-20240115_020000.tar.gz -C /data

# Restart
docker-compose up -d app

# Verify
curl https://your-domain.com/health
```

## Upgrade Procedures

### Minor Update (patch version)
```bash
docker-compose pull
docker-compose up -d
```

### Major Update
1. Read release notes for breaking changes
2. Create backup: `./backup.sh`
3. Pull new image: `docker-compose pull`
4. Run database migrations (auto on startup)
5. Update configuration if needed
6. Deploy: `docker-compose up -d`
7. Verify health and functionality
8. Rollback if issues: restore from backup

## Troubleshooting

### Application Won't Start
```bash
# Check logs
docker-compose logs -f app

# Verify configuration
docker-compose exec app /server -check-config

# Test database
docker-compose exec app sqlite3 /data/db.sqlite ".tables"
```

### Sync Not Working
```bash
# Check file watcher
docker-compose exec app ls -la /notes

# Verify WebSocket
curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
  -H "Sec-WebSocket-Version: 13" \
  https://your-domain.com/ws
```

### Email Not Sending
```bash
# Test Postmark connection
curl -X POST \
  https://api.postmarkapp.com/server \
  -H "X-Postmark-Server-Token: YOUR_TOKEN" \
  -H "Accept: application/json"

# Check email queue
docker-compose exec app sqlite3 /data/db.sqlite \
  "SELECT * FROM email_queue WHERE status='failed'"
```

## Scaling

### Vertical Scaling
- Increase container memory/CPU limits
- SQLite WAL mode handles concurrent reads well
- For heavy write loads, consider libsql replication

### Horizontal Scaling (Advanced)
```yaml
# Requires shared storage and load balancer
services:
  app1:
    # ... same config ...
    environment:
      - DB_PATH=/shared/db.sqlite
      - FILESTORE_PATH=/shared/files
  
  app2:
    # ... same config ...
    environment:
      - DB_PATH=/shared/db.sqlite
      - FILESTORE_PATH=/shared/files
  
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    ports:
      - "80:80"
```

**Note**: Horizontal scaling requires:
- Shared filesystem (NFS, EFS)
- libsql replication or external database
- Sticky sessions for WebSocket

## Support

- **Documentation**: `/docs` route (when authenticated)
- **Health Check**: `GET /health`
- **Metrics**: `GET /metrics` (Prometheus format)
- **Logs**: `docker-compose logs -f app`
- **Issues**: https://github.com/your-org/cairnquire/issues
