P2P · no leader · git-backed

Configuration files that keep themselves in sync.

uRep is a tiny, decentralized replication agent for configuration files. Every node is equal. Every change is a git commit. Every message is encrypted with your cluster token.

minimal setup
urep --token '9cd1f4c1eee8049b6f5da0ed36773db4' \
     --tag nginx \
     --path /etc/nginx/nginx.conf \
     --post-hook 'nginx -s reload'

Why uRep

Small surface, serious guarantees.

No database to operate, no coordinator to elect. Just files, git, and an encrypted mesh that converges.

01

No leader

Every node is equal. No coordinator, no quorum, no failover dance.

02

Git is the state

Each node mirrors files into a SHA-256 git repo. History is the source of truth.

03

Tag subscriptions

A node listens only to messages tagged for it. A waf node never sees nginx configs.

04

Hooks that travel

Pre/post commands ride with the file and run on the receiver, not the sender.

05

Encrypted by default

One cluster token derives HKDF keys. ChaCha20-Poly1305 protects every frame.

06

Container-first

One image, a couple of ENV vars, and a state volume. urep is the entrypoint.

How it works

Watch. Commit. Replicate. Apply.

The loop is deliberately boring: detect a change, record it in git, send it to matching peers, and let last-write-wins resolve the rest.

1

Watch

inotify on the parent directory. Debounce, re-hash the whole file, detect atomic saves.

2

Commit

Write the mirror, commit with an HLC version, mode, tags, and hook metadata.

3

Replicate

Encrypted FILE_PUT to peers whose tags match. LWW on apply, hooks on the receiver.

watcher ──► commit ──► replicator ──► transport (TCP + AEAD)
   │              │              │
   │              ▼              ▼
   │         repo/ (git)     discovery (UDP multicast + gossip)
   │                             │
   └──────────── ◄── apply ◄── write ◄── hooks
                        │
                        ▼
                   state.json · events.jsonl · peers.json

Quick start

Two nodes, one file.

Start urep on two hosts with the same token and tag. Node A watches the file; node B receives the change, writes it atomically, and runs the hook on its side. No config file, no coordinator.

  • Same --token and --tag on both nodes
  • Multicast discovery on one LAN; UREP_SEEDS across subnets
  • Hooks run on the receiver, never on the sender
  • Two nodes on one host: give each its own --listen port
  • Verify with urep status, urep peers, urep log
two nodes
# node A — 10.0.0.11
urep --token 'cluster-secret' \
     --prefix web-01 \
     --tag nginx \
     --path /etc/nginx/nginx.conf \
     --post-hook 'nginx -t && nginx -s reload'

# node B — 10.0.0.12 (seeds point at node A)
UREP_TOKEN='cluster-secret' \
UREP_SEEDS=10.0.0.11:7801 \
urep --prefix web-02 \
     --tag nginx \
     --path /etc/nginx/nginx.conf \
     --post-hook 'nginx -t && nginx -s reload'

# on either node
urep status
urep peers

Project status

Built in the open, milestone by milestone.

M0–M4 are implemented: Docker harness, git-backed state, encrypted transport, discovery, and replication with hooks.

M0–M4 implemented 80+ tests Python 3.12 Docker Apache-2.0