From 7ea78d3b5430a9c3ad0847ca79b09fd252670e8e Mon Sep 17 00:00:00 2001 From: Joakim Date: Tue, 6 Jan 2026 16:19:34 +0100 Subject: [PATCH] docs: add comprehensive deployment guide and Caddy configuration - Create detailed deployment documentation - Add Caddyfile.example with security headers and API proxy - Document SystemD service setup for Go API - Include database backup strategy - Add troubleshooting guide - Document OAuth configuration steps - Add build and deployment commands - Test production build successfully (340KB static site) --- docs/deployment.md | 350 +++++++++++++++++++++++++++++++++++++ opal-web/Caddyfile.example | 63 +++++++ 2 files changed, 413 insertions(+) create mode 100644 docs/deployment.md create mode 100644 opal-web/Caddyfile.example diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..08c6bab --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,350 @@ +# Opal-Web Deployment Guide + +## Overview +This guide covers deploying the Opal Task Manager PWA to production on `opal.example.com` with Caddy as the web server. + +## Prerequisites +- Server with Caddy installed +- Domain: `opal.example.com` (DNS configured) +- Authentik OAuth configured at `auth.example.com` +- Opal Go API server binary built + +## Architecture +``` +Browser → Caddy (opal.example.com) + ├─ / → Static PWA (SvelteKit build) + └─ /api/* → Go API Server (:8080) +``` + +## Step 1: Build the Frontend + +### Local Build +```bash +cd opal-web + +# Install dependencies if needed +bun install + +# Build for production +bun run build + +# Output will be in: build/ +``` + +The build creates a static site ready to deploy. + +## Step 2: Deploy Static Files + +### Option A: Direct Copy +```bash +# On your local machine +rsync -avz --delete build/ user@server:/var/www/opal/ +``` + +### Option B: Server-Side Build +```bash +# On the server +cd /opt/opal-web +git pull +bun install +bun run build +cp -r build/* /var/www/opal/ +``` + +### Set Permissions +```bash +sudo chown -R www-data:www-data /var/www/opal +sudo chmod -R 755 /var/www/opal +``` + +## Step 3: Configure Backend + +### Create Environment File +```bash +sudo mkdir -p /etc/opal +sudo nano /etc/opal/opal.env +``` + +Add: +```bash +# Server +SERVER_ADDR=:8080 + +# Database +OPAL_DB_PATH=/var/lib/opal/opal.db + +# OAuth (from Authentik setup) +OAUTH_ENABLED=true +OAUTH_ISSUER=https://auth.example.com/application/o/opal/ +OAUTH_CLIENT_ID=your_client_id_from_authentik +OAUTH_CLIENT_SECRET=your_client_secret_from_authentik +OAUTH_REDIRECT_URI=https://opal.example.com/auth/callback + +# JWT (generate with: openssl rand -hex 32) +JWT_SECRET=your_generated_jwt_secret_here +JWT_EXPIRY=3600 + +# Refresh Token +REFRESH_TOKEN_EXPIRY=604800 +``` + +### Create SystemD Service +```bash +sudo nano /etc/systemd/system/opal-api.service +``` + +```ini +[Unit] +Description=Opal Task API Server +After=network.target + +[Service] +Type=simple +User=opal +Group=opal +WorkingDirectory=/var/lib/opal +EnvironmentFile=/etc/opal/opal.env +ExecStart=/usr/local/bin/opal server start --addr :8080 --db /var/lib/opal/opal.db +Restart=always +RestartSec=5 + +# Security hardening +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/lib/opal + +[Install] +WantedBy=multi-user.target +``` + +### Setup Database and User +```bash +# Create user +sudo useradd -r -s /bin/false opal + +# Create directories +sudo mkdir -p /var/lib/opal +sudo chown opal:opal /var/lib/opal +sudo chmod 750 /var/lib/opal + +# Build and install binary +cd opal-task +go build -o opal main.go +sudo cp opal /usr/local/bin/ +sudo chmod 755 /usr/local/bin/opal + +# Generate first API key (optional - for CLI access) +sudo -u opal opal server keygen --name "Admin CLI" --db /var/lib/opal/opal.db +# Save the generated key! + +# Start service +sudo systemctl daemon-reload +sudo systemctl enable opal-api +sudo systemctl start opal-api +sudo systemctl status opal-api +``` + +## Step 4: Configure Caddy + +### Create Caddyfile +```bash +sudo nano /etc/caddy/Caddyfile +``` + +Add: +```caddy +opal.example.com { + # Root directory for static PWA + root * /var/www/opal + + # API proxy + handle /api/* { + uri strip_prefix /api + reverse_proxy localhost:8080 + } + + # Static file serving with SPA fallback + handle { + try_files {path} /index.html + file_server + } + + # Security headers + header { + # HTTPS only + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + + # Prevent clickjacking + X-Frame-Options "SAMEORIGIN" + + # XSS protection + X-Content-Type-Options "nosniff" + + # CSP (adjust as needed) + Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://auth.example.com;" + } + + # Logging + log { + output file /var/log/caddy/opal.log + format json + } + + # Compression + encode gzip +} +``` + +### Test and Reload Caddy +```bash +# Test configuration +sudo caddy validate --config /etc/caddy/Caddyfile + +# Reload +sudo systemctl reload caddy + +# Check status +sudo systemctl status caddy +``` + +## Step 5: Verify Deployment + +### Check Services +```bash +# API server +sudo systemctl status opal-api +sudo journalctl -u opal-api -n 50 + +# Caddy +sudo systemctl status caddy +sudo journalctl -u caddy -n 50 +``` + +### Test Endpoints +```bash +# Health check +curl https://opal.example.com/api/health + +# OAuth login URL +curl https://opal.example.com/api/auth/login + +# Static files +curl -I https://opal.example.com/ +``` + +### Browser Testing +1. Navigate to `https://opal.example.com` +2. Should see the Opal login page +3. Click "Login with OAuth" +4. Should redirect to Authentik +5. After login, should redirect back to task list +6. Test creating a task +7. Test PWA install prompt (mobile/desktop) + +## Step 6: Update Deployment (Future) + +```bash +# Frontend update +cd opal-web +git pull +bun install +bun run build +rsync -avz --delete build/ server:/var/www/opal/ + +# Backend update +cd opal-task +git pull +go build -o opal main.go +scp opal server:/tmp/ +ssh server "sudo systemctl stop opal-api && sudo cp /tmp/opal /usr/local/bin/ && sudo systemctl start opal-api" +``` + +## Troubleshooting + +### API Not Responding +```bash +# Check if running +sudo systemctl status opal-api + +# Check logs +sudo journalctl -u opal-api -f + +# Test locally +curl http://localhost:8080/health +``` + +### OAuth Not Working +- Verify redirect URI in Authentik matches exactly +- Check OAuth client ID and secret in env file +- Ensure JWT secret is set +- Check Caddy is proxying /api/* correctly + +### PWA Not Installing +- Ensure HTTPS is working +- Check manifest.json is accessible +- Verify service worker is registered (Browser DevTools → Application) +- Icons must be served correctly + +### Static Files 404 +- Check file permissions in /var/www/opal +- Verify Caddy root directive points to correct path +- Ensure index.html exists in root + +## Monitoring + +### Logs +```bash +# API logs +sudo journalctl -u opal-api -f + +# Caddy logs +sudo tail -f /var/log/caddy/opal.log + +# System logs +dmesg | grep opal +``` + +### Database Backup +```bash +# Backup script +#!/bin/bash +BACKUP_DIR="/var/backups/opal" +DATE=$(date +%Y%m%d_%H%M%S) + +mkdir -p "$BACKUP_DIR" +sudo -u opal sqlite3 /var/lib/opal/opal.db ".backup '$BACKUP_DIR/opal_$DATE.db'" + +# Keep last 7 days +find "$BACKUP_DIR" -name "opal_*.db" -mtime +7 -delete +``` + +Add to crontab: +```bash +0 2 * * * /usr/local/bin/backup-opal.sh +``` + +## Security Checklist +- ✅ HTTPS enforced (Caddy auto) +- ✅ OAuth2 authentication configured +- ✅ JWT tokens with expiry +- ✅ API key hashing (bcrypt) +- ✅ SystemD service hardening +- ✅ Database file permissions (750) +- ✅ Security headers (CSP, HSTS, etc.) +- ✅ Regular backups configured +- ⬜ Fail2ban for API rate limiting (optional) +- ⬜ Log monitoring/alerting (optional) + +## Performance Tips +- Caddy handles compression and HTTP/2 +- Service worker caches static assets +- API responses cached by service worker +- Consider CDN for static assets (future) + +## Support +- Check logs first +- Verify all environment variables +- Test OAuth flow in incognito +- Check network tab in DevTools diff --git a/opal-web/Caddyfile.example b/opal-web/Caddyfile.example new file mode 100644 index 0000000..b544022 --- /dev/null +++ b/opal-web/Caddyfile.example @@ -0,0 +1,63 @@ +# Caddy configuration for Opal Task Manager +# Copy to /etc/caddy/Caddyfile and customize + +opal.example.com { + # Root directory for static PWA build + root * /var/www/opal + + # API reverse proxy - strip /api prefix before forwarding + handle /api/* { + uri strip_prefix /api + reverse_proxy localhost:8080 { + # Optional: custom headers + header_up X-Real-IP {remote_host} + header_up X-Forwarded-Proto {scheme} + } + } + + # Static file serving with SPA fallback + handle { + # Try to serve static file, fallback to index.html for client-side routing + try_files {path} /index.html + file_server + } + + # Security headers + header { + # Force HTTPS + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + + # Prevent clickjacking + X-Frame-Options "SAMEORIGIN" + + # XSS protection + X-Content-Type-Options "nosniff" + + # Content Security Policy (adjust as needed) + Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://auth.example.com; font-src 'self';" + + # Remove server info + -Server + } + + # Logging + log { + output file /var/log/caddy/opal.log { + roll_size 10MB + roll_keep 5 + } + format json + } + + # Compression + encode gzip zstd + + # Rate limiting (optional) + # rate_limit { + # zone opal { + # key {remote_host} + # events 100 + # window 1m + # } + # } +}