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.
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 32SEC_OPERATOR_EMAIL- real address (SEC + Yahoo user-agent)LLM_PROVIDER_URL- Ornith LAN URL, e.g.http://10.37.0.165:30081/v1(notlocalhost)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.
- Gitea → Site Administration → Actions → Runners → Create new runner. Copy the token.
- Unraid Docker: add insecure registry
10.37.0.86:3003(Gitea HTTP) so pulls/pushes work. Restart Docker. - 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
- Confirm the runner is Idle on the Gitea runners page.
- Repo → Settings → Actions → Secrets:
REGISTRY_TOKEN= a Gitea token withwrite:packageandwrite:repository. - Optional repo variable
REGISTRY(default10.37.0.86:3003). - 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 composefor you. It publishes images; you (or User Scripts / Compose Manager) roll them. - Do not publish
IFLOW_SESSION_SECRET,IFLOW_CRYPTO_KEY, or Gitea tokens.