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)
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user