# 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`, `db_name`) - Prod image selection: set `IMAGE_TAG` in TrueNAS app env / `infra/docker/.env.prod` - Release push tag: set `release.imageTag` in `infra/deploy/devtoprod.config.json` --- ## 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 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 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 SESSION_COOKIE_SECURE=false CURATOR_USERNAME=curator CURATOR_PASSWORD=your-secure-password ``` `npm run dev:migrate` creates auth tables and bootstraps the first curator when `users` is empty. 2. Run: ```powershell cd C:\Users\SNAP\Nextcloud\Personal\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 C:\Users\SNAP\Nextcloud\Personal\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 C:\Users\SNAP\Nextcloud\Personal\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 SESSION_COOKIE_SECURE=false ``` 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` | --- ## 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**. **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 | ## Image sync | Command | Where | Direction | |---------|-------|-----------| | `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).