Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Sekai takes chunk-level deduplicated snapshots of Minecraft Java region files (.mca) and rebuilds the world from any snapshot quickly. It is built for frequent hot-recovery — rewinding a live server after griefing or a bad update — rather than long-term archival.

Identical chunk payloads are stored once and shared across snapshots via content-addressed storage, so an unchanged backup costs roughly one metadata row instead of a full world copy. Rollback reproduces the exact bytes captured for the snapshot, atomically and verbatim.

Sekai ships two things:

  • the sekai CLI documented in this guide,
  • a reusable Rust library (sekai-app over sekai-core) for server-management software. The library API is documented with rustdoc (cargo doc); this book covers operating the CLI only.

Design background lives in ARCHITECTURE.md: layering, the two-layer hashing model, and the MVCC/GC data model. The machine-readable output contract lives in docs/json.md.

Installation

Prebuilt binaries are attached to each GitHub Release (sekai-<target triple>.tar.gz). Alternatively, with a Rust toolchain installed (the pinned version is in rust-toolchain.toml):

cargo install sekai-cli

The installed binary is named sekai. To build from a checkout instead (for development):

cargo install --path ./crates/cli

Verify the setup:

sekai --help

Quickstart

# Record the current world state (pause the server first; see
# "Server coordination")
sekai --store ./sekai-store backup ./world

# Preview what a backup would record, without writing anything
sekai --store ./sekai-store status ./world

# List snapshots
sekai --store ./sekai-store list

# Rebuild the world from snapshot 1 (overwrites region files)
sekai --store ./sekai-store rollback ./world 1

# Compare chunk NBT between snapshots 1 and 2
sekai --store ./sekai-store diff 1 2 --in overworld:0,0

# Name snapshot 2 for later reference
sekai --store ./sekai-store tag stable 2

# Preview unreferenced blobs, then collect them
sekai --store ./sekai-store gc --dry-run
sekai --store ./sekai-store gc

# Delete old snapshots (keep newest 10), then reclaim their blobs
sekai --store ./sekai-store prune --keep-last 10
sekai --store ./sekai-store gc

# Rebuild snapshot 1 into a fresh directory (live world untouched)
sekai --store ./sekai-store export 1 ./restored

# Inspect region files without touching anything
sekai debug scan ./world

--store names the backup store directory (created when missing). Every command accepts --json for machine-readable output and, except list and tag, --timing for a per-phase breakdown. backup, status, rollback, diff, export, gc, and prune take --progress for a stderr progress bar (refused with --json). backup, status, rollback, diff, and export accept a scope (--in, --region, --kind); nothing selected means the whole world.

Server coordination

sekai never touches the server process. Pausing writes around a backup belongs to the caller or the administrator — never to the tool:

  1. save-off (stop the server from writing region files),
  2. save-all (flush pending writes to disk),
  3. run sekai backup,
  4. save-on (resume).

Backing up a world that is being written concurrently can capture torn sectors. Reads tolerate trailing partial sectors the way vanilla does, but genuinely corrupt runs fail loudly instead of being backed up silently. Rollback and export read only from the store, so they need no server coordination beyond stopping the server before you let players back in — rollback overwrites live region files.

World layouts

Three server families share the .mca format but not the directory layout. Pass the same path on every run:

  • Vanilla: one world folder (region/, DIM-1/, DIM1/, and since 26.1 dimensions/minecraft/<name>/).
  • Bukkit-family (Bukkit/Spigot/Paper/Purpur, pre-26.1 layout): the server root, holding <base>/, <base>_nether/DIM-1/, and <base>_the_end/DIM1/, where base is the level-name (world by default). Paper 26.1+ migrates to the vanilla layout.
  • Plugin worlds (Multiverse et al.): arbitrary folders, detected by their contents.

The full namespace rules live in ARCHITECTURE.md (“World Layouts”). Two practical consequences: custom-dimension folders are content-hashed, so renaming one orphans its history; and rollback restores moved folders through discovered siblings when possible, failing loudly (UnknownRegionPath) rather than writing somewhere wrong.

Concepts

  • Snapshot: one recorded world state, numbered in creation order (list shows them oldest first). Snapshots store only fresh rows; reads resolve effective state through fallback, so metadata grows with changes, not with universe size.
  • Blob (CAS key): the exact raw chunk payload, hashed with framing included. Identical payloads share one stored copy across all snapshots; rollback restores these bytes verbatim, rewinding volatile tags such as LastUpdate to capture time.
  • Diff hash: an opt-in volatile view over decompressed NBT with non-essential tags excluded. It speeds up change detection only and never alters stored blobs.
  • Tombstone: an explicit “chunk absent” row. A chunk deleted between backups is recorded as a tombstone, not as silence — this is what lets rollback delete it again faithfully.
  • Derived state: region_state fingerprints speed up the next backup. Wiping them costs at most one full ingest, never wrong data.

The authoritative definitions are in ARCHITECTURE.md (“Data & Hashing Model” and “History, State, and GC Model”).

