Skip to main content
Devices are machines running Spacedrive. Each device has a unique identity, can pair with others, and participates in library synchronization.

Architecture

Devices operate across three layers:

Identity Layer

Manages device configuration and cryptographic keys.
// Device initialization
let device_manager = DeviceManager::init()?;
let device_id = device_manager.device_id()?;
Storage locations:
  • macOS: ~/Library/Application Support/com.spacedrive/device.json
  • Linux: ~/.config/spacedrive/device.json
  • Windows: %APPDATA%/Spacedrive/device.json

Domain Layer

Provides the rich device model used throughout the application.
pub struct Device {
    pub id: Uuid,
    pub name: String,
    pub slug: String,               // URL-safe identifier for addressing
    pub os: OperatingSystem,
    pub hardware_model: Option<String>,
    pub network_addresses: Vec<String>,
    pub is_online: bool,
    pub last_seen_at: DateTime<Utc>,
}
Devices are stored per library, not globally. Each library database contains device records for all participating devices.
The devices table is the source of truth for sync state within a library. It tracks which devices are online, when they were last seen, and their sync watermarks.

Device Slugs for Unified Addressing

Each device has a unique slug used in Spacedrive’s unified addressing scheme. The slug is a URL-safe identifier generated from the device name:
// Slug generation
let name = "Jamie's MacBook Pro";
let slug = name.to_lowercase()
    .chars()
    .map(|c| if c.is_alphanumeric() { c } else { '-' })
    .collect::<String>()
    .trim_matches('-');
// Result: "jamies-macbook-pro"
Slugs enable human-readable local file URIs:
local://jamies-macbook/Users/james/Documents/report.pdf
local://home-server/mnt/storage/media/movies/Inception.mkv
local://work-desktop/C:/Projects/spacedrive/README.md
The database enforces slug uniqueness with a UNIQUE constraint. If two devices would have the same slug (e.g., both named “MacBook Pro”), one must be renamed before they can sync. See Unified Addressing for complete details on URI formats and slug resolution.

Network Layer

Handles P2P connections and device pairing through Iroh.
The network layer uses mDNS for local discovery and QUIC for encrypted communication.

Device Pairing

Pairing establishes trust between devices using a cryptographic handshake.

Pairing Flow

1

Generate Code

Device A generates a pairing code valid for 5 minutes.
const pairing = await client.action("network.pair.generate", {});
console.log(`Pairing code: ${pairing.code}`); // "ABCD-1234-EFGH"
2

Enter Code

Device B joins using the pairing code.
await client.action("network.pair.join", {
  code: "ABCD-1234-EFGH"
});
3

Establish Trust

Devices exchange cryptographic signatures and derive session keys for future communication.

Paired Device States

  • Discovered: Found via mDNS but not paired
  • Pairing: Authentication in progress
  • Paired: Trusted and persisted
  • Connected: Active P2P connection
  • Disconnected: Paired but offline

Library Participation

After pairing, devices must register with libraries to enable sync.

Registration Process

  1. Paired devices discover each other’s libraries
  2. User selects which libraries to join
  3. Device records are created in each library’s database
  4. Sync begins automatically

Device-Owned Data

Each device owns specific data that only it can modify:
  • Locations: Filesystem paths on that device
  • Entries: Files and folders within those locations
  • Volumes: Physical drives attached to the device
This ownership model is fundamental to Spacedrive’s conflict-free sync design.

Sync Participation

Spacedrive uses a leaderless sync model. All devices are peers with no central authority.
The devices table is the source of truth for all sync state within a library:
  • Connection State: is_online, last_seen_at track real-time availability
  • Sync Enablement: sync_enabled controls whether a device participates
  • Watermarks: Track what data has been synchronized
    • last_state_watermark: Timestamp for device-owned data (locations, entries)
    • last_shared_watermark: HLC for shared resources (tags, albums)

How Devices Participate in Sync

Devices sync data using two protocols based on ownership:
  • Device-owned data (locations, entries): Owner broadcasts state, peers apply. Tracked via last_state_watermark.
  • Shared resources (tags, collections): Any device can modify. Changes ordered via HLC. Tracked via last_shared_watermark.
For detailed protocol documentation, see Library Sync.

Sync State Management

Query sync-enabled devices in a library:
// Get all devices that can sync
let sync_devices = entities::device::Entity::find()
    .filter(entities::device::Column::SyncEnabled.eq(true))
    .all(db)
    .await?;

// Get only online devices ready for immediate sync
let online_devices = entities::device::Entity::find()
    .filter(entities::device::Column::SyncEnabled.eq(true))
    .filter(entities::device::Column::IsOnline.eq(true))
    .all(db)
    .await?;

Watermark Tracking

Watermarks enable incremental sync by tracking the last successfully synchronized state:
// Get device's sync watermarks
let device = entities::device::Entity::find()
    .filter(entities::device::Column::Uuid.eq(device_id))
    .one(db)
    .await?;

