From 6f656b712109dcfa82a87a4ffaf9720956730e4f Mon Sep 17 00:00:00 2001 From: Arlind Date: Wed, 17 Jun 2026 13:16:57 +0200 Subject: [PATCH] chore(deploy): align deploy artifacts to the target server's conventions Recon of the live box (Ubuntu 24.04 x86_64, nginx 1.24, certbot 2.9) showed established conventions from the existing fenja / bifrost-customer services. Match them so the portal looks like a first-class citizen: - service runs as the existing `fenja` user, journald logging + full hardening block (ProtectKernelModules, LockPersonality), ExecStart on /usr/bin/node (box upgraded globally to Node 22) - code in /opt/bifrost-portal, in-dir .env (EnvironmentFile), data under the shared /opt/fenja/data/bifrost-portal (ReadWritePaths) - nginx: 1.24 `listen ... ssl http2` syntax, certbot options-ssl-nginx + dhparam includes, server_tokens off, sites-available/bifrost-portal (no .conf) symlinked; 12m body size for photo uploads; port 4322 (free) - deploy.sh / backup.sh point at the new paths - DEPLOY.md rewritten as a server-specific runbook incl. the global Node 22 upgrade + retest of the existing apps, and pnpm via corepack Co-Authored-By: Claude Opus 4.8 (1M context) --- .env.production.example | 22 +-- DEPLOY.md | 194 +++++++++++----------- deploy/bifrost-portal.service | 46 +++-- deploy/nginx/bifrost-portal.fenja.ai.conf | 59 ++++--- scripts/backup.sh | 12 +- scripts/deploy.sh | 2 +- 6 files changed, 177 insertions(+), 158 deletions(-) diff --git a/.env.production.example b/.env.production.example index 9c22f30..2b6c0e5 100644 --- a/.env.production.example +++ b/.env.production.example @@ -1,23 +1,25 @@ # Production environment for bifrost-portal.fenja.ai -# Copy to the EnvironmentFile path referenced by the systemd unit -# (default: /etc/bifrost-portal.env) and fill in real values. Keep it -# chmod 600, owned by the service user. NEVER commit the real file. +# Copy to /opt/bifrost-portal/.env on the server and fill in real values. +# Keep it chmod 600, owned by fenja:fenja. NEVER commit the real file. +# (Matches the existing apps' convention of an in-dir .env loaded via +# EnvironmentFile in the systemd unit.) # Long random string used to sign sessions and invite tokens. # Generate with: openssl rand -hex 32 BIFROST_SECRET=change-me-openssl-rand-hex-32 -# Absolute path to the SQLite database. Lives OUTSIDE the deploy dir so +# Absolute path to the SQLite database. Lives under the shared /opt/fenja/data +# tree (the only path the service may write to) and OUTSIDE the deploy dir, so # redeploys never touch it. Honored by src/lib/db.ts and scripts/migrate.js. -BIFROST_DB_PATH=/var/lib/bifrost-portal/bifrost.db +BIFROST_DB_PATH=/opt/fenja/data/bifrost-portal/bifrost.db -# Absolute path to the runtime uploads dir (event photos). Also outside the -# deploy dir. Honored by src/lib/uploads.ts. -BIFROST_UPLOAD_DIR=/var/lib/bifrost-portal/uploads +# Absolute path to the runtime uploads dir (event photos). Honored by +# src/lib/uploads.ts. +BIFROST_UPLOAD_DIR=/opt/fenja/data/bifrost-portal/uploads # Bind address + port for the Node standalone server. Loopback only — nginx -# is the only thing that should reach it. 4321 is the dev port; 4322 keeps -# us clear of it. Verify nothing else on the box uses 4322 (see DEPLOY.md). +# is the only thing that should reach it. 4322 is free on this box (3000/3001 +# are the existing fenja / bifrost-customer apps). HOST=127.0.0.1 PORT=4322 diff --git a/DEPLOY.md b/DEPLOY.md index 6db393b..cf3d5b8 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -1,165 +1,171 @@ # 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, coexisting with the other Fenja site. -nginx terminates TLS and reverse-proxies the `bifrost-portal.fenja.ai` hostname -to the app on `127.0.0.1:4322`. Data is a single SQLite file plus an uploads dir. +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) | +| 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 | `/var/lib/bifrost-portal/` (`bifrost.db`, `uploads/`, `backups/`) | -| Service user | `bifrost` (unprivileged) | +| Persistent data | `/opt/fenja/data/bifrost-portal/` (`bifrost.db`, `uploads/`, `backups/`) | +| Service user | `fenja` (existing) | | systemd unit | `bifrost-portal.service` | -| Env file | `/etc/bifrost-portal.env` (chmod 600) | +| Env file | `/opt/bifrost-portal/.env` (chmod 600) | +| Server | Ubuntu 24.04, x86_64, nginx 1.24.0, certbot 2.9.0 | -Artifacts referenced below all live in this repo: `deploy/bifrost-portal.service`, +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. Pre-flight — confirm the port is free +## 0. Toolchain — upgrade Node to 22, enable pnpm -Another Fenja app already runs on this box. Make sure nothing holds `4322`: +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 -sudo ss -ltnp | grep ':4322' || echo "4322 is free" +# 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 it's taken, pick another loopback port and change it in **both** `/etc/bifrost-portal.env` -(`PORT=`) and `deploy/nginx/bifrost-portal.fenja.ai.conf` (`proxy_pass`). +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 VPS IP, same target as the existing -Fenja site. Confirm before requesting a cert: +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. One-time server provisioning - -Run as a sudo-capable user. Assumes Node 22, pnpm, nginx, sqlite3, certbot, git -are already present (the existing Fenja app implies most are). +## 2. Provision dirs (reuse the `fenja` user) ```bash -# Service user (no login shell, no home spam) -sudo useradd --system --create-home --home-dir /var/lib/bifrost-portal \ - --shell /usr/sbin/nologin bifrost - -# Persistent data dirs -sudo install -d -o bifrost -g bifrost /var/lib/bifrost-portal -sudo install -d -o bifrost -g bifrost /var/lib/bifrost-portal/uploads -sudo install -d -o bifrost -g bifrost /var/lib/bifrost-portal/backups - # Code dir -sudo install -d -o bifrost -g bifrost /opt/bifrost-portal +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 ``` -Clone the repo into the code dir (uses the same `git.fenja.ai` remote this repo -already points at — make sure the `bifrost` user, or the user running deploys, -has a deploy key that can read it): +## 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 bifrost git clone ssh://git@git.fenja.ai:2222/joh/project-bifrost-platform.git \ +sudo -u fenja git clone ssh://git@git.fenja.ai:2222/joh/project-bifrost-platform.git \ /opt/bifrost-portal ``` -## 3. Environment file +## 4. Environment file ```bash -sudo cp /opt/bifrost-portal/.env.production.example /etc/bifrost-portal.env -sudo chown bifrost:bifrost /etc/bifrost-portal.env -sudo chmod 600 /etc/bifrost-portal.env -# Generate the session secret and paste it in as BIFROST_SECRET: -openssl rand -hex 32 -sudo nano /etc/bifrost-portal.env +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 ``` -Make sure `BIFROST_DB_PATH`, `BIFROST_UPLOAD_DIR`, `HOST`, `PORT`, `NODE_ENV` -match the table above. `BIFROST_SECRET` is what signs sessions and invite -tokens — rotating it later logs everyone out and invalidates pending invites. +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. -## 4. First build + database +## 5. First build + database ```bash cd /opt/bifrost-portal -sudo -u bifrost pnpm install --frozen-lockfile # rebuilds better-sqlite3 for this arch -sudo -u bifrost pnpm build +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 bifrost --preserve-env=BIFROST_DB_PATH \ - bash -c 'set -a; source /etc/bifrost-portal.env; set +a; node scripts/migrate.js' +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 bifrost \ - bash -c 'set -a; source /etc/bifrost-portal.env; set +a; pnpm db:seed:production' +# 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 and must be built **on the server** for its CPU -> arch (ARM on a Hetzner CAX11). That's why we `pnpm install` here rather than -> copying `node_modules`. `scripts/deploy.sh` does this on every deploy. +> `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. -## 5. systemd service +## 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 # should be active (running) -curl -fsS http://127.0.0.1:4322/login >/dev/null && echo "app responding" +sudo systemctl status bifrost-portal --no-pager +curl -fsS http://127.0.0.1:4322/login >/dev/null && echo "app responding on 4322" ``` -Allow the `bifrost` user to restart just this unit without a password (used by -`deploy.sh`): +Let `fenja` restart just this unit without a password (used by `deploy.sh`): ```bash -echo 'bifrost ALL=(root) NOPASSWD: /usr/bin/systemctl restart bifrost-portal, /usr/bin/systemctl status bifrost-portal' \ +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 ``` -## 6. nginx + TLS +## 7. nginx + TLS ```bash sudo cp /opt/bifrost-portal/deploy/nginx/bifrost-portal.fenja.ai.conf \ - /etc/nginx/sites-available/ -sudo ln -s /etc/nginx/sites-available/bifrost-portal.fenja.ai.conf \ - /etc/nginx/sites-enabled/ + /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 edits the file to wire up the cert paths): +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 -``` - -Verify, then optionally enable HSTS (uncomment the `Strict-Transport-Security` -line in the nginx conf and reload) once you're happy HTTPS is solid: - -```bash curl -fsSI https://bifrost-portal.fenja.ai/login | head -n1 ``` -## 7. Nightly backups +> 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 bifrost crontab -e +sudo -u fenja crontab -e # add: -15 3 * * * /opt/bifrost-portal/scripts/backup.sh >> /var/log/bifrost-backup.log 2>&1 +15 3 * * * /opt/bifrost-portal/scripts/backup.sh >> /opt/fenja/data/bifrost-portal/backup.log 2>&1 ``` -`backup.sh` writes 30-day-retained `.backup` snapshots to -`/var/lib/bifrost-portal/backups`. Per SPEC §7.3, add a second cron line to sync -that dir to the Hetzner Storage Box (rclone/rsync) for offsite copies. +`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. --- @@ -168,36 +174,38 @@ that dir to the Hetzner Storage Box (rclone/rsync) for offsite copies. After pushing to `master` on `git.fenja.ai`: ```bash -sudo -u bifrost bash -c 'cd /opt/bifrost-portal && ./scripts/deploy.sh' +sudo -u fenja bash -c 'cd /opt/bifrost-portal && ./scripts/deploy.sh' ``` -It pulls, installs (rebuilding native deps), builds, migrates, and restarts. -The DB and uploads are untouched. +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 bifrost git log --oneline -n 10 # find the good commit -sudo -u bifrost bash -c 'BRANCH= ./scripts/deploy.sh' # or: git reset --hard then deploy +sudo -u fenja git log --oneline -n 10 +sudo -u fenja bash -c 'BRANCH= ./scripts/deploy.sh' ``` -Schema migrations are forward-only — a code rollback past a migration may need a -DB restore. To restore a backup (stop the app first): +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 bifrost bash -c 'gunzip -c /var/lib/bifrost-portal/backups/bifrost-YYYYMMDD-HHMMSS.db.gz > /var/lib/bifrost-portal/bifrost.db' +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 not running or wrong port. `systemctl status bifrost-portal`, - `journalctl -u bifrost-portal -n 50`, and re-check `PORT` matches `proxy_pass`. -- **App starts then exits** — usually a missing/invalid `/etc/bifrost-portal.env` - or unwritable `BIFROST_DB_PATH`. Check `journalctl -u bifrost-portal`. -- **`better-sqlite3` errors on boot** — native module built for the wrong arch. - Re-run `pnpm install --frozen-lockfile` on the server and rebuild. -- **Migrations hit the wrong DB** — confirm the env file is sourced; `migrate.js` - honors `BIFROST_DB_PATH` (verified) and falls back to the repo-local dev db otherwise. +- **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`. diff --git a/deploy/bifrost-portal.service b/deploy/bifrost-portal.service index 8459a6e..8cc6f4e 100644 --- a/deploy/bifrost-portal.service +++ b/deploy/bifrost-portal.service @@ -1,37 +1,49 @@ -# systemd unit for the Bifrost portal (bifrost-portal.fenja.ai) -# Install to /etc/systemd/system/bifrost-portal.service -# Then: sudo systemctl daemon-reload && sudo systemctl enable --now bifrost-portal +# ───────────────────────────────────────────────────────────── +# Systemd unit for the Bifrost portal (bifrost-portal.fenja.ai). +# Mirrors the conventions of the existing fenja.service / bifrost-customer.service +# on this box: runs as the `fenja` user, logs to journald, writes only to +# /opt/fenja/data. Astro SSR standalone server (dist/server/entry.mjs). # -# Assumes: -# - code checkout at /opt/bifrost-portal (built in place: dist/server/entry.mjs) -# - environment file at /etc/bifrost-portal.env (chmod 600, see .env.production.example) -# - a dedicated unprivileged service user `bifrost` -# - persistent data under /var/lib/bifrost-portal (db + uploads) +# Install to: /etc/systemd/system/bifrost-portal.service +# +# sudo cp deploy/bifrost-portal.service /etc/systemd/system/bifrost-portal.service +# sudo systemctl daemon-reload +# sudo systemctl enable --now bifrost-portal +# sudo systemctl status bifrost-portal +# sudo journalctl -u bifrost-portal -f +# ───────────────────────────────────────────────────────────── [Unit] -Description=Bifrost portal (Astro SSR, Node standalone) -After=network-online.target -Wants=network-online.target +Description=Bifrost portal (Astro SSR) +After=network.target [Service] Type=simple -User=bifrost -Group=bifrost +User=fenja +Group=fenja WorkingDirectory=/opt/bifrost-portal -EnvironmentFile=/etc/bifrost-portal.env +EnvironmentFile=/opt/bifrost-portal/.env ExecStart=/usr/bin/node /opt/bifrost-portal/dist/server/entry.mjs Restart=on-failure -RestartSec=3 +RestartSec=5 -# Hardening — the service only needs to read its code and write its data dir. +# stdout / stderr → journald +StandardOutput=journal +StandardError=journal +SyslogIdentifier=bifrost-portal + +# ─── Hardening (matches the other Fenja units) ─── NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true -ReadWritePaths=/var/lib/bifrost-portal ProtectKernelTunables=true +ProtectKernelModules=true ProtectControlGroups=true RestrictSUIDSGID=true +LockPersonality=true +# Only the shared data dir is writable (db, uploads, backups live here) +ReadWritePaths=/opt/fenja/data [Install] WantedBy=multi-user.target diff --git a/deploy/nginx/bifrost-portal.fenja.ai.conf b/deploy/nginx/bifrost-portal.fenja.ai.conf index 9d97ef4..6436e91 100644 --- a/deploy/nginx/bifrost-portal.fenja.ai.conf +++ b/deploy/nginx/bifrost-portal.fenja.ai.conf @@ -1,66 +1,63 @@ # nginx site for bifrost-portal.fenja.ai # Reverse-proxies to the Bifrost portal Node server on 127.0.0.1:4322. -# Coexists with other Fenja sites on this box — it only claims this hostname. +# Coexists with the existing project-bifrost.fenja.ai site; only claims this +# hostname. Matches that site's conventions (nginx 1.24, certbot TLS includes). # -# Install: -# sudo cp deploy/nginx/bifrost-portal.fenja.ai.conf /etc/nginx/sites-available/ -# sudo ln -s /etc/nginx/sites-available/bifrost-portal.fenja.ai.conf /etc/nginx/sites-enabled/ +# Install (no .conf extension, to match the existing sites-available layout): +# sudo cp 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 # -# TLS: obtain the cert first (see DEPLOY.md). Either run -# sudo certbot --nginx -d bifrost-portal.fenja.ai -# (certbot edits this file in place), OR issue with --webroot and keep the -# 443 block below as-is. The :80 block must exist before certbot runs. +# TLS: the :80 block must be live first so certbot's ACME challenge succeeds. +# Then: sudo certbot --nginx -d bifrost-portal.fenja.ai (edits this file). -# HTTP — ACME challenge + redirect everything else to HTTPS. server { listen 80; listen [::]:80; server_name bifrost-portal.fenja.ai; + # Let certbot's renewals reach .well-known/acme-challenge on port 80 location /.well-known/acme-challenge/ { root /var/www/html; } + # Everything else goes to HTTPS location / { return 301 https://$host$request_uri; } } -# HTTPS — terminates TLS, proxies to the Node app. server { - listen 443 ssl; - listen [::]:443 ssl; - http2 on; + listen 443 ssl http2; + listen [::]:443 ssl http2; server_name bifrost-portal.fenja.ai; ssl_certificate /etc/letsencrypt/live/bifrost-portal.fenja.ai/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/bifrost-portal.fenja.ai/privkey.pem; - # Modern TLS defaults (Mozilla "intermediate"). If certbot manages this - # file it may append its own ssl_* includes — harmless duplicates aside. - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers off; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; - # Event photo uploads can be a few MB; keep headroom above the app's limit. - client_max_body_size 12m; - - # Security headers. HSTS only after you've confirmed HTTPS works end-to-end. + # ─── Security headers ─── + # HSTS — confirm the cert + redirect loop is solid before relying on it. + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; add_header Referrer-Policy strict-origin-when-cross-origin always; - # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + # Event photo uploads can be a few MB (the existing site uses 32k — this + # site accepts image uploads, so it needs headroom). + client_max_body_size 12m; + + # Don't leak nginx version + server_tokens off; location / { - proxy_pass http://127.0.0.1:4322; + proxy_pass http://127.0.0.1:4322; proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - # Upgrade headers in case any route uses them; harmless otherwise. - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 60s; } } diff --git a/scripts/backup.sh b/scripts/backup.sh index 243392f..2db97a4 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -4,17 +4,17 @@ # (safe while the app is running — consistent, no locking issues with WAL). # Keeps 30 days of compressed snapshots. # -# Install as a cron job (as the `bifrost` user): +# Install as a cron job (as the `fenja` user): # crontab -e -# 15 3 * * * /opt/bifrost-portal/scripts/backup.sh >> /var/log/bifrost-backup.log 2>&1 +# 15 3 * * * /opt/bifrost-portal/scripts/backup.sh >> /opt/fenja/data/bifrost-portal/backup.log 2>&1 # -# Per SPEC §7.3 the offsite target is a Hetzner Storage Box; sync BACKUP_DIR -# there separately (e.g. rclone/rsync in a second cron line). +# For offsite copies, sync BACKUP_DIR to remote storage separately +# (e.g. rclone/rsync in a second cron line). set -euo pipefail -DB_PATH="${BIFROST_DB_PATH:-/var/lib/bifrost-portal/bifrost.db}" -BACKUP_DIR="${BACKUP_DIR:-/var/lib/bifrost-portal/backups}" +DB_PATH="${BIFROST_DB_PATH:-/opt/fenja/data/bifrost-portal/bifrost.db}" +BACKUP_DIR="${BACKUP_DIR:-/opt/fenja/data/bifrost-portal/backups}" RETENTION_DAYS="${RETENTION_DAYS:-30}" mkdir -p "$BACKUP_DIR" diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 3f7dc78..62264b0 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -15,7 +15,7 @@ set -euo pipefail APP_DIR="${APP_DIR:-/opt/bifrost-portal}" SERVICE="${SERVICE:-bifrost-portal}" BRANCH="${BRANCH:-master}" -ENV_FILE="${ENV_FILE:-/etc/bifrost-portal.env}" +ENV_FILE="${ENV_FILE:-/opt/bifrost-portal/.env}" cd "$APP_DIR"