# fund-lab production deployment (systemd --user) Loopback-only services behind the existing `cloudflared` tunnel. The tunnel ingress already maps `fund.somhairle.bid -> http://127.0.0.1:8098`. ## Topology | Component | Listen | Unit / mechanism | |-----------|--------|------------------| | API (`FundLab.Api.dll`, Release publish) | `127.0.0.1:5080` | `fundlab-api.service` | | Web gateway (nginx: static `dist/` + `/api`,`/health` proxy) | `127.0.0.1:5176` (frontend) and `127.0.0.1:8098` (tunnel origin) | `fundlab-gateway.service` | | PostgreSQL 16 (real data dir, docker named volume) | `127.0.0.1:55435` | docker container `fundlab-pg` (`--restart unless-stopped`) | Public request path: `cloudflared` -> `127.0.0.1:8098` (gateway) -> static `src/FundLab.Web/dist` for `/`, and reverse-proxy to the API for `/api/*` and `/health`. ### Port note (necessary deviation) The tunnel origin is `8098`, but a single origin must serve **both** the frontend (`/`) and the API (`/api`, `/health`). The API therefore listens on `127.0.0.1:5080` and the nginx gateway owns `8098` (and `5176`). `curl http://127.0.0.1:8098/health` still returns the API health payload because the gateway proxies it. ## Files - `fundlab-api.service`, `fundlab-gateway.service` — systemd user units. - `nginx-fundlab.conf` — gateway config (high ports, unprivileged user; temp dirs under `var/fund-lab/`). - Runtime env/secrets: `.env.deploy` at the repo root (git-ignored via `.env.*`; contains `FUND_LAB_DATABASE_URL`, `FUND_LAB_AUTH_TOKEN`, `ASPNETCORE_URLS`, `FUND_LAB_AKSHARE_PYTHON`). Never commit it. - Logs: `var/fund-lab/{api,gateway,nginx-*.log}` (git-ignored via `var/`). ## Build ```sh # API (Release; FS3511 warning suppressed only for publish) dotnet publish src/FundLab.Api/FundLab.Api.fsproj -c Release \ -p:TreatWarningsAsErrors=false -o src/FundLab.Api/publish # Frontend -> src/FundLab.Web/dist cd src/FundLab.Web PATH="$PWD/../../.tools/node-v22.23.2/bin:$PATH" npm run build ``` ## Install / run ```sh systemctl --user link "$PWD/scripts/systemd/fundlab-api.service" systemctl --user link "$PWD/scripts/systemd/fundlab-gateway.service" systemctl --user daemon-reload systemctl --user enable --now fundlab-api.service fundlab-gateway.service ``` `loginctl` linger is already enabled for this user, so the units start on boot. ## Verify ```sh curl -sS http://127.0.0.1:5080/health curl -sS http://127.0.0.1:5176/ # frontend HTML curl -sS http://127.0.0.1:8098/health curl -sS https://fund.somhairle.bid/health curl -sS https://fund.somhairle.bid/ # frontend HTML (not Somhairle's Dream) curl -sS -H "Authorization: Bearer $FUND_LAB_AUTH_TOKEN" \ https://fund.somhairle.bid/api/portfolio/summary ``` Auth is a static Bearer token (`FUND_LAB_AUTH_TOKEN`); there is no `/login` endpoint. `/health` is anonymous; everything under `/api` requires the token.