- 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>
213 lines
6.2 KiB
Markdown
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.
|