wardrobegod/depot
type-two 03772cff9b Depot backup to Drive, and publish the first asset through the hub
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>
2026-07-24 22:35:26 +10:00
..
archive.sh Depot backup to Drive, and publish the first asset through the hub 2026-07-24 22:35:26 +10:00
README.md Depot backup to Drive, and publish the first asset through the hub 2026-07-24 22:35:26 +10:00
server.py Absorb the 3GOD depot; retire the name, keep the service 2026-07-24 22:16:05 +10:00
test_hardening.py Absorb the 3GOD depot; retire the name, keep the service 2026-07-24 22:16:05 +10:00
viewer.html Absorb the 3GOD depot; retire the name, keep the service 2026-07-24 22:16:05 +10:00

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:1176const 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.