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:

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:

Real-Time Sync

The daemon provides continuous synchronization:

  1. File Watcher: Detects local file changes using OS-native APIs
  2. WebSocket Connection: Receives instant notifications of server changes
  3. 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:

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

  1. STUN Query: Detect NAT type and public address
  2. ICE Gathering: Collect host, server-reflexive, and relay candidates
  3. Signaling: Exchange candidates via server
  4. Connectivity Check: Test each candidate pair
  5. Hole Punching: Send simultaneous UDP packets to open NAT
  6. 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

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

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:

# 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:

  1. Client reconnects to server via WebSocket
  2. Local commits are pushed to server
  3. Conflicts (if any) are detected and resolved
  4. Remote changes are pulled and applied