MCPcopy Index your code
hub / github.com/Etoile-Bleu/ZamSync

github.com/Etoile-Bleu/ZamSync @v1.3.3

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.3.3 ↗ · + Follow
543 symbols 1,843 edges 76 files 69 documented · 13% updated 2d agov1.3.3 · 2026-06-27★ 6213 open issues
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

ZamSync

Resilient data replication for systems that cannot assume connectivity.

Works over 2G, satellite, or no internet for hours • No dropped events. Ever.

CI status Latest release MIT license Rust 1.75+ Platforms Docker Sponsor Read the article on dev.to


ZamSync demo


Two nodes. Three commands.

# Node A: start listening
zamsync serve ./a 0.0.0.0:7000

# Node B: write an event and sync
zamsync submit ./b '{"patient": "P-001", "type": "visit"}'
zamsync sync   ./b 127.0.0.1:7000 $(cat ./a/.node_id)
[node-b] handshake ok  peer=a3f2c1d8
[node-b] sent 1 event
[node-b] sync complete in 12ms

Connection dropped mid-transfer? Run sync again. Only missing events are retransmitted.

No Rust, no Cargo. One binary, zero runtime dependencies.

curl -fsSL -o zamsync \
  https://github.com/Etoile-Bleu/ZamSync/releases/latest/download/zamsync-linux-x86_64
chmod +x zamsync && sudo mv zamsync /usr/local/bin/

Why ZamSync exists

Built for environments where connectivity is a privilege, not a guarantee.

District clinics in rural Bhutan sync patient records to a central hospital hub over 2G cellular -- 600ms latency, 30 kbps bandwidth, and frequent power outages. ZamSync was designed to work correctly through:

  • hours or days of complete disconnection
  • mid-transfer connection cuts -- resume exactly where it left off
  • power loss during a sync session -- the WAL survives crashes
  • duplicate deliveries and partial writes -- idempotent by design

Single static binary. Runs on a Raspberry Pi 2. 512 MB RAM minimum.

Long-term vision: enable adoption in rural public health systems such as Bhutan's clinic network, where offline-first synchronization is not an optimization -- it is a requirement.

Want the full story behind the design decisions? Read the deep-dive article on dev.to.


Features

Category Feature
Core Append-only WAL with CRC32 integrity and crash recovery
Replication Version Vector deduplication -- no duplicate events ever
Ordering Hybrid Logical Clocks (HLC) for total deterministic ordering
Security Mutual TLS (mTLS) with a hub-signed PKI -- rogue nodes rejected at handshake
Encryption WAL encryption at rest (ChaCha20-Poly1305), per-record random nonce
Access control --policy own -- clinic A cannot read clinic B's records
Validation JSON schema enforcement on submit and replicated events
Observability Prometheus metrics, structured tracing, audit trail (JSON Lines)
Operations WAL compaction, key rotation, daemon mode, benchmarking, event retention (expire, snapshot)
Efficiency Zstd level-3 compression on all frames, chunked batches (256 events/frame)
Projection zamsync project -- project WAL events to SQLite; idempotent INSERT OR IGNORE on (origin_node_id, seq)
REST API Embedded HTTP server (--http) -- POST /submit, GET /events, GET /events/stream (SSE)
Platforms x86_64-linux, aarch64-linux, armv7-linux, x86_64-windows (static musl binaries)

Architecture

ZamSync uses hexagonal architecture -- the sync core is pure logic with no I/O. Storage and transport are pluggable adapters.

  +----------------------------------------------------------+
  |                    zamsync (CLI binary)                   |
  +--------------------+--------------------+-----------------+
                       |                    |
          +------------+-------+  +---------+----------+  +------------------+
          |   zamsync-core     |  | zamsync-storage    |  | zamsync-network  |
          |                    |  |                    |  |                  |
          | Event, HLC         |  | ZamEngine          |  | TcpTransport     |
          | VersionVector      |  | WalEventStore      |  | TlsTcpTransport  |
          | SyncMessage        |  | FilePeerStore      |  | wire protocol    |
          | port traits        |  | WAL (CRC32)        |  | mTLS (rustls)    |
          +--------------------+  +--------------------+  +------------------+

Sync protocol

  Clinic (initiator)              Hospital (responder)
       |                                  |
       |-- Handshake(node_id, vv) ------->|
       |                                  |  compare VVs, find gaps
       |<-- EventBatch(events...) --------|
       |<-- EventBatch(events...) --------|  chunked, 256 events/frame
       |<-- SyncComplete -----------------|
       |                                  |
       |-- EventBatch(my_events...) ----->|
       |-- SyncComplete ----------------->|
       |                                  |

Events are idempotent -- if a session is interrupted mid-transfer, the next sync only sends what's missing.


Installation

Binary (no dependencies, recommended)

Static musl binaries -- work on any Linux, no libc version requirements.

# Linux x86_64
curl -fsSL -o zamsync \
  https://github.com/Etoile-Bleu/ZamSync/releases/latest/download/zamsync-linux-x86_64
