customer-presentation/INSTALL.md
Arlind Ukshini 88863183e1 update docs: minimal env, WSL deploy, join tracking, rsync excludes
- align auth docs with the simplified POST /auth/login flow
- drop CODE_PEPPER / SMTP / MAIL_FROM / mail.js / request-code references
- document the bifrost_joins table and bin/joins.js CLI
- OPERATIONS.md: WSL setup, exclude data/.env/node_modules on promote rsync
- INSTALL.md: 3-value /etc/fenja/env, drop SMTP prereq

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:10:08 +02:00

213 lines
6.2 KiB
Markdown

# Install
End-to-end setup for a fresh Ubuntu VPS running Nginx, Node, and SQLite. 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
- 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
# NOTE: the --exclude list on the promote rsync must include `data`,
# `.env`, and `node_modules` so a later redeploy doesn't wipe them.
sudo rsync -a --delete \
--exclude data --exclude .env --exclude node_modules \
/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
`/etc/fenja/env` is intentionally minimal — the server only reads three values (see `.env.example`). There are no secrets: auth is email-only against the invite list and the mail stack was removed.
```bash
sudo nano /etc/fenja/env
```
Paste:
```
PORT=3000
NODE_ENV=production
PUBLIC_ORIGIN=https://project-bifrost.fenja.ai
```
Lock permissions (matches PROJECT.md invariant — root:fenja, mode 640):
```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 `[bifrost] listening on 127.0.0.1:3000`. (There is no `[mail] SMTP relay reachable` line — the mail stack was removed in the auth simplification.)
## 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 `<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.