Init
This commit is contained in:
+154
@@ -0,0 +1,154 @@
|
||||
# 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):
|
||||
|
||||
```bash
|
||||
# 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):**
|
||||
|
||||
```bash
|
||||
npx tsx server/mint-token.ts "my laptop"
|
||||
```
|
||||
|
||||
**Over HTTP** (only if `ADMIN_KEY` is set in the environment):
|
||||
|
||||
```bash
|
||||
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 mode** → **Load 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 `POST`s 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 build` → `up -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.
|
||||
|
||||
```bash
|
||||
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).
|
||||
Reference in New Issue
Block a user