spacedrive/docs/core/devices.mdx
2025-11-14 21:31:21 -08:00

416 lines
11 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
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
<Info>
Spacedrive uses a leaderless sync model. All devices are peers with no central
authority.
</Info>
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](/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?;
```
### Watermark Tracking
Watermarks enable incremental sync by tracking the last successfully synchronized state:
```rust
// 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
```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,
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:
```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, -- 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:
```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