373 lines
7.6 KiB
Markdown
373 lines
7.6 KiB
Markdown
# Deployment Guide — ETC PRS Viewer & Editor
|
||
|
||
This guide covers deploying the app to a **Digital Ocean Droplet** running Ubuntu 24.04.
|
||
|
||
---
|
||
|
||
## Prerequisites
|
||
|
||
- A Digital Ocean Droplet (1 GB RAM minimum, 2 GB recommended)
|
||
- A domain name pointed at your droplet's IP
|
||
- SSH access to the droplet
|
||
|
||
---
|
||
|
||
## 1. Initial Server Setup
|
||
|
||
```bash
|
||
# Update packages
|
||
sudo apt update && sudo apt upgrade -y
|
||
|
||
# Install Node.js 20 (LTS)
|
||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||
sudo apt install -y nodejs
|
||
|
||
# Verify
|
||
node --version # should be v20.x
|
||
npm --version
|
||
|
||
# Install build tools (needed for better-sqlite3)
|
||
sudo apt install -y build-essential python3
|
||
|
||
# Install PM2 globally
|
||
sudo npm install -g pm2
|
||
|
||
# Install Nginx
|
||
sudo apt install -y nginx
|
||
|
||
# Install Certbot for SSL
|
||
sudo apt install -y certbot python3-certbot-nginx
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Create the App User and Directories
|
||
|
||
```bash
|
||
# Create a dedicated user for the app (no login shell)
|
||
sudo useradd --system --shell /bin/false --home /opt/etc-prs prs
|
||
|
||
# Create app directory
|
||
sudo mkdir -p /opt/etc-prs/app
|
||
sudo chown -R prs:prs /opt/etc-prs
|
||
|
||
# Create data directory for SQLite (outside the app, survives redeployment)
|
||
sudo mkdir -p /var/lib/etc-prs
|
||
sudo chown -R prs:prs /var/lib/etc-prs
|
||
|
||
# Create log directory
|
||
sudo mkdir -p /var/log/etc-prs
|
||
sudo chown -R prs:prs /var/log/etc-prs
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Deploy the Application
|
||
|
||
```bash
|
||
# Clone or copy your project to the server
|
||
# Option A: Git
|
||
cd /opt/etc-prs
|
||
sudo -u prs git clone https://github.com/yourusername/etc-prs-ui.git app
|
||
|
||
# Option B: Copy files via scp from your local machine
|
||
# scp -r ./etc-prs-ui user@your-droplet-ip:/tmp/
|
||
# sudo mv /tmp/etc-prs-ui /opt/etc-prs/app
|
||
# sudo chown -R prs:prs /opt/etc-prs/app
|
||
|
||
cd /opt/etc-prs/app
|
||
|
||
# Install dependencies (as the prs user)
|
||
sudo -u prs npm install
|
||
|
||
# Build the app
|
||
sudo -u prs npm run build
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Environment Configuration
|
||
|
||
```bash
|
||
# Create the production .env file
|
||
sudo -u prs nano /opt/etc-prs/app/.env
|
||
```
|
||
|
||
Add the following contents:
|
||
|
||
```env
|
||
DATABASE_URL=/var/lib/etc-prs/personalities.db
|
||
RATE_LIMIT_PUBLISH=5
|
||
RATE_LIMIT_READ=100
|
||
PUBLIC_BASE_URL=https://yourdomain.com
|
||
```
|
||
|
||
Replace `yourdomain.com` with your actual domain.
|
||
|
||
---
|
||
|
||
## 5. Configure PM2
|
||
|
||
Create the PM2 ecosystem file:
|
||
|
||
```bash
|
||
sudo -u prs nano /opt/etc-prs/app/ecosystem.config.cjs
|
||
```
|
||
|
||
```javascript
|
||
module.exports = {
|
||
apps: [{
|
||
name: 'etc-prs',
|
||
script: '/opt/etc-prs/app/build/index.js',
|
||
cwd: '/opt/etc-prs/app',
|
||
user: 'prs',
|
||
env: {
|
||
NODE_ENV: 'production',
|
||
PORT: '3000',
|
||
HOST: '127.0.0.1',
|
||
},
|
||
error_file: '/var/log/etc-prs/error.log',
|
||
out_file: '/var/log/etc-prs/out.log',
|
||
log_date_format: 'YYYY-MM-DD HH:mm:ss',
|
||
restart_delay: 3000,
|
||
max_restarts: 10,
|
||
}]
|
||
};
|
||
```
|
||
|
||
Start the app with PM2:
|
||
|
||
```bash
|
||
# Start
|
||
sudo -u prs pm2 start /opt/etc-prs/app/ecosystem.config.cjs
|
||
|
||
# Save the process list so PM2 restores it on reboot
|
||
sudo -u prs pm2 save
|
||
|
||
# Set PM2 to start on system boot
|
||
sudo pm2 startup systemd -u prs --hp /opt/etc-prs
|
||
# Run the command PM2 outputs
|
||
```
|
||
|
||
Verify the app is running:
|
||
|
||
```bash
|
||
sudo -u prs pm2 status
|
||
# Should show etc-prs as "online"
|
||
|
||
# Check logs
|
||
sudo -u prs pm2 logs etc-prs --lines 50
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Configure Nginx
|
||
|
||
```bash
|
||
sudo nano /etc/nginx/sites-available/etc-prs
|
||
```
|
||
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name yourdomain.com www.yourdomain.com;
|
||
|
||
# Gzip
|
||
gzip on;
|
||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
|
||
|
||
# Security headers
|
||
add_header X-Frame-Options "SAMEORIGIN";
|
||
add_header X-Content-Type-Options "nosniff";
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin";
|
||
|
||
# Proxy to SvelteKit
|
||
location / {
|
||
proxy_pass http://127.0.0.1:3000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection 'upgrade';
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_cache_bypass $http_upgrade;
|
||
|
||
# Increase timeout for large uploads
|
||
proxy_read_timeout 30s;
|
||
client_max_body_size 2M;
|
||
}
|
||
}
|
||
```
|
||
|
||
Enable and test:
|
||
|
||
```bash
|
||
sudo ln -s /etc/nginx/sites-available/etc-prs /etc/nginx/sites-enabled/
|
||
sudo nginx -t # should say "syntax is ok"
|
||
sudo systemctl reload nginx
|
||
```
|
||
|
||
---
|
||
|
||
## 7. SSL with Let's Encrypt
|
||
|
||
```bash
|
||
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
|
||
```
|
||
|
||
Follow the prompts. Certbot will automatically modify your Nginx config to add HTTPS and set up auto-renewal.
|
||
|
||
Verify auto-renewal works:
|
||
|
||
```bash
|
||
sudo certbot renew --dry-run
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Database Backups
|
||
|
||
Set up a nightly cron job to back up the SQLite database:
|
||
|
||
```bash
|
||
sudo mkdir -p /var/backups/etc-prs
|
||
|
||
# Create backup script
|
||
sudo nano /opt/etc-prs/backup.sh
|
||
```
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
DATE=$(date +%Y-%m-%d)
|
||
BACKUP_DIR=/var/backups/etc-prs
|
||
DB_PATH=/var/lib/etc-prs/personalities.db
|
||
|
||
# Create backup using SQLite's online backup
|
||
sqlite3 "$DB_PATH" ".backup $BACKUP_DIR/personalities-$DATE.db"
|
||
|
||
# Keep only the last 30 days of backups
|
||
find "$BACKUP_DIR" -name "personalities-*.db" -mtime +30 -delete
|
||
|
||
echo "Backup completed: personalities-$DATE.db"
|
||
```
|
||
|
||
```bash
|
||
sudo chmod +x /opt/etc-prs/backup.sh
|
||
|
||
# Add to cron (runs at 2am daily)
|
||
sudo crontab -e
|
||
```
|
||
|
||
Add this line:
|
||
|
||
```cron
|
||
0 2 * * * /opt/etc-prs/backup.sh >> /var/log/etc-prs/backup.log 2>&1
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Redeployment
|
||
|
||
When you push updates:
|
||
|
||
```bash
|
||
cd /opt/etc-prs/app
|
||
|
||
# Pull latest code
|
||
sudo -u prs git pull
|
||
|
||
# Install any new dependencies
|
||
sudo -u prs npm install
|
||
|
||
# Rebuild
|
||
sudo -u prs npm run build
|
||
|
||
# Restart the app (zero-downtime reload)
|
||
sudo -u prs pm2 reload etc-prs
|
||
```
|
||
|
||
The SQLite database at `/var/lib/etc-prs/personalities.db` is **never touched** by redeployment since it lives outside the app directory.
|
||
|
||
---
|
||
|
||
## 10. Useful Commands
|
||
|
||
```bash
|
||
# View live logs
|
||
sudo -u prs pm2 logs etc-prs
|
||
|
||
# Restart app
|
||
sudo -u prs pm2 restart etc-prs
|
||
|
||
# Stop app
|
||
sudo -u prs pm2 stop etc-prs
|
||
|
||
# Check Nginx status
|
||
sudo systemctl status nginx
|
||
|
||
# View Nginx error log
|
||
sudo tail -f /var/log/nginx/error.log
|
||
|
||
# Open SQLite database directly
|
||
sqlite3 /var/lib/etc-prs/personalities.db
|
||
|
||
# Example queries
|
||
sqlite3 /var/lib/etc-prs/personalities.db "SELECT id, name, manufacturer, channel_count, created_at FROM personalities ORDER BY created_at DESC LIMIT 20;"
|
||
sqlite3 /var/lib/etc-prs/personalities.db "SELECT COUNT(*) FROM personalities;"
|
||
|
||
# Delete a personality by ID (if you need to moderate)
|
||
sqlite3 /var/lib/etc-prs/personalities.db "DELETE FROM personalities WHERE id = 'the_id_here';"
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Firewall
|
||
|
||
```bash
|
||
# Allow SSH, HTTP, and HTTPS
|
||
sudo ufw allow OpenSSH
|
||
sudo ufw allow 'Nginx Full'
|
||
sudo ufw enable
|
||
sudo ufw status
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Creating Admin Users
|
||
|
||
Admins are managed via a CLI script — there is no self-registration UI.
|
||
|
||
```bash
|
||
# Create your first admin (run from the app directory on the server)
|
||
cd /opt/etc-prs/app
|
||
node scripts/create-admin.js your-username your-password
|
||
|
||
# To update an existing admin's password (same command — it upserts)
|
||
node scripts/create-admin.js your-username new-password
|
||
|
||
# To add another admin
|
||
node scripts/create-admin.js another-user their-password
|
||
```
|
||
|
||
Requirements:
|
||
- Username: 2–32 characters
|
||
- Password: minimum 8 characters (use something strong)
|
||
|
||
The admin panel is available at `https://yourdomain.com/admin`.
|
||
|
||
---
|
||
|
||
## Local Development
|
||
|
||
```bash
|
||
# Install dependencies
|
||
npm install
|
||
|
||
# Start dev server (uses ./dev.db automatically)
|
||
npm run dev
|
||
|
||
# The app runs at http://localhost:5173
|
||
# SQLite database is created at ./dev.db on first run
|
||
```
|