chmod +x zamsync && sudo mv zamsync /usr/local/bin/

# Linux ARM64 (Raspberry Pi 4, AWS Graviton)
curl -fsSL -o zamsync \
  https://github.com/Etoile-Bleu/ZamSync/releases/latest/download/zamsync-linux-aarch64
chmod +x zamsync && sudo mv zamsync /usr/local/bin/

# Linux ARMv7 (Raspberry Pi 2 / 3)
curl -fsSL -o zamsync \
  https://github.com/Etoile-Bleu/ZamSync/releases/latest/download/zamsync-linux-armv7
chmod +x zamsync && sudo mv zamsync /usr/local/bin/

# Windows (PowerShell)
Invoke-WebRequest `
  -Uri "https://github.com/Etoile-Bleu/ZamSync/releases/latest/download/zamsync-windows-x86_64.exe" `
  -OutFile zamsync.exe

Verify:

sha256sum -c SHA256SUMS.txt
zamsync info ./test-node
# Node ID: a3f2c1d8e5b6...  events: 0  peers: 0

Docker

docker pull ghcr.io/etoile-bleu/zamsync:latest

docker run --rm \
  -v /var/lib/zamsync:/data \
  -p 7000:7000 \
  ghcr.io/etoile-bleu/zamsync:latest \
  serve /data 0.0.0.0:7000

Build from source

git clone https://github.com/Etoile-Bleu/ZamSync.git
cd ZamSync
cargo build --release
# binary at: target/release/zamsync

Examples

Submit events and inspect the log

zamsync submit ./node '{"patient": "P-001", "ward": "3B", "type": "admission"}'
zamsync submit ./node '{"patient": "P-001", "type": "discharge"}'
zamsync submit ./node '{"patient": "P-002", "ward": "ICU", "type": "admission"}'

zamsync info ./node
node_id  : 2748582051
data_dir : ./node
events   : 3
vv       : node 2748582051 @ seq 3
wal size : 1 KB
oldest   : 2026-06-17
newest   : 2026-06-17
zamsync audit ./node --format json | jq '{seq: .seq, type: .payload_type, size: .payload_size}'
{"seq": 1, "type": "json", "size": 54}
{"seq": 2, "type": "json", "size": 36}
{"seq": 3, "type": "json", "size": 44}

Two-node sync

# Terminal 1 -- hub node listens
zamsync serve ./hub 0.0.0.0:7000

# Terminal 2 -- clinic submits and syncs
zamsync submit ./clinic '{"patient": "P-042", "type": "visit"}'
zamsync sync   ./clinic 127.0.0.1:7000 $(cat ./hub/.node_id)
[clinic] connecting to 127.0.0.1:7000...
[clinic] handshake ok  peer=hub
[clinic] sent 1 event
[clinic] received 0 events
[clinic] sync complete in 8ms
# Hub now has the clinic's event
zamsync info ./hub
node_id  : 3001442771
data_dir : ./hub
events   : 1
vv       : node 1482937654 @ seq 1
wal size : 1 KB
oldest   : 2026-06-17
newest   : 2026-06-17

Encrypted WAL + audit trail

# Generate a node with a WAL encryption key
zamsync keygen ./secure-node

# All writes are encrypted at rest
zamsync submit ./secure-node '{"sensitive": "data"}' \
  --key-file ./secure-node/data.key

# Audit requires the key
zamsync audit ./secure-node --key-file ./secure-node/data.key
2026-06-14T12:00:01Z  seq=1  node=a3f2c1d8  size=22B  sha256=4a8f2c...

Projecting to SQLite

zamsync project reads the local WAL and inserts every event into a SQLite database via INSERT OR IGNORE, making your sync data queryable with standard SQL tools. Re-running is always safe: already-present rows are skipped via a UNIQUE(origin_node_id, seq) constraint.

# Basic: project all events into projection.db (default path)
zamsync project ./node

# Custom output path
zamsync project ./node --target sqlite://./data/events.db

# Dry run: preview what would be projected without writing anything
zamsync project ./node --dry-run

# Tune batch size for large WALs (default: 100)
zamsync project ./node --batch-size 500
3 projected, 0 already present

Query the database directly once projected:

sqlite3 ./node/projection.db \
  "SELECT origin_node_id, seq, hlc_ms, payload FROM zamsync_events ORDER BY hlc_ms;"
import sqlite3, json
conn = sqlite3.connect("./node/projection.db")
for row in conn.execute("SELECT seq, payload FROM zamsync_events ORDER BY hlc_ms"):
    print(row[0], json.loads(row[1]))

Schema:

Column Type Description
origin_node_id INTEGER Node that originally submitted the event
seq INTEGER Sequence number on the origin node
hlc_ms INTEGER HLC wall-clock component (Unix ms)
hlc_logical INTEGER HLC logical counter (tie-breaker)
event_type INTEGER Application-defined event type
payload BLOB Raw event payload (JSON or binary)
projected_at TEXT ISO 8601 timestamp of when this row was inserted

