Files

6.9 KiB

FORGE — setup & sync

1. Database

FORGE needs a Postgres database named forge. Either:

  • Bundled (compose): docker compose up -d db starts postgres:16-alpine with database/user/password forge on a named volume. DATABASE_URL in .env.example already points at it.
  • Your own Postgres: create a forge database and set DATABASE_URL=postgresql://USER:PASS@HOST:5432/forge in .env.

The schema self-bootstraps on boot (initDB() in server/db.ts) — no migrations. On the very first boot, if the tickets table is empty, the bundled archives in server/data/ are seeded (idempotent upsert).

Seed data

The board ships pre-populated from server/data/active_archive.json + closed_archive.json, seeded on the first boot of an empty tickets table. Those archives are generated from a Let it Snow chrome.storage.local dump (the sn_tickets, analytics_meta_cache, and jira_status_map keys):

# 1. drop the raw dump at repo root (gitignored — it contains real PII)
#    storage-dump.json
node scripts/dump-to-archives.mjs            # or: npm run dump-to-archives
# → rewrites server/data/{active,closed}_archive.json

# 2. reload the DB from the regenerated archives (destructive: TRUNCATE + reseed)
npm run reseed

A ticket number can appear in both the active list and the closed cache; the loader applies closed first, then active, so the live active board wins over the stale closed snapshot. npm run reseed also works standalone whenever you want to reset the DB to the bundled archives.

The raw storage-dump.json is a dev artifact and is gitignored. The derived server/data/*.json still contain real ticket text and are baked into the Docker image — keep the repo internal, or point the seed at a mounted volume.

2. Run

command notes
Dev npm run dev server :3000 + Vite client :5173 (proxies /api)
Build npm run build tsc + client production build
Serve npm start serves the built client from the Express server
Docker docker compose up -d --build app on host :3099, bundled DB

3. Log in (read-API auth)

The whole read UI (/api/tickets, /api/stats, …) is gated behind a username/password login; /healthz and the token-authed /api/sync are not.

  • Set AUTH_USER / AUTH_PASS and a SESSION_SECRET in .env (see .env.example). SESSION_SECRET is required in production.
  • On first boot the account is seeded into app_users with a bcrypt hash. Sessions are stored in Postgres (user_sessions, auto-created), so they survive restarts. Cookies are httpOnly + secure in production (HTTPS via the reverse proxy).
  • Changing AUTH_PASS later does not update an already-seeded account — delete the app_users row and reboot to re-seed, or update the hash directly.

The database role is least-privilege: DATABASE_URL uses a forge_app role that owns the app's tables but is not a Postgres superuser.

4. Mint a sync token

The Chrome extension authenticates with a bearer token (fg_<id>_<secret>, SHA-256 verifier stored server-side — the raw token is shown once).

CLI (recommended):

npx tsx server/mint-token.ts "my laptop"

Over HTTP (only if ADMIN_KEY is set in the environment):

curl -X POST https://forge.mycloud.dp.ua/api/tokens \
  -H "x-admin-key: $ADMIN_KEY" -H 'content-type: application/json' \
  -d '{"label":"my laptop"}'

Copy the fg_… value — it is not recoverable later.

5. Install the Chrome extension

  1. chrome://extensions → enable Developer modeLoad unpacked → select the extension/ folder.
  2. Open the extension's options (Server & token settings). Enter:
    • Server URL — e.g. https://forge.mycloud.dp.ua (or http://localhost:3000)
    • Sync token — the fg_… value from step 4
    • Click Test connection (hits /healthz), then Save. Chrome will ask to grant access to the server origin — accept it.
  3. Sign in to https://rbassist.service-now.com in the same browser.
  4. Click the extension toolbar icon → Sync now.

What the sync does

The service worker runs a collector in the ServiceNow page context, so it reuses your live session cookie and CSRF token (g_ck). It pages the sc_req_item Table API for active=true tickets in Marketing Web Presence groups, maps them into FORGE's ticket shape, and POSTs them to /api/sync in chunks of 100 with Authorization: Bearer <token>. The server upserts by ticket number, so re-syncing is safe and never duplicates.

6. Deploy to the Synology NAS

FORGE ships to the NAS as a docker-compose stack (bundled Postgres + app), fronted by the Synology reverse proxy (forge.mycloud.dp.ua → the published app port 3089). Two scripts (ported from the Husky template):

  • scripts/push-to-nas.sh (local, npm run deploy) — preflight (pinned SSH key, IdentitiesOnly) → test gate → rsync the tree to the NAS (excludes node_modules/dist/secrets/storage-dump.json) → run deploy.sh over SSH. Falls back to tar-over-ssh if macOS's openrsync is the only rsync.
  • scripts/deploy.sh (on the NAS) — Synology PATH+sudo handling → optional git pull (--pull) → compose buildup -d --remove-orphans → poll the app container until Docker reports healthy. --fresh tears down first but never passes -v, so the forge-db volume (your ticket data) is preserved.

One-time, on the NAS: create ${NAS_PATH}/.env with the production secrets — SESSION_SECRET, AUTH_USER, AUTH_PASS, POSTGRES_PASSWORD (compose sets DATABASE_URL/PORT/NODE_ENV itself). See .env.example. Secrets live only on the NAS and are never synced.

Config is baked into push-to-nas.sh (defaults: NAS_HOST=mycloud.dp.ua, NAS_USER=d.tkachenko, NAS_PORT=2323, NAS_PATH=/volume1/docker/forge, NAS_KEY=~/.ssh/id_ed25519). Override via env vars or an optional .deploy.env (see .deploy.env.example). Key-based SSH to the NAS must already work.

npm run deploy                 # test → sync → build → up → health
npm run deploy -- --fresh      # recreate the stack (db volume kept)
npm run deploy -- --pull       # git pull on the NAS first
SKIP_TESTS=1 npm run deploy     # emergency bypass of the local test gate

The reverse-proxy mapping is configured once in DSM, not by the script.

Security notes

  • GATSBY_-style client exposure does not apply here, but the same rule holds: the API token and g_ck are never logged. Only the SHA-256 of the token secret is stored; token_id is a non-secret locator.
  • POST /api/sync is rate-limited (60 req/min) and rejects any request without a valid, unrevoked, unexpired token (401).
  • server/data/*.json contains real ticket data (names, comments). It ships with the repo for the demo seed — treat the repo as internal, or delete the archives and rely on live syncs only (the app runs fine with an empty seed).