From bdddadc4d6815770a8cf41da91b90d9209f3c0b3 Mon Sep 17 00:00:00 2001 From: Danila Khodjaef Date: Mon, 6 Jul 2026 09:58:01 +0300 Subject: [PATCH] Document development-first workflow with weekly prod releases. Default all day-to-day work to gallery_dev and devgallery; add Cursor rule and update docs, env examples, and deploy guide for scheduled prod promotion. --- .cursor/rules/dev-first-workflow.mdc | 43 ++++ .env.example | 3 +- Documentation/FAC.md | 15 +- Documentation/basics.md | 10 +- Documentation/environments.md | 33 ++- Documentation/setup.md | 2 + README.md | 2 + infra/docker/.env.prod.example | 5 +- infra/docker/DEPLOY-truenas.md | 298 ++++++++++++++------------- 9 files changed, 245 insertions(+), 166 deletions(-) create mode 100644 .cursor/rules/dev-first-workflow.mdc diff --git a/.cursor/rules/dev-first-workflow.mdc b/.cursor/rules/dev-first-workflow.mdc new file mode 100644 index 0000000..9386619 --- /dev/null +++ b/.cursor/rules/dev-first-workflow.mdc @@ -0,0 +1,43 @@ +--- +description: Default to dev for all changes; prod only when explicitly requested +alwaysApply: true +--- + +# Development-first workflow + +**Default target is always development** unless the user explicitly names **prod** / **production**. + +## What "dev" means + +| Layer | Dev default | +|-------|-------------| +| Code | Edit in this repo; run `npm run dev:web` on the dev PC | +| Database | `gallery_dev` via root `.env` (`DB_NAME=gallery_dev`) | +| Data / images | `data/images/` in the repo | +| Public URL | https://devgallery.mysuperlab.netcraze.pro | +| Migrations / seeds / scripts | `npm run migrate`, `npm run seed`, etc. against **dev** | + +## Production is scheduled, not daily + +Prod (`gallery_prod`, TrueNAS Docker, https://gallery.mysuperlab.netcraze.pro) is updated **roughly weekly** (or when the user explicitly asks for prod): + +1. Validate on dev +2. `npm run db:backup` → `npm run db:restore:prod` (if DB/data changed) +3. `npm run images:sync-to-prod` (if images changed) +4. `npm run docker:publish` (if code changed) +5. Restart **gallery-web** on TrueNAS + +Do **not** edit `infra/docker/.env.prod`, run `db:restore:prod`, `images:sync-to-prod`, or `docker:publish` unless the user clearly targets prod. + +## When the user says "fix", "change", or "modify" + +- Assume **dev** code, **dev** DB, and **dev** data paths +- Test against devgallery or localhost +- Mention prod steps only as optional follow-up for the next release + +## When the user says "prod" or "production" + +- Use `gallery_prod`, `infra/docker/.env.prod`, TrueNAS compose, and prod deploy docs +- Require explicit confirmation before destructive prod operations (`db:restore:prod`, etc.) + +Full operator guide: `Documentation/environments.md` diff --git a/.env.example b/.env.example index ed062ab..c7604f8 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ -# Development — copy to .env (not committed) +# Development only — copy to .env (not committed). All day-to-day work uses gallery_dev. +# Promote to prod on a scheduled release (~weekly); see Documentation/environments.md DB_HOST=192.168.10.122 DB_PORT=5432 DB_USER=gallery diff --git a/Documentation/FAC.md b/Documentation/FAC.md index bef3593..1b25b2c 100644 --- a/Documentation/FAC.md +++ b/Documentation/FAC.md @@ -2,6 +2,8 @@ Quick cheat sheet for daily operations. All `npm` commands run from the **repository root** unless noted. +**Default target: dev.** Day-to-day commands use `gallery_dev`, repo `data/images/`, and https://devgallery.mysuperlab.netcraze.pro. Prod (`gallery_prod`, Docker, `docker:publish`) is for **scheduled releases** (~weekly) — see [Promote dev → prod](#promote-dev--prod-scheduled-release) below. + **Environments:** | | Dev | Prod | @@ -283,14 +285,17 @@ Expect JSON with `min_year` / `max_year`. HTML shell only from `curl` on `/` is --- -## Promote dev → prod (checklist) +## Promote dev → prod (scheduled release) -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` -4. `npm run images:sync-to-prod` +~Weekly (or when explicitly releasing to prod). Not part of daily dev. + +1. Test on https://devgallery.mysuperlab.netcraze.pro +2. `npm run db:backup` +3. `npm run db:restore:prod -- --file db/DataBackup/gallery_dev_data_....txt` (if DB/catalog changed) +4. `npm run images:sync-to-prod` (if images changed) 5. `npm run docker:publish` (if code changed) 6. Restart **gallery-web** on TrueNAS +7. Verify https://gallery.mysuperlab.netcraze.pro --- diff --git a/Documentation/basics.md b/Documentation/basics.md index 96e6957..063766c 100644 --- a/Documentation/basics.md +++ b/Documentation/basics.md @@ -80,11 +80,9 @@ Gallery/ ## Runtime modes -### Production (TrueNAS Docker) +**Default:** develop and test on **dev** (`gallery_dev`, devgallery URL). **Production** is updated on a scheduled release (~weekly), not on every edit. See [environments.md — Development-first workflow](environments.md#development-first-workflow-default). -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`) +### Public development (`dev:web`) — primary ```bash npm run dev:web # Vite :5173 + API :3451 — https://devgallery.mysuperlab.netcraze.pro @@ -92,6 +90,10 @@ npm run dev:web # Vite :5173 + API :3451 — https://devgallery.mysuperlab.net Uses database **`gallery_dev`** on the same PostgreSQL host. +### Production (TrueNAS Docker) — scheduled releases + +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`**). Deploy via `npm run docker:publish` and the promote checklist in [environments.md](environments.md). + ### Production-style single process (local) ```bash diff --git a/Documentation/environments.md b/Documentation/environments.md index cfb1258..52d1cab 100644 --- a/Documentation/environments.md +++ b/Documentation/environments.md @@ -9,6 +9,23 @@ Gallery uses **one PostgreSQL server** on TrueNAS (`192.168.10.122`) with **two --- +## 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 | @@ -286,15 +303,19 @@ See also [Drunkmeyou gitea-https-keenetic-npm-setup.md](../../Drunkmeyou/Documen --- -## Promote dev → prod +## 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**. **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) +1. Finish and test on https://devgallery.mysuperlab.netcraze.pro +2. `npm run db:backup` +3. `npm run db:restore:prod -- --file db/DataBackup/gallery_dev_data_....txt` (type `yes`) — skip if only code changed and prod DB should stay as-is +4. `npm run images:sync-to-prod` — skip if no new/changed images +5. `npm run docker:publish` — required when application code changed +6. **TrueNAS Web UI** → restart **gallery-web** +7. Verify https://gallery.mysuperlab.netcraze.pro --- diff --git a/Documentation/setup.md b/Documentation/setup.md index 9270d16..5e7f4d9 100644 --- a/Documentation/setup.md +++ b/Documentation/setup.md @@ -8,6 +8,8 @@ ## Environment +**Development-first:** The root `.env` is for **dev only** (`DB_NAME=gallery_dev`). All routine work — code, migrations, seeds, image scripts, curator edits — targets dev. Production uses `infra/docker/.env.prod` and TrueNAS; promote on a scheduled release (~weekly), not after every change. See [environments.md — Development-first workflow](environments.md#development-first-workflow-default). + Copy the example file and fill in credentials: ```bash diff --git a/README.md b/README.md index 3f5a24d..53ba965 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ Operator guide: [Documentation/environments.md](Documentation/environments.md). ## Development +**Workflow:** All changes are made and tested on **dev** (`gallery_dev`, https://devgallery.mysuperlab.netcraze.pro). Production is updated on a **scheduled release** (~weekly): promote DB/images, build/push the Docker image, restart TrueNAS. See [Documentation/environments.md](Documentation/environments.md#development-first-workflow-default). + **Public dev (Keenetic):** ```bash diff --git a/infra/docker/.env.prod.example b/infra/docker/.env.prod.example index e386ad3..eead4ae 100644 --- a/infra/docker/.env.prod.example +++ b/infra/docker/.env.prod.example @@ -1,5 +1,6 @@ -# Copy to infra/docker/.env.prod on TrueNAS or build PC (do not commit). -# Used by compose.prod.yaml and npm run db:migrate:prod / db:restore:prod. +# Production only — copy to infra/docker/.env.prod (do not commit). +# Used for scheduled releases: db:restore:prod, docker:publish, TrueNAS gallery-web. +# Day-to-day development uses root .env → gallery_dev. DB_HOST=192.168.10.122 DB_PORT=5432 diff --git a/infra/docker/DEPLOY-truenas.md b/infra/docker/DEPLOY-truenas.md index 3f50395..40bb97e 100644 --- a/infra/docker/DEPLOY-truenas.md +++ b/infra/docker/DEPLOY-truenas.md @@ -1,149 +1,151 @@ -# Deploy Gallery on TrueNAS Scale - -Production deployment for the **Express API + built Vite SPA** container. PostgreSQL stays on the host at `192.168.10.122:5432`. Public URL: **https://gallery.mysuperlab.netcraze.pro** (Keenetic → TrueNAS `:5173`, protocol to device **http**). - -See also [Documentation/environments.md](../../Documentation/environments.md). - -## Architecture - -```text -Browser (HTTPS) - → Keenetic (KeenDNS, SSL termination) - → gallery-web container on TrueNAS (:5173, HTTP) - → PostgreSQL (192.168.10.122:5432) → gallery_prod - → /mnt/BasePool/Applications/Gallery/data/images (volume) - → SMB share Gallery → \\192.168.10.122\Gallery (image sync from dev PC) -``` - -## Prerequisites - -| Item | Notes | -|------|-------| -| TrueNAS Scale 25.04+ | Apps → Custom App support | -| PostgreSQL | `gallery_prod` (dev: `gallery_dev` on same host) — split via [pgAdmin script](../../db/split-dev-prod-pgadmin.sql) | -| Gitea registry | `gitea.mysuperlab.netcraze.pro` — image **`danilka/gallery-web:latest`** pushed before deploy | -| SMB share | **`Gallery`** at `/mnt/BasePool/Applications/Gallery` for `images:sync-to-prod` | -| Keenetic | `gallery.mysuperlab.netcraze.pro` → `192.168.10.122:5173`, protocol **`http`**, Preserve Host ON | - -If deploy fails with **`manifest unknown`**, the image is not in Gitea yet — complete [§1 Build and push](#1-build-and-push-image-dev-machine) first. - -## 1. Build and push image (dev machine) - -**Where:** Dev PC — **PowerShell as Administrator** (LAN push hosts entry), **Docker Desktop running** - -```powershell -cd C:\Users\SNAP\Nextcloud\Personal\Repo\Gallery -docker login gitea.mysuperlab.netcraze.pro -npm run docker:publish -``` - -Push only (already built): `npm run docker:publish:push-only` - -Image: `gitea.mysuperlab.netcraze.pro/danilka/gallery-web:latest` - -### Gitea registry tokens - -| Machine | Token scope | -|---------|-------------| -| Build PC | `write:package` | -| TrueNAS pull | `read:package` | - -Register on TrueNAS: **Apps → Configuration → Sign in to a Docker registry** — URL `https://gitea.mysuperlab.netcraze.pro`, username `danilka` (lowercase for registry). - -### Registry token URL fix - -If `docker push` fails with internal HTTP token URL, set Gitea `ROOT_URL` to `https://gitea.mysuperlab.netcraze.pro/` and restart Gitea. Full walkthrough: [Drunkmeyou gitea-https-keenetic-npm-setup.md](../../../Drunkmeyou/Documentation/gitea-https-keenetic-npm-setup.md). - -### Offline fallback (no registry) - -On **dev PC**: `.\infra\docker\save-for-truenas.ps1` → copy `gallery-web.tar` via SMB `Gallery` share. - -On **TrueNAS shell**: `sudo bash truenas-load-image.sh /path/to/gallery-web.tar` - -In Custom App YAML: `pull_policy: if_not_present`, then redeploy. - -## 2. Prepare TrueNAS storage - -**Where:** TrueNAS — **Shell**, as **root** - -```bash -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 -``` - -Enable SMB share **`Gallery`** → `/mnt/BasePool/Applications/Gallery` (for image sync from dev PC). - -## 3. Sync images (first deploy) - -**Where:** Dev PC — PowerShell (normal), repo root - -```powershell -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` - -## 4. Install Custom App - -**Where:** TrueNAS — **Web UI** - -1. **Apps → Discover Apps → Custom App** -2. Paste [`compose.truenas.yaml`](compose.truenas.yaml) -3. Set `DB_PASSWORD` (replace `YOUR_POSTGRES_PASSWORD`) -4. Deploy → wait for **gallery-web** **Running** - -## 5. Keenetic (production) - -| Field | Value | -|-------|-------| -| Domain | `gallery.mysuperlab.netcraze.pro` | -| Upstream | `192.168.10.122:5173` | -| Protocol to device | **`http`** | -| Preserve Host | ON | - -Wrong IP or `https` to device → **502 / 504** from Keenetic (`Server: Web server`). - -## 6. Verify - -**Bypass Keenetic** (should be instant JSON): - -```powershell -curl.exe -s http://192.168.10.122:5173/api/bounds -``` - -**Public URL:** - -```powershell -curl.exe -sk https://gallery.mysuperlab.netcraze.pro/api/bounds -``` - -Open **https://gallery.mysuperlab.netcraze.pro/** — timeline and `/images/paintings/...` should load. - -On TrueNAS shell: `bash infra/docker/truenas-verify.sh` - -## 7. Update production - -After code changes on dev PC: - -```powershell -npm run docker:publish -``` - -Restart **gallery-web** on TrueNAS (or rely on `pull_policy: always`). - -## Troubleshooting - -| Issue | Fix | -|-------|-----| -| **`manifest unknown`** | Run `npm run docker:publish` first; or offline `truenas-load-image.sh` | -| **502 / 504 public URL** | Keenetic → `192.168.10.122:5173`, protocol **`http`**; verify LAN curl above | -| Container cannot reach Postgres | `DB_HOST=192.168.10.122`; `extra_hosts` in compose | -| Empty timeline | `DB_NAME=gallery_prod`; run pgAdmin split if still on legacy `Gallery` | -| Missing images | `npm run images:sync-to-prod`; check volume mount and `chown 1001:1001` | -| Pull 401 | Gitea registry credentials on TrueNAS; `read:package` token | -| Push fails on dev PC | Gitea `ROOT_URL` HTTPS fix; use Admin PowerShell for LAN push | +# Deploy Gallery on TrueNAS Scale + +Production deployment for the **Express API + built Vite SPA** container. PostgreSQL stays on the host at `192.168.10.122:5432`. Public URL: **https://gallery.mysuperlab.netcraze.pro** (Keenetic → TrueNAS `:5173`, protocol to device **http**). + +**Not for daily work.** Develop on dev (`gallery_dev`, https://devgallery.mysuperlab.netcraze.pro). Use this guide when **releasing** to prod (~weekly): build/push image, sync data/images if needed, restart the TrueNAS app. + +See also [Documentation/environments.md](../../Documentation/environments.md#development-first-workflow-default). + +## Architecture + +```text +Browser (HTTPS) + → Keenetic (KeenDNS, SSL termination) + → gallery-web container on TrueNAS (:5173, HTTP) + → PostgreSQL (192.168.10.122:5432) → gallery_prod + → /mnt/BasePool/Applications/Gallery/data/images (volume) + → SMB share Gallery → \\192.168.10.122\Gallery (image sync from dev PC) +``` + +## Prerequisites + +| Item | Notes | +|------|-------| +| TrueNAS Scale 25.04+ | Apps → Custom App support | +| PostgreSQL | `gallery_prod` (dev: `gallery_dev` on same host) — split via [pgAdmin script](../../db/split-dev-prod-pgadmin.sql) | +| Gitea registry | `gitea.mysuperlab.netcraze.pro` — image **`danilka/gallery-web:latest`** pushed before deploy | +| SMB share | **`Gallery`** at `/mnt/BasePool/Applications/Gallery` for `images:sync-to-prod` | +| Keenetic | `gallery.mysuperlab.netcraze.pro` → `192.168.10.122:5173`, protocol **`http`**, Preserve Host ON | + +If deploy fails with **`manifest unknown`**, the image is not in Gitea yet — complete [§1 Build and push](#1-build-and-push-image-dev-machine) first. + +## 1. Build and push image (dev machine) + +**Where:** Dev PC — **PowerShell as Administrator** (LAN push hosts entry), **Docker Desktop running** + +```powershell +cd C:\Users\SNAP\Nextcloud\Personal\Repo\Gallery +docker login gitea.mysuperlab.netcraze.pro +npm run docker:publish +``` + +Push only (already built): `npm run docker:publish:push-only` + +Image: `gitea.mysuperlab.netcraze.pro/danilka/gallery-web:latest` + +### Gitea registry tokens + +| Machine | Token scope | +|---------|-------------| +| Build PC | `write:package` | +| TrueNAS pull | `read:package` | + +Register on TrueNAS: **Apps → Configuration → Sign in to a Docker registry** — URL `https://gitea.mysuperlab.netcraze.pro`, username `danilka` (lowercase for registry). + +### Registry token URL fix + +If `docker push` fails with internal HTTP token URL, set Gitea `ROOT_URL` to `https://gitea.mysuperlab.netcraze.pro/` and restart Gitea. Full walkthrough: [Drunkmeyou gitea-https-keenetic-npm-setup.md](../../../Drunkmeyou/Documentation/gitea-https-keenetic-npm-setup.md). + +### Offline fallback (no registry) + +On **dev PC**: `.\infra\docker\save-for-truenas.ps1` → copy `gallery-web.tar` via SMB `Gallery` share. + +On **TrueNAS shell**: `sudo bash truenas-load-image.sh /path/to/gallery-web.tar` + +In Custom App YAML: `pull_policy: if_not_present`, then redeploy. + +## 2. Prepare TrueNAS storage + +**Where:** TrueNAS — **Shell**, as **root** + +```bash +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 +``` + +Enable SMB share **`Gallery`** → `/mnt/BasePool/Applications/Gallery` (for image sync from dev PC). + +## 3. Sync images (first deploy) + +**Where:** Dev PC — PowerShell (normal), repo root + +```powershell +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` + +## 4. Install Custom App + +**Where:** TrueNAS — **Web UI** + +1. **Apps → Discover Apps → Custom App** +2. Paste [`compose.truenas.yaml`](compose.truenas.yaml) +3. Set `DB_PASSWORD` (replace `YOUR_POSTGRES_PASSWORD`) +4. Deploy → wait for **gallery-web** **Running** + +## 5. Keenetic (production) + +| Field | Value | +|-------|-------| +| Domain | `gallery.mysuperlab.netcraze.pro` | +| Upstream | `192.168.10.122:5173` | +| Protocol to device | **`http`** | +| Preserve Host | ON | + +Wrong IP or `https` to device → **502 / 504** from Keenetic (`Server: Web server`). + +## 6. Verify + +**Bypass Keenetic** (should be instant JSON): + +```powershell +curl.exe -s http://192.168.10.122:5173/api/bounds +``` + +**Public URL:** + +```powershell +curl.exe -sk https://gallery.mysuperlab.netcraze.pro/api/bounds +``` + +Open **https://gallery.mysuperlab.netcraze.pro/** — timeline and `/images/paintings/...` should load. + +On TrueNAS shell: `bash infra/docker/truenas-verify.sh` + +## 7. Update production + +After code changes on dev PC: + +```powershell +npm run docker:publish +``` + +Restart **gallery-web** on TrueNAS (or rely on `pull_policy: always`). + +## Troubleshooting + +| Issue | Fix | +|-------|-----| +| **`manifest unknown`** | Run `npm run docker:publish` first; or offline `truenas-load-image.sh` | +| **502 / 504 public URL** | Keenetic → `192.168.10.122:5173`, protocol **`http`**; verify LAN curl above | +| Container cannot reach Postgres | `DB_HOST=192.168.10.122`; `extra_hosts` in compose | +| Empty timeline | `DB_NAME=gallery_prod`; run pgAdmin split if still on legacy `Gallery` | +| Missing images | `npm run images:sync-to-prod`; check volume mount and `chown 1001:1001` | +| Pull 401 | Gitea registry credentials on TrueNAS; `read:package` token | +| Push fails on dev PC | Gitea `ROOT_URL` HTTPS fix; use Admin PowerShell for LAN push | \ No newline at end of file