Add dev/prod environments with TrueNAS Docker production deploy.

Split PostgreSQL into gallery_dev and gallery_prod, add Docker/Gitea deploy tooling,
SMB image sync, pgAdmin split script, dev:web on Keenetic :5173, and operator docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-07-04 15:15:19 +03:00
co-authored by Cursor
parent 02d238b043
commit 2edf577faf
34 changed files with 1742 additions and 80 deletions
+18 -5
View File
@@ -1,16 +1,29 @@
# Art Gallery — REST API
Base URL:
Base URL (paths are the same on every host; only the origin changes):
- **Production (public):** `http://gallery.mysuperlab.netcraze.pro`
- **Production (LAN):** `http://192.168.10.70:3520`
- **Development (direct):** `http://localhost:3520` or `http://localhost:3001` depending on `PORT` in `.env`
- **Development (Vite proxy):** `http://localhost:5173` (same paths)
| Context | Base URL |
|---------|----------|
| **Production (public)** | `https://gallery.mysuperlab.netcraze.pro` |
| **Production (LAN)** | `http://192.168.10.122:5173` |
| **Development (public)** | `https://devgallery.mysuperlab.netcraze.pro` |
| **Development (LAN)** | `http://192.168.10.70:5173` |
| **Local Vite proxy** | `http://localhost:5173` (proxies `/api` and `/images` to API on `:3451`) |
| **Local API only** | `http://localhost:3451` (when using `npm run dev:web`) |
See [environments.md](environments.md) for Keenetic rules, databases, and deploy.
All JSON responses use `Content-Type: application/json`. Errors return `{ "error": "message" }` with an appropriate HTTP status.
Static images are served at `/images/<relative-path>` from `IMAGE_DIR`.
**Quick check:**
```powershell
curl.exe -sk https://gallery.mysuperlab.netcraze.pro/api/bounds
curl.exe -sk https://devgallery.mysuperlab.netcraze.pro/api/bounds
```
---
## `GET /api/bounds`
+8 -2
View File
@@ -2,14 +2,20 @@
PostgreSQL schema for the virtual gallery. Canonical DDL lives in **`db/schema.sql`**; **`server/migrate.js`** (`npm run migrate`) applies that file plus idempotent incremental scripts in `db/migrate-*.sql`. This document describes the logical model.
Connection settings come from `.env` (see [setup.md](setup.md)).
Connection settings come from `.env` (dev) or `infra/docker/.env.prod` (prod scripts). See [environments.md](environments.md) and [setup.md](setup.md).
### One-time split (legacy `Gallery` → `gallery_prod` + `gallery_dev`)
Run [`db/split-dev-prod-pgadmin.sql`](../db/split-dev-prod-pgadmin.sql) in **pgAdmin** on the dev PC (postgres superuser). Alternative: `npm run db:split-databases` with `PGUSER=postgres`.
## Overview
| Item | Typical value |
|------|----------------|
| Engine | PostgreSQL 14+ |
| Database | `Gallery` |
| Database (dev) | `gallery_dev` |
| Database (prod) | `gallery_prod` |
| Legacy name | `Gallery` (one-time split → prod + dev) |
| App user | `gallery` |
| Time fields | Integer years (negative = BCE) |
+22 -11
View File
@@ -71,32 +71,43 @@ Gallery/
├── data/images/ # Local portraits and paintings (+ thumbs/)
├── db/ # schema.sql, setup-admin.sql, migrate-*.sql
├── server/migrate.js # npm run migrate — schema + incremental migrations
├── deploy/ # Production nginx + systemd examples
├── deploy/ # Legacy nginx + systemd examples (optional)
├── infra/docker/ # Production Dockerfile, TrueNAS compose, deploy scripts
├── Documentation/ # This folder
│ └── environments.md # Dev/prod URLs, DB split, sync, deploy
└── .env # DB and port config (not committed)
```
## Runtime modes
### Production-style (single process)
### Production (TrueNAS Docker)
Production runs in **`gallery-web`** on TrueNAS port **5173**, database **`gallery_prod`**, public URL **https://gallery.mysuperlab.netcraze.pro**. Images: `/mnt/BasePool/Applications/Gallery/data/images` (SMB share **`Gallery`**). See [environments.md](environments.md).
### Public development (`dev:web`)
```bash
npm run start:prod # build client + serve on PORT (default 3520)
# or: npm run build && npm run start
npm run dev:web # Vite :5173 + API :3451 — https://devgallery.mysuperlab.netcraze.pro
```
Serves `/api/*`, `/images/*`, and the built SPA from `client/dist` if it exists.
Uses database **`gallery_dev`** on the same PostgreSQL host.
**Deployed URLs:** public http://gallery.mysuperlab.netcraze.pro · LAN http://192.168.10.70:3520 — see [setup.md](setup.md#production-deployment).
### Development (two processes)
### Production-style single process (local)
```bash
npm run dev:server # API on PORT from .env (3520 production, 3001 typical dev)
npm run dev:client # Vite on :5173, proxies /api and /images to PORT
npm run start:prod # build client + serve on PORT from .env
```
Use the Vite URL during frontend work for HMR. When the public domain is proxied to Vite (see [`deploy/nginx-gallery.conf`](../deploy/nginx-gallery.conf)), both `dev:server` and `dev:client` must stay running or visitors see **503**.
Serves `/api/*`, `/images/*`, and the built SPA from `client/dist`.
### Local HMR (two processes)
```bash
npm run dev:server # API on PORT from .env
npm run dev:client # Vite on :5173, proxies /api and /images
```
Use for fast frontend iteration without Keenetic. Legacy nginx config in [`deploy/nginx-gallery.conf`](../deploy/nginx-gallery.conf) proxied the public domain to Vite `:5173`.
## User navigation flow
+10
View File
@@ -22,6 +22,15 @@ data/images/
File names are sanitised `{Artist}_{Title}.{ext}`. The image service can rediscover files on disk even when DB paths are empty (`server/image-service.js``syncPaintingFromDisk`).
### Dev vs production image storage
| Environment | Path on disk | Sync |
|-------------|--------------|------|
| **Development** | `./data/images/` in repo | Working copy on dev PC |
| **Production** | `/mnt/BasePool/Applications/Gallery/data/images` on TrueNAS | SMB `\\192.168.10.122\Gallery\data\images` |
Promote dev → prod files: `npm run images:sync-to-prod` (after `net use \\192.168.10.122\Gallery`). Refresh dev from prod: `npm run images:sync-from-prod`. See [environments.md](environments.md).
## Scripts overview
| Script | npm command | Role |
@@ -37,6 +46,7 @@ File names are sanitised `{Artist}_{Title}.{ext}`. The image service can redisco
| `update-influences.js` | `npm run update-influences` | Applies influence graph; creates missing artists/works |
| `fetch-missing-images.js` | `npm run fetch-images` | Downloads files for paintings missing on disk |
| `image-fetcher.js` | *(library)* | Wikimedia / museum resolution used by fetch scripts and API |
| `sync-images-to-prod.ps1` / `sync-images-from-prod.ps1` | `npm run images:sync-*` | Robocopy via SMB `\\192.168.10.122\Gallery` |
| `regenerate-thumbnails.js` | `npm run regenerate-thumbnails` | Rebuild thumbs from full images via `sharp` |
| `audit-painting-images.js` | `npm run audit-painting-images` | Detect thumb/full aspect-ratio mismatches |
| `find-duplicate-paintings.js` | `npm run find-duplicates` | Report exact and near-duplicate catalog rows |
+316
View File
@@ -0,0 +1,316 @@
# 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` |
---
## 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 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 images:sync-to-prod` |
| Build + push Docker image | Dev PC | **PowerShell as Administrator** | admin (for LAN hosts entry) | `npm run 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 C:\Users\SNAP\Nextcloud\Personal\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 C:\Users\SNAP\Nextcloud\Personal\Repo\Gallery
$env:PGHOST="192.168.10.122"; $env:PGUSER="postgres"; $env:PGPASSWORD="YOUR_POSTGRES_PASSWORD"
npm run db:split-databases
```
### 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
```
2. Run:
```powershell
cd C:\Users\SNAP\Nextcloud\Personal\Repo\Gallery
npm run 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 C:\Users\SNAP\Nextcloud\Personal\Repo\Gallery
npm run images:sync-to-prod
```
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 C:\Users\SNAP\Nextcloud\Personal\Repo\Gallery
docker login gitea.mysuperlab.netcraze.pro
npm run 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. **Apps** → **Settings** → register Gitea registry (`gitea.mysuperlab.netcraze.pro`, token with `read:package`)
5. Deploy → wait for **gallery-web** to show **Running**
### 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.
---
## 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
```
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 db:sync-from-prod` |
| Pull prod images to dev | `npm run images:sync-from-prod` |
---
## Promote dev → prod
**Where:** Dev PC unless noted
1. `npm run db:backup`
2. Test on https://devgallery.mysuperlab.netcraze.pro
3. `npm run db:restore:prod -- --file db/DataBackup/gallery_dev_data_....txt` (type `yes`)
4. `npm run images:sync-to-prod`
5. **TrueNAS Web UI** → restart **gallery-web** (or `npm run docker:publish` if code changed)
---
## Database scripts
| Command | Where | Purpose |
|---------|-------|---------|
| pgAdmin + `split-dev-prod-pgadmin.sql` | Dev PC pgAdmin | One-time split (recommended) |
| `npm run db:split-databases` | Dev PC PowerShell | Same split (needs `PGUSER=postgres`) |
| `npm run db:sync-from-prod` | Dev PC PowerShell | Clone prod → dev |
| `npm run db:backup` | Dev PC PowerShell | Dev backup |
| `npm run db:restore:prod` | Dev PC PowerShell | Restore into prod |
## Image sync
| Command | Where | Direction |
|---------|-------|-----------|
| `npm run images:sync-to-prod` | Dev PC PowerShell | Dev → TrueNAS volume |
| `npm run images:sync-from-prod` | 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).
+30 -43
View File
@@ -20,10 +20,10 @@ cp .env.example .env
| `DB_PORT` | Port (default `5432`) |
| `DB_USER` | Database user |
| `DB_PASSWORD` | Database password |
| `DB_NAME` | Database name (`Gallery`) |
| `PORT` | API listen port (default `3001`; production uses `3520`) |
| `DB_NAME` | Database name (`gallery_dev` for dev; prod uses `gallery_prod`) |
| `PORT` | API listen port (`3451` for `dev:web`; prod container uses `5173`) |
| `HOST` | Bind address (default `0.0.0.0` — required for LAN access) |
| `PUBLIC_URL` | Optional public URL shown at startup (e.g. `http://gallery.mysuperlab.netcraze.pro`) |
| `PUBLIC_URL` | Public URL (dev: `https://devgallery.mysuperlab.netcraze.pro`; prod: `https://gallery.mysuperlab.netcraze.pro`) |
| `TRUST_PROXY` | Set to `true` when behind nginx/reverse proxy (honours `X-Forwarded-*`) |
| `IMAGE_DIR` | Root for cached images (default `./data/images`) |
@@ -89,14 +89,22 @@ Image fetch can take hours if you run it for the entire catalog. The first line
| Command | Purpose |
|---------|---------|
| `npm run dev:web` | **Public dev stack** — Vite `:5173`, API `:3451` (Keenetic → devgallery…) |
| `npm run docker:publish` | Build + push prod image to Gitea |
| `npm run images:sync-to-prod` | Copy `data/images/` → TrueNAS via SMB `Gallery` share |
| `npm run images:sync-from-prod` | Copy prod images → dev repo |
| `npm run db:sync-from-prod` | Clone `gallery_prod``gallery_dev` |
| `npm run db:backup` / `db:restore:prod` | Dev backup / promote to prod |
| `npm run build` | Build production SPA into `client/dist` |
| `npm run start` | API + static SPA on `HOST`:`PORT` |
| `npm run start:prod` | Build client, then start server |
| `npm run server` | Alias for `start` |
| `npm run dev:server` | API with nodemon reload |
| `npm run dev:server` | API with nodemon reload (local `:3520` / `:5173` workflow) |
| `npm run dev:client` | Vite dev server on :5173 |
| `npm run dev` | Alias for `start` |
See [environments.md](environments.md) for dev/prod URLs, database split, Docker deploy, and sync commands.
**Production frontend:** build the client, then start the server:
```bash
@@ -105,53 +113,30 @@ npm run build
npm run start
```
Open http://localhost:3520 (or your configured `HOST`/`PORT`).
Open http://localhost:5173 (Vite) or http://localhost:3451 (API only).
## Production deployment
This install is intended to run at:
Production runs as **`gallery-web`** on TrueNAS at **https://gallery.mysuperlab.netcraze.pro** (Keenetic → `:5173`). Full guide: [environments.md](environments.md) and [infra/docker/DEPLOY-truenas.md](../infra/docker/DEPLOY-truenas.md).
| Access | URL |
|--------|-----|
| Public (reverse proxy) | http://gallery.mysuperlab.netcraze.pro |
| LAN direct | http://192.168.10.70:3520 |
| Production (Keenetic) | https://gallery.mysuperlab.netcraze.pro |
| Development (Keenetic) | https://devgallery.mysuperlab.netcraze.pro |
| LAN direct (prod container) | http://192.168.10.122:5173 |
| LAN direct (dev PC) | http://192.168.10.70:5173 |
### 1. Configure `.env`
Quick deploy checklist:
Copy `.env.example``.env` and set at least:
1. One-time DB split in **pgAdmin** on dev PC: [`db/split-dev-prod-pgadmin.sql`](../db/split-dev-prod-pgadmin.sql)
2. Dev `.env``DB_NAME=gallery_dev`, `PORT=3451`, `PUBLIC_URL=https://devgallery.mysuperlab.netcraze.pro`
3. `net use \\192.168.10.122\Gallery``npm run images:sync-to-prod`
4. `npm run docker:publish` → TrueNAS Custom App from `infra/docker/compose.truenas.yaml`
5. Keenetic: both domains → `:5173`, protocol to device **`http`**, correct IP per environment
```env
PORT=3520
HOST=0.0.0.0
PUBLIC_URL=http://gallery.mysuperlab.netcraze.pro
TRUST_PROXY=true
```
### Legacy deployment (optional)
`HOST=0.0.0.0` lets the app accept connections on the machines LAN IP (`192.168.10.70`). The client uses relative `/api` and `/images` paths, so no frontend URL changes are needed.
### 2. Build and start
```bash
npm install
cd client && npm install && cd ..
npm run start:prod
```
Or use the systemd unit in [`deploy/gallery.service`](../deploy/gallery.service) (adjust `User`, `WorkingDirectory`, and `EnvironmentFile`).
### 3. Reverse proxy (public domain)
Point `gallery.mysuperlab.netcraze.pro` at the host running the app. Example nginx config: [`deploy/nginx-gallery.conf`](../deploy/nginx-gallery.conf).
**Development (default in repo):** nginx forwards to **Vite on `127.0.0.1:5173`**. Run both `npm run dev:server` (API on `3520`) and `npm run dev:client` (`5173`). Vite proxies `/api` and `/images` to the API. If either process stops, the public hostname may return **503** (reverse proxy cannot reach upstream).
**Production (built SPA):** change nginx `proxy_pass` to `http://127.0.0.1:3520` after `npm run build` and `npm run start` — Node serves `client/dist` and the API on one port. Prefer the systemd unit in [`deploy/gallery.service`](../deploy/gallery.service) so the process restarts automatically.
Keep `TRUST_PROXY=true` in `.env` so Express sees the correct client IP and scheme.
### 4. Firewall
Allow inbound **TCP 3520** on the gallery host if clients reach it directly on the LAN (`192.168.10.70:3520`). The public hostname only needs **80/443** on the reverse-proxy host.
Node on the dev PC at `:3520` with nginx → Vite `:5173` is superseded by TrueNAS Docker prod and `npm run dev:web` for public dev. See [`deploy/nginx-gallery.conf`](../deploy/nginx-gallery.conf) and [`deploy/gallery.service`](../deploy/gallery.service) only if you need a local nginx/systemd setup.
## Maintenance scripts
@@ -226,14 +211,16 @@ Gitea: [Danilka/Art-gallery](https://gitea.mysuperlab.netcraze.pro/Danilka/Art-g
git clone https://gitea.mysuperlab.netcraze.pro/Danilka/Art-gallery.git
```
After clone: copy `.env.example``.env`, install dependencies, run `npm run setup` against your Postgres instance, then the post-seed steps above.
After clone: copy `.env.example``.env`, install dependencies, run [one-time DB split](Documentation/environments.md#step-b--split-the-database-pgadmin), then post-seed steps. Deploy: [environments.md](Documentation/environments.md).
## Troubleshooting
| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| Empty timeline | DB not seeded | `npm run seed` |
| 503 on public URL | Vite or API not running behind nginx | Start `npm run dev:server` + `npm run dev:client`, or switch nginx to production `:3520` |
| 502 / 504 on public URL | Keenetic rule wrong (IP, port, or `https` to device) | Dev → `192.168.10.70:5173`; prod → `192.168.10.122:5173`; protocol **`http`** — see [environments.md](environments.md) |
| 503 on public URL | Dev servers not running | `npm run dev:web` (or `dev:server` + `dev:client` for local HMR) |
| **`manifest unknown`** on TrueNAS deploy | Image not in Gitea | `npm run docker:publish` on dev PC first |
| 500 on all `/api/*` | Wrong `.env` or Postgres down | Check connection, logs |
| “Biographical information not yet available” | Bios not fetched | `npm run fetch-artist-bios` |
| Placeholder portraits on timeline | `portrait_path` not set | `npm run fetch-artist-images` |