customer-presentation/INSTALL.md
2026-04-22 14:39:16 +02:00

6 KiB

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

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:

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:

sudo apt update
sudo apt install -y certbot python3-certbot-nginx

Create a minimal placeholder Nginx block so certbot has something to attach to:

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

rsync -avz --delete \
  --exclude node_modules --exclude data --exclude .env --exclude .git \
  ./ user@vps:/tmp/fenja-upload/

On the VPS:

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

cd /opt/fenja
sudo -u fenja npm ci --omit=dev

Should finish with added N packages, found 0 vulnerabilities.

5. Create the environment file

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 <noreply@project-bifrost.fenja.ai>"

Lock permissions:

sudo chown root:fenja /etc/fenja/env
sudo chmod 640 /etc/fenja/env

6. Systemd unit

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:

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:

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:

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:

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:

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:

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

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 <script>: extract to a separate .js file and reference with src="..." defer.
  • Two Nginx configs for the same domain: whichever sorts first alphabetically in sites-enabled/ wins. Keep only one.
  • Certbot placeholder config lingered: the old one served /var/www/project-bifrost.fenja.ai statically and shadowed the real one until removed.