The backup tool preserves a consistent PostgreSQL snapshot and every retained engine referenced by its published releases. Restoring only the database can leave a saved room unable to start. This archive includes traveler credentials, inventory, private keepsakes and their audit metadata, chests, receipts, upgrade history, outboxes, world source, and the exact executable/client files. It covers the PostgreSQL persistent profile; legacy editor and beta files use separate storage.
#Create and verify
Use Node 22.23.4 and PostgreSQL client tools (pg_dump and pg_restore) matching the source database's major version. Set the same PGHOST, PGUSER, PGDATABASE, PGPASSWORD and DW_DATA_DIR as the room, or use DW_DATABASE_URL for the connection. A URL takes precedence over the separate PG variables. The tool reads the current schema (6); it never applies a migration. Older databases and archives require their matching earlier backup tool (for example 542fe9d for schema 5 or 9a6ab5f for schema 4); the current tool refuses a mismatched schema rather than silently omitting tables.
Choose one destination:
# Private filesystem object directory on your backup disk.
export DW_BACKUP_DIRECTORY=/path/to/private-backups
npm run backup -- createFor an existing private S3 or compatible bucket, leave DW_BACKUP_DIRECTORY unset:
export DW_BACKUP_BUCKET=your-private-backup-bucket
export AWS_REGION=us-east-1
# Supply credentials through the SDK's normal AWS credential chain.
# For a compatible service, also set DW_BACKUP_ENDPOINT and, if needed,
# DW_BACKUP_PATH_STYLE=true.
npm run backup -- createDW_BACKUP_PREFIX defaults to darkwind-backups. The tool does not create buckets, change access policies, schedule backups, or delete archived objects. Use a bucket with private access and permission to get/put objects under this prefix. An S3 configuration error fails the command; it does not fall back to a local directory.
Keep the returned JSON receipt somewhere independent of the source host. Its backup field is the immutable SHA256:BYTES identifier needed to find and verify the manifest. The receipt also reports capture time, each room's committed sequence, engine count and elapsed time.
npm run backup -- verify SHA256:BYTESVerification downloads and hashes the dump and every engine file, checks each retained manifest and the exact Node version, and removes its temporary verification directory. It does not contact the database or change a room. A successful hash check is not a substitute for a restore rehearsal.
The room may stay online during capture. An exported PostgreSQL snapshot gives the dump, room sequences and logical table digests the same database view. Transactions committed after that view are deliberately outside the backup. Engine objects are immutable; the tool reads all referenced files and checks them against their retained manifests before publishing the final backup manifest. A missing engine refuses completion. A failed upload can leave unreferenced objects, but cannot publish a successful receipt for an incomplete archive.
#Restore into a separate target
Create a new, empty database and choose a new directory whose parent exists. Preserve the source database and engine directory. Point the connection variables at the new target; if using DW_DATABASE_URL, change that URL too.
createdb darkwind_restored
export PGDATABASE=darkwind_restored
export DW_DATA_DIR=/path/to/new-restored-world
npm run backup -- restore SHA256:BYTESRestore refuses any existing target directory or nonempty target database. It verifies all stored bytes, installs and syncs the retained engines, restores SQL with pg_restore --single-transaction --exit-on-error, then compares every logical control-table digest and room sequence with the snapshot. It never uses --clean, drops a database, or changes source state.
A restore-pending.json marker blocks the production bootstrap until verification finishes. If the process stops after SQL committed but before its final acknowledgement, retry verification without replaying SQL:
npm run backup -- finish SHA256:BYTESfinish checks the target database and retained files against that exact manifest before removing its matching marker. It is safe to repeat before opening the restored room. If the SQL restore did not finish, this check fails and leaves startup blocked; use another empty database/new directory for a fresh restore. Do not remove the marker to bypass verification. A failed SQL restore can leave verified engine files in the new target directory; it does not overwrite an existing world.
Stop the original room before promoting the restored copy and allow its old lease to expire (at most 30 seconds). Database leases cannot fence two independent database copies. Launch with the current production bootstrap and the restored connection/directory; it selects the snapshot's pinned engine. Do not rebuild or republish the room to replace missing backup assets.
npm run dev:persistentOpen the same origin and room with the browser's still-valid return credential to recover the same traveler. A different origin has different local storage; moving a credential between origins and account recovery are separate work. The browser acceptance test restores a new database and engine directory, makes the original engine location unavailable, then reconnects both original travelers and takes the saved lantern out of their chest.
#Docker and Coolify
compose.persistent.yaml includes an optional maintenance profile and Dockerfile.backup. The helper contains pinned Node and PostgreSQL tools. Its source engine volume is read-only. It runs on demand; normal room startup does not run a backup.
For a local archive, set DW_BACKUP_DIRECTORY=/backups. Docker creates/reuses the darkwind-persistent-backups volume. For S3, leave that variable empty and set the S3 variables above. From the Compose resource's directory:
docker compose -f compose.persistent.yaml --profile maintenance build backup
docker compose -f compose.persistent.yaml --profile maintenance run --rm backup create
docker compose -f compose.persistent.yaml --profile maintenance run --rm backup verify SHA256:BYTESA backup volume on the same host does not survive loss of that host. Copy it off-host or use the remote object store. Backups contain private traveler/world data; preserve their access controls. Native production restore is covered locally; Docker/Coolify execution requires its separate host acceptance and is not inferred from those tests.
For restore, provision an empty database and a separate writable restore volume. Override PGDATABASE (or the PG host settings) and DW_DATA_DIR=/restore/world, mounting the new volume at /restore. Keep the source engine mount read-only and leave the original resource intact until the restored world has been checked. Do not direct the backup helper at the running room's database for restore.
#Budgets and recovery scope
Objects are bounded to 128 MiB; each engine retains its existing 128 MiB total/32 MiB file limits. A backup supports 16 engines, 128 rooms, 200,000 rows per control table and 256 MiB of logical row data. Object transfers run sequentially with per-object bounds; backup also holds the compressed database dump in memory. These are prototype limits, not capacity measurements. Retention, object garbage collection and a backup catalog UI remain future work; keep the manifest receipt.
This is point-in-time recovery. It does not provide continuous WAL archiving, a zero-loss remote replica, or a five-second motion recovery guarantee. The captured sequence makes the recovery boundary explicit. Local rehearsal timings belong to their fixture and host; they are not production recovery objectives.
Run the tests with:
npm run check
npm test
npm run test:control
npm run test:browser:backupsThe native suite covers concurrent snapshot capture, exact table/engine restoration, corrupt/missing objects, refused occupied targets, failed SQL restore, uncertain acknowledgement recovery and blocked premature startup. S3 adapter tests exercise signed requests, conditional writes, duplicate responses, corruption and authorization failure against an isolated HTTP fixture; they do not qualify a remote provider account.
Implementation references: PostgreSQL consistent dump/snapshot options, transactional restore options, and S3 conditional object writes.
#Vehicle ownership qualification — October 28
The supported control-table catalog now includes dw_vehicle_owners from vehicle
transfer migration 15. Omitting it caused exact-schema checks to refuse backups
on an otherwise supported current database. Logical snapshot/restore verification
now includes those rows; unknown tables and migration mismatches still fail
closed. The restore integration check explicitly seeds and compares vehicle
ownership, in addition to inventory, accounts, floating state and retained files.
Its current qualification result is recorded in ENGINE_PROGRESS.
Build queues use their own database in compose.builds.yaml. Their acceptance
checks now create and remove a uniquely named disposable database, including
worker/CLI subprocesses, instead of leaving build tables in the control test
catalog. A control backup still refuses unknown tables; it does not silently
omit a colocated build queue. Back up a dedicated build database separately.