Make explicit that /opt/bifrost-portal is its own repo/remote, separate from the existing /opt/fenja and /opt/bifrost-customer apps on the box. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
217 lines
8 KiB
Markdown
217 lines
8 KiB
Markdown
# Deploying the Bifrost portal — `bifrost-portal.fenja.ai`
|
|
|
|
This app runs as a **Node standalone SSR server** (Astro `@astrojs/node`) behind
|
|
the **existing nginx** on the Fenja VPS, alongside the `fenja` and
|
|
`bifrost-customer` apps. It follows the conventions already established on that
|
|
box (verified by inspection): `fenja` service user, systemd + journald, code in
|
|
`/opt/<app>`, data under `/opt/fenja/data`, in-dir `.env`, certbot TLS.
|
|
|
|
You run every step here. Nothing in this repo touches the live server on its own.
|
|
|
|
| Thing | Value |
|
|
|---|---|
|
|
| Hostname | `bifrost-portal.fenja.ai` |
|
|
| App bind | `127.0.0.1:4322` (loopback only; 3000/3001 are the existing apps) |
|
|
| Code | `/opt/bifrost-portal` (git checkout, built in place) |
|
|
| Persistent data | `/opt/fenja/data/bifrost-portal/` (`bifrost.db`, `uploads/`, `backups/`) |
|
|
| Service user | `fenja` (existing) |
|
|
| systemd unit | `bifrost-portal.service` |
|
|
| Env file | `/opt/bifrost-portal/.env` (chmod 600) |
|
|
| Server | Ubuntu 24.04, x86_64, nginx 1.24.0, certbot 2.9.0 |
|
|
|
|
Repo artifacts referenced below: `deploy/bifrost-portal.service`,
|
|
`deploy/nginx/bifrost-portal.fenja.ai.conf`, `.env.production.example`,
|
|
`scripts/deploy.sh`, `scripts/backup.sh`.
|
|
|
|
---
|
|
|
|
## 0. Toolchain — upgrade Node to 22, enable pnpm
|
|
|
|
The box currently has Node **v20** at `/usr/bin/node`, shared by the running
|
|
`fenja` and `bifrost-customer` services. We're upgrading it **globally** to 22.
|
|
|
|
> ⚠️ This moves those two live apps onto Node 22 as well. Restart and smoke-test
|
|
> them right after (last line of this step). Have a moment of downtime tolerance.
|
|
|
|
```bash
|
|
# Upgrade the NodeSource apt repo to the 22.x channel and install:
|
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
|
sudo apt-get install -y nodejs
|
|
node -v # expect v22.x
|
|
|
|
# pnpm via corepack (bundled with Node 22) — no separate install:
|
|
sudo corepack enable
|
|
corepack enable pnpm
|
|
pnpm -v # expect a pnpm 9.x/10.x shim
|
|
|
|
# Move the existing apps onto Node 22 and confirm they still work:
|
|
sudo systemctl restart fenja bifrost-customer
|
|
sudo systemctl status fenja bifrost-customer --no-pager
|
|
curl -fsS https://project-bifrost.fenja.ai/ >/dev/null && echo "existing site OK"
|
|
```
|
|
|
|
If either existing app misbehaves on 22, that's the risk we accepted — roll the
|
|
NodeSource repo back to `setup_20.x` and reinstall to recover them.
|
|
|
|
## 1. DNS
|
|
|
|
Add an A/AAAA record `bifrost-portal` → the same VPS IP as `project-bifrost`.
|
|
Confirm before requesting a cert:
|
|
|
|
```bash
|
|
dig +short bifrost-portal.fenja.ai
|
|
```
|
|
|
|
## 2. Provision dirs (reuse the `fenja` user)
|
|
|
|
```bash
|
|
# Code dir
|
|
sudo install -d -o fenja -g fenja /opt/bifrost-portal
|
|
|
|
# Persistent data under the shared, service-writable tree
|
|
sudo install -d -o fenja -g fenja /opt/fenja/data/bifrost-portal
|
|
sudo install -d -o fenja -g fenja /opt/fenja/data/bifrost-portal/uploads
|
|
sudo install -d -o fenja -g fenja /opt/fenja/data/bifrost-portal/backups
|
|
```
|
|
|
|
## 3. Clone the repo
|
|
|
|
The `fenja` user needs read access to `git.fenja.ai` (a deploy key on its
|
|
account, or your forwarded agent for the first clone):
|
|
|
|
```bash
|
|
sudo -u fenja git clone ssh://git@git.fenja.ai:2222/joh/project-bifrost-platform.git \
|
|
/opt/bifrost-portal
|
|
```
|
|
|
|
> **Keep the git checkouts separate.** This portal (`project-bifrost-platform`)
|
|
> and the existing apps (`/opt/fenja`, `/opt/bifrost-customer`) are independent
|
|
> git projects with their own remotes. `/opt/bifrost-portal` is a self-contained
|
|
> checkout — never nest it inside another app's tree, never point its remote at
|
|
> theirs, and only ever run `scripts/deploy.sh` from inside `/opt/bifrost-portal`.
|
|
|
|
## 4. Environment file
|
|
|
|
```bash
|
|
sudo -u fenja cp /opt/bifrost-portal/.env.production.example /opt/bifrost-portal/.env
|
|
sudo chmod 600 /opt/bifrost-portal/.env
|
|
openssl rand -hex 32 # paste as BIFROST_SECRET
|
|
sudo -u fenja nano /opt/bifrost-portal/.env
|
|
```
|
|
|
|
Confirm `BIFROST_DB_PATH`, `BIFROST_UPLOAD_DIR`, `HOST`, `PORT`, `NODE_ENV`
|
|
match the table above. `BIFROST_SECRET` signs sessions and invite tokens —
|
|
rotating it later logs everyone out and invalidates pending invites.
|
|
|
|
## 5. First build + database
|
|
|
|
```bash
|
|
cd /opt/bifrost-portal
|
|
sudo -u fenja pnpm install --frozen-lockfile # builds native better-sqlite3 for this box
|
|
sudo -u fenja pnpm build
|
|
|
|
# Create + migrate the production DB at BIFROST_DB_PATH:
|
|
sudo -u fenja bash -c 'set -a; source /opt/bifrost-portal/.env; set +a; node scripts/migrate.js'
|
|
|
|
# Seed the real pilot data (ONE TIME only — skip on later deploys):
|
|
sudo -u fenja bash -c 'set -a; source /opt/bifrost-portal/.env; set +a; pnpm db:seed:production'
|
|
```
|
|
|
|
> `better-sqlite3` is native; `pnpm install` builds/fetches it for this machine.
|
|
> `scripts/deploy.sh` re-runs install on every deploy, so an arch/Node change is
|
|
> always reconciled.
|
|
|
|
## 6. systemd service
|
|
|
|
```bash
|
|
sudo cp /opt/bifrost-portal/deploy/bifrost-portal.service /etc/systemd/system/
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now bifrost-portal
|
|
sudo systemctl status bifrost-portal --no-pager
|
|
curl -fsS http://127.0.0.1:4322/login >/dev/null && echo "app responding on 4322"
|
|
```
|
|
|
|
Let `fenja` restart just this unit without a password (used by `deploy.sh`):
|
|
|
|
```bash
|
|
echo 'fenja ALL=(root) NOPASSWD: /usr/bin/systemctl restart bifrost-portal, /usr/bin/systemctl status bifrost-portal' \
|
|
| sudo tee /etc/sudoers.d/bifrost-portal
|
|
sudo chmod 440 /etc/sudoers.d/bifrost-portal
|
|
```
|
|
|
|
## 7. nginx + TLS
|
|
|
|
```bash
|
|
sudo cp /opt/bifrost-portal/deploy/nginx/bifrost-portal.fenja.ai.conf \
|
|
/etc/nginx/sites-available/bifrost-portal
|
|
sudo ln -s /etc/nginx/sites-available/bifrost-portal \
|
|
/etc/nginx/sites-enabled/bifrost-portal
|
|
sudo nginx -t && sudo systemctl reload nginx # :80 block live for ACME
|
|
```
|
|
|
|
Issue the certificate (certbot wires the cert paths into the file):
|
|
|
|
```bash
|
|
sudo certbot --nginx -d bifrost-portal.fenja.ai
|
|
sudo nginx -t && sudo systemctl reload nginx
|
|
curl -fsSI https://bifrost-portal.fenja.ai/login | head -n1
|
|
```
|
|
|
|
> The site config already includes the `Strict-Transport-Security` (HSTS)
|
|
> header to match the existing site. If you want to verify HTTPS end-to-end
|
|
> first, comment that line, reload, confirm, then re-enable.
|
|
|
|
## 8. Nightly backups
|
|
|
|
```bash
|
|
sudo -u fenja crontab -e
|
|
# add:
|
|
15 3 * * * /opt/bifrost-portal/scripts/backup.sh >> /opt/fenja/data/bifrost-portal/backup.log 2>&1
|
|
```
|
|
|
|
`backup.sh` writes 30-day-retained online `.backup` snapshots to
|
|
`/opt/fenja/data/bifrost-portal/backups`. Add a second cron line to sync that
|
|
dir offsite (rclone/rsync) if you want off-box copies.
|
|
|
|
---
|
|
|
|
## Ongoing deploys
|
|
|
|
After pushing to `master` on `git.fenja.ai`:
|
|
|
|
```bash
|
|
sudo -u fenja bash -c 'cd /opt/bifrost-portal && ./scripts/deploy.sh'
|
|
```
|
|
|
|
Pulls, installs (rebuilding native deps), builds, migrates, restarts. The DB and
|
|
uploads in `/opt/fenja/data/bifrost-portal` are untouched.
|
|
|
|
## Rollback
|
|
|
|
```bash
|
|
cd /opt/bifrost-portal
|
|
sudo -u fenja git log --oneline -n 10
|
|
sudo -u fenja bash -c 'BRANCH=<good-sha> ./scripts/deploy.sh'
|
|
```
|
|
|
|
Migrations are forward-only — a rollback past a migration may need a DB restore
|
|
(stop the app first):
|
|
|
|
```bash
|
|
sudo systemctl stop bifrost-portal
|
|
sudo -u fenja bash -c 'gunzip -c /opt/fenja/data/bifrost-portal/backups/bifrost-YYYYMMDD-HHMMSS.db.gz > /opt/fenja/data/bifrost-portal/bifrost.db'
|
|
sudo systemctl start bifrost-portal
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
- **502 from nginx** — app down or wrong port. `systemctl status bifrost-portal`,
|
|
`journalctl -u bifrost-portal -n 50`, check `PORT` matches the `proxy_pass`.
|
|
- **App starts then exits** — bad/missing `/opt/bifrost-portal/.env` or an
|
|
unwritable `BIFROST_DB_PATH`. `journalctl -u bifrost-portal`.
|
|
- **`better-sqlite3` errors on boot** — native module built for the wrong Node
|
|
ABI. Re-run `pnpm install --frozen-lockfile` and restart.
|
|
- **Migrations hit the wrong DB** — make sure the `.env` is sourced; `migrate.js`
|
|
honors `BIFROST_DB_PATH` (verified) and otherwise falls back to a repo-local db.
|
|
- **Existing apps broke after the Node 22 upgrade** — roll NodeSource back to
|
|
`setup_20.x`, `apt-get install -y nodejs`, restart `fenja` + `bifrost-customer`.
|