Sync & P2P
Ebla combines server-authoritative sync with P2P acceleration for reliable, fast file synchronization across devices.
How Sync Works
Block-Level Deduplication
Ebla doesn't sync entire files. Instead, files are split into variable-size blocks using content-defined chunking:
┌─────────────────────────────────────────────────────────────┐
│ Original File (10 MB) │
└─────────────────────────────────────────────────────────────┘
│
▼ Content-Defined Chunking
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│Block1│ │Block2│ │Block3│ │Block4│ │Block5│ │Block6│ │Block7│
│ 1MB │ │ 2MB │ │ 1.5MB│ │ 1MB │ │ 2MB │ │ 1.5MB│ │ 1MB │
│sha256│ │sha256│ │sha256│ │sha256│ │sha256│ │sha256│ │sha256│
└──────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘
Benefits of block-level sync:
- Deduplication: Identical blocks are stored once, even across different files
- Efficient Updates: Only changed blocks are uploaded, not entire files
- Resumable: Interrupted uploads resume from the last successful block
- P2P Friendly: Blocks can be fetched from any peer that has them
Commit Model
Every sync operation creates a commit, similar to Git:
Commit abc123
├── Parent: commit xyz789
├── Author: user@example.com (Device: MacBook)
├── Time: 2026-01-15 10:30:00 UTC
└── Files:
├── [A] documents/report.pdf (3 blocks, 5.2 MB)
├── [M] notes/meeting.md (1 block, 12 KB)
└── [D] old/draft.txt
Actions:
- [A] Add: New file created
- [M] Modify: Existing file changed
- [D] Delete: File removed
- [R] Rename: File moved or renamed
Real-Time Sync
The daemon provides continuous synchronization:
- File Watcher: Detects local file changes using OS-native APIs
- WebSocket Connection: Receives instant notifications of server changes
- Polling Fallback: Every 5 minutes if WebSocket disconnects
# Watch sync activity
$ ebla daemon start
[10:30:00] Watching for changes...
[10:30:15] Local change detected: documents/report.pdf
[10:30:15] Chunking documents/report.pdf...
[10:30:16] Uploading 2 new blocks...
[10:30:17] Committed: abc123 (1 file, 5.2 MB)
[10:32:00] Server notification: new commit xyz789
[10:32:00] Downloading 1 file from server...
[10:32:01] Applied: notes/meeting.md
Conflict Resolution
When Conflicts Happen
A conflict occurs when multiple devices modify the same file before syncing:
Timeline:
─────────────────────────────────────────────────────────────────
Time │ Device A │ Device B │ Server
─────────────────────────────────────────────────────────────────
T1 │ File: v1 │ File: v1 │ HEAD: v1
T2 │ Edit → v2a │ Edit → v2b │
T3 │ Sync (offline) │ Sync → v2b committed │ HEAD: v2b
T4 │ Comes online │ │
T5 │ Sync → CONFLICT │ │
─────────────────────────────────────────────────────────────────
Conflict Detection
Ebla uses Hybrid Logical Clocks (HLC) and vector clocks to detect conflicts:
- HLC: Tracks physical time with logical ordering guarantees
- Vector Clocks: Track per-device modification history
- Divergent Commits: Commits with the same parent from different devices
Resolution Strategies
Configure per-file or per-library conflict resolution:
| Strategy | Description | Best For |
|---|---|---|
lww |
Last writer wins (by timestamp) | Simple files, logs |
three-way |
Three-way merge using common ancestor | Text files, code, markdown |
ours |
Always keep local version | Local-authoritative files |
theirs |
Always keep remote version | Server-authoritative files |
union |
Keep both versions (renamed) | Binary files |
manual |
Mark as unresolved, user must resolve | Critical files |
Three-Way Merge
For text files, Ebla performs automatic three-way merge:
Base (common ancestor): Version A: Version B:
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Line 1 │ │ Line 1 │ │ Line 1 │
│ Line 2 │ │ Line 2 (modified) │ │ Line 2 │
│ Line 3 │ │ Line 3 │ │ Line 3 (modified) │
│ Line 4 │ │ Line 4 │ │ Line 4 │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
▼ Three-Way Merge ▼
Merged Result:
┌─────────────────────┐
│ Line 1 │
│ Line 2 (from A) │ ← Non-conflicting change
│ Line 3 (from B) │ ← Non-conflicting change
│ Line 4 │
└─────────────────────┘
Viewing and Resolving Conflicts
# View unresolved conflicts
$ ebla status
Conflicts:
documents/report.pdf
Local version: 2026-01-15 10:30 (Device A)
Remote version: 2026-01-15 10:25 (Device B)
# Resolve with local version
$ ebla conflict resolve documents/report.pdf --ours
# Resolve with remote version
$ ebla conflict resolve documents/report.pdf --theirs
# View in admin UI
Open: http://your-server:6333/admin/libraries/:id → Conflicts tab
P2P Acceleration
How P2P Works
Ebla uses P2P to accelerate block transfers when possible:
Block Download Priority:
┌─────────────────────────────────────────────────────────────┐
│ 1. LAN Peers │
│ └─ Direct TCP connection via mDNS discovery │
│ └─ Fastest option, no internet required │
├─────────────────────────────────────────────────────────────┤
│ 2. NAT-Traversed Peers │
│ └─ Hole-punched UDP/TCP via STUN │
│ └─ Works across most NAT types │
├─────────────────────────────────────────────────────────────┤
│ 3. TURN-Relayed Peers │
│ └─ Via TURN relay server │
│ └─ Works for symmetric NAT (slower) │
├─────────────────────────────────────────────────────────────┤
│ 4. Server │
│ └─ Direct download from server │
│ └─ Always available, but uses server bandwidth │
└─────────────────────────────────────────────────────────────┘
LAN Discovery (mDNS)
On local networks, clients discover each other automatically using multicast DNS:
$ ebla p2p status
P2P Status: enabled
NAT Type: Port Restricted Cone
Public Address: 203.0.113.45:54321
Discovered Peers:
PEER ID DEVICE IP LIBRARIES STATUS
peer_abc123 MacBook Pro 192.168.1.10 3 online (LAN)
peer_def456 Linux Server 192.168.1.20 2 online (LAN)
peer_ghi789 iPhone 203.0.113.80 1 online (NAT)
NAT Traversal
For peers on different networks, Ebla uses STUN/TURN/ICE:
| NAT Type | Description | P2P Support |
|---|---|---|
| None (Public IP) | Direct internet access | Full |
| Full Cone | Any external host can send | Full |
| Restricted Cone | Only hosts we've sent to | Hole punching |
| Port Restricted | Only exact IP:port we've sent to | Hole punching |
| Symmetric | Different port for each destination | TURN relay required |
Connection Flow
- STUN Query: Detect NAT type and public address
- ICE Gathering: Collect host, server-reflexive, and relay candidates
- Signaling: Exchange candidates via server
- Connectivity Check: Test each candidate pair
- Hole Punching: Send simultaneous UDP packets to open NAT
- TURN Fallback: Use relay for symmetric-to-symmetric NAT
P2P Configuration
# ~/.ebla/config.toml
[p2p]
enabled = true # Enable/disable P2P
port = 0 # P2P listening port (0 = random)
lan_only = false # Set true to disable NAT traversal
[p2p.nat]
enabled = true # Enable STUN/TURN
# Custom STUN servers (optional, defaults to Google)
stun_servers = [
"stun.l.google.com:19302",
"stun.cloudflare.com:3478"
]
# TURN relay (optional, for symmetric NAT)
turn_server = "turn.example.com:3478"
turn_username = "user"
turn_password = "secret"
# Force relay (for debugging)
prefer_relay = false
P2P Privacy
- Opt-in: P2P is enabled by default but can be disabled
- Hash-Only: Peers only see block hashes, not file contents or names
- Authenticated: Server-coordinated discovery requires authentication
- Encrypted Signaling: NAT traversal uses encrypted signaling via server
Ignore Rules
.eblaignore File
Exclude files from sync using gitignore-style patterns:
# .eblaignore
# System files
.DS_Store
Thumbs.db
desktop.ini
# Build artifacts
*.o
*.pyc
__pycache__/
node_modules/
dist/
build/
# IDE files
.idea/
.vscode/
*.swp
*.swo
# Git
.git/
# Secrets (never sync!)
.env
.env.local
*.pem
*.key
credentials.json
# Large files
*.iso
*.dmg
*.zip
Pattern Syntax
*- Match any characters except/**- Match any characters including/?- Match single character[abc]- Match character class!- Negate pattern (re-include)/suffix - Match directories only/prefix - Match from root only
Version History
Viewing History
# Show commit history
$ ebla log
commit abc123 (HEAD)
Author: user@example.com (MacBook Pro)
Date: 2026-01-15 10:30:00
Added quarterly reports
A documents/Q1-2026.pdf
M documents/summary.md
commit xyz789
Author: user@example.com (Linux Server)
Date: 2026-01-14 16:45:00
Updated meeting notes
M notes/2026-01-14.md
# Compact format
$ ebla log --oneline
abc123 Added quarterly reports (2 files)
xyz789 Updated meeting notes (1 file)
def456 Initial sync (156 files)
Viewing Diffs
# Diff working tree vs HEAD
$ ebla diff
# Diff specific commit
$ ebla diff abc123
# Diff between commits
$ ebla diff xyz789..abc123
# File changes only
$ ebla diff --name-only
Restoring Files
# Restore file from specific commit
$ ebla checkout abc123 documents/report.pdf
# Restore to different location
$ ebla checkout abc123 documents/report.pdf -o /tmp/old-report.pdf
# Preview without writing
$ ebla checkout abc123 documents/report.pdf --dry-run
# Force overwrite dirty file
$ ebla checkout abc123 documents/report.pdf --force
Offline Support
Working Offline
Ebla works fully offline with local commit journaling:
- Local Commits: Changes are committed locally, synced when online
- Cached History: Server commit history is cached for offline viewing
- File Access: All synced files remain accessible
- Search: Local embeddings enable offline semantic search
# Offline status
$ ebla status
Server: http://server:6333 (offline - last seen 2 hours ago)
Libraries:
My Documents (e299620b)
Status: 3 local commits pending sync
Local path: /home/user/Documents
Files: 156
# View local commits
$ ebla log --local
[LOCAL] commit loc_abc123
Author: user@example.com
Date: 2026-01-15 10:30:00
Edited report while offline
Sync Resume
When connectivity is restored:
- Client reconnects to server via WebSocket
- Local commits are pushed to server
- Conflicts (if any) are detected and resolved
- Remote changes are pulled and applied