# 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/`, 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 ``` ## 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= ./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`.