Files
Jamie Pine 61dea60558 CI green on sources: runner toolchain, clippy, typecheck, tests compile (#3102)
* ci: install rustfmt and clippy components on every runner

Self-hosted runners have rustc preinstalled, so setup-rust skipped the
dtolnay/rust-toolchain step that added the components and the Rust
Formatting job died with 'cargo-fmt is not installed'. Add the components
to rust-toolchain.toml and run rustup component add unconditionally.

* setup: install FFmpeg development libraries on Linux and macOS

crates/ffmpeg is a default workspace member that links libav* through
ffmpeg-sys-next, so a fresh machine fails the first cargo build with
'libavutil.pc not found'. Install the libav dev packages and pkg-config
from scripts/setup.sh for every supported package manager, warn from
xtask setup when pkg-config cannot find them, and document the requirement
in the README build section.

* test: call the trash directly in delete_strategy_test

bcc7337 gave LocalDeleteStrategy::move_to_trash a JobContext parameter so
it can record where the item went; the integration test still passed one
argument and stopped every cargo test target from compiling. The two trash
cases now call ops::files::trash::trash with the same arguments the
strategy forwards, which is what they were asserting on.

* interface: clear the 30 Tauri typecheck errors

Remove the 23 unused imports and locals, apply the rename rules' serde
defaults (regex and whole_name false, start and step 1) where the modal
renders optional fields, drop the size prop ToggleGroup never accepted,
and type SourceCard on ts-client's SourceInfo so a filesystem source with
no adapter renders its data type instead. Register the /redundancy/duplicates
route the dashboard already navigates to, which is what the DuplicatesView
import was for.

* clippy: make cargo clippy --workspace -D warnings pass on Linux

sd-store names its sqlx row tuples with type aliases. sd-ffmpeg converts
with try_from and f32::from where the cast was lossless, takes raw
borrows for FFI out-pointers, resamples from an integer position, and
allows the pointer-alignment and s32-to-f32 precision casts with the
reason. sd-mobile-core sets DaemonRequest.device (None, local execution)
on the two envelopes it builds. file-opening-macos and file-opening-windows
pull their platform bindings only on that target and gate their sources,
matching sd-desktop-macos. The Tauri build script drops externalBin from
the config when no daemon sidecar exists, so a check or clippy run that
never built the daemon still passes, and the desktop binary's macOS-only
commands return on other hosts without unreachable code. Workspace
rust-version moves to 1.95, the floor gpui already imposes, so the MSRV
lint stops flagging APIs the pinned 1.97.1 toolchain uses.

* docs: make the stale doctests compile

sd-media-metadata examples still imported from the old image module and
asserted a plus code that belonged to the other example's coordinates (the
value is verified against the reference Open Location Code encoder);
MediaLocation has no Display, so the parse example compares coordinates.
The sd-task-system example unwraps dispatch and matches the (TaskId,
TaskOutput) pair Done now carries. The spacedrive-sdk sketch names types
that do not exist yet and the job-derive expansion only resolves inside
sd-core, so both are marked ignore with the reason beside them.

* build: pin bun to 1.3.4 everywhere

package.json said bun@1.3.0 while CI installed 1.3.4, and any newer bun
rewrites bun.lockb on install, so the lockfile churned on every machine.
Pin packageManager to the CI version, document it, and install with
--frozen-lockfile in the TypeScript job so a drifting lockfile fails
instead of being silently rewritten.

* status: register the CI green on sources work and the libav requirement

* docs: make the sd-core doctests compile too

Five sd-core examples referenced the crate by its old name, used an arrow
glyph in a rust fence, or called methods without the trait in scope; the
copy strategy sketch needs a running JobContext so it is marked ignore.

* ci: stop shrinking the runner's root disk before installing a toolchain

easimon/maximize-build-space moves the root filesystem's free space into an
LVM volume mounted at the workspace and leaves root with the reserve, which
on the Blacksmith runner came out at 100 MB after the swap file. rustup
installs the pinned 1.97.1 toolchain under /home/runner/.rustup on root, so
both Rust jobs died with 'No space left on device'. The runner has 82 GB
free on root, which is plenty for a clippy target directory, so the step
goes.

