Files
Art-gallery/Documentation/environments.md
T
Danila KhodjaefandCursor cfee69c9a6 Keep prod users local and fix post-restore id sequences.
Prod restore skips users/session/audit, syncs serial sequences after load, and user create re-aligns users_id_seq so new accounts are not misreported as duplicates.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 19:26:52 +03:00

367 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 repos **`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 ~12 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). **`users`**, **`session`**, and **`curator_audit_log`** are never copied by harmonize or `devtoprod:db:restore` — each env keeps its own staff accounts and audit history.
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).