Files
etcprs/DEPLOY.md
2026-03-18 03:06:27 -06:00

7.6 KiB
Raw Permalink Blame History

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

# 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

# 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

# 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

# Create the production .env file
sudo -u prs nano /opt/etc-prs/app/.env

Add the following contents:

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:

sudo -u prs nano /opt/etc-prs/app/ecosystem.config.cjs
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:

# 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:

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

sudo nano /etc/nginx/sites-available/etc-prs
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:

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

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:

sudo certbot renew --dry-run

8. Database Backups

Set up a nightly cron job to back up the SQLite database:

sudo mkdir -p /var/backups/etc-prs

# Create backup script
sudo nano /opt/etc-prs/backup.sh
#!/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"
sudo chmod +x /opt/etc-prs/backup.sh

# Add to cron (runs at 2am daily)
sudo crontab -e

Add this line:

0 2 * * * /opt/etc-prs/backup.sh >> /var/log/etc-prs/backup.log 2>&1

9. Redeployment

When you push updates:

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

# 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

# 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.

# 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: 232 characters
  • Password: minimum 8 characters (use something strong)

The admin panel is available at https://yourdomain.com/admin.


Local Development

# 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