# Install End-to-end setup for a fresh Ubuntu VPS running Nginx, Node, SQLite, and an SMTP relay. Done once per server. Each block is ordered; don't skip. Throughout: replace `project-bifrost.fenja.ai` with your actual domain and `user@vps` with your SSH user. ## 0. Prerequisites - Ubuntu 22.04+ VPS with sudo - Nginx installed and running - An A record pointing the subdomain at the VPS's public IP - SMTP relay credentials (STARTTLS on port 587) with SPF/DKIM/DMARC set on the sending domain - Node 20+ installed (`curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash - && sudo apt install -y nodejs`) ## 1. Create the service user and directories ```bash sudo useradd -r -s /usr/sbin/nologin fenja sudo mkdir -p /home/fenja /opt/fenja /etc/fenja sudo chown fenja:fenja /home/fenja /opt/fenja ``` ## 2. DNS + TLS Confirm the subdomain resolves to the VPS: ```bash dig +short project-bifrost.fenja.ai curl -s ifconfig.me ``` Both must match. If the domain is behind Cloudflare, set the DNS record to "DNS only" (grey cloud) for this subdomain — Let's Encrypt needs direct access. Install certbot and issue the cert: ```bash sudo apt update sudo apt install -y certbot python3-certbot-nginx ``` Create a minimal placeholder Nginx block so certbot has something to attach to: ```bash sudo tee /etc/nginx/sites-available/project-bifrost <<'EOF' server { listen 80; server_name project-bifrost.fenja.ai; root /var/www/html; } EOF sudo ln -sf /etc/nginx/sites-available/project-bifrost /etc/nginx/sites-enabled/project-bifrost sudo nginx -t && sudo systemctl reload nginx sudo certbot --nginx -d project-bifrost.fenja.ai ``` Agree to ToS; say yes to the HTTP → HTTPS redirect. ## 3. Upload the code On your laptop, from the project folder. Upload to staging, then move with sudo (your SSH user doesn't own `/opt/fenja`): ```bash rsync -avz --delete \ --exclude node_modules --exclude data --exclude .env --exclude .git \ ./ user@vps:/tmp/fenja-upload/ ``` On the VPS: ```bash sudo rsync -a --delete /tmp/fenja-upload/ /opt/fenja/ sudo chown -R fenja:fenja /opt/fenja sudo -u fenja mkdir -p /opt/fenja/data sudo chmod 750 /opt/fenja/protected /opt/fenja/data rm -rf /tmp/fenja-upload ``` ## 4. Install dependencies ```bash cd /opt/fenja sudo -u fenja npm ci --omit=dev ``` Should finish with `added N packages, found 0 vulnerabilities`. ## 5. Create the environment file ```bash sudo nano /etc/fenja/env ``` Paste (fill in real values): ``` PORT=3000 NODE_ENV=production PUBLIC_ORIGIN=https://project-bifrost.fenja.ai # Generate with: openssl rand -hex 32 (64 hex chars) CODE_PEPPER=... SMTP_HOST=smtp.yourrelay.tld SMTP_PORT=587 SMTP_USER=... SMTP_PASS=... MAIL_FROM="Fenja AI " ``` Lock permissions: ```bash sudo chown root:fenja /etc/fenja/env sudo chmod 640 /etc/fenja/env ``` ## 6. Systemd unit ```bash sudo cp /opt/fenja/deploy/fenja.service /etc/systemd/system/fenja.service sudo nano /etc/systemd/system/fenja.service ``` Change `EnvironmentFile=/opt/fenja/.env` to: ``` EnvironmentFile=/etc/fenja/env ``` Enable and start: ```bash sudo systemctl daemon-reload sudo systemctl enable --now fenja sudo journalctl -u fenja -n 20 ``` Must show `[mail] SMTP relay reachable` and `[bifrost] listening on 127.0.0.1:3000`. ## 7. Nginx rate-limit zone Add to `/etc/nginx/nginx.conf` inside the `http { ... }` block (anywhere near the top): ``` limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=10r/m; ``` ## 8. Replace the Nginx config The placeholder from step 2 needs to be replaced with the real reverse-proxy config. First, check for conflicting older configs that might shadow the new one: ```bash ls /etc/nginx/sites-enabled/ sudo grep -rln project-bifrost /etc/nginx/sites-available/ ``` If you see any file other than `project-bifrost` (e.g. `project-bifrost.fenja.ai` or `default`), remove them: ```bash sudo rm -f /etc/nginx/sites-enabled/default sudo rm -f /etc/nginx/sites-enabled/project-bifrost.fenja.ai sudo rm -f /etc/nginx/sites-available/project-bifrost.fenja.ai ``` Then install the real config: ```bash sudo cp /opt/fenja/deploy/nginx.conf /etc/nginx/sites-available/project-bifrost sudo ln -sf /etc/nginx/sites-available/project-bifrost /etc/nginx/sites-enabled/project-bifrost sudo nginx -t sudo systemctl reload nginx ``` ## 9. Verify From your laptop: ```bash curl -I https://project-bifrost.fenja.ai/ # 200, with X-Frame-Options, CSP, etc. curl -I https://project-bifrost.fenja.ai/timeline.js # 302 → / curl -I https://project-bifrost.fenja.ai/vendor/d3-array.min.js # 302 → / ``` All three must behave as expected. The 200 response must include `X-Frame-Options`, `Content-Security-Policy`, and `Strict-Transport-Security` — their presence confirms Nginx is proxying to Node, not serving static files. ## 10. Ops hygiene Nightly SQLite backup: ```bash sudo tee /etc/cron.d/fenja-backup <<'EOF' 0 3 * * * fenja sqlite3 /opt/fenja/data/fenja.sqlite ".backup /opt/fenja/data/backup-$(date +\%F).sqlite" 0 4 * * * fenja find /opt/fenja/data -name 'backup-*.sqlite' -mtime +14 -delete EOF ``` Uptime monitoring: set up an HTTP(S) check against `https://project-bifrost.fenja.ai/` at a 5-minute interval (UptimeRobot free tier works). ## 11. First invite ```bash sudo -u fenja node /opt/fenja/bin/invite.js add you@yourdomain.com ``` Open `https://project-bifrost.fenja.ai/` in a fresh browser, walk the flow end-to-end. --- ## Gotchas we hit - **Windows Node 24 + better-sqlite3 11.x**: no prebuilt binaries. Use `better-sqlite3 ^12.2.0`. - **PowerShell blocks `npm`**: `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`. - **`useradd -r` with no home**: npm can't write its cache. `mkdir /home/fenja && chown fenja:fenja`. - **CSP blocking inline `