* ci: run clippy on the Ubuntu 24.04 Blacksmith runner; let sd-server check without bun

The Blacksmith migration wrote the runner label into the matrix's target
field and left runs-on at GitHub's ubuntu-22.04, whose FFmpeg 4.4 predates
the libav 6 API sd-ffmpeg uses (ch_layout, av_dict_iterate), so the job
could never compile sd-ffmpeg. It now runs on blacksmith-4vcpu-ubuntu-2404
with FFmpeg 6.1, the same as a developer machine on 24.04, and the target
field holds a target triple again.

sd-server's build script skipped the web build when bun was absent and
RustEmbed then failed on the missing apps/web/dist. A debug build now gets
an empty dist with a warning so check and clippy pass; a release build
without bun and without a prebuilt dist still refuses, so a server never
ships without its UI by accident.

* build: let the desktop crate compile without a built frontend

tauri::generate_context! panics when frontendDist is missing, which is how
every check or clippy run looks before bun run build. The build script now
creates an empty apps/tauri/dist for debug builds with a warning; a release
build without one still refuses.

* status: record the CI result for the sources green-up
2026-10-04 20:15:07 +00:00
..

Spacedrive Server

HTTP server for Spacedrive with embedded daemon (RPC only, no web UI).

Overview

sd-server runs the Spacedrive daemon and exposes RPC endpoints over HTTP. Perfect for:

  • NAS deployments (TrueNAS, Unraid, Synology, etc.)
  • Headless servers
  • Remote access to your Spacedrive libraries
  • Docker/container environments
  • CLI-only usage with sd-cli

Architecture

┌─────────────────────────────────────────┐
│         sd-server (HTTP Server)         │
│  ┌───────────────────────────────────┐  │
│  │  Axum HTTP Server (Port 8080)     │  │
│  │  ├─ /health (healthcheck)         │  │
│  │  └─ /rpc (proxy to daemon)        │  │
│  └───────────────────────────────────┘  │
│               ↓                          │
│  ┌───────────────────────────────────┐  │
│  │  Embedded Daemon                  │  │
│  │  (Unix socket: daemon.sock)       │  │
│  │  ├─ Core VDFS                     │  │
│  │  ├─ Indexing                      │  │
│  │  ├─ P2P Networking                │  │
│  │  └─ File Operations               │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘

Unlike Tauri (desktop app), the server:

  • Embeds the daemon instead of spawning a separate process
  • Provides only RPC endpoints (no bundled web UI)
  • Proxies RPC requests to daemon via Unix socket
  • Provides basic auth for security

Quick Start

Development (without Docker)

  1. Build the server:

    cargo build -p sd-server
    
  2. Run the server:

    # Development mode (creates temp data dir)
    cargo run -p sd-server
    
    # Production mode (requires DATA_DIR)
    DATA_DIR=/path/to/data cargo run -p sd-server --release
    
  3. Access the RPC endpoint:

Perfect for TrueNAS, Unraid, or any Docker-compatible NAS.

  1. Create a .env file:

    # REQUIRED: Set your credentials
    SD_AUTH=admin:your-secure-password
    
    # Optional: Change port
    PORT=8080
    
    # Optional: Disable auth (NOT RECOMMENDED)
    # SD_AUTH=disabled
    
  2. Start with docker-compose:

    cd apps/server
    docker-compose up -d
    
  3. Access the server:

    • Navigate to http://your-nas-ip:8080
    • Login with credentials from .env

Configuration

Environment Variables

Variable Description Default Required
DATA_DIR Path to Spacedrive data directory /data (in Docker) Yes (production)
PORT HTTP server port 8080 No
SD_AUTH Authentication credentials (format: user:pass,user2:pass2) None Recommended
SD_P2P Enable P2P networking true No
RUST_LOG Log level info,sd_core=debug No

Authentication

