feat(pewpewstudio): laser job studio on nh3-docker :8185 (api + Caddy front), Homepage tile

Prime's directive via pewpew-dev. Images are built on nh3-dev from a clean
archive of vh/pewpewstudio (26bba3c; hamr over gitea SSH via BuildKit --ssh)
and loaded on nh3-docker. Caddy serves the SPA and strips /api/ in front of
the backend. It refuses foreign Host headers inside a route block, because the
Host rewrite would otherwise bypass the backend's TrustedHost. The DB, assets
and the app-edited shop config live on named volumes inside nh3-docker's
nightly vzdump. Verified: SPA, /api/machines and /api/generate/workflows 200;
foreign Host 400.
This commit is contained in:
vh
2026-10-13 21:39:44 -07:00
parent 5e8307ed7a
commit d1192ca868
5 changed files with 160 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
# pewpewstudio
Laser job studio (FastAPI backend + React SPA, gitea `vh/pewpewstudio`). It composes Thunder Aurora marking jobs and
returns LightBurn `.lbrn` files as browser downloads, and never touches hardware. Deployed 2026-10-13 on Prime's directive
via pewpew-dev (thread `01M4WATRFQX3V7TSJ4XPGABWMN`).
| | |
|---|---|
| **URL** | `http://10.100.50.40:8185/` (= `http://nh3-docker.nh3.internal:8185/`). Homepage tile in *AI - Studios*. |
| **Host** | nh3-docker: fast local disk for SQLite WAL (fsync ~2.7 ms, unlike ana-docker's HDD pool), and it reaches arbo `100.64.0.6:8201` and Worldtree DEMO `10.250.50.152:8080`. |
| **Auth** | NONE in v1. LAN/mesh only: published on the host's LAN address, with no public ingress. |
| **Images** | `pewpewstudio:<rev>` (api) and `pewpewstudio-web:<rev>` (Caddy + `web/dist`), built on nh3-dev and loaded here; `PEWPEW_REV` in `.env` picks the pair. |
| **State** | volume `pewpewstudio_state` (`/state`: `shop.db` + `assets/`) and volume `pewpewstudio_config` (`/config`: machine profiles, lenses, denylist, sizing, fonts, which **the app edits from the browser**). Both are covered by nh3-pve's nightly 21:00 whole-VM vzdump of VM 100. |
| **Secrets** | `.env` (0600, infra-ops): `WORLDTREE_API_KEY` = vault `nh3-dev/.config/worldtree/pewpewstudio-demo-key`. |
## Topology
`web` (Caddy, published `10.100.50.40:8185 → :80`) serves the SPA at `/`. It sends `/api/*` to `api:8000` with the
prefix **stripped** (the backend has no `/api` routes and no CORS, so both must be one origin) and Host rewritten to
`localhost`, which the backend's TrustedHost accepts. Because of that rewrite, **Caddy refuses any Host other than
`10.100.50.40` / `nh3-docker.nh3.internal` with 400**, to keep the backend's DNS-rebinding defence. Add a name to
`conf/Caddyfile` before publishing one. Verified 2026-10-13: foreign Host → 400; IP and name → 200.
⚠ That check must stay inside `route { }`. Outside it, Caddy orders `respond` after `handle_path`, and the check
silently never runs: a foreign Host got 200 until it was wrapped.
⚠ The Caddy config is mounted as the DIRECTORY `/opt/docker/conf/pewpewstudio → /etc/caddy`. deploy-stack replaces the
file, and a single-file bind mount keeps showing the old inode.
## Build and deploy (from nh3-dev)
```bash
REV=<short sha>; C=$(mktemp -d)
git -C ~/development/pewpewstudio archive $REV | tar -x -C $C
cp stacks/pewpewstudio/build/Dockerfile $C/Dockerfile.eshpfi
# hamr is a git dependency over gitea SSH (10.250.50.70:222); BuildKit forwards lkraven's gitea key:
(cd $C && docker build -f Dockerfile.eshpfi --ssh default=$HOME/.ssh/id_rsa --target api -t pewpewstudio:$REV . \
&& docker build -f Dockerfile.eshpfi --target web -t pewpewstudio-web:$REV .)
docker save pewpewstudio:$REV pewpewstudio-web:$REV | ssh infra-ops@10.100.50.40 docker load
scripts/deploy-stack.sh nh3-docker pewpewstudio # compose + conf/Caddyfile; never .env
# on nh3-docker: set PEWPEW_REV=$REV in .env (in place, keep 0600), then
cd /opt/docker/compose/pewpewstudio && docker compose up -d
```
**First deploy only:** the config volume was seeded once from the repo's `config/` plus the gitignored
`config/machine.local.toml` from nh3-dev's checkout (machine parameters, no secrets). Without a `machine.*.toml` (the
registry skips `machine.example.toml`), startup fails "no machine.*.toml files". **Never reseed a populated volume**:
it holds the shop's own edits. The database started fresh; the dev `shop.db` was smoke-test data.
## Checks
- `GET /` 200 (the SPA); `GET /api/machines` 200 (one machine, `aurora-mopa-100w-f160`); `GET /api/generate/workflows` 200.
- The api healthcheck is `GET /machines` on loopback.