Self-hostable game-server control panel: controller + agent + 26 game modules. One-line install (prebuilt release, no Go required): curl -fsSL https://git.pdxtechs.com/dbledeez/panel/raw/branch/main/install.sh | sudo bash Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
28 KiB
7 Days to Die — Cluster Playbook
The single operator + architecture doc for the Refuge 7DTD cluster: how shared characters/inventory work, how new servers auto-fix themselves on create, the one rule you must never break, and exactly what we do at the 3.0 wipe.
Deep "why" lives in
CANONICAL_NIM_FREEZE.md(root cause) and the manual fallback innim-freeze/REMAP_RUNBOOK.md. This doc is the one to read first.
1. The one thing to understand
7DTD stores everything — your inventory, the blocks in the world, the trees — by a numeric id, not by name. The engine hands out those numbers automatically when it boots, and the assignment drifts whenever the build changes. "Build" = the exact set of mods + their load order + the RefugeBot DLL version + the engine version. Nothing else matters.
- Same build everywhere → same numbers everywhere. Inventory transfers cleanly, worlds load. (Proven: two cluster servers on the same build produce byte-identical id tables.)
- Different builds → different numbers → "morph". A glass block saved as id 21895 on one server reads as a car hood on another that put a different block at 21895. Characters can even reset to level 1.
Everything below — the canonical table, the stamp, the decoration remap, the auto-provision — exists for one reason: to bridge servers/worlds that were baked under different builds so they all agree on the numbers.
2. The golden rule (non-negotiable)
Every server in the cluster must run the byte-identical build at all times.
Same mods, same load order, same RefugeBot build, same game version — on all members, always. Break this and inventory morphs. The build can evolve over time, but only as a coordinated, all-servers-at-once maintenance event (see §6), never one server at a time.
3. The two moving parts
| Part | What it is | Who maintains it |
|---|---|---|
| The canonical | One frozen name ↔ id table (blockmappings.nim + itemmappings.nim) every server stamps on, so the shared player inventory (.ttp) decodes identically everywhere. Lives at /cluster/canonical/ (host: …/7dtdcluster/<id>/canonical/). |
nim-freeze/rebuild.py (re-anchor / extend) |
| The decoration remap | A world bakes its decorations (decoration.7dt / multiblocks.7dt) with the engine's native ids at generation. Those must be rewritten to the canonical's ids or the world NREs on load. |
nim-freeze/remap_7dt.py, now automatic (see §4) |
Why both: the canonical fixes inventory, the remap fixes the world the
decorations live in. The deep mechanics are in CANONICAL_NIM_FREEZE.md.
4. How it works now — auto-provision on create (the steady state)
You don't run any of the remap tooling by hand anymore. The game container's entrypoint + the agent do it for you. A brand-new clustered world becomes canon-compatible on its own, in two boots:
- Gen boot. You create the server (cluster picked), Update (downloads the game), and Start. The engine generates the world and bakes its decorations with native ids. The world is up but not yet aligned to the cluster.
- Align boot (automatic). The agent's auto-provision watcher notices the
fresh world's decorations are in the native band and bounces the container
once. On that boot, the entrypoint remaps
decoration.7dt+multiblocks.7dtnative → canonical (using the world's own native table), drops a.canon-provisionedmarker, and stamps the canonical. The world comes back clean, on canon, joinable, transfer-safe.
What you see (visibility): in the server's Console —
[panel] auto-provision: aligning the new world's decorations to the cluster…
then remapped decoration.7dt native->canonical then
decorations aligned to cluster canonical — world is now canon-compatible then
StartGame done. The card briefly shows "aligning decorations to cluster…".
It is a strict no-op for everything else: existing canon worlds (season10/creative/insane/pvp) and non-cluster servers never enter the remap branch (their decorations are already in the canon band), so recreating/restarting them does nothing. The remap is stable — once a world is canon, it stays canon across every save and reboot (the engine preserves loaded decoration ids; it only writes native ids at generation).
The backstop: even if the auto-bounce is ever missed, the entrypoint aligns the world on its next ordinary restart. The auto-bounce just makes it zero-touch.
Files that implement it (for the next engineer)
modules/7dtd/entrypoint.sh— the remap block (right before the canonical-stamp guard) + the.panel-save-basewriter. Gated on the decoration band only.modules/7dtd/Dockerfile— addspython3-minimal+ the vendored remapper.modules/7dtd/nim-freeze/remap_7dt.vendored.py— self-contained in-container remapper.agent/internal/dispatch/decoremap_provision.go— the watcher (single-flight, probes the running container, bounces once on a native-band world).
5. Adding a new server (operator steps)
- Add server in the panel, module 7 Days to Die, and pick the cluster in the create form. (This wires the shared Player folder + the canonical mount.)
- Update (installs the game) → Start. Let it generate the world.
- RWG worlds only: once the world has generated, set the "Shared player world"
in the Cluster tab to the new (seed-named) world. The picker writes
CLUSTER_PLAYER_SAVEand triggers a recreate, so the entrypoint stamps + aligns the right dir. Also point Region Medic at the same<World>/<GameName>(Cluster tab → Region Medic card), or Medic keeps healing the old, now-deleted world. Until both are set, alignment can't target the world. - That's it. The world auto-aligns to the cluster (watch the Console). When you see
StartGame doneafter the alignment bounce, it's joinable and transfer-safe.
Is the new world done? Don't let players join until all three pass:
- Console shows
StartGame doneafter theauto-provision: aligning…line.- Save dir has a
.canon-provisionedfile.- Decorations read in the 24xxx band — verify it yourself (read-only):
docker run --rm -v panel-<inst>-saves:/sv:ro debian:12-slim sh -c \ 'f="/sv/.local/share/7DaysToDie/Saves/<World>/<GameName>/decoration.7dt"; \ echo "first deco id = $(( $(od -An -tu2 -j17 -N2 "$f") & 0x7FFF ))"'
>= 24000= canon (good).< 24000= still native (NOT aligned — do not let players in).If after 2 boots there's still no
.canon-provisioned/ the band is native (the §12 #3 case): do one more panel Restart — the entrypoint backstop aligns it on that boot — and re-check. Still native after that → manual repair vianim-freeze/REMAP_RUNBOOK.md.
6. Changing mods (a maintenance event, not a casual edit)
Because of the golden rule, a mod change is a whole-cluster operation:
- Update every server's mods together (same set, same order, same RefugeBot build). Never one server at a time.
- Rebuild the canonical so it contains the new names (else they append per-world
again → morph). This is two steps — ⚠️
rebuild.pydoes NOT discover new mod content; it only re-anchors an existing name universe. Doing 2b without 2a silently produces an incomplete canonical and re-introduces the exact wipe bug this system prevents.- 2a — Re-harvest the complete name universe (the hard part). Blocks = the
<base>:<shape>cross-product (shapes.xml× the 10 shape-helper bases) ∪ the engine's authoritative block dump (telnetexportcurrentconfigs, or the runtimeBlocklist) ∪ every named block; items = every<item name=>∪ every<item_modifier name=>. The original harvest ran as two ultracode workflows; its inputs/outputs live on figaro at~/nimcanon/registry/but the generator is not committed — exact greps/commands are inCANONICAL_NIM_FREEZE.md→ "After a mod change". Do not attempt a mod change without re-running this harvest. - 2b — Re-anchor against season10's CURRENT, STOPPED
.nim(theWest Apeeni Mountains/MyGameanchor — verify 38,626/2,719+ first, §13) with the new universe:nim-freeze/rebuild.pypreserves every existing id and appends new names at fresh ids (so old worlds + the ~88 shared characters still decode). VerifyGATE PASS: True, then copy into/cluster/canonical/(chmod 644).
- 2a — Re-harvest the complete name universe (the hard part). Blocks = the
- Re-stamp every member so they pick up the new canonical — a panel Restart
is enough (the entrypoint re-stamps from
/cluster/canonicalon every boot; season10 too, by Restart only — never Rebuild). New worlds genned after the change auto-align (§4); existing worlds keep working (their content + the canonical's existing ids are unchanged).
Adding content is safe (append). Removing/reordering content is where ids shift — that's exactly what a wipe (§7) resets cleanly.
7. The 3.0 wipe roadmap (the easy button)
A version wipe is the moment to make the whole thing bulletproof and remap-free, because you're discarding all the old worlds anyway. Plan:
- Freeze + hash the 3.0 build. Lock the exact mod set + load order + RefugeBot DLL +
engine version, and record a hash of the artifact set (e.g.
tar c Mods/ | sha256sum) into a committed3.0-build.lock. That snapshot is the contract going forward. - Wipe all worlds — through the panel, never docker CLI. Per server: delete the old
Saves/<World>/<GameName>dirs (mount the saves volume, per the runbook's volume-access pattern) and set a freshworld_seedviaenv-config, or delete-preserve-volumes + recreate. Characters too —.ttpis build-dependent; a wipe is a fresh start for inventories (carrying them across a build change needs the.7rg/.ttpcodec work in §8). - Re-anchor the canonical to the LIVE 3.0 runtime. "Capture the current runtime's
table" = the
REMAP_RUNBOOK.md"CAPTURE the world's native table" sub-procedure: on a fresh 3.0 world, Stop → move the canonical aside (back it up first!) → write the 8-byte empty.nim→ Start (the engine writes the world's authoritative native table) → copy it out → restore the canonical. Feed that as the new anchor torebuild.pytogether with a freshly-harvested 3.0 universe (§6 step 2a). Nowcanonical == runtimeand the gap that forces per-world remapping is gone. - From then on, fresh maps just work with zero remap — every world is born from the same starting numbers. The auto-provision/stamp machinery stays in place as a harmless safety net; it simply has nothing to fix.
- Only a future mod change re-introduces the need to rebuild the canonical (§6).
⚠️ The 3.0 wipe is irreversible and depends on the (currently uncommitted) §6 harvest workflow. Do NOT execute it from this strategy alone — when 3.0 lands, write a dedicated, tested 3.0 runbook with the exact harvest + capture + wipe commands first. The plan above is sound; the runnable commands are the gap to close before you pull the trigger.
Net: today = same-mods rule + auto-provision handles new servers; at 3.0 = clean re-anchor and new maps need no special handling at all. It's a ~one-evening job when 3.0 lands; nothing to prep now.
The truly permanent fix that removes even the maintenance events: pin every block/item
to an explicit id= in a top-priority modlet so the runtime can never drift from the
canonical (untested on this engine version — a worthwhile experiment), or freeze the
RefugeBot build the canonical was anchored under.
8. Known limits / frontier
- RWG needs one pick. A random-gen world's save folder name is seed-derived and unknown until after gen, so the operator sets "Shared player world" once (§5 step 3). Auto-align kicks in after that.
- Regions + player
.ttpare not remapped. The codecs forRegion/*.7rg(explored chunk blocks) andPlayer/*.ttp(inventory) weren't cracked, so alignment is done on a fresh, 0-region world before exploration (which is exactly when a new server provisions). The 87 shared characters are fine (already canon-written). Solving these codecs (decompileAssembly-CSharp.dll) is what would let you migrate characters across a build change instead of wiping them.
9. Quick reference
| Want to… | Do this |
|---|---|
| Add a server | §5 (auto-aligns; RWG needs the one Cluster-tab pick) |
| Confirm a world is aligned | save dir has .canon-provisioned; decorations in 24xxx band |
| Change mods | §6 (all servers together + rebuild canonical) |
| Wipe for 3.0 | §7 (freeze build → wipe → re-anchor canonical to live runtime) |
| Manually fix a stuck world | nim-freeze/REMAP_RUNBOOK.md |
| Understand the root cause | CANONICAL_NIM_FREEZE.md |
| Re-anchor / extend the canonical | nim-freeze/rebuild.py |
| Deploy controller / agent / module | §10 (+ the panel-deploy skill) |
| Recover a wedged agent (config fields won't load / 504s) | restart panel-agent on figaro — §10 / §11 |
| Known issues & open work | §12 (season10 console, the "setting up" indicator, probe-path) |
| What's deployed vs. uncommitted | §13 |
10. Operational architecture & deploy (for the next engineer)
Where it runs
| Piece | Host | Notes |
|---|---|---|
| Controller (dashboard + gRPC) | kaiten <user>@<controller-host> |
binary /home/refuge/panel/bin/controller, unit panel-controller.service, HTTP :8180, gRPC :8443 |
| Agent (owns the docker game containers) | figaro <user>@<agent-host> |
binary /home/refuge/panel/bin/agent, unit panel-agent.service, dials the controller |
Cluster shared dir (the /cluster bind) |
figaro …/panel/data/7dtdcluster/cl_37e3a7f72cdc/ |
holds canonical/ + Player/; bind-mounted into every member as /cluster |
| Game containers | figaro, image panel-7dtd:latest |
named panel-<instance> (e.g. panel-rg-season-10) |
SSH everywhere with ~/.ssh/bubbly_ed25519, user refuge. The full deploy recipe is the panel-deploy skill — follow it. The NOPASSWD sudoers is exact-match (single-service systemctl forms only).
Panel access & conventions (read before driving anything)
- Dashboard:
http://<controller-host>:8180on the LAN (controller HTTP). Log in with your panel account. On kaiten itself thehttp://127.0.0.1:8180/api/...routes are unauthenticated — that's what the read-only diagnosticcurls in this doc use. - Server name → ids: an instance's id is its slug. "Season 10" ⇒ instance id
rg-season-10⇒ containerpanel-rg-season-10⇒ saves volumepanel-rg-season-10-saves. List them all (with status) viaGET /api/instances. Full live table in §13. - UI = API: the
POST /api/instances/<id>/{env-config,start,stop,rebuild}calls in these docs are exactly what the dashboard's Cluster-tab / Maintenance buttons fire. Use either — just target the right<id>. - "Stop / Start / Restart" always means the PANEL action (agent-owned, clean shutdown). NEVER
docker stop/startapanel-*container — the agent owns lifecycle + state reconciliation. Confirm a container is stopped via the grey card, or read-onlydocker inspect -f '{{.State.Running}}' panel-<id>→false. (Read-onlydocker exec/logs/inspectanddocker runsidecars are fine.) - "Restart" vs "Rebuild" (load-bearing): Restart = panel Stop then Start, same image — safe for any server including season10. Rebuild/Recreate = adopts
panel-7dtd:latest— needed to pick up a module/image change, but forbidden forrg-season-10(it must stay on its old image; §13). - New entrypoint env vars MUST be declared in
module.yamlenv:or the agent silently drops them (symptom: the var is empty at runtime even though you set it).CLUSTER_ID+CLUSTER_PLAYER_SAVEare already declared — add any newCLUSTER_*/ provisioning var there too.
Three deployable pieces
- Controller (Go + the dashboard HTML in
controller/cmd/controller/static/{new,index}.html) —GOOS=linux GOARCH=amd64 go build -o bin/controller-linux ./controller/cmd/controller, scp to kaitenbin/controller.new, bak + swap +systemctl restart panel-controller.service.- ⚠️ After every controller deploy, HARD-refresh the browser (Ctrl-Shift-R). A normal refresh serves the old cached JS against the new backend and the page half-breaks (fields won't load, console blank). It looks exactly like a real outage but is just stale cache — rule this out first.
- Agent (Go) —
… go build -o bin/agent-linux ./agent/cmd/agent, scp to figaro, bak + swap +systemctl restart panel-agent.service.- Restarting the agent does NOT stop the game containers — it re-attaches/rehydrates them cleanly (stats + rcon + log streams re-open). Safe anytime; it is also the recovery for a wedged agent (§11).
- Module (
modules/7dtd/{entrypoint.sh,Dockerfile,nim-freeze/remap_7dt.vendored.py}) — sync the module dir Windows → kaiten → figaro, then build the image on figaro (where the containers run; kaiten holds the source-of-truth copy but builds nothing):Existing containers pin to the old image ID — a container only adopts the new image via Settings → Maintenance → Rebuild (or delete-preserve-volumes + recreate).# from the Windows repo C:\Users\dbled\sources\panel : scp -r modules/7dtd/ <user>@<controller-host>:/home/refuge/panel/modules/ ssh <user>@<controller-host> 'rsync -av /home/refuge/panel/modules/7dtd/ <user>@<agent-host>:/home/refuge/panel/modules/7dtd/' ssh <user>@<agent-host> 'cd /home/refuge/panel/modules/7dtd && docker build -t panel-7dtd:latest .'- ⚠️ Never Rebuild/recreate
rg-season-10. It is the live released server and the canonical anchor; it must stay on its current (old) image..Config.Imageshows the name:latestfor every container — verify by image ID, not name.
- ⚠️ Never Rebuild/recreate
Do NOT docker stop/start a panel-* game container from the CLI — let the panel do it (the agent owns lifecycle + state reconciliation). Read-only docker exec, docker logs, docker inspect, and docker run sidecars are fine.
11. Incident log & landmines
2026-06-13 — agent-wide hang (ExecCapture connection leak) ✅ FIXED + HARDENED
- Symptom: config fields wouldn't load on any server (the durable
serverconfig.xmleditor →/api/instances/{id}/files/readreturned HTTP 504 "timeout waiting for agent response"); one server's console went blank; the agent was idle (0% CPU) but unresponsive to file ops; no panic. - Root cause: the auto-provision watcher (
agent/internal/dispatch/decoremap_provision.go) polls a running container with a docker exec (DockerRuntime.ExecCapture) every 20s.ExecCaptureranstdcopy.StdCopy(…att.Reader)on the hijacked exec stream with noctxenforcement on the Read. When it caught the just-bouncedcluster-seed-testmid-restart, the read blocked forever and leaked a docker SDK connection each cycle → the SDK client's connection pool exhausted → every later docker op (CopyFileFromContainerfor config reads, a log-stream re-attach) hung. It is not a Go-mutex deadlock (the only mutex indocker.go—hijackedStream.mu— just guards the stdio-attachClose()and is unrelated); it's connection-pool starvation. - Recover:
ssh <user>@<agent-host> 'sudo -n /usr/bin/systemctl restart panel-agent.service'— clears the leaked connections; game containers keep running. Confirm recovery: re-open any server's Config tab (fields load within a few seconds), or on kaitencurl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:8180/api/instances/rg-season-10/files/read?path=/game-saves/serverconfig.xml'→ expect 200, not 504. - Permanent fix (shipped):
ExecCapturenow runsStdCopyon a goroutine andselects onctx.Done(); on timeout itatt.Close()s to unblock the read, then drains the goroutine before reading the buffers (race-free). On the timeout path it returns the partial stdout/stderr with exit code-1andexec copy: <ctx err>, so callers can tell a wedged exec from a clean one. An exec can no longer hang the whole agent. - Landmine for the future: any agent docker exec/stream read that ignores
ctxcan leak connections and wedge the entire agent the same way. If you add exec-based features, bound the read byctx. (The log-stream demux atdocker.go:271is a long-livedFollowstream — a different model, closes on the dockerrc's EOF/ctx — and is fine.) - Recognize it next time:
files/read504s for every server + agent at 0% CPU + no crash ⇒ restart the agent first, then hunt for a new unbounded docker read.
12. Known issues & open work
-
season10's panel console is blank (separate from §11; pre-existing). season10's docker json-log has grown to 13 MB and it is actively spamming exceptions:
No ItemClass entry for type 678xx(×611) +Log:Exception/ThreadManager:UpdateMainThreadTasks. Docker's own--follow --tail 400serves season10 instantly and the other four servers stream fine, but the agent's SDK log-follow stalls on season10's stream before forwarding a single line (blocked-inside, not thrashing — no "log stream ended" retries). Re-attaching (two agent restarts) did not fix it. It will likely clear on a season10 container restart — a plain panel Stop → Start, which keeps season10 on its current image and is safe; this is not a Rebuild/recreate (that would adoptpanel-7dtd:latestand break the canonical anchor — forbidden, see §10/§13). It may re-blank once the exception spam refills the log. Durable fix: resolve theNo ItemClass entryitem-config gap (kills the console stall, the log bloat, and the exceptions at once), or harden the agent log-follow — mechanism not fully pinned (the agent has no pprof endpoint, so no clean goroutine dump was possible). Do NOT recreate season10 to fix this (old-image constraint, §10). -
The "setting up" provisioning indicator doesn't surface. The agent watcher emits
detail="Finishing cluster setup…"and the dashboard renders a matching amber "setting up" pill — but the agent's own state-reconciliation loop emitsdetail="announced:running"a beat later and overwrites it, so the card flips green prematurely. The Console line (auto-provision: aligning the new world's decorations to the cluster…) does show and survives a refresh — that part works. Proper fix: a dedicated provisioning state field the reconciler respects, not the shareddetailstring. (The card-pill matcher is live but dormant innew.html/index.html; harmless.) -
Auto-provision watcher probe-path caveat (zero-touch isn't universal yet). The watcher gates "gen finished" on
main.ttwonly; it readsdecoration.7dtsolely to detect the already-canon (≥24000) case. A missingdecoration.7dtstill yieldsNEEDS_ALIGNand a bounce — the graceful-stop save writes the native deco, which the entrypoint then remaps on the align boot. For some RWG worlds (e.g. Tuxeno oncluster-seed-test/pvp) that two-step doesn't always complete zero-touch, but the entrypoint backstop still aligns the world on its next ordinary restart, so it ends up correct. Worlds that bake deco at the save path at gen (e.g. refuge-mall) provision fully zero-touch. Treat the entrypoint remap as the reliable path; the watcher bounce is the optimization. -
No ItemClass entry for type 678xxon season10 (root of #1). A set of high-band item ids (mod / RefugeBot runtime items) the merged config references but doesn't define. Tracing the missing items cleans up the console stall, the 13 MB log bloat, and the on-server exceptions in one shot. Not urgent — season10 runs fine (20 FPS, players on).
13. Handoff status — what's deployed, what's uncommitted
Cluster: cl_37e3a7f72cdc. Shared Player/ ≈ 88 .ttp. Live members:
| Server | Instance id | Container / saves volume | Active world (CLUSTER_PLAYER_SAVE) |
Role |
|---|---|---|---|---|
| Season 10 | rg-season-10 |
panel-rg-season-10 / panel-rg-season-10-saves |
West Apeeni Mountains/MyGame (canonical anchor) |
master / anchor — DO NOT Rebuild |
| Creative | creative |
panel-creative / panel-creative-saves |
Tefasizi County/Refuge |
creative |
| Insane Survival | insane-survival |
panel-insane-survival / panel-insane-survival-saves |
Yixacove Valley/Refuge |
survival |
| PvP | cluster-seed-test |
panel-cluster-seed-test / panel-cluster-seed-test-saves |
Tuxeno Territory/RefugeTHREEPVP |
pvp |
| Mall | refuge-mall |
panel-refuge-mall / panel-refuge-mall-saves |
New Vebape Territory/RefugeMall |
mall |
Canonical anchor — the single highest-blast-radius fact. The complete frozen table (38,626 blocks / 2,719 items) lives ONLY at season10's
…/Saves/West Apeeni Mountains/MyGame/{block,item}mappings.nim. season10's other save dirs are stale partials (e.g.Navezgane/MyGame= 3,356 blocks,Iupu Territory/Troutdale= 5,028) and itsCLUSTER_PLAYER_SAVEenv is empty (old image, predates the picker — so don't trust the env to find the anchor). Any re-anchor (§6 / §7) MUST copy from exactlyWest Apeeni Mountains/MyGameand verify the counts read 38,626 / 2,719 (or higher — never lower) before runningrebuild.py. Anchoring against a partial silently wipes the ~88 shared characters.
Deployed (live):
- Controller on kaiten — has the create-form cluster note + the auto-provision "starting" chip + the (dormant) "setting up" card-pill code.
- Agent on figaro — has the auto-provision watcher and the hardened
ExecCapture(§11). - Image
panel-7dtd:lateston figaro — has the entrypoint remap block +python3-minimal+ the vendoredremap_7dt.py. Running on creative / insane-survival / cluster-seed-test / refuge-mall. rg-season-10deliberately on the OLD image (no.panel-save-base, no remap block) — the live anchor; do not move it.- refuge-mall — created via the panel this session and registered as a RefugeBot tenant (prod DB row Id=31 +
refugebot.jsonin its saves volume), connected.
⚠️ UNCOMMITTED — nothing from this work is in git. C:\Users\dbled\sources\panel is a git working tree but all of the below is uncommitted: the controller dashboard edits, the agent Go (decoremap_provision.go, the dispatch.go hook + provisionGuard, the ExecCapture hardening), the module (entrypoint.sh remap block, Dockerfile, nim-freeze/remap_7dt.vendored.py), and these docs. Rollback points if a deploy goes bad: controller.bak-<ts> on kaiten, agent.bak-<ts> on figaro (newest = the binary just before the current one).
If/when you commit, suggested grouping: (1) canonical + deco freeze/remap, (2) auto-provision (entrypoint + watcher), (3) the ExecCapture hardening, (4) docs. Note in the message that the cluster is live so a reviewer treats it as load-bearing.
14. Disaster recovery — the three irreplaceable trees
These three live on figaro and cannot be regenerated from the repo — they are the source of truth. If figaro's disk dies, this is what hurts:
| Tree | Path on figaro | What it is | If lost |
|---|---|---|---|
| The canonical | …/panel/data/7dtdcluster/cl_37e3a7f72cdc/canonical/{block,item}mappings.nim |
the frozen stamp source every member uses | rebuildable from season10's West Apeeni Mountains/MyGame anchor via rebuild.py (needs ~/nimcanon) |
| Shared characters | …/panel/data/7dtdcluster/cl_37e3a7f72cdc/Player/ (= /cluster/Player) |
the ~88 player .ttp (every character + inventory) |
unrecoverable — everyone resets to level 1 |
| The rebuild toolchain | ~/nimcanon/ (tool/, registry/, canonical_final/, remap/) |
the only parser + name registry + capture tooling to rebuild any of the above | must re-harvest from scratch (the §6 harvest — currently uncommitted) |
⚠️ Open risk — confirm these are actually backed up off-figaro.
~/nimcanonis persisted out of/tmp(survives a reboot) but a disk loss is unaddressed in these docs. Recommendation: snapshot all three trees to another host on a schedule (reuse the panel's backup system or a periodictar). Restore order: canonical → Player → validate with an in-game transfer (CANONICAL_NIM_FREEZE "Verify a server is frozen"). Treat this as the top open risk until a backup is confirmed.