Daemon mode (autonomous sync)

# Clinic node syncs automatically every 5 minutes
zamsync daemon ./clinic 192.168.1.10:7000 $(cat ./hub/.node_id) \
  --tls \
  --key-file ./clinic/data.key \
  --interval 300 \
  --metrics 0.0.0.0:9090
[daemon] starting  peer=192.168.1.10:7000  interval=300s
[daemon] sync #1   sent=3  received=12  duration=1.2s
[daemon] sleeping 300s...
[daemon] sync #2   sent=0  received=7   duration=0.8s

PKI Setup -- Multi-Clinic Deployment

                    +--------------+
                    |  Hub / CA    |
                    |  (hospital)  |
                    +------+-------+
               signs       |       signs
          +----------------+--------------+
          |                               |
   +------+------+              +---------+---+
   |  Clinic A   |              |  Clinic B   |
   |  node.crt   |              |  node.crt   |
   +-------------+              +-------------+
     All share the same ca.crt -- mTLS enforced
# 1. Initialize the hub (once)
zamsync keygen /var/lib/hub

# 2. Sign a certificate for each clinic
zamsync sign /var/lib/clinic-a --ca /var/lib/hub
zamsync sign /var/lib/clinic-b --ca /var/lib/hub

# 3. Copy clinic directories to physical devices
scp -r /var/lib/clinic-a pi@clinic-a.local:/var/lib/zamsync

# 4. Hub serves with mTLS, per-clinic isolation, and concurrent connections
zamsync serve /var/lib/hub 0.0.0.0:7000 --tls --policy own --max-peers 32

# 5. Clinics sync automatically
zamsync daemon /var/lib/zamsync 192.168.1.10:7000 $(cat /var/lib/hub/.node_id) \
  --tls --key-file /var/lib/zamsync/data.key --interval 300

A rogue node that presents a certificate from a different CA is rejected at the TLS handshake before any data is exchanged.

mTLS security demo


WAL Encryption

The WAL is encrypted at rest using ChaCha20-Poly1305 AEAD with a random 96-bit nonce per record. All commands accept --key-file:

zamsync submit  ./node "event" --key-file ./node/data.key
zamsync serve   ./node 0.0.0.0:7000 --key-file ./node/data.key
zamsync sync    ./node 192.168.1.10:7000 <peer-id> --key-file ./node/data.key
zamsync audit   ./node --key-file ./node/data.key
zamsync compact ./node --key-file ./node/data.key

Key rotation

zamsync rekey ./node --old-key ./node/data.key --new-key ./node/data.key.new
mv ./node/data.key.new ./node/data.key

WAL encryption demo


Access Control

--policy own on serve enforces per-tenant isolation server-side:

Clinic A submits: A1, A2, A3
Clinic B submits: B1, B2

When clinic A syncs -> receives A1, A2, A3  (not B1, B2)
When clinic B syncs -> receives B1, B2       (not A1, A2, A3)
Hub sees all events.

![Access control d

Extension points exported contracts — how you extend this code

StateStore (Interface)
(no doc) [14 implementers]
crates/zamsync-core/src/ports/state_store.rs
Transport (Interface)
(no doc) [5 implementers]
crates/zamsync-core/src/ports/transport.rs
EventStore (Interface)
(no doc) [3 implementers]
crates/zamsync-core/src/ports/event_store.rs
PeerStore (Interface)
(no doc) [2 implementers]
crates/zamsync-core/src/ports/peer_store.rs

Core symbols most depended-on inside this repo

get
called by 75
crates/zamsync-core/src/sync.rs
clone
called by 74
crates/zamsync-storage/src/encryption.rs
submit
called by 62
crates/zamsync-storage/src/engine.rs
sync
called by 60
crates/zamsync-core/src/event.rs
update
called by 38
crates/zamsync-core/src/sync.rs
flag_value
called by 31
src/util.rs
connect
called by 22
crates/zamsync-network/src/transport/tcp/transport.rs
scan_events
called by 17
crates/zamsync-storage/src/engine.rs

Shape

Function 290
Method 182
Class 60
Enum 7
Interface 4

Languages

Rust99%
Python1%

Modules by API surface

src/http.rs42 symbols
crates/zamsync-storage/src/engine.rs30 symbols
src/color.rs23 symbols
crates/zamsync-network/src/transport/tls_tcp/transport.rs21 symbols
crates/zamsync-storage/src/wal.rs20 symbols
crates/zamsync-core/src/event.rs20 symbols
src/cmd/audit.rs19 symbols
crates/zamsync-storage/src/adapters/wal_event_store.rs19 symbols
crates/zamsync-network/src/transport/tcp/transport.rs18 symbols
crates/zamsync-core/src/sync.rs17 symbols
crates/zamsync-network/src/tls.rs16 symbols
tests/cli_integration.rs15 symbols

Datastores touched

mydbDatabase · 1 repos

For agents

$ claude mcp add ZamSync \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page