# Deploying the audit endpoint on the IONOS VPS The website itself stays on the Bitpalast webspace (static files via FTP). Only this endpoint runs on the VPS, alongside the existing Git and Nextcloud services. Nothing here modifies their configuration. Assumes Debian or Ubuntu with `sudo`. Adjust package commands for other distributions. --- ## 0. DNS Create an **A record** `api.sascha-bach.de` pointing at the VPS IP address (and an AAAA record if the VPS has IPv6). Wait until it resolves: ```bash dig +short api.sascha-bach.de ``` Nothing below works until this answers. --- ## 1. Survey the machine first ```bash ssh @ # Which web server is in front? The nginx config below assumes nginx. systemctl is-active nginx apache2 2>/dev/null # Is port 3001 free? Gitea commonly sits on 3000, so check before assuming. sudo ss -tlnp | grep -E ':(3000|3001)\b' || echo "3001 frei" # Node 20 or newer present? node --version 2>/dev/null || echo "Node fehlt" ``` If **Apache** is in front instead of nginx, skip step 6 and set up an Apache `ProxyPass` for `api.sascha-bach.de` instead — the rest is unchanged. If **3001 is taken**, pick another free port and change it in three places: `.env` (`PORT`), the nginx `proxy_pass` lines, and nothing else. --- ## 2. Install Node (only if missing) ```bash curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt-get install -y nodejs node --version # must be >= 20 ``` --- ## 3. Dedicated service user The endpoint must not run as root and must not share the Git user's account. ```bash sudo useradd --system --create-home --home-dir /var/lib/auditapi \ --shell /usr/sbin/nologin auditapi ``` --- ## 4. Fetch the code ```bash sudo mkdir -p /opt/audit-endpoint sudo chown auditapi:auditapi /opt/audit-endpoint sudo -u auditapi git clone https://git.sascha-bach.de/saschabach/portfolio-page.git \ /opt/audit-endpoint ``` If the repository is private, use a deploy token or an SSH key belonging to `auditapi`. ```bash cd /opt/audit-endpoint/backend sudo -u auditapi npm install --omit=dev ``` --- ## 5. Configuration ```bash sudo -u auditapi cp /opt/audit-endpoint/backend/.env.example \ /opt/audit-endpoint/backend/.env sudo -u auditapi nano /opt/audit-endpoint/backend/.env ``` Fill in: | Variable | Value | | --- | --- | | `PORT` | `3001` (or the free port from step 1) | | `ALLOWED_ORIGINS` | `https://sascha-bach.de,https://www.sascha-bach.de` — the **website** origin, not the API origin | | `AUDIT_RECIPIENT` | where requests are delivered | | `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASS` | mailbox credentials | | `SMTP_FROM` | an address **on your own domain**, so SPF and DMARC pass | Lock the file down — it holds the mailbox password: ```bash sudo chmod 600 /opt/audit-endpoint/backend/.env sudo chown auditapi:auditapi /opt/audit-endpoint/backend/.env ``` Smoke-test before wiring up systemd: ```bash cd /opt/audit-endpoint/backend && sudo -u auditapi node server.js # expect: Audit endpoint listening on 127.0.0.1:3001 # Ctrl+C ``` --- ## 6. systemd service ```bash sudo cp /opt/audit-endpoint/backend/deploy/audit-endpoint.service \ /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now audit-endpoint systemctl status audit-endpoint --no-pager curl -s http://127.0.0.1:3001/health # → {"status":"ok"} ``` --- ## 7. nginx vhost Adding a file and reloading never touches the running Git or Nextcloud vhosts. ```bash sudo cp /opt/audit-endpoint/backend/deploy/nginx-api.sascha-bach.de.conf \ /etc/nginx/sites-available/api.sascha-bach.de sudo ln -s /etc/nginx/sites-available/api.sascha-bach.de \ /etc/nginx/sites-enabled/ # Validates the WHOLE config. If this fails, nothing has changed yet. sudo nginx -t # reload, not restart - existing connections to git/nextcloud survive sudo systemctl reload nginx ``` --- ## 8. TLS certificate ```bash sudo apt-get install -y certbot python3-certbot-nginx # if missing sudo certbot --nginx -d api.sascha-bach.de ``` certbot only edits the `api.sascha-bach.de` block. Verify renewal works: ```bash sudo certbot renew --dry-run ``` --- ## 9. Verify from outside ```bash curl -sI https://api.sascha-bach.de/health # → HTTP/2 200 curl -s -X POST https://api.sascha-bach.de/api/audit-request \ -H 'Content-Type: application/json' \ -H 'Origin: https://sascha-bach.de' \ -d '{"name":"Test","company":"Test GmbH","email":"du@example.de", "url":"https://example.de","role":"client","motivation":"Test", "businessConfirmation":"true","gdprConsent":"true","website":""}' # → {"success":true,...} and an email arrives ``` Then confirm the protections actually bite: ```bash # Foreign origin → rejected curl -s -X POST https://api.sascha-bach.de/api/audit-request \ -H 'Content-Type: application/json' -H 'Origin: https://evil.example' \ -d '{}' -o /dev/null -w '%{http_code}\n' # Fourth request within 15 minutes → 429 ``` --- ## 10. Point the website at it Back on the workstation, in the project root: ```bash echo 'VITE_AUDIT_ENDPOINT=https://api.sascha-bach.de/api/audit-request' > .env npm run deploy ``` Vite bakes the value in at build time, so this must happen **before** the upload. `.env` is gitignored. The hostname must match in three places or the browser blocks the request: - `public/.htaccess` → `connect-src 'self' https://api.sascha-bach.de` - `VITE_AUDIT_ENDPOINT` - the nginx `server_name` and its certificate --- ## Updating later ```bash cd /opt/audit-endpoint sudo -u auditapi git pull cd backend && sudo -u auditapi npm install --omit=dev sudo systemctl restart audit-endpoint ``` ## Logs ```bash journalctl -u audit-endpoint -f ``` Request contents are never logged, only that a request was forwarded — see the note in `services/emailService.js`.