MirrorUP
Incremental mirror sync: only changed files and changed region chunks are transferred.
Installation command
!!MCDR plugin install mirror_upAuthor
Repository
Homepage
Synced at
...
Last update
...
Latest version
Total downloads
17
Back to catalogue
MirrorUP
Incremental mirror sync for MCDR: only files that actually changed are transferred, and .mca regions are synced chunk by chunk.
Features
- File-level increment: a cross-run index remembers every file's
size / mtime / hash, so unchanged files are never read or copied again. - Chunk-level increment: only dirty chunks inside a region are touched (timestamp filter + hash confirmation); unchanged chunks are reused in place.
- Mirror drift recovery: files modified on the mirror side are detected on the next round and restored.
- Atomic writes: data goes to a temporary file first, then
os.replace; a half-written file never appears on the mirror, and failures are retried next round. - In-place region writes:
.mcauses append-and-commit with an 8 KB header journal, so changing one chunk writes about one chunk; fragmentation is compacted automatically. - External chunks too: regions whose chunks live in
.mccfiles only move their placeholder records;.mcccontents are synced as ordinary files, and orphaned.mccfiles are cleaned up. - Cross-machine sync: the main server can run as a read-only
exporter(HTTP + Bearer token, optional TLS pinning), so the mirror does not need the main server's disk mounted. - Automatic server stop:
rcon, MCSManager (mcsm), custom commands (external) andself(the server this MCDR manages). If the target cannot be stopped, nothing is written. - Multiple mirror targets: one source, several mirror directories, each with its own stop method.
- Verify & history:
!!mu verifychecks source / mirror / index consistency (--deepcompares hashes),!!mu historylists past rounds. - Scheduled sync: interval from 60 seconds to 7 days, toggleable at runtime.
Requirements
MCDR ≥ 2.12.0, Python ≥ 3.9, xxhash.
Install
- Put the plugin into MCDR's
plugins/directory (the packedMirrorUP-v0.6.3.mcdror theMirrorUP/folder). pip install xxhash. MCDR only verifies requirements, it does not install them; a missing package showsRequirementCheckFailurein the log.- Restart MCDR.
config/mirror_up/config.jsonis generated on first load.
Quick start
- Edit
source.root(main server directory) andtarget.root(mirror directory). Relative paths are resolved against the MCDR root, not the server working directory. !!mu diffprints the plan without touching anything.!!mu syncstops the mirror (if needed), writes the difference and starts it again.- For periodic sync:
!!mu schedule set 30mthen!!mu schedule on.
Configuration (highlights)
| Key | Default | Meaning |
|---|---|---|
source.root | ./server | Main server directory (the one containing world/, mods/) |
source.transport | local | local reads the disk directly; agent pulls from an exporter |
role | driver | driver runs sync; exporter only serves data |
targets | one default | [{"name","root","enabled","mirror_control"?}], per-target overrides |
sets | world, mods | Directories to sync; config/ and server.properties are excluded by default |
sync.concurrency | 4 | Parallel copy workers |
sync.verify_target | true | Detect files changed on the mirror each round |
sync.region_delta | true | Chunk-level .mca sync; false falls back to whole files |
sync.region_write_mode | inplace | inplace appends and commits; rebuild writes a new file and replaces |
sync.journal | true | 8 KB region header snapshot for inplace; false forces rebuild |
source_flush.enabled | true | save-off + save-all flush around the scan; auto-disabled for agent transport and self targets |
mirror_control.adapter | manual | manual only checks; rcon / external / mcsm / self stop and start the server |
schedule.interval_seconds | 3600 | Auto-sync interval, 60 – 604800 seconds |
In-place sync: run the command on the server you want to sync
Use the self adapter when the plugin is installed on every server and players run !!mu sync wherever they are:
- Main server:
role: "exporter", serves its world read-only viaagent_server+ token. - Mirror:
role: "driver",source.transport: "agent"pointing at the main server, and a single target pointing at itself:
"targets": [
{ "name": "self", "root": "./server",
"mirror_control": { "adapter": "self", "notify_players_sec": 10 } }
]
adapter: "self" means "the target is the server this MCDR manages". When something changed, the plugin:
- announces the restart (
notify_players_secseconds,0disables it); - calls
server.stop()— MCDR itself keeps running; - waits for
session.lockto be released, then writes the difference; - calls
server.start()and waits until the server is reachable again.
Because only the server process stops, the sync thread keeps running. When nothing changed, none of these steps happen — it is just a stat sweep.
Cross-machine sync (optional)
- Main server (same plugin installed):
role: "exporter",agent_server.host: "0.0.0.0", a randomtokenof at least 16 characters, then!!mu reload. The log shows the exporter listening. - Mirror (driver):
source.transport: "agent",source.agent.url: "http://<main-ip>:25580", same token. - For public networks, enable TLS (
agent_server.tls) and pin the certificate withsource.agent.tls_pin_sha256.
The exporter is read-only: it never writes and never runs sync commands. source_flush is disabled automatically in this mode; make sure the main server's world is flushed (save-all flush) before syncing.
Commands
| Command | Permission | Meaning |
|---|---|---|
!!mu sync | 2 | Sync once |
!!mu sync --dry-run | 2 | Plan only, nothing is written |
!!mu sync --full | 2 | Ignore the index, re-hash everything |
!!mu sync --force | 2 | Skip safety checks (empty source, version gate) |
!!mu diff | 2 | Same as --dry-run |
!!mu status | 0 | Index size, last sync per target, pending retries |
!!mu verify [--deep] | 2 | Check source / mirror / index; --deep compares hashes |
!!mu history [N] | 0 | Recent rounds (default 10, max 50) |
!!mu cancel | 2 | Cancel the running sync |
| `!!mu schedule on | off` | 3 |
!!mu schedule set 30m | 3 | Set the interval (s/m/h/d) |
!!mu index rebuild | 3 | Drop the index; next round is a full sync |
!!mu reload | 3 | Reload the configuration |
Notes
- The mirror must be fully stopped for writes.
manualusessession.lock;rcon/external/selfstop it automatically. If it cannot be stopped, the sync aborts and nothing is written. - No backups. Overwritten chunks and mod files have no previous versions.
- Write volume ≠ read volume. With
inplace, changing one chunk writes that chunk plus 16 KB (new header + journal);rebuildrewrites the whole region. - Configuration files are not synced.
config/andserver.propertiesare outside the default sets; adding them overwrites the mirror's own configuration. - Empty-source guard: if the source scans to 0 files while the index has records, the sync aborts instead of wiping the mirror. Use
--forceonly when you really mean it. - Version gate: source and mirror
level.datMC versions must match.
Version
0.6.3 — file-level + chunk-level increment, in-place region writes (header journal + compaction), mirror drift recovery, scheduled sync, cross-machine agent transport (with TLS), .mcc external chunk increment and orphan cleanup, multiple mirror targets, verify & history, MCSM/rcon/external/self auto stop, in-place sync (adapter: "self"), Python API.
0.6.2 fix: !!MCDR plugin install mirror_up used to fail with Invalid chat component: Illegal character in path because MCDR builds the PyPI link from the raw requirement string (xxhash>=3.0) and > is not a legal URL character. The catalogue reads the dependency list from requirements.txt in the repository (not from the requirements field in mcdreforged.plugin.json), so 0.6.1 only changing the latter had no effect; 0.6.2 also changes requirements.txt to plain xxhash.
0.6.3 fix: loading the plugin as role: "exporter" could block for seconds while binding the agent port, because HTTPServer.server_bind() calls socket.getfqdn() for a reverse DNS lookup; on macOS this made MCDR's watchdog report TaskExecutor has not responded for 10 seconds. The server now binds directly without the lookup.
License
GPL-3.0 © MiYui34.
Introduction source: README_en.md