Files
investor-flow/docs/DEPLOY_UNRAID.md
T
Investor Flow Build 7649eaf399
CI / Test (push) Canceled after 0s
CI / Build and push (push) Canceled after 0s
feat: welcome desk auth and operator password reset
First visit lands on /welcome instead of burying signup in Settings.
Add a host CLI to reset passwords without a session, plus Settings
change-password. Refresh as-built design docs and the Unraid operator guide.
2026-08-19 21:19:57 -04:00

8.0 KiB

Deploy Investor Flow on Unraid

Two containers: backend (:3001, SQLite + queue) and frontend (:3000, Next.js, proxies /api to the backend). Git, Actions, and the container registry live on this Unraid box at http://unraid.local:3003/transnet/investor-flow (HTTP registry also at 10.37.0.86:3003).

This is the operator guide. As-built product/tech status lives in FUNCTIONAL_DESIGN.md and TECH_DESIGN.md.

What you get

Path Role
docker-compose.yml Build-from-git stack (laptop or Unraid checkout)
deploy/unraid/compose.pull.yml Pull prebuilt images from the Gitea registry
deploy/unraid/.env.example Secrets + ports + data dir + image names
deploy/unraid/act-runner-compose.yml Gitea act_runner (label ubuntu-latest)
deploy/unraid/user-scripts/sync-and-up.sh Cron fallback: git pull + compose up --build
.github/workflows/ci.yml Tests on every push / PR; build + push images on main
.github/workflows/cd.yml Manual image rebuild (workflow_dispatch) without a new commit

The path a commit takes

write code  →  git push origin main
                    │
                    ▼
              Gitea Actions (act_runner)
                    │
         ┌──────────┴──────────┐
         ▼                     ▼
   Job: test              Job: images
   frontend tests         (only if test passed
   backend tests           and the event is a
   backend typecheck       push to main)
                               │
                               ▼
                    Gitea registry tags
                    …-backend:{sha,latest}
                    …-frontend:{sha,latest}
                               │
                               ▼
                    Unraid pulls (or rebuilds)
                    and recreates the two containers

Publishing images is not the same as rolling the running stack. After CI is green you still pull (mode A) or rebuild (mode B) on Unraid.

Unraid layout (required)

Use a cache (or single-disk) path for SQLite. /mnt/user/... is FUSE and can corrupt WAL files.

/mnt/cache/appdata/investor-flow/
  repo/          git checkout (this repo)
  data/          investor-flow.db + harvest files
  .env           secrets (not in git)

First-time stack

SSH to Unraid (or Unraid terminal):

mkdir -p /mnt/cache/appdata/investor-flow/data
git clone http://unraid.local:3003/transnet/investor-flow.git \
  /mnt/cache/appdata/investor-flow/repo

cp /mnt/cache/appdata/investor-flow/repo/deploy/unraid/.env.example \
  /mnt/cache/appdata/investor-flow/.env
nano /mnt/cache/appdata/investor-flow/.env

Required in .env:

  • IFLOW_SESSION_SECRET - openssl rand -hex 32
  • SEC_OPERATOR_EMAIL - real address (SEC + Yahoo user-agent)
  • LLM_PROVIDER_URL - Ornith LAN URL, e.g. http://10.37.0.165:30081/v1 (not localhost)
  • IFLOW_DATA_DIR=/mnt/cache/appdata/investor-flow/data

If you copy today's Mac DB into data/, also set IFLOW_CRYPTO_KEY to the same value the Mac used. If the Mac never set one, encrypted X/FRED rows used the built-in dev key; re-enter those keys in Admin after a fresh Unraid DB instead of guessing.

Optional: copy the existing database (stop the Mac backend first so WAL is clean):

# from the Mac, after stopping :3001
scp app/server/data/investor-flow.db* \
  root@unraid.local:/mnt/cache/appdata/investor-flow/data/

First start (build on Unraid)

cd /mnt/cache/appdata/investor-flow/repo
docker compose --env-file /mnt/cache/appdata/investor-flow/.env up -d --build

Open http://unraid.local:3000. Backend health: http://unraid.local:3001/health.

Compose Manager plugin: create a stack whose project directory is the repo checkout and whose env file is /mnt/cache/appdata/investor-flow/.env.

