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