diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-22 07:30:09 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-22 07:30:09 +0800 |
| commit | 3d95b9c736a4cf59cf26bd9e5c7c3162ee8c0fd7 (patch) | |
| tree | 843b04ddb0c3307f05cd1b5d26d4d70aec2ff0dc /scripts/systemd | |
| parent | 6ba727422137b35c00b9bb983e574d23a4d15e24 (diff) | |
| download | fund-lab-3d95b9c736a4cf59cf26bd9e5c7c3162ee8c0fd7.tar.gz | |
Add fund-lab production systemd --user units and gateway config (3d-28)
Diffstat (limited to 'scripts/systemd')
| -rw-r--r-- | scripts/systemd/README.md | 72 | ||||
| -rw-r--r-- | scripts/systemd/fundlab-api.service | 18 | ||||
| -rw-r--r-- | scripts/systemd/fundlab-gateway.service | 18 | ||||
| -rw-r--r-- | scripts/systemd/nginx-fundlab.conf | 70 |
4 files changed, 178 insertions, 0 deletions
diff --git a/scripts/systemd/README.md b/scripts/systemd/README.md new file mode 100644 index 0000000..7e3fdaa --- /dev/null +++ b/scripts/systemd/README.md @@ -0,0 +1,72 @@ +# 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. diff --git a/scripts/systemd/fundlab-api.service b/scripts/systemd/fundlab-api.service new file mode 100644 index 0000000..ecdbd9b --- /dev/null +++ b/scripts/systemd/fundlab-api.service @@ -0,0 +1,18 @@ +[Unit] +Description=fund-lab API (loopback 5080, real Postgres + real AKShare interpreter) +Documentation=file:/home/somhairle/projects/fund-lab/scripts/systemd/README.md +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +WorkingDirectory=/home/somhairle/projects/fund-lab +EnvironmentFile=/home/somhairle/projects/fund-lab/.env.deploy +ExecStart=/usr/bin/dotnet /home/somhairle/projects/fund-lab/src/FundLab.Api/publish/FundLab.Api.dll +Restart=on-failure +RestartSec=3 +StandardOutput=append:/home/somhairle/projects/fund-lab/var/fund-lab/api.log +StandardError=append:/home/somhairle/projects/fund-lab/var/fund-lab/api.log + +[Install] +WantedBy=default.target diff --git a/scripts/systemd/fundlab-gateway.service b/scripts/systemd/fundlab-gateway.service new file mode 100644 index 0000000..24500ef --- /dev/null +++ b/scripts/systemd/fundlab-gateway.service @@ -0,0 +1,18 @@ +[Unit] +Description=fund-lab web gateway (loopback 5176 frontend + 8098 tunnel origin -> static dist + /api,/health proxy) +Documentation=file:/home/somhairle/projects/fund-lab/scripts/systemd/README.md +After=network-online.target fundlab-api.service +Wants=network-online.target + +[Service] +Type=simple +WorkingDirectory=/home/somhairle/projects/fund-lab +ExecStartPre=/bin/mkdir -p /home/somhairle/projects/fund-lab/var/fund-lab/nginx-tmp +ExecStart=/usr/sbin/nginx -c /home/somhairle/projects/fund-lab/scripts/systemd/nginx-fundlab.conf -g 'daemon off;' +Restart=on-failure +RestartSec=3 +StandardOutput=append:/home/somhairle/projects/fund-lab/var/fund-lab/gateway.log +StandardError=append:/home/somhairle/projects/fund-lab/var/fund-lab/gateway.log + +[Install] +WantedBy=default.target diff --git a/scripts/systemd/nginx-fundlab.conf b/scripts/systemd/nginx-fundlab.conf new file mode 100644 index 0000000..e6fdcd4 --- /dev/null +++ b/scripts/systemd/nginx-fundlab.conf @@ -0,0 +1,70 @@ +# fund-lab production gateway (run as the unprivileged user; high ports only). +# Serves the built frontend from src/FundLab.Web/dist on both the frontend port +# (5176) and the cloudflared tunnel origin port (8098), and reverse-proxies +# /api and /health to the fund-lab API on 127.0.0.1:5080. + +worker_processes 1; +pid /home/somhairle/projects/fund-lab/var/fund-lab/nginx.pid; +error_log /home/somhairle/projects/fund-lab/var/fund-lab/nginx-error.log warn; + +events { + worker_connections 256; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + access_log /home/somhairle/projects/fund-lab/var/fund-lab/nginx-access.log; + + client_body_temp_path /home/somhairle/projects/fund-lab/var/fund-lab/nginx-tmp/client; + proxy_temp_path /home/somhairle/projects/fund-lab/var/fund-lab/nginx-tmp/proxy; + fastcgi_temp_path /home/somhairle/projects/fund-lab/var/fund-lab/nginx-tmp/fastcgi; + uwsgi_temp_path /home/somhairle/projects/fund-lab/var/fund-lab/nginx-tmp/uwsgi; + scgi_temp_path /home/somhairle/projects/fund-lab/var/fund-lab/nginx-tmp/scgi; + + upstream fundlab_api { + server 127.0.0.1:5080; + } + + server { + listen 127.0.0.1:5176; + server_name _; + root /home/somhairle/projects/fund-lab/src/FundLab.Web/dist; + index index.html; + + location = /health { + proxy_pass http://fundlab_api; + proxy_set_header Host $host; + } + + location /api/ { + proxy_pass http://fundlab_api; + proxy_set_header Host $host; + } + + location / { + try_files $uri /index.html; + } + } + + server { + listen 127.0.0.1:8098; + server_name _; + root /home/somhairle/projects/fund-lab/src/FundLab.Web/dist; + index index.html; + + location = /health { + proxy_pass http://fundlab_api; + proxy_set_header Host $host; + } + + location /api/ { + proxy_pass http://fundlab_api; + proxy_set_header Host $host; + } + + location / { + try_files $uri /index.html; + } + } +} |
