PUBLISHED. wg-harvest-demo.glb (a dressed character: character_kit body + a garment harvested off a Ready Player Me donor) went to the live depot through wardrobegod's own export hub. 773 -> 774 assets, and fetching it back over the PUBLIC Cloudflare front — exactly what thriftgod does at runtime — returns 200, the exact byte count, and valid glTF magic. The whole publish path is proven end to end, not just the upload half. BACKUP. depot/archive.sh, deployed to ultra:~/depot-backups/ (outside ~/Documents because of the launchd TCC trap, absolute homebrew paths), following the existing MeshGod flow: ultra rsyncs the VPS asset dir, rclone pushes to gdrive:DB-BACKUP/3god-depot alongside the sibling archives. Not Gitea: git has no delta compression for binary blobs, so 774 GLBs would grow that repo without bound on every re-upload, and Gitea sits on the disk-tight old box. Wrong tool for 7.5 GB of meshes that only ever get added to. One deliberate divergence from the MeshGod script it is modelled on: NO PRUNING. MeshGod deletes VPS .glb older than 180 days because its gallery keeps thumb+index so stale entries still list. This depot is live runtime infrastructure — two shipping games resolve assets from it BY FILENAME at runtime, so pruning an old mesh is a 404 in a released game. Archive only. Also inherits the fleet-wide risk already logged in the backup-verify skill: gdrive: uses rclone's shared Google client_id, retired by Google during 2026, and every Drive flow dies with it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
81 lines
4.0 KiB
Markdown
81 lines
4.0 KiB
Markdown
# depot — the asset CDN (formerly "3GOD")
|
|
|
|
3GOD was "three.js god": an easy way to eyeball a GLB and drop it into a game. **wardrobegod's
|
|
stage covers the viewing half far better now**, so the *bench* is retired. What survives is the
|
|
part that quietly became infrastructure: a small HTTP asset server that two shipping games fetch
|
|
from at runtime.
|
|
|
|
- `server.py` (271 lines) — the depot itself. Absorbed verbatim from `3GOD/server.py`.
|
|
- `viewer.html` — the old browse/upload UI. Kept for reference only; **superseded** by
|
|
wardrobegod's stage, grid and publish flow. Not deployed by us.
|
|
- `test_hardening.py` — its security tests (SSRF allowlist, tag XSS escape, meta race).
|
|
|
|
## Why this is not just deletable
|
|
|
|
The live instance runs on the dealgod VPS (container `3god`, assets bind-mounted at
|
|
`/opt/3god/assets`, published on `100.94.195.115:8788`, fronted at `https://digalot.fyi/3god`)
|
|
and holds **773 assets**. Two shipping games read it *at runtime*:
|
|
|
|
| consumer | line |
|
|
|---|---|
|
|
| thriftgod | `web/index.html:1176` — `const DEPOT = 'https://digalot.fyi/3god';` |
|
|
| procity | `web/js/.../loaders.js:7` — same URL; `LOCAL_DEPOT` is null unless `?localdepot=1` |
|
|
|
|
**Keep the `/3god` URL even though the name is retired.** It is hardcoded in both games; changing
|
|
it means editing two shipping codebases for no benefit. The name now survives only as a URL path
|
|
nobody looks at.
|
|
|
|
## Publishing (what wardrobegod does)
|
|
|
|
```
|
|
POST http://100.94.195.115:8788/api/upload?name=<file>.glb # raw body, direct over tailnet
|
|
```
|
|
|
|
**Not** via `https://digalot.fyi/3god`. Auth trusts the raw socket peer against a tailnet
|
|
allow-list, so through the Cloudflare front the peer is CF and every write 403s. Measured:
|
|
`/api/list` reports `authed=false` via CF, `authed=true` direct. (thriftgod's own shipped
|
|
`tools/prop_campaign.py --publish` defaults to the CF URL and is broken for this reason.)
|
|
|
|
## The filename hazard
|
|
|
|
`clean_name` **deletes** illegal characters rather than substituting them, and matching is
|
|
case-sensitive. So `shop!-cat.glb` becomes `shop-cat.glb` — an existing, *different* mesh — and
|
|
is then served with no error at all. `[A-Za-z0-9._ -]` survive verbatim; the first character must
|
|
be alphanumeric; a missing extension gets `.glb` appended.
|
|
|
|
wardrobegod reproduces this rule in `god3_name()` and refuses to publish over an existing name
|
|
unless `overwrite:true` is passed. Do not bypass that check.
|
|
|
|
## Migration note
|
|
|
|
The depot's `/api/meta` tag/note store was checked before retiring the UI: **0 of 773 assets had
|
|
any tags or notes**, so nothing needed migrating into wardrobegod's manifest. Tagging now lives in
|
|
`library/index.json`.
|
|
|
|
## Retiring the repo
|
|
|
|
`ssh://git@100.71.119.27:222/monster/3GOD.git` (Gitea on the old box) can be archived once this
|
|
copy is committed. The running VPS service is unaffected — it is deployed, not sourced live from
|
|
that checkout. ultra's `~/Documents/3GOD` is a dev copy holding 4 files and its `:8788` is closed.
|
|
|
|
## Backup
|
|
|
|
`archive.sh` (deployed to `ultra:~/depot-backups/archive.sh` — outside `~/Documents` because
|
|
launchd + TCC) follows the MeshGod flow: ultra rsyncs the VPS asset dir, then rclones to
|
|
**`gdrive:DB-BACKUP/3god-depot`**. 7.5 GB / 774 assets.
|
|
|
|
**Not git.** Git has no delta compression for binary blobs, so 774 GLBs would grow the Gitea repo
|
|
without bound on every re-upload, and Gitea lives on the disk-tight old box.
|
|
|
|
**Deliberate difference from the MeshGod flow: NO PRUNING.** MeshGod prunes VPS `.glb` older than
|
|
180 days because its gallery keeps thumb+index so stale entries still list. This depot is live
|
|
runtime infrastructure — thriftgod and procity fetch assets *by filename at runtime*, so deleting
|
|
an old GLB breaks a shipping game with a 404. Archive only, never prune.
|
|
|
|
Verify freshness the same way as the other flows:
|
|
`rclone lsl gdrive:DB-BACKUP/3god-depot --include "*.glb" --max-age 36h`
|
|
|
|
⚠️ Shares the whole-fleet risk noted in the backup-verify skill: the `gdrive:` remote uses
|
|
rclone's shared Google client_id, which Google retires during 2026. When it dies every Drive flow
|
|
breaks at once, this one included.
|