if let Some(d) = device {
    // State watermark: last timestamp synced for device-owned data
    let state_watermark: Option<DateTime<Utc>> = d.last_state_watermark;

    // Shared watermark: last HLC synced for shared resources
    let shared_watermark: Option<HLC> = d.last_shared_watermark
        .as_ref()
        .and_then(|s| serde_json::from_str(s).ok());
}
When devices reconnect after being offline, they compare watermarks to determine what data needs to be synchronized incrementally, avoiding full re-sync.

API Reference

List Paired Devices

const devices = await client.query("network.devices.list", {
    connectedOnly: false
});

// Response
{
    devices: [
        {
            id: "device-uuid",
            name: "Jamie's MacBook",
            deviceType: "Laptop",
            isConnected: true,
            lastSeen: "2024-10-12T..."
        }
    ],
    total: 3,
    connected: 2
}

Device Events

Devices emit events when their state changes:
// Device comes online
{
    kind: "ResourceChanged",
    resourceType: "device",
    resource: { id: "...", isOnline: true }
}

// Device goes offline
{
    kind: "ResourceChanged",
    resourceType: "device",
    resource: { id: "...", isOnline: false }
}

Platform Considerations

Mobile Devices

iOS and Android require special initialization:
// iOS: Pass UIDevice name to Rust
let deviceName = UIDevice.current.name
core.initializeDevice(withName: deviceName)

Device Types

  • Desktop: Windows, Linux desktops
  • Laptop: MacBooks, portable computers
  • Mobile: iOS, Android devices
  • Server: Headless installations

Security

Each device maintains:
  • Ed25519 key pair: Unique cryptographic identity
  • Session keys: Derived after pairing for encrypted communication
  • Trust levels: Verified (paired) or blocked
Never share device keys or pairing codes over insecure channels.

Troubleshooting

Pairing Issues

If devices won’t pair:
  • Verify both devices are on the same network
  • Check firewall settings for mDNS (port 5353)
  • Ensure pairing code hasn’t expired (5 minute timeout)
  • Restart the application on both devices

Sync Not Working

If changes aren’t syncing between devices:
  • Confirm devices are paired (check paired device list)
  • Verify both devices have joined the library
  • Check devices.is_online status in the library database
  • Verify devices.sync_enabled = 1 for both devices
  • Compare watermarks to check if sync is progressing
  • Check network connectivity between devices
  • Review sync status in the UI

Debug Commands

# Check device registration and sync state in library
sqlite3 ~/Spacedrive/Libraries/My\ Library.sdlibrary/database.db \
  "SELECT uuid, name, is_online, sync_enabled, last_sync_at,
          last_state_watermark, last_shared_watermark
   FROM devices;"

# Check which devices are online and sync-enabled
sqlite3 ~/Spacedrive/Libraries/My\ Library.sdlibrary/database.db \
  "SELECT uuid, name, is_online, last_seen_at
   FROM devices
   WHERE sync_enabled = 1;"

# View watermarks for a specific device
sqlite3 ~/Spacedrive/Libraries/My\ Library.sdlibrary/database.db \
  "SELECT name, last_state_watermark, last_shared_watermark
   FROM devices
   WHERE uuid='device-uuid';"

# Monitor sync activity
RUST_LOG=sd_core::sync=debug,sd_core::service::sync=debug cargo run

Implementation Details

Global Device ID Cache

For performance, the current device ID is cached globally:
use sd_core::device::get_current_device_id;

let device_id = get_current_device_id(); // Fast lookup

Device Registration Query

-- Each library database contains
CREATE TABLE devices (
    id INTEGER PRIMARY KEY,
    uuid TEXT UNIQUE NOT NULL,
    name TEXT NOT NULL,
    slug TEXT UNIQUE NOT NULL,   -- URL-safe identifier for unified addressing
    os TEXT NOT NULL,
    os_version TEXT,
    hardware_model TEXT,
    network_addresses TEXT, -- JSON array
    is_online BOOLEAN DEFAULT 0,
    last_seen_at TEXT NOT NULL,

    -- Sync management fields
    sync_enabled BOOLEAN DEFAULT 1,
    last_sync_at TEXT,
    last_state_watermark TEXT,   -- Timestamp for device-owned data
    last_shared_watermark TEXT,  -- HLC for shared resources (JSON)

    capabilities TEXT,  -- JSON object
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

Connection State Management

The sync system subscribes to network connection events and updates the devices table automatically:
// When a device connects (network event)
entities::device::Entity::update_many()
    .col_expr(entities::device::Column::IsOnline, Expr::value(true))
    .col_expr(entities::device::Column::LastSeenAt, Expr::value(Utc::now()))
    .filter(entities::device::Column::Uuid.eq(peer_id))
    .exec(db)
    .await?;

// When a device disconnects (network event)
entities::device::Entity::update_many()
    .col_expr(entities::device::Column::IsOnline, Expr::value(false))
    .col_expr(entities::device::Column::LastSeenAt, Expr::value(Utc::now()))
    .filter(entities::device::Column::Uuid.eq(peer_id))
    .exec(db)
    .await?;
The devices table is the single source of truth - the network layer updates it, and the sync layer queries it to determine which peers to sync with. You can also check real-time connection status via the DeviceRegistry:
// Get current connection status (in-memory state)
let networking = context.get_networking().await?;
let registry = networking.device_registry();
let is_connected = registry.is_device_connected(device_id);