Scope selection

backup, rollback, diff, and export accept a scope: the whole world by default, or per-dimension areas. Entries compose by union; --kind (repeatable, empty means all) applies to every area.

sekai --store ./sekai-store backup ./world --in overworld --kind region --kind entities
sekai --store ./sekai-store rollback ./world 3 --in overworld:0,0..31,31
sekai --store ./sekai-store diff 1 2 --in overworld:0,0
sekai --store ./sekai-store export 3 ./restored --region overworld:0,0
  • --in DIM selects a whole dimension; --in DIM:x,z one chunk; --in DIM:x0,z0..x1,z1 an inclusive chunk rectangle.
  • --region DIM:RX,RZ selects every chunk of one region file (rectangle shorthand).
  • --kind selects region families (region, entities, poi).

The scope is a filter, not a partition: a scoped backup reads as the truth for its scope only (out-of-scope coordinates record nothing and resolve through fallback), and a scoped rollback or export never touches files outside the scope. See ADR-0003.

Backup

sekai --store ./sekai-store backup ./world

Records the current world state as a new snapshot and prints the snapshot id, chunk/blob counts, tombstones, skipped regions, and carried chunks.

  • --with-diff additionally derives volatile diff views alongside blobs (slower ingest; the hot path stays decode-free without it).
  • --jobs N sets the ingest worker count (0 means one per CPU).
  • --progress shows a stderr progress bar (refused with --json).
  • Scope flags (--in, --region, --kind) restrict what is recorded; see Scope selection.
  • For a dry-run preview of the above, see status under Inspection.
  • When the store directory is created by the backup, the CLI says so on stderr (human output only).

Unchanged regions are skipped by fingerprint; unchanged chunks inside changed regions resolve through fallback and cost no new blobs. A backup with no changes still writes one snapshot row.

Rollback

sekai --store ./sekai-store rollback ./world 1
sekai --store ./sekai-store rollback ./world @stable --in overworld

Rebuilds the world from a snapshot, overwriting region files atomically (temp file in the target directory + fsync + rename; in-place mutation never happens). The snapshot argument accepts <id> or @tag (see Tags). Files are stamped with the snapshot time, and volatile tags are rewound to capture values.

The default policy is strict:

  • region files unknown to the snapshot (created afterwards) are deleted;
  • chunks unknown to the snapshot vanish on rebuild;
  • tombstoned chunks are removed (fully tombstoned regions delete the file, never leaving a header-only shell);
  • a blob missing from CAS aborts loudly (corruption — a partial world would be worse than none).

Each behavior is configurable without changing the default:

FlagEffect
--keep-post-snapshot-fileskeep snapshot-unknown files instead of deleting them
--keep-post-snapshot-chunkskeep live bytes for snapshot-unknown chunks inside rebuilt regions
--keep-tombstoned-chunkskeep live bytes for tombstoned chunks; fully tombstoned files are left alone
--on-missing-blob skip-chunkskip chunks whose blob is missing (default abort)
--on-missing-file derived-only|errorignore sibling folders, or fail, instead of guessing a location (default sibling-first)
  • Scope flags (--in, --region, --kind) rebuild and delete only inside the scope; see Scope selection. Combine with --timing/--json as usual.
  • Before rebuilding, the CLI echoes the target snapshot, scope, and policy to stderr (human output only).

Export

sekai --store ./sekai-store export 1 ./restored

Rebuilds a snapshot into a fresh directory. Unlike rollback, the live world is never touched — export shares the rollback restore set but writes every file under the output directory at its layout-derived path. The snapshot argument accepts <id> or @tag (see Tags).

  • The output directory is created when missing and must otherwise be empty; anything else fails loudly so export can never clobber data.
  • --flavor legacy|new|bukkit selects the output layout (legacy: region/, DIM-1/, DIM1/; new: dimensions/minecraft/<name>/; bukkit: split folders). --base names the overworld folder for the bukkit flavor (level-name, default world).
  • Scope flags (--in, --region, --kind) restrict what is exported; tombstoned regions produce no files. See Scope selection.
  • --on-missing-blob skip-chunk skips chunks whose blob is missing (default abort, as in rollback).
  • Only vanilla namespaces are derivable; custom dimensions fail loudly (UnknownRegionPath) instead of landing somewhere wrong.

Tags

sekai --store ./sekai-store tag stable 1
sekai --store ./sekai-store tag stable @prev --force
sekai --store ./sekai-store tag -d stable
sekai --store ./sekai-store tag

Tags give snapshots human-readable aliases so rollback targets stay readable (rollback ./world @stable instead of an ID). Names use [A-Za-z0-9._-], run 1–64 bytes, and are never all digits, so @123 cannot be confused with snapshot 123.

  • Snapshot arguments to rollback, export, and diff accept <id> or @tag; reports always carry the resolved numeric ID.
  • Creating over an existing name fails unless --force moves it. Deleting a missing tag fails loudly.
  • Bare tag lists all tags in name order; list shows each snapshot’s tags alongside.
  • Tags are metadata only and constrain nothing: pruning a tagged snapshot drops its tags with it.