Reverse proxy (SWAG / NPM): point the host at frontend :3000 only. The browser talks same-origin /api; the Next container reaches the backend on the compose network (IFLOW_BACKEND_URL=http://backend:3001).

Gitea runner (CI + image publish)

One-time. Without a runner labeled ubuntu-latest, Gitea marks every workflow cancelled.

  1. Gitea → Site Administration → Actions → Runners → Create new runner. Copy the token.
  2. Unraid Docker: add insecure registry 10.37.0.86:3003 (Gitea HTTP) so pulls/pushes work. Restart Docker.
  3. Start the runner:
cd /mnt/cache/appdata/investor-flow/repo
export GITEA_RUNNER_REGISTRATION_TOKEN=...   # from step 1
export GITEA_INSTANCE_URL=http://10.37.0.86:3003
docker compose -f deploy/unraid/act-runner-compose.yml up -d
  1. Confirm the runner is Idle on the Gitea runners page.
  2. Repo → Settings → Actions → Secrets: REGISTRY_TOKEN = a Gitea token with write:package and write:repository.
  3. Optional repo variable REGISTRY (default 10.37.0.86:3003).
  4. Push to main. CI should run tests; on success it should push:
  • 10.37.0.86:3003/transnet/investor-flow-backend:latest (and :${sha})
  • 10.37.0.86:3003/transnet/investor-flow-frontend:latest (and :${sha})

Manual republish of the current main SHA (no new commit): Gitea → Actions → CD → Run workflow.

Two ways to run the stack after CI

A. Pull prebuilt images (preferred)

Point .env at the registry tags:

IFLOW_IMAGE_BACKEND=10.37.0.86:3003/transnet/investor-flow-backend:latest
IFLOW_IMAGE_FRONTEND=10.37.0.86:3003/transnet/investor-flow-frontend:latest

Then:

cd /mnt/cache/appdata/investor-flow/repo
docker compose -f deploy/unraid/compose.pull.yml \
  --env-file /mnt/cache/appdata/investor-flow/.env pull
docker compose -f deploy/unraid/compose.pull.yml \
  --env-file /mnt/cache/appdata/investor-flow/.env up -d

compose.pull.yml reads GITEA_REGISTRY (default unraid.local:3003) and IFLOW_TAG (default latest). Keep those consistent with what CI pushed.

B. Build from the git checkout

Leave image names as investor-flow-backend:local / investor-flow-frontend:local and:

cd /mnt/cache/appdata/investor-flow/repo
git fetch --prune origin main && git merge --ff-only origin/main
docker compose --env-file /mnt/cache/appdata/investor-flow/.env up -d --build

Or schedule deploy/unraid/user-scripts/sync-and-up.sh in Unraid User Scripts (every 10 minutes is enough). It fast-forwards main and rebuilds only when HEAD moved, then waits for both /health endpoints.

Do not mix A and B on the same compose project unless you know which tags .env pins. After you switch to A, Unraid no longer compiles Next/Node on the array.

Day-2 operations

Task How
Logs docker compose logs -f --tail=200 backend frontend
Restart docker compose up -d
Update (pull mode) compose.pull.yml pull then up -d
Update (build mode) git merge --ff-only origin/main then up -d --build
Backup DB copy /mnt/cache/appdata/investor-flow/data/investor-flow.db* (stop backend first, or use SQLite backup)
Reset a password No email reset. On the host: docker compose exec backend node --experimental-strip-types src/cli/reset-password.ts --list then --email you@x --password 'newpass' (add --clear-2fa if TOTP blocks login). Locally: cd app/server && npm run reset-password -- --email you@x --password 'newpass'. Sign in, then change it in Settings.
Ornith keep LLM_PROVIDER_URL on the LAN IP; user LLM endpoint in Settings can stay http://10.37.0.165:30081/v1
Queue / SMTP / X / FRED Admin UI at /admin/* on the frontend

The backend process must stay up for dealer-map snapshots, confluence evaluation, alert producers, and the adapter queue. restart: unless-stopped plus Unraid array autostart covers that.

What this is not

  • Not Vercel. Next standalone + the Node backend both run on Unraid.
  • CI does not SSH into Unraid or call docker compose for you. It publishes images; you (or User Scripts / Compose Manager) roll them.
  • Do not publish IFLOW_SESSION_SECRET, IFLOW_CRYPTO_KEY, or Gitea tokens.