Files
spacedrive/docs/core/devices.mdx
Jamie Pine 091a608762 refactor(core): enhance volume ownership model and update sync documentation
This commit refines the volume ownership model by ensuring that entries and locations inherit ownership from their respective volumes. It updates the documentation to clarify the sync ownership flow, emphasizing the seamless transfer of ownership when external drives are connected to different devices. Additionally, it improves the overall clarity of the sync state machine and related processes, ensuring that the documentation accurately reflects the system's behavior and enhances understanding for developers and users.
2026-01-11 00:19:10 -08:00

415 lines
12 KiB
Plaintext

---
title: Devices
sidebarTitle: Devices
---
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.
```rust
// 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.
```rust
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.
<Info>
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.
</Info>
### 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:
```rust
// 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](/docs/core/addressing) for complete details on URI formats and slug resolution.
### Network Layer
Handles P2P connections and device pairing through Iroh.
<Note>
The network layer uses mDNS for local discovery and QUIC for encrypted
communication.
</Note>
## Device Pairing
Pairing establishes trust between devices using a cryptographic handshake.
### Pairing Flow
<Steps>
<Step title="Generate Code">
Device A generates a pairing code valid for 5 minutes.
```typescript
const pairing = await client.action("network.pair.generate", {});
console.log(`Pairing code: ${pairing.code}`); // "ABCD-1234-EFGH"
```
</Step>
<Step title="Enter Code">
Device B joins using the pairing code.
```typescript
await client.action("network.pair.join", {
code: "ABCD-1234-EFGH"
});
```
</Step>
<Step title="Establish Trust">
Devices exchange cryptographic signatures and derive session keys for future communication.
</Step>
</Steps>
### 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
Ownership flows through volumes. Each device owns its volumes, and volumes own the locations and entries on them:
- **Volumes**: Physical drives attached to the device
- **Locations**: Filesystem paths on a volume
- **Entries**: Files and folders within those locations
This indirection enables portable storage: when an external drive moves between machines, updating the volume's device reference transfers ownership of all associated data instantly.
<Info>
See [Library Sync](/docs/core/library-sync) for details on the ownership chain and portable volume handling.
</Info>
## Sync Participation
<Info>
Spacedrive uses a leaderless sync model. All devices are peers with no central
authority.
</Info>
The `devices` table tracks device state within a library:
- **Connection State**: `is_online`, `last_seen_at` track real-time availability
- **Sync Enablement**: `sync_enabled` controls whether a device participates
- **Last Sync**: `last_sync_at` records when the device last synced
Watermarks for incremental sync are stored separately in sync.db on a per-resource basis. See [Library Sync](/docs/core/library-sync) for details.
### How Devices Participate in Sync
Devices sync data using two protocols based on ownership:
- **Device-owned data** (volumes, locations, entries): Owner broadcasts state, peers apply. Entries inherit ownership through their volume's device. 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](/docs/core/library-sync).
### Sync State Management
Query sync-enabled devices in a library:
```rust
// 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?;
```
### Incremental Sync
When devices reconnect after being offline, they use watermarks to determine what data needs synchronization, avoiding full re-sync. Watermarks are tracked per-resource in sync.db. See [Library Sync](/docs/core/library-sync) for implementation details.
## API Reference
### List Paired Devices
```typescript
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:
```typescript
// 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:
```swift
// 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
<Warning>
Never share device keys or pairing codes over insecure channels.
</Warning>
## 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
```bash
# 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
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 (stored in sync.db, not devices table)
sqlite3 ~/Spacedrive/Libraries/My\ Library.sdlibrary/sync.db \
"SELECT * FROM device_resource_watermarks;"
# 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:
```rust
use sd_core::device::get_current_device_id;
let device_id = get_current_device_id(); // Fast lookup
```
### Device Registration Query
```sql
-- 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,
os TEXT NOT NULL,
os_version TEXT,
hardware_model TEXT,
-- Hardware specifications
cpu_model TEXT,
cpu_architecture TEXT,
cpu_cores_physical INTEGER,
cpu_cores_logical INTEGER,
cpu_frequency_mhz INTEGER,
memory_total_bytes INTEGER,
form_factor TEXT,
manufacturer TEXT,
gpu_models TEXT, -- JSON array
boot_disk_type TEXT,
boot_disk_capacity_bytes INTEGER,
swap_total_bytes INTEGER,
-- Network and status
network_addresses TEXT, -- JSON array
is_online BOOLEAN DEFAULT 0,
last_seen_at TEXT NOT NULL,
capabilities TEXT, -- JSON object
-- Sync coordination
sync_enabled BOOLEAN DEFAULT 1,
last_sync_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
```
<Note>
Watermarks for incremental sync are stored in sync.db, not the devices table. See [Library Sync](/docs/core/library-sync) for watermark tracking details.
</Note>
### Connection State Management
The sync system subscribes to network connection events and updates the `devices` table automatically:
```rust
// 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:
```rust
// 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);
```
## Related Documentation
- [Sync System](/docs/core/sync) - How devices synchronize data
- [Libraries](/docs/core/libraries) - Multi-device library management
- [Networking](/docs/core/networking) - P2P connection details