Maintenance

sekai --store ./sekai-store gc --dry-run
sekai --store ./sekai-store gc

gc collects CAS blobs no snapshot references. Planning is read-only; applying re-verifies candidates against fresh metadata before unlinking, so the two-phase shape (gc plan then apply) holds even though the CLI runs both in one invocation. gc --dry-run prints the plan only, and combines with --timing (the plan phase is measured; apply_ms is 0) but not with --progress, which has no apply phase to report.

GC removes only orphan blobs, never metadata; snapshot deletion is prune (fold-into-next-retained, then gc to reclaim).

Pruning snapshots

sekai --store ./sekai-store prune --keep-last 10 --dry-run
sekai --store ./sekai-store prune --keep-last 10
sekai --store ./sekai-store prune --before @stable

prune deletes old snapshots. Retention keeps the newest --keep-last N intersected with --before and newer (at least one selector is required); a selection retaining nothing fails loudly instead of wiping the store.

Deletion folds oldest-first: each retired snapshot moves its still-effective rows onto the next retained snapshot and drops rows already superseded there, so every retained snapshot restores exactly as before. Tags on deleted snapshots disappear with them. Pruning never unlinks blobs — run gc afterwards to reclaim the dereferenced ones. --dry-run prints the planned deletions only.

For a full secondary backup of everything — snapshots, metadata, and blobs — copy or archive the entire store directory to an external location. Per-command file writes are crash-ordered (blobs before metadata commits, unlinks after), so a copied store is self-consistent.

Inspection

These commands never write to the world or the store.

sekai --store ./sekai-store list
sekai --store ./sekai-store status ./world
sekai --store ./sekai-store diff 1 2 --in overworld:0,0
sekai debug scan ./world
  • list prints snapshots oldest first with raw Unix-millis timestamps (RFC 3339 rendering is human-output only). Tags pointing at each snapshot are shown alongside; see Tags. --stat appends per-snapshot change statistics (fresh/tombstone/new-blob/effective counts). An empty store prints a note to stderr and exits 0.
  • status previews what a backup would record: the basis snapshot (latest, null when the store is empty), changed regions (new and deleted files split out), chunks that would be newly recorded, tombstones, and blobs absent from CAS. clean means nothing in scope differs from the latest snapshot; counts match a subsequent backup unless the world changes in between. There is deliberately no backup --dry-run: status is that command.
  • diff compares chunk NBT between two snapshots, or between the live world (--world) and a snapshot. One --in DIM:x,z selection keeps the single-chunk output; several selections switch to grouped output, omitting chunks without differences. Scope flags (--in, --region, --kind) select the compared chunks; see Scope selection. A chunk absent or tombstoned on a side diffs as an empty compound there — missing coordinates are never errors (see ADR-0006); only corrupt payloads and missing blobs fail loudly. --show-values prints concrete old/new values in human output.
  • debug scan lists region files with size, mtime, chunk count, and header hash, plus per-phase timings with --timing.

Automation

Every command accepts --json: stdout carries exactly one JSON document, stderr stays silent on success, and colors never appear. Every command except list and tag accepts --timing, which prints a per-phase table in human mode and merges the same block into the JSON document in --json mode. --progress draws a stderr bar and is refused with --json.

Exit codes: 0 success, 1 runtime failure (stdout holds the error envelope), 2 usage error (clap prints to stderr).

Dry runs by command:

PreviewDry-run means
statusthe whole command (backup has no --dry-run by design)
gc --dry-run, prune --dry-runplan only, nothing deleted
rollback, exportnone (rollback is destructive by definition; export targets an empty directory, so it is trivially reversible)

The full contract — flag matrix, envelope, per-command payloads, and the compatibility promise — is docs/json.md. That page is the reference; this book does not duplicate it.

Troubleshooting

  • unknown snapshot: N — the id does not exist. Check list; ids are never reused.
  • blob missing from CAS: <hex> — store corruption (or a store copied incompletely). Rollback aborts rather than writing a partial world; --on-missing-blob skip-chunk (rollback/export) continues without the affected chunks. Restore the store from the secondary copy, then investigate.
  • cannot derive region path for dim ... (UnknownRegionPath) — a custom-dimension region whose folder is unknown, or --on-missing-file error refusing to guess. Run it against a world tree with the same folder layout the backup came from so sibling derivation can work, or accept that the region cannot be placed.
  • unsupported schema version — the store was written by a newer (or older) binary. The project is pre-release: recreate the store, don’t migrate.
  • Region parse failures name the file (failed to process region file ...). Torn tails from interrupted saves are tolerated when unreferenced; genuinely corrupt sector runs fail loudly by design.
  • Slow commands: rerun with --timing (release build) and compare phases; for backup-path slowdowns prefer --timing --json, which carries the per-region breakdown.