IMPORTANT: Always set SD_AUTH in production!

# Single user
SD_AUTH=admin:securepassword123

# Multiple users
SD_AUTH=admin:pass1,user:pass2,readonly:pass3

# Disable (NOT RECOMMENDED - only for trusted networks)
SD_AUTH=disabled

Uses HTTP Basic Authentication. The server will return 401 Unauthorized if credentials don't match.

Data Storage

The server stores all data in DATA_DIR:

$DATA_DIR/
├── daemon/
│   └── daemon.sock          # Unix socket for RPC
├── libraries/
│   └── *.sdlibrary/         # Library databases
├── logs/                     # Application logs
└── current_library_id.txt   # Last opened library

Docker volumes: Mounted at /data inside the container.

TrueNAS Setup

Using TrueNAS SCALE (Docker)

  1. Navigate to Apps in TrueNAS web UI

  2. Click "Launch Docker Image"

  3. Configure:

    • Image: Build locally or use pre-built image
    • Port: Map 8080 to host
    • Volume: Mount /mnt/pool/spacedrive to /data
    • Environment:
      • SD_AUTH=admin:yourpassword
      • TZ=America/New_York (your timezone)
  4. Add storage pools (optional):

    • Mount your datasets as read-only volumes
    • Example: /mnt/tank/photos → /photos in container

Manual Docker Run

docker run -d \
  --name spacedrive \
  -p 8080:8080 \
  -p 7373:7373 \
  -v /mnt/pool/spacedrive:/data \
  -v /mnt/pool/media:/media:ro \
  -e SD_AUTH=admin:password \
  -e TZ=UTC \
  --restart unless-stopped \
  spacedrive/server:latest

Building

# Build server (RPC only)
cargo build --release -p sd-server

# Run server
./target/release/sd-server --data-dir /path/to/data

You can connect with:

  • sd-cli (CLI client)
  • Custom HTTP clients via /rpc
  • Tauri desktop app configured to connect to this server
  • Future web UI (not yet implemented)

Development Workflow

# Run server in dev mode
cargo run -p sd-server

# Server starts on http://localhost:8080
# Use sd-cli or custom client to interact with RPC endpoint

API Endpoints

GET /health

Health check endpoint.

Response: 200 OK with body "OK"

POST /rpc

JSON-RPC proxy to daemon.

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "query:libraries.list",
  "params": { "include_stats": false }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [...]
}

Comparison: Server vs Tauri

Feature Server Tauri
Platform Linux/Docker macOS/Windows/Linux
UI None (RPC only) Native webview
Daemon Embedded in process Spawned as child process
Access Remote over HTTP Local only
Auth HTTP Basic Auth Not needed (local)
Use Case NAS, headless servers, CLI Desktop workstations

Both use the same Spacedrive core!

Troubleshooting

Server won't start

  • Check DATA_DIR exists and is writable
  • Verify port 8080 is not in use: lsof -i :8080
  • Check logs: RUST_LOG=debug cargo run -p sd-server

Can't connect to daemon

  • Ensure daemon.sock exists in $DATA_DIR/daemon/
  • Check daemon logs in $DATA_DIR/logs/
  • Try removing stale socket: rm $DATA_DIR/daemon/daemon.sock

Authentication failing

  • Verify SD_AUTH format: username:password
  • Check browser is sending Basic Auth header
  • Test with curl:
    curl -u admin:password http://localhost:8080/health
    

Docker build failing

  • Ensure you're building from repository root:
    docker build -f apps/server/Dockerfile .
    
  • Check Docker has enough memory (4GB+ recommended)

Contributing

The server app is part of the Spacedrive v2 monorepo.

Project structure:

apps/server/
├── src/
│   └── main.rs          # Server implementation
├── Cargo.toml           # Dependencies
├── Dockerfile           # Container image
└── docker-compose.yml   # Docker setup

Making changes:

  1. Server code: Edit apps/server/src/main.rs
  2. Daemon integration: See core/src/infra/daemon/

License

Apache-2.0 - See LICENSE file in repository root.