Configuration
11. Tier-Specific Configuration
Section titled “11. Tier-Specific Configuration”11.1 Tier 1: Vibe Coder (iroh)
Section titled “11.1 Tier 1: Vibe Coder (iroh)”SyncConfig { backend: RelayBackend::Iroh, ..Default::default()}Uses iroh public network. No relay infrastructure needed.
iroh Version Strategy:
- Using iroh 0.96 (pre-1.0, requires cargo patch for curve25519-dalek)
- iroh-blobs 0.98 for large content transfer
- Self-hosted infrastructure available via iroh-relay and iroh-dns-server
11.2 Tier 2: Home Developer
Section titled “11.2 Tier 2: Home Developer”SyncConfig { backend: RelayBackend::SyncRelay { node_id: "your-sync-relay-node-id".parse()?, relay_url: None, // Uses default iroh-relay, or set custom }, ..Default::default()}Self-hosted Docker container. Discovered via mDNS on LAN or DNS for remote. Cloudflare Tunnel can proxy the QUIC connection.
11.3 Tier 3: Vercel-style
Section titled “11.3 Tier 3: Vercel-style”SyncConfig { backend: RelayBackend::SyncRelay { node_id: "fly-relay-node-id".parse()?, relay_url: Some("https://relay.fly.dev".parse()?), }, ..Default::default()}Container deployed to Fly.io. NodeId published via DNS TXT record at a known domain.
11.4 Tier 4-5: Managed Cloud
Section titled “11.4 Tier 4-5: Managed Cloud”SyncConfig { backend: RelayBackend::ManagedCloud { api_key: "cn_live_xxxx".into(), }, ..Default::default()}API key authenticates to CrabNebula managed infrastructure. Discovery service resolves API key to a sync-relay NodeId.
11.5 Tier 6: Enterprise
Section titled “11.5 Tier 6: Enterprise”SyncConfig { backend: RelayBackend::Enterprise { node_id: "enterprise-relay-node-id".parse()?, auth: EnterpriseAuth::Oidc { issuer: "https://auth.corp.com".into(), client_id: "sync-client".into(), }, }, ..Default::default()}Customer-deployed with enterprise auth integration. Dedicated sync-relay identified by NodeId.
12. Error Handling
Section titled “12. Error Handling”12.1 Error Codes
Section titled “12.1 Error Codes”| Code | Name | Description |
|---|---|---|
| 1000 | INVALID_MESSAGE | Malformed message |
| 1001 | UNKNOWN_GROUP | Group ID not found |
| 1002 | UNAUTHORIZED | Auth failed |
| 1003 | DEVICE_REVOKED | Device has been revoked from group |
| 1004 | NOT_BLOB_OWNER | Only blob creator can force delete |
| 2000 | RATE_LIMITED | Too many requests |
| 2001 | BLOB_TOO_LARGE | Exceeds 1 MB limit |
| 2002 | GROUP_QUOTA_EXCEEDED | Group storage full |
| 2003 | INVALID_PUSH_TOKEN | Push token format invalid |
| 3000 | RELAY_OVERLOADED | Server at capacity |
| 3001 | RELAY_SHUTTING_DOWN | Graceful shutdown |
12.2 Reconnection Strategy
Section titled “12.2 Reconnection Strategy”⚠️ Thundering Herd Mitigation Required: After relay restart, all clients reconnect simultaneously, potentially crashing the database or exhausting connection limits. Clients MUST implement jittered backoff.
Attempt 1: Wait 1s + jitterAttempt 2: Wait 2s + jitterAttempt 3: Wait 4s + jitterAttempt 4: Wait 8s + jitterAttempt 5: Wait 16s + jitterAttempt 6+: Wait 30s (max) + jitter
Jitter: 0-5000ms random (not ±20%)Reset: On successful connectionImplementation:
async fn reconnect_with_backoff(attempt: u32) { let base_delay = Duration::from_millis(1000 * 2u64.pow(attempt.min(5))); let jitter = Duration::from_millis(rand::thread_rng().gen_range(0..5000)); let delay = (base_delay + jitter).min(Duration::from_secs(30)); tokio::time::sleep(delay).await;}13. Configuration Reference
Section titled “13. Configuration Reference”13.1 Relay Configuration (relay.toml)
Section titled “13.1 Relay Configuration (relay.toml)”[server]bind = "127.0.0.1:8080"max_connections = 1000
[storage]database = "/data/relay.db"max_blob_size = 1048576 # 1 MBmax_group_storage = 104857600 # 100 MBdefault_ttl = 604800 # 7 days
[cleanup]interval = 3600 # 1 hourvacuum_on_cleanup = true
[limits]messages_per_minute = 100connections_per_ip = 1013.2 Client Configuration
Section titled “13.2 Client Configuration”[sync]backend = "sync-relay"# Multi-relay fan-out: list relays in preference order (first = primary)relay_addresses = ["primary-node-id", "secondary-node-id"]# or for DNS-based discovery:# relay_discovery = "https://sync.example.com/.well-known/iroh"auto_reconnect = truereconnect_delay_ms = 1000max_reconnect_delay_ms = 30000Push fan-out sends encrypted blobs to all configured relays concurrently. The primary relay’s acknowledgement is returned to the caller; secondary results are fire-and-forget. Pull failover tries each relay in order until one responds. Each relay tracks its own cursor independently.
See docs/MULTI-RELAY-SPEC.md for full multi-relay architecture.
13.3 Environment Variables
Section titled “13.3 Environment Variables”| Variable | Purpose | Default |
|---|---|---|
SYNC_RELAY_ADDRESSES | Override relay NodeIds (comma-separated) | Config file |
SYNC_API_KEY | Managed Cloud API key | None |
SYNC_LOG_LEVEL | Logging verbosity | info |
SYNC_DEVICE_NAME | Human-readable name | Hostname |
14. Best Practices
Section titled “14. Best Practices”14.1 Blob Size Strategy
Section titled “14.1 Blob Size Strategy”⚠️ The 1MB Blob Trap: While sync-relay limits blobs to 1MB (appropriate for state deltas), developers will inevitably try to sync images, videos, and large files. This section provides guidance.
What Belongs in Sync Blobs:
| ✅ Sync via Relay | ❌ Store Elsewhere |
|---|---|
| Transaction records | Images/photos |
| User preferences | Videos |
| App state deltas | PDF documents |
| Small JSON (<100KB) | Large binary files |
| CRDT operations | User-generated media |
Large Asset Strategy:
Implementation Example:
// ❌ WRONG: Syncing large files directlyasync fn save_photo_wrong(photo_bytes: &[u8], sync: &SyncClient) { // This will fail if photo > 1MB sync.push(photo_bytes).await?; // ERROR: BLOB_TOO_LARGE}
// ✅ CORRECT: Sync metadata, store asset externallyasync fn save_photo_correct( photo_bytes: &[u8], storage: &ObjectStorage, sync: &SyncClient,) -> Result<PhotoRecord> { // 1. Upload to object storage let storage_url = storage.upload("photos", photo_bytes).await?;
// 2. Create metadata record let record = PhotoRecord { id: Uuid::new_v4(), storage_url, mime_type: "image/jpeg".into(), size_bytes: photo_bytes.len(), created_at: now(), };
// 3. Sync only the metadata (< 1KB) let metadata_blob = serde_json::to_vec(&record)?; sync.push(&metadata_blob).await?;
Ok(record)}Recommended Object Storage Options:
| Provider | Best For | Pricing Model |
|---|---|---|
| Cloudflare R2 | Cost-effective, no egress | Pay per storage |
| Supabase Storage | Integrated with Supabase | Generous free tier |
| AWS S3 | Enterprise, existing AWS | Pay per everything |
| Self-hosted MinIO | Full control | Infrastructure cost |