Lightweight, filesystem-native cross-machine message sync daemon.
No Redis. No RabbitMQ. Just files and HTTP. Single static binary.
A single machine can run both roles simultaneously — the hub and its own client share the same process boundary cleanly:
┌──────────────────────────────────┐
│ Machine 1 │
│ │
│ ┌─────────────┐ ┌───────────┐ │
│ │ Hub server │ │ Daemon │ │
│ │ (mesync │◄►│ (mesync │ │
│ │ serve) │ │ start) │ │
│ └──────┬──────┘ └───────────┘ │
└──────────┼───────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ Machine 2 │ │ Machine 3 │
│ (mesync start) │ │ (mesync start) │
└──────────────────┘ └──────────────────┘
How it works:
- Machine 1 runs
mesync serve(hub) andmesync start(client) at the same time - Machine 1's client points
MESYNC_SERVER_URLathttp://localhost:8080— it syncs with its own hub - Machines 2 and 3 point at Machine 1's hub IP and sync the same way
- A broadcast from any machine reaches all others except itself
- A targeted message (
--target machine-2) goes only to that host
Each client machine polls every 5 seconds:
- Outbox scan — JSON files in
~/.daemon/outbox/are sent to the hub server, then archived tosent/(orfailed/on error). - Inbox pull — New messages from the server are written to
~/.daemon/inbox/and a matching hook script is executed if present. - Cleanup — Expired files in
sent/are removed automatically.
~/.daemon/
├── config.toml # optional configuration file
├── outbox/ # local → server (pending)
├── inbox/ # server → local (received)
├── sent/ # archived successful sends
├── failed/ # failed sends (retry manually)
└── hooks/ # executable scripts triggered on inbox messages
cargo build --release
# binary at ./target/release/mesync
sudo cp target/release/mesync /usr/local/bin/On the hub machine (Machine 1):
# Start the hub server in the background
mesync serve &
# Start the daemon (connects to the local hub)
export MESYNC_SERVER_URL=http://localhost:8080
mesync startOn each additional client machine (Machine 2, 3 ...):
export MESYNC_SERVER_URL=http://machine1:8080
mesync startSend a message from any script:
mesync send task.event --payload '{"job": "build", "repo": "myapp"}'
mesync send notify.alert --priority high --target machine-2 --payload '{"msg": "disk full"}'Inspect queues:
mesync list outbox
mesync list inbox
mesync list failed
mesync retry # re-queue failed messages
mesync flush sent # clear sent archiveSettings are resolved in this order (highest priority first):
- Environment variable
~/.daemon/config.toml- Built-in default
Place at ~/.daemon/config.toml (or $MESYNC_BASE_DIR/config.toml):
# Hub server to connect to
server_url = "http://hub.internal:8080"
# Override the hostname used for message routing
# host_id = "my-machine"
# Seconds between daemon poll cycles (default: 5)
poll_interval = 10
# How long before sent/ files are deleted, in seconds (default: 86400 = 24h)
sent_ttl = 3600
# HTTP request timeout in seconds for send/pull/ack calls (default: 10)
http_timeout = 15| Variable | Default | Description |
|---|---|---|
MESYNC_SERVER_URL |
http://localhost:8080 |
Hub server URL |
MESYNC_HOST_ID |
system hostname | Identity for message routing |
MESYNC_BASE_DIR |
~/.daemon |
Root directory for queues and config |
MESYNC_POLL_INTERVAL |
5 |
Seconds between daemon ticks |
MESYNC_SENT_TTL |
86400 |
Seconds before sent files are purged |
MESYNC_HTTP_TIMEOUT |
10 |
HTTP request timeout in seconds |
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "task.event",
"source": "hostname",
"target": null,
"priority": "normal",
"created_at": "2024-01-15T10:30:00.123456+00:00",
"payload": {}
}| Field | Values | Notes |
|---|---|---|
priority |
high / normal / low |
Controls send order within a poll cycle |
target |
hostname or null |
null broadcasts to all registered hosts |
Place an executable file at ~/.daemon/hooks/<message.type>. It receives the full message JSON on stdin:
#!/bin/bash
# ~/.daemon/hooks/task.event
msg=$(cat)
echo "$msg" | jq .payload | my-worker- Atomic writes — messages are written via
rename(2)so no partial files are ever visible. - Idempotent delivery — UUID per message; inbox rejects duplicates before ack'ing.
- Ordered delivery — files named
YYYYMMDDTHHmmss_<uuid>.jsonfor natural sort order. - Single binary —
cargo build --releaseproduces one self-contained executable.