SCHEDULE. games.monsterrobot.depot-archive installed on ultra, daily 05:30 — after meshgod at 05:00, matching the existing games.monsterrobot.* launchd convention, plist kept in the repo beside the script. plutil-linted and bootstrapped; launchctl reports it registered. PRUNE. John wants old GLBs off the live VPS while keeping everything backed up. Age alone cannot decide that: two shipping games resolve depot assets BY FILENAME at runtime, so a three-year-old mesh some game still names is load-bearing, not stale, and deleting it is a 404 in a released build. So prune.py deletes only when all three hold — older than --days, named in NO consumer's source (scanning js/html/json/ts across thriftgod, procity and 90sDJsim, including the JSON manifests that also carry depot refs), and an identical-SIZE copy confirmed in the Drive archive. Size mismatch counts as unbacked; it never deletes on a maybe. Dry-run by default, and apply re-verifies every rule rather than trusting the earlier plan. Thumbnails and meta.json are left alone so the depot keeps listing pruned entries, as the MeshGod archive does. Measured today it deletes NOTHING, correctly: of 774 assets, 35 are game-referenced and 724 are newer than a 180-day cutoff — every asset in the depot is under 30 days old. Reported as-is rather than loosening the cutoff to manufacture a result. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
105 lines
5.1 KiB
Markdown
105 lines
5.1 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.
|
|
|
|
### Schedule
|
|
`games.monsterrobot.depot-archive` — launchd on ultra, daily **05:30** (after meshgod at 05:00),
|
|
logging to `~/depot-backups/launchd.log`. Plist kept here alongside the script.
|
|
|
|
### Pruning (`prune.py`)
|
|
Reclaims VPS disk **without** the 404 risk, because age alone is not a safe criterion — a
|
|
three-year-old asset a game still names is load-bearing, not stale. A GLB is deleted only when
|
|
**all three** hold:
|
|
1. older than `--days`
|
|
2. its name appears in **no** consumer's source (js/html/json/ts across thriftgod, procity, 90sDJsim)
|
|
3. an **identical-size copy is confirmed in the Drive archive**
|
|
|
|
Dry-run by default (`plan`); `apply` re-verifies every rule before deleting. Thumbnails and
|
|
`meta.json` are never touched, so the depot keeps listing pruned assets — same behaviour as the
|
|
MeshGod archive.
|
|
|
|
```bash
|
|
python3 ~/depot-backups/prune.py plan --days 180 # on ultra, where rclone lives
|
|
```
|
|
|
|
As of 2026-07-24 this deletes **nothing**: of 774 assets, 35 are game-referenced and 724 are
|
|
newer than the cutoff. Every asset in the depot is under 30 days old. The mechanism is armed for
|
|
when that changes.
|