Files
esh-pfi-infrastructure/stacks/pewpewstudio/README.md
T
vh d1192ca868 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.
2026-10-13 21:39:44 -07:00

3.6 KiB

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)

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.