feat: Single-port deployment with improved error handling and SvelteKit static build

- Frontend now uses @sveltejs/adapter-static for production builds
- Backend serves both API and static files from single port (originally port 3000)
- Removed all throw statements from services to avoid Elysia prototype errors
- Fixed favicon serving and SvelteKit assets path handling
- Added ecosystem.config.js for PM2 process management
- Comprehensive deployment documentation (PM2 + HAProxy)
- Updated README with single-port architecture
- Created start.sh script for easy production start
This commit is contained in:
2026-01-16 13:58:03 +01:00
parent 413a6e9831
commit 907dc48f1b
12 changed files with 683 additions and 132 deletions

461
README.md
View File

@@ -227,79 +227,422 @@ This gives you:
- ✅ Simple production deployment
### Production Mode
In production, you can either:
1. **Build static files** and serve from Elysia
2. **Keep the proxy setup** with a proper reverse proxy (nginx, caddy)
3. **Use SvelteKit adapter** for Node/Bun to serve everything from one process
In production, the application serves everything from a single port:
- **Backend** on port 3001 serves both API and static frontend files
- Frontend static files are served from `src/frontend/build/`
- SPA routing is handled by backend fallback
- **Single port** simplifies HAProxy configuration
## Production Deployment
### Building for Production
This guide covers deployment using **PM2** for process management and **HAProxy** as a reverse proxy/load balancer.
### Architecture Overview
```
Internet
┌─────────────────┐
│ HAProxy │ Port 443 (HTTPS)
│ (Port 80/443) │ Port 80 (HTTP → HTTPS redirect)
└────────┬────────┘
┌─────────────────┐
│ Backend │ Port 3001
│ Managed by │ ├─ API Routes (/api/*)
│ PM2 │ ├─ Static Files (/*)
│ │ └─ SPA Fallback
└────────┬────────┘
├─────────────────┬──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌─────────────┐
│ SQLite │ │ Frontend │ │ ARRL LoTW │
│ DB │ │ Build │ │ External API│
└─────────┘ └──────────┘ └─────────────┘
```
### Prerequisites
- Server with SSH access
- Bun runtime installed
- PM2 installed globally: `bun install -g pm2` or `npm install -g pm2`
- HAProxy installed
- Domain with DNS pointing to server
### Step 1: Build the Application
```bash
# Build the frontend
# Clone repository on server
git clone <repository-url>
cd award
# Install dependencies
bun install
# Build frontend (generates static files in src/frontend/build/)
bun run build
```
### Step 2: Configure Environment Variables
Create `.env` in the project root:
```bash
# Application URL
VITE_APP_URL=https://awards.dj7nt.de
# API Base URL (empty for same-domain)
VITE_API_BASE_URL=
# JWT Secret (generate with: openssl rand -base64 32)
JWT_SECRET=your-generated-secret-here
# Environment
NODE_ENV=production
# Database path (absolute path recommended)
DATABASE_PATH=/path/to/award/award.db
```
**Security**: Ensure `.env` has restricted permissions:
```bash
chmod 600 .env
```
### Step 3: Initialize Database
```bash
# Push database schema
bun run db:push
# Verify database was created
ls -la award.db
```
### Step 4: Create PM2 Ecosystem Configuration
Create `ecosystem.config.js` in the project root:
```javascript
module.exports = {
apps: [
{
name: 'award-backend',
script: 'src/backend/index.js',
interpreter: 'bun',
cwd: '/path/to/award',
env: {
NODE_ENV: 'production',
PORT: 3001
},
instances: 1,
exec_mode: 'fork',
autorestart: true,
watch: false,
max_memory_restart: '500M',
error_file: './logs/backend-error.log',
out_file: './logs/backend-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
},
{
name: 'award-frontend',
script: 'bun',
args: 'run preview',
cwd: '/path/to/award/src/frontend',
env: {
NODE_ENV: 'production',
PORT: 5173
},
instances: 1,
exec_mode: 'fork',
autorestart: true,
watch: false,
max_memory_restart: '300M',
error_file: './logs/frontend-error.log',
out_file: './logs/frontend-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
}
]
};
```
**Create logs directory:**
```bash
mkdir -p logs
```
### Step 5: Start Applications with PM2
```bash
# Start all applications
pm2 start ecosystem.config.js
# Save PM2 process list
pm2 save
# Setup PM2 to start on system reboot
pm2 startup
# Follow the instructions output by the command above
```
**Useful PM2 Commands:**
```bash
# View status
pm2 status
# View logs
pm2 logs
# Restart all apps
pm2 restart all
# Restart specific app
pm2 restart award-backend
# Stop all apps
pm2 stop all
# Monitor resources
pm2 monit
```
### Step 6: Configure HAProxy
Edit `/etc/haproxy/haproxy.cfg`:
```haproxy
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# Default ciphers to use
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
ssl-default-bind-options no-sslv3
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
# Statistics page (optional - secure with auth)
listen stats
bind *:8404
mode http
stats enable
stats uri /stats
stats refresh 30s
stats realm HAProxy\ Statistics
stats auth admin:your-secure-password
# Frontend redirect HTTP to HTTPS
frontend http-in
bind *:80
http-request redirect scheme https
# Frontend HTTPS
frontend https-in
bind *:443 ssl crt /etc/ssl/private/awards.dj7nt.de.pem
default_backend award-backend
# Backend configuration
backend award-backend
# Health check
option httpchk GET /api/health
# Single server serving both frontend and API
server award-backend 127.0.0.1:3001 check
```
**SSL Certificate Setup:**
Using Let's Encrypt with Certbot:
```bash
# Install certbot
apt install certbot
# Generate certificate
certbot certonly --standalone -d awards.dj7nt.de
# Combine certificate and key for HAProxy
cat /etc/letsencrypt/live/awards.dj7nt.de/fullchain.pem > /etc/ssl/private/awards.dj7nt.de.pem
cat /etc/letsencrypt/live/awards.dj7nt.de/privkey.pem >> /etc/ssl/private/awards.dj7nt.de.pem
# Set proper permissions
chmod 600 /etc/ssl/private/awards.dj7nt.de.pem
```
### Step 7: Start HAProxy
```bash
# Test configuration
haproxy -c -f /etc/haproxy/haproxy.cfg
# Restart HAProxy
systemctl restart haproxy
# Enable HAProxy on boot
systemctl enable haproxy
# Check status
systemctl status haproxy
```
### Step 8: Verify Deployment
```bash
# Check PM2 processes
pm2 status
# Check HAProxy stats (if enabled)
curl http://localhost:8404/stats
# Test health endpoint
curl https://awards.dj7nt.de/api/health
# Check logs
pm2 logs
tail -f /var/log/haproxy.log
```
### Updating the Application
```bash
# Pull latest changes
git pull
# Install updated dependencies
bun install
# Rebuild frontend (if UI changed)
bun run build
# Preview the production build locally
bun run preview
# Restart PM2
pm2 restart award-backend
```
### Deployment Options
### Database Backups
#### Option 1: Static Site + Backend Server
1. Build the frontend: `bun run build`
2. Serve `src/frontend/build/` with Elysia using `@elysiajs/static`
3. Backend runs on one port serving both frontend and API
#### Option 2: Reverse Proxy (Recommended)
Use nginx or Caddy to proxy:
- `/` → SvelteKit frontend (port 5173 or static files)
- `/api` → Elysia backend (port 3001)
**Example nginx configuration:**
```nginx
server {
server_name awards.dj7nt.de;
# Frontend
location / {
proxy_pass http://localhost:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# Backend API
location /api {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
#### Option 3: Single Process with SvelteKit Node Adapter
Use `@sveltejs/adapter-node` to build for Node/Bun:
- Everything runs in one process
- API routes handled by SvelteKit (need to migrate from Elysia)
### Environment Variables for Production
Make sure to set these in your production environment:
Set up automated backups:
```bash
VITE_APP_URL=https://awards.dj7nt.de
VITE_API_BASE_URL= # Leave empty for same-domain
JWT_SECRET=<strong-random-string>
NODE_ENV=production
# Create backup script
cat > /usr/local/bin/backup-award.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/backups/award"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
# Backup database
cp /path/to/award/award.db $BACKUP_DIR/award_$DATE.db
# Keep last 30 days
find $BACKUP_DIR -name "award_*.db" -mtime +30 -delete
EOF
chmod +x /usr/local/bin/backup-award.sh
# Add to crontab (daily at 2 AM)
crontab -e
# Add line: 0 2 * * * /usr/local/bin/backup-award.sh
```
### Monitoring
**PM2 Monitoring:**
```bash
# Real-time monitoring
pm2 monit
# View logs
pm2 logs --lines 100
```
**HAProxy Monitoring:**
- Access stats page: `http://your-server:8404/stats`
- Check logs: `tail -f /var/log/haproxy.log`
**Log Files Locations:**
- PM2 logs: `./logs/backend-error.log`, `./logs/frontend-error.log`
- HAProxy logs: `/var/log/haproxy.log`
- System logs: `journalctl -u haproxy -f`
### Security Checklist
- [ ] HTTPS enabled with valid SSL certificate
- [ ] Firewall configured (ufw/firewalld)
- [ ] JWT_SECRET is strong and randomly generated
- [ ] .env file has proper permissions (600)
- [ ] Database backups automated
- [ ] PM2 stats page secured with authentication
- [ ] HAProxy stats page secured (if publicly accessible)
- [ ] Regular security updates applied
- [ ] Log rotation configured for application logs
### Troubleshooting
**Application won't start:**
```bash
# Check PM2 logs
pm2 logs --err
# Check if ports are in use
netstat -tulpn | grep -E ':(3001|5173)'
# Verify environment variables
pm2 env 0
```
**HAProxy not forwarding requests:**
```bash
# Test backend directly
curl http://localhost:3001/api/health
curl http://localhost:5173/
# Check HAProxy configuration
haproxy -c -f /etc/haproxy/haproxy.cfg
# View HAProxy logs
tail -f /var/log/haproxy.log
```
**Database issues:**
```bash
# Check database file permissions
ls -la award.db
# Verify database integrity
bun run db:studio
```
---
## Features in Detail
### Background Job Queue