Admins can filter and review curator_audit_log (date/time, actor, action, resource, details) via /api/audit against the environment DB. Co-authored-by: Cursor <cursoragent@cursor.com>
367 lines
17 KiB
Markdown
367 lines
17 KiB
Markdown
# Development and production environments
|
||
|
||
Gallery uses **one PostgreSQL server** on TrueNAS (`192.168.10.122`) with **two databases**. The **dev PC** (`192.168.10.70`) runs `npm run dev:web` on port **5173**, published at **`https://devgallery.mysuperlab.netcraze.pro`** via Keenetic.
|
||
|
||
| Environment | Database | Public URL | App host |
|
||
|-------------|----------|------------|----------|
|
||
| **Development** | `gallery_dev` | `https://devgallery.mysuperlab.netcraze.pro` | Dev PC `192.168.10.70:5173` |
|
||
| **Production** | `gallery_prod` | `https://gallery.mysuperlab.netcraze.pro` | TrueNAS container `192.168.10.122:5173` |
|
||
|
||
Version tracking:
|
||
- Runtime version endpoint: `/api/version` (reports `app_env`, `app_version`, `image_tag`, `git_sha`, `built_at`, `db_name`)
|
||
- `npm run prod:docker:publish` auto-creates a release tag like `20260709-155412-f72ddcc` (build time + git commit), pushes it and `latest`
|
||
- Version metadata is baked into the Docker image; TrueNAS only needs `gallery-web:latest` with `pull_policy: always`
|
||
|
||
---
|
||
|
||
## Development-first workflow (default)
|
||
|
||
**All day-to-day work happens on dev.** Code edits, database changes, catalog updates, image fixes, migrations, and testing use **`gallery_dev`**, the repo’s **`data/images/`**, and **https://devgallery.mysuperlab.netcraze.pro** (or `http://localhost:5173` on the dev PC).
|
||
|
||
Production is **not** updated on every change. Promote to prod on a **regular cadence** (typically about once a week) or when you explicitly decide to release:
|
||
|
||
| Phase | Where | What |
|
||
|-------|-------|------|
|
||
| **Daily** | Dev PC + `gallery_dev` | Feature work, fixes, seed/migrate, curator edits, image pipeline |
|
||
| **Release** | Dev PC → TrueNAS | Backup dev → restore prod DB (if data changed) → sync images → `docker:publish` → restart **gallery-web** |
|
||
|
||
Unless a task explicitly says **prod** or **production**, assume the target is **dev** — including when asking an assistant to change, fix, or modify code, data, or the database.
|
||
|
||
**Never** point the dev PC `.env` at `gallery_prod`. Prod-only files (`infra/docker/.env.prod`, `db:restore:prod`, `images:sync-to-prod`) are for scheduled releases, not routine development.
|
||
|
||
---
|
||
|
||
## Where to run what (quick reference)
|
||
|
||
| Step | Machine | Interface | Privilege | What |
|
||
|------|---------|-----------|-----------|------|
|
||
| Stop dev servers | Dev PC `192.168.10.70` | PowerShell (normal) | your user | Close `dev:web` / `dev:server` / `dev:client` terminals |
|
||
| **DB split (one-time)** | Dev PC | **pgAdmin** → Query Tool on `postgres` | **postgres superuser** | [`db/split-dev-prod-pgadmin.sql`](../db/split-dev-prod-pgadmin.sql) step by step |
|
||
| Dev migrate / dev:web | Dev PC | PowerShell (normal) | your user | `npm run dev:migrate`, `npm run dev:web` |
|
||
| Image dir on TrueNAS | TrueNAS `192.168.10.122` | **Shell** (SSH or UI → System Settings → Shell) | root / sudo | `bash infra/docker/truenas-setup.sh` |
|
||
| Copy images to prod | Dev PC | PowerShell (normal) | SMB `\\192.168.10.122\Gallery` | `net use` then `npm run devtoprod:images` |
|
||
| Build + push Docker image | Dev PC | **PowerShell as Administrator** | admin (for LAN hosts entry) | `npm run prod:docker:publish` |
|
||
| Install prod app | TrueNAS | **Web UI** → Apps → Custom App | admin | Paste `infra/docker/compose.truenas.yaml` |
|
||
| Verify prod | Dev PC or TrueNAS | PowerShell / browser | any | `curl.exe -sk https://gallery.mysuperlab.netcraze.pro/api/bounds` |
|
||
|
||
You do **not** need `psql` on TrueNAS. Database work is done from **pgAdmin on the dev PC** connected to `192.168.10.122:5432`.
|
||
|
||
---
|
||
|
||
## One-time setup (full walkthrough)
|
||
|
||
### Step A — Stop Gallery on the dev PC
|
||
|
||
**Where:** Dev PC — PowerShell or open terminal tabs (no admin needed)
|
||
|
||
Close anything using ports **5173**, **3451**, or **3520**:
|
||
|
||
- Stop `npm run dev:web`, `npm run dev:server`, `npm run dev:client`
|
||
- If prod container already runs on TrueNAS: **TrueNAS Web UI** → Apps → **gallery-web** → **Stop**
|
||
|
||
### Step B — Split the database (pgAdmin)
|
||
|
||
**Where:** Dev PC — **pgAdmin** (not TrueNAS shell)
|
||
|
||
1. Open **pgAdmin**.
|
||
2. Add/connect to server:
|
||
- **Host:** `192.168.10.122`
|
||
- **Port:** `5432`
|
||
- **Maintenance database:** `postgres`
|
||
- **Username:** `postgres` (superuser — **not** `gallery`)
|
||
- **Password:** your postgres password
|
||
3. Tree: **Servers** → your server → **Databases** → click **`postgres`**
|
||
4. **Tools** → **Query Tool** (or right-click `postgres` → Query Tool)
|
||
5. **File** → **Open** → `Gallery\db\split-dev-prod-pgadmin.sql`
|
||
6. Run **each STEP separately** (highlight from `-- STEP N` through that section, press **F5** / Execute):
|
||
|
||
| Step | Action |
|
||
|------|--------|
|
||
| **STEP 0** | Pre-flight — should show database `Gallery` |
|
||
| **STEP 1** | Terminate connections |
|
||
| **STEP 2** | `ALTER DATABASE "Gallery" RENAME TO gallery_prod` (use lowercase variant only if STEP 0 showed `gallery`) |
|
||
| **STEP 3** | `CREATE DATABASE gallery_dev WITH TEMPLATE gallery_prod` (wait ~1–2 min) |
|
||
| **STEP 4** | `GRANT` to user `gallery` |
|
||
| **STEP 5** | Verify — should show `gallery_prod` and `gallery_dev` |
|
||
| **STEP 6** | **New Query Tool on `gallery_dev`** (not postgres!) — `SELECT current_database()` then `SELECT count(*) FROM paintings` |
|
||
|
||
If STEP 2 fails with “database already exists”, STEP 0 likely already shows `gallery_prod` — skip STEP 2 and continue from STEP 3 (skip STEP 3 too if `gallery_dev` exists).
|
||
|
||
**Alternative (dev PC with psql installed):**
|
||
|
||
```powershell
|
||
cd T:\Repo\Gallery
|
||
psql -h 192.168.10.122 -U postgres -d postgres -f db/split-dev-prod.sql
|
||
```
|
||
|
||
**Alternative (Node, postgres password in env):**
|
||
|
||
```powershell
|
||
cd T:\Repo\Gallery
|
||
$env:PGHOST="192.168.10.122"; $env:PGUSER="postgres"; $env:PGPASSWORD="YOUR_POSTGRES_PASSWORD"
|
||
npm run infra:db:split-dev-prod
|
||
```
|
||
|
||
### Step C — Configure dev PC and test
|
||
|
||
**Where:** Dev PC — PowerShell (normal), repo root
|
||
|
||
1. Edit `.env` (copy from `.env.example` if needed):
|
||
|
||
```env
|
||
DB_NAME=gallery_dev
|
||
PORT=3451
|
||
PUBLIC_URL=https://devgallery.mysuperlab.netcraze.pro
|
||
SESSION_SECRET=your-long-random-secret
|
||
CURATOR_USERNAME=curator
|
||
CURATOR_PASSWORD=your-secure-password
|
||
```
|
||
|
||
Omit `SESSION_COOKIE_SECURE` so cookies follow the request scheme (`TRUST_PROXY` + HTTPS → Secure). Set `true`/`false` to force. `npm run dev:migrate` creates auth tables/roles and bootstraps the first **admin** when `users` is empty. Reset that account later with `npm run dev:reset-curator`. Create additional staff via the in-app **Users** page. Admins browse curator actions on **Activity** (`/api/audit`), which always reads the DB named by `DB_NAME` for that environment (`gallery_dev` here; `gallery_prod` on prod). Audit history is not synced by harmonize/devtoprod.
|
||
|
||
2. Run:
|
||
|
||
```powershell
|
||
cd T:\Repo\Gallery
|
||
npm run dev:migrate
|
||
npm run dev:web
|
||
```
|
||
|
||
3. Open **https://devgallery.mysuperlab.netcraze.pro** (or http://localhost:5173 on the dev PC)
|
||
|
||
### Step D — Prepare prod image folder on TrueNAS
|
||
|
||
**Where:** TrueNAS — **Shell** (SSH to `192.168.10.122`, or TrueNAS UI → **System Settings** → **Shell**)
|
||
|
||
```bash
|
||
# Copy script to TrueNAS first, or paste commands manually:
|
||
mkdir -p /mnt/BasePool/Applications/Gallery/data/images/portraits
|
||
mkdir -p /mnt/BasePool/Applications/Gallery/data/images/paintings/thumbs
|
||
chown -R 1001:1001 /mnt/BasePool/Applications/Gallery
|
||
chmod -R u+rwX,g+rwX /mnt/BasePool/Applications/Gallery
|
||
```
|
||
|
||
Or from a checkout on TrueNAS: `bash infra/docker/truenas-setup.sh`
|
||
|
||
### Step E — Copy images dev → prod volume
|
||
|
||
**Where:** Dev PC — PowerShell (normal), repo root
|
||
|
||
Requires SMB share **`Gallery`** → `/mnt/BasePool/Applications/Gallery` on TrueNAS.
|
||
|
||
**Connect to the share first** (once per Windows session), then sync:
|
||
|
||
```powershell
|
||
# Map share (use your TrueNAS SMB user/password)
|
||
net use \\192.168.10.122\Gallery /user:YOUR_TRUENAS_USER
|
||
|
||
cd T:\Repo\Gallery
|
||
npm run devtoprod:images
|
||
```
|
||
|
||
UNC destination: `\\192.168.10.122\Gallery\data\images`
|
||
|
||
Type `yes` when prompted. First run copies ~1000+ files (several minutes).
|
||
|
||
If `net use` fails, open `\\192.168.10.122\Gallery` in File Explorer and sign in, then retry.
|
||
|
||
### Step F — Build and push Docker image
|
||
|
||
**Where:** Dev PC — **PowerShell as Administrator** (for fast LAN push to Gitea)
|
||
|
||
Prerequisites: **Docker Desktop running**, logged in to Gitea.
|
||
|
||
```powershell
|
||
cd T:\Repo\Gallery
|
||
docker login gitea.mysuperlab.netcraze.pro
|
||
npm run prod:docker:publish
|
||
```
|
||
|
||
Use normal PowerShell with `-SkipHosts` if you already added `192.168.10.122 gitea.mysuperlab.netcraze.pro` to `C:\Windows\System32\drivers\etc\hosts`.
|
||
|
||
### Step G — Install production on TrueNAS
|
||
|
||
**Where:** TrueNAS — **Web UI** (browser)
|
||
|
||
1. **Apps** → **Discover Apps** → **Custom App** → **Install via Docker Compose**
|
||
2. Paste contents of `infra/docker/compose.truenas.yaml` from the repo
|
||
3. Replace `YOUR_POSTGRES_PASSWORD` with the `gallery` user password
|
||
4. Replace `REPLACE_WITH_LONG_RANDOM_SECRET` and `REPLACE_WITH_SECURE_PASSWORD` for `SESSION_SECRET` and `CURATOR_PASSWORD`
|
||
5. **Apps** → **Settings** → register Gitea registry (`gitea.mysuperlab.netcraze.pro`, token with `read:package`)
|
||
6. Deploy → wait for **gallery-web** to show **Running**
|
||
7. Run `npm run dev:migrate` against `gallery_prod` if auth tables are not yet applied (or migrate from dev PC with prod env)
|
||
|
||
### Step H — Verify production
|
||
|
||
**Where:** Dev PC — PowerShell (normal) or any browser
|
||
|
||
```powershell
|
||
curl.exe -sk https://gallery.mysuperlab.netcraze.pro/api/bounds
|
||
curl.exe -s http://192.168.10.122:5173/api/bounds
|
||
```
|
||
|
||
Open **https://gallery.mysuperlab.netcraze.pro/** — timeline and sample painting images should load. Click an artist portrait or movement label to enter a 3D hall. **Curator login** (top-right) unlocks debug mode and Checkup.
|
||
|
||
---
|
||
|
||
## Environment files
|
||
|
||
| File | Git | Purpose |
|
||
|------|-----|---------|
|
||
| [`.env`](../.env) | ignored | **Dev** — `DB_NAME=gallery_dev`, `PORT=3451` |
|
||
| [`.env.example`](../.env.example) | tracked | Dev template |
|
||
| [`infra/docker/.env.prod`](../infra/docker/.env.prod) | ignored | **Prod** — migrate/restore prod scripts |
|
||
| [`infra/docker/.env.prod.example`](../infra/docker/.env.prod.example) | tracked | Prod template |
|
||
| [`infra/docker/compose.truenas.yaml`](../infra/docker/compose.truenas.yaml) | tracked | TrueNAS Custom App |
|
||
|
||
Never point dev `.env` at `gallery_prod`. Prod scripts refuse dev env files.
|
||
|
||
---
|
||
|
||
## Dev public access (Keenetic)
|
||
|
||
**Where:** Keenetic router Web UI (not dev PC)
|
||
|
||
| Field | Value |
|
||
|-------|-------|
|
||
| Domain | `devgallery.mysuperlab.netcraze.pro` |
|
||
| Upstream IP | `192.168.10.70` |
|
||
| Upstream port | **`5173`** |
|
||
| **Protocol to device** | **`http`** (not https) |
|
||
| Preserve Host | ON |
|
||
|
||
Vite on the dev PC speaks **plain HTTP** only. Keenetic terminates HTTPS from the browser, then must forward **HTTP** to `192.168.10.70:5173`.
|
||
|
||
If **Protocol to device** is `https`, Keenetic tries TLS against Vite → **502 Bad Gateway** (`Server: Web server`).
|
||
|
||
**Verify from dev PC** (servers must be running: `npm run dev:web`):
|
||
|
||
```powershell
|
||
# PowerShell: curl is an alias — use curl.exe for -k, or Invoke-WebRequest
|
||
curl.exe -s -o NUL -w "HTTP %{http_code}`n" http://192.168.10.70:5173/
|
||
curl.exe -sk -o NUL -w "HTTP %{http_code}`n" https://devgallery.mysuperlab.netcraze.pro/
|
||
|
||
# Or native PowerShell (skip cert check):
|
||
Invoke-WebRequest -Uri https://devgallery.mysuperlab.netcraze.pro/ -SkipCertificateCheck | Select-Object StatusCode
|
||
```
|
||
|
||
Expect **HTTP 200** (not 502).
|
||
|
||
**`.env`:** use the same scheme as the browser URL. If Keenetic serves HTTPS publicly:
|
||
|
||
```env
|
||
PUBLIC_URL=https://devgallery.mysuperlab.netcraze.pro
|
||
TRUST_PROXY=true
|
||
SESSION_SECRET=your-long-random-secret
|
||
```
|
||
|
||
Omit `SESSION_COOKIE_SECURE` for auto Secure cookies behind Keenetic HTTPS. Prod (`infra/docker/.env.prod`): set `SESSION_COOKIE_SECURE=true` and the same `SESSION_SECRET` / `CURATOR_*` vars on the TrueNAS app environment.
|
||
|
||
Restart `npm run dev:web` after changing `PUBLIC_URL`.
|
||
|
||
---
|
||
|
||
## Production public access (Keenetic)
|
||
|
||
**Where:** Keenetic router Web UI
|
||
|
||
| Field | Value |
|
||
|-------|-------|
|
||
| Domain | `gallery.mysuperlab.netcraze.pro` |
|
||
| Upstream IP | **`192.168.10.122`** (TrueNAS — not the dev PC) |
|
||
| Upstream port | **`5173`** |
|
||
| **Protocol to device** | **`http`** (container speaks HTTP, not HTTPS) |
|
||
| Preserve Host | ON |
|
||
|
||
The prod container listens on plain HTTP on port **5173**. Keenetic terminates HTTPS from the browser and must forward **HTTP** to TrueNAS.
|
||
|
||
**Verify the app first (bypass Keenetic):**
|
||
|
||
```powershell
|
||
curl.exe -s http://192.168.10.122:5173/api/bounds
|
||
```
|
||
|
||
Expect JSON immediately (~200). If this works but the public URL returns **502/504**, the Keenetic rule is wrong (IP, port, or `https` to device).
|
||
|
||
**Then verify public URL:**
|
||
|
||
```powershell
|
||
curl.exe -sk https://gallery.mysuperlab.netcraze.pro/api/bounds
|
||
```
|
||
|
||
See also [Drunkmeyou gitea-https-keenetic-npm-setup.md](../../Drunkmeyou/Documentation/gitea-https-keenetic-npm-setup.md) — same Keenetic HTTPS → HTTP upstream pattern.
|
||
|
||
---
|
||
|
||
## Daily development
|
||
|
||
**Where:** Dev PC — PowerShell (normal), repo root
|
||
|
||
| Task | Command |
|
||
|------|---------|
|
||
| Public dev URL (Keenetic) | `npm run dev:web` |
|
||
| Fast local HMR (no Keenetic) | `npm run dev:server` + `npm run dev:client` |
|
||
| Refresh dev DB from prod | `npm run prodto:dev:db` |
|
||
| Pull prod images to dev | `npm run prodto:dev:images` |
|
||
| Merge dev ↔ prod catalog + images (incremental) | `npm run harmonize` — see [harmonize-dev-prod.md](harmonize-dev-prod.md) |
|
||
|
||
---
|
||
|
||
## Promote dev → prod (scheduled release)
|
||
|
||
Run this when you are ready to ship dev to production — **not** after every small change. Typical cadence: **about once a week**.
|
||
|
||
For **incremental** dev ↔ prod merge (both sides edited), use [harmonize-dev-prod.md](harmonize-dev-prod.md) instead of full restore.
|
||
|
||
**One command (recommended):** copy [`infra/deploy/devtoprod.config.example.json`](../infra/deploy/devtoprod.config.example.json) to `infra/deploy/devtoprod.config.json`, edit it, then `npm run devtoprod:release` or `deploy-dev-to-prod.cmd`. See [deploy-dev-to-prod.md → One-command release](deploy-dev-to-prod.md#one-command-release-automated).
|
||
|
||
**Manual steps:** detailed runbook (per-change decision matrix, schema migration, rollback): [deploy-dev-to-prod.md](deploy-dev-to-prod.md).
|
||
|
||
**Where:** Dev PC unless noted
|
||
|
||
1. Finish and test on https://devgallery.mysuperlab.netcraze.pro
|
||
2. `npm run devtoprod:thumbnails` — skip if no painting/portrait images changed
|
||
3. `npm run dev:db:backup`
|
||
4. `npm run devtoprod:db:restore -- --file db/DataBackup/gallery_dev_data_....txt` (type `yes`) — skip if only code changed and prod DB should stay as-is
|
||
5. `npm run devtoprod:images` — skip if no new/changed images
|
||
6. `npm run prod:docker:publish` — required when application code changed
|
||
7. **TrueNAS Web UI** → restart **gallery-web**
|
||
8. Verify https://gallery.mysuperlab.netcraze.pro
|
||
|
||
---
|
||
|
||
## Database scripts
|
||
|
||
| Command | Where | Purpose |
|
||
|---------|-------|---------|
|
||
| pgAdmin + `split-dev-prod-pgadmin.sql` | Dev PC pgAdmin | One-time split (recommended) |
|
||
| `npm run infra:db:split-dev-prod` | Dev PC PowerShell | Same split (needs `PGUSER=postgres`) |
|
||
| `npm run prodto:dev:db` | Dev PC PowerShell | Clone prod → dev |
|
||
| `npm run dev:db:backup` | Dev PC PowerShell | Dev backup |
|
||
| `npm run devtoprod:db:restore` | Dev PC PowerShell | Restore into prod |
|
||
| `npm run harmonize` | Dev PC PowerShell | Bidirectional catalog + image merge — [harmonize-dev-prod.md](harmonize-dev-prod.md) |
|
||
| `npm run harmonize:db` | Dev PC PowerShell | DB merge only |
|
||
| `npm run harmonize:images` | Dev PC PowerShell | Image merge + artists/paintings checkup/path sync + regenerate thumbs on both sides |
|
||
|
||
## Image sync
|
||
|
||
| Command | Where | Direction |
|
||
|---------|-------|-----------|
|
||
| `npm run harmonize` | Dev PC PowerShell | Bidirectional merge (mtime newer wins) — [harmonize-dev-prod.md](harmonize-dev-prod.md) |
|
||
| `npm run devtoprod:release` | Dev PC PowerShell | Full config-driven promote (see [deploy-dev-to-prod.md](deploy-dev-to-prod.md#one-command-release-automated)) |
|
||
| `npm run devtoprod:thumbnails` | Dev PC PowerShell | Rebuild painting + portrait thumbs on dev before promote |
|
||
| `npm run devtoprod:images` | Dev PC PowerShell | Dev → TrueNAS volume |
|
||
| `npm run prodto:dev:images` | Dev PC PowerShell | TrueNAS → dev repo |
|
||
|
||
## Safety guards
|
||
|
||
- Prod restore reads only `infra/docker/.env.prod`
|
||
- Dev backup refuses `_prod` database names without `--prod`
|
||
- Destructive prod ops require typing `yes` or `CONFIRM_PROD=1`
|
||
|
||
## Legacy deployment
|
||
|
||
Node on dev PC `:3520` + nginx → Vite `:5173` is optional; see [`deploy/nginx-gallery.conf`](../deploy/nginx-gallery.conf). Production should use TrueNAS Docker on `:5173`.
|
||
|
||
Deploy details: [`infra/docker/DEPLOY-truenas.md`](../infra/docker/DEPLOY-truenas.md).
|