mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-19 05:45:01 -04:00
refactor(Docker): remove legacy Docker setup and streamline server deployment
This commit removes the outdated Dockerfile, docker-compose.yml, and related documentation for the Spacedrive server, consolidating the deployment process into a single Dockerfile located in the apps/server directory. The new setup supports multi-architecture builds and includes enhanced media processing capabilities. Additionally, a self-hosting guide is introduced to assist users in deploying the server on their infrastructure, ensuring a more efficient and user-friendly experience.
This commit is contained in:
71
.github/workflows/release.yml
vendored
71
.github/workflows/release.yml
vendored
@@ -102,6 +102,71 @@ jobs:
|
||||
# name: cli-${{ matrix.platform }}
|
||||
# path: dist/*
|
||||
|
||||
# Server builds for self-hosting
|
||||
server-build:
|
||||
strategy:
|
||||
matrix:
|
||||
settings:
|
||||
- host: ubuntu-22.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
platform: linux-x86_64
|
||||
- host: ubuntu-22.04
|
||||
target: aarch64-unknown-linux-gnu
|
||||
platform: linux-aarch64
|
||||
name: Server - ${{ matrix.settings.platform }}
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Setup System and Rust
|
||||
uses: ./.github/actions/setup-system
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
target: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Install cross-compilation tools (ARM)
|
||||
if: matrix.settings.target == 'aarch64-unknown-linux-gnu'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu
|
||||
|
||||
- name: Setup native dependencies
|
||||
run: cargo run -p xtask -- setup
|
||||
|
||||
- name: Build server binary
|
||||
run: |
|
||||
cargo build --release --bin sd-server --features sd-core/heif,sd-core/ffmpeg --target ${{ matrix.settings.target }}
|
||||
env:
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
|
||||
|
||||
- name: Prepare server binary
|
||||
run: |
|
||||
mkdir -p dist
|
||||
cp target/${{ matrix.settings.target }}/release/sd-server dist/sd-server-${{ matrix.settings.platform }}
|
||||
chmod +x dist/sd-server-${{ matrix.settings.platform }}
|
||||
|
||||
- name: Generate checksum
|
||||
run: |
|
||||
cd dist
|
||||
sha256sum sd-server-${{ matrix.settings.platform }} > sd-server-${{ matrix.settings.platform }}.sha256
|
||||
|
||||
- name: Create archive
|
||||
run: |
|
||||
cd dist
|
||||
tar -czf sd-server-${{ matrix.settings.platform }}.tar.gz sd-server-${{ matrix.settings.platform }} sd-server-${{ matrix.settings.platform }}.sha256
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: server-${{ matrix.settings.platform }}
|
||||
path: dist/sd-server-${{ matrix.settings.platform }}.tar.gz
|
||||
|
||||
# V2 Desktop builds
|
||||
desktop-main:
|
||||
strategy:
|
||||
@@ -229,12 +294,12 @@ jobs:
|
||||
if: always() && runner.os == 'macOS'
|
||||
run: security delete-keychain signing_temp.keychain || true
|
||||
|
||||
# Create unified release with CLI and Desktop artifacts
|
||||
# Create unified release with Server, CLI, and Desktop artifacts
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: self-hosted
|
||||
name: Create Release
|
||||
needs: [desktop-main]
|
||||
needs: [server-build, desktop-main]
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
@@ -247,8 +312,10 @@ jobs:
|
||||
draft: true
|
||||
files: |
|
||||
cli-*/*
|
||||
server-*/*
|
||||
*/*.dmg
|
||||
*/*.msi
|
||||
*/*.deb
|
||||
*/*.tar.xz
|
||||
*/*.tar.gz
|
||||
*/*.sig
|
||||
|
||||
20
.github/workflows/server.yml
vendored
20
.github/workflows/server.yml
vendored
@@ -1,12 +1,13 @@
|
||||
name: Server release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/server.yml'
|
||||
- 'apps/server/docker/*'
|
||||
- 'apps/server/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -81,10 +82,12 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
if [ "$GITHUB_EVENT_NAME" == "release" ]; then
|
||||
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
||||
IMAGE_TAG="${GITHUB_REF##*/}"
|
||||
EXTRA_TAG="latest"
|
||||
else
|
||||
IMAGE_TAG="$(git rev-parse --short "$GITHUB_SHA")"
|
||||
EXTRA_TAG="staging"
|
||||
fi
|
||||
IMAGE_TAG="${IMAGE_TAG,,}"
|
||||
IMAGE_NAME="${GITHUB_REPOSITORY,,}/server"
|
||||
@@ -92,6 +95,7 @@ jobs:
|
||||
echo "Building ${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
|
||||
echo "tag=${IMAGE_TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "extra_tag=${EXTRA_TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "name=${IMAGE_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "repo=${GITHUB_REPOSITORY}" >> "$GITHUB_OUTPUT"
|
||||
echo "repo_ref=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> "$GITHUB_OUTPUT"
|
||||
@@ -105,16 +109,16 @@ jobs:
|
||||
id: build-image
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
tags: ${{ steps.image_info.outputs.tag }} ${{ github.event_name == 'release' && 'latest' || 'staging' }}
|
||||
archs: amd64
|
||||
tags: ${{ steps.image_info.outputs.tag }} ${{ steps.image_info.outputs.extra_tag }}
|
||||
archs: amd64, arm64
|
||||
image: ${{ steps.image_info.outputs.name }}
|
||||
layers: 'false'
|
||||
context: ./apps/server/docker
|
||||
context: .
|
||||
build-args: |
|
||||
REPO=${{ steps.image_info.outputs.repo }}
|
||||
REPO_REF=${{ steps.image_info.outputs.repo_ref }}
|
||||
containerfiles: |
|
||||
./apps/server/docker/Dockerfile
|
||||
./apps/server/Dockerfile
|
||||
|
||||
- name: Push image to ghcr.io
|
||||
uses: redhat-actions/push-to-registry@v2
|
||||
|
||||
158
DOCKER.md
158
DOCKER.md
@@ -1,158 +0,0 @@
|
||||
# Spacedrive Docker Deployment
|
||||
|
||||
Quick guide for running Spacedrive daemon in Docker.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Build the image
|
||||
docker compose build
|
||||
|
||||
# Start daemon
|
||||
docker compose up -d
|
||||
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
|
||||
# Check status
|
||||
docker exec spacedrive-daemon sd-cli status
|
||||
```
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
- **x86_64** (amd64) - Servers, TrueNAS, Intel/AMD systems
|
||||
- **ARM64** (aarch64) - Raspberry Pi 3/4/5, Apple Silicon (via emulation)
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `docker-compose.yml` to customize:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
# Mount directories to index
|
||||
- /path/to/your/photos:/mnt/photos:ro
|
||||
- /path/to/your/documents:/mnt/docs:ro
|
||||
|
||||
environment:
|
||||
# Optional: Set instance name
|
||||
- SPACEDRIVE_INSTANCE=myserver
|
||||
```
|
||||
|
||||
## CLI Access
|
||||
|
||||
```bash
|
||||
# Run any CLI command
|
||||
docker exec spacedrive-daemon sd-cli <command>
|
||||
|
||||
# Examples:
|
||||
docker exec spacedrive-daemon sd-cli library list
|
||||
docker exec spacedrive-daemon sd-cli location add /mnt/photos
|
||||
docker exec spacedrive-daemon sd-cli search "vacation"
|
||||
```
|
||||
|
||||
## Data Persistence
|
||||
|
||||
Data is stored in the `spacedrive-data` Docker volume. To backup:
|
||||
|
||||
```bash
|
||||
# Backup volume
|
||||
docker run --rm -v spacedrive-data:/data -v $(pwd):/backup \
|
||||
alpine tar czf /backup/spacedrive-backup.tar.gz /data
|
||||
|
||||
# Restore volume
|
||||
docker run --rm -v spacedrive-data:/data -v $(pwd):/backup \
|
||||
alpine tar xzf /backup/spacedrive-backup.tar.gz -C /
|
||||
```
|
||||
|
||||
## Building for Specific Platform
|
||||
|
||||
```bash
|
||||
# Build for ARM64 (Raspberry Pi)
|
||||
docker build --platform linux/arm64 -t spacedrive:arm64 .
|
||||
|
||||
# Build for x86_64
|
||||
docker build --platform linux/amd64 -t spacedrive:amd64 .
|
||||
```
|
||||
|
||||
## TrueNAS Deployment
|
||||
|
||||
1. Enable Apps in TrueNAS SCALE
|
||||
2. Create custom app using the provided `docker-compose.yml`
|
||||
3. Mount your pools as volumes:
|
||||
```yaml
|
||||
volumes:
|
||||
- /mnt/pool1:/mnt/pool1:ro
|
||||
- /mnt/pool2:/mnt/pool2:ro
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container exits immediately
|
||||
|
||||
Check logs:
|
||||
```bash
|
||||
docker compose logs
|
||||
```
|
||||
|
||||
### Can't access daemon
|
||||
|
||||
Verify it's running:
|
||||
```bash
|
||||
docker ps
|
||||
docker exec spacedrive-daemon sd-cli status
|
||||
```
|
||||
|
||||
### Out of disk space
|
||||
|
||||
Check Docker disk usage:
|
||||
```bash
|
||||
docker system df
|
||||
```
|
||||
|
||||
Clean up old data:
|
||||
```bash
|
||||
docker system prune
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
### Resource Limits
|
||||
|
||||
Add to `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2.0'
|
||||
memory: 2G
|
||||
```
|
||||
|
||||
### Custom Data Directory
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
# Use host directory instead of volume
|
||||
- /path/to/spacedrive/data:/data
|
||||
```
|
||||
|
||||
### Network Access (Future API)
|
||||
|
||||
```yaml
|
||||
ports:
|
||||
- "8080:8080" # Expose API port
|
||||
```
|
||||
|
||||
## Full Documentation
|
||||
|
||||
See [Linux Deployment Guide](./docs/cli/linux-deployment.mdx) for complete documentation including:
|
||||
- Native binary installation
|
||||
- Systemd service setup
|
||||
- Raspberry Pi specific configuration
|
||||
- TrueNAS integration
|
||||
- Performance tuning
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Documentation: https://docs.spacedrive.com
|
||||
- GitHub Issues: https://github.com/spacedriveapp/spacedrive/issues
|
||||
76
Dockerfile
76
Dockerfile
@@ -1,76 +0,0 @@
|
||||
# Multi-stage Dockerfile for Spacedrive CLI and Daemon
|
||||
# Supports: x86_64 and aarch64 Linux
|
||||
|
||||
# ============================================================================
|
||||
# Builder Stage - Compile Rust binaries
|
||||
# ============================================================================
|
||||
FROM rust:1.81-slim-bookworm AS builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /build
|
||||
|
||||
# Copy workspace files
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY apps/ apps/
|
||||
COPY core/ core/
|
||||
COPY crates/ crates/
|
||||
COPY xtask/ xtask/
|
||||
|
||||
# Copy dependencies (specta, opendal)
|
||||
COPY specta/ specta/
|
||||
COPY opendal/ opendal/
|
||||
|
||||
# Build release binaries
|
||||
# Note: We only build CLI features, no FFmpeg or AI models needed
|
||||
RUN cargo build --release --bin sd-cli --bin sd-daemon
|
||||
|
||||
# ============================================================================
|
||||
# Runtime Stage - Minimal image with only runtime dependencies
|
||||
# ============================================================================
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
libssl3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user
|
||||
RUN useradd -m -u 1000 spacedrive
|
||||
|
||||
# Create data directory
|
||||
RUN mkdir -p /data && chown spacedrive:spacedrive /data
|
||||
|
||||
# Copy binaries from builder
|
||||
COPY --from=builder /build/target/release/sd-cli /usr/local/bin/sd-cli
|
||||
COPY --from=builder /build/target/release/sd-daemon /usr/local/bin/sd-daemon
|
||||
|
||||
# Set permissions
|
||||
RUN chmod +x /usr/local/bin/sd-cli /usr/local/bin/sd-daemon
|
||||
|
||||
# Switch to non-root user
|
||||
USER spacedrive
|
||||
|
||||
# Set data directory as volume
|
||||
VOLUME /data
|
||||
|
||||
# Expose any ports if needed (future: add when API is enabled)
|
||||
# EXPOSE 8080
|
||||
|
||||
# Set environment variables
|
||||
ENV SPACEDRIVE_DATA_DIR=/data
|
||||
|
||||
# Healthcheck
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||
CMD sd-cli status || exit 1
|
||||
|
||||
# Default command: start daemon in foreground
|
||||
CMD ["sd-cli", "--data-dir", "/data", "start", "--foreground"]
|
||||
@@ -135,7 +135,7 @@ Peer-to-peer synchronization without central coordinators. Device-specific data
|
||||
**Apps**
|
||||
|
||||
- **CLI** - Command-line interface (available now)
|
||||
- **Server** - Headless daemon for Docker deployment (available now)
|
||||
- **Server** - Headless daemon for Docker deployment ([self-hosting guide](https://v2.spacedrive.com/overview/self-hosting))
|
||||
- **Tauri** - Cross-platform desktop with React frontend (macOS and Linux now, Windows in alpha.2)
|
||||
- **Web** - Web interface and shared UI components (available now)
|
||||
- **Mobile** - React Native mobile app (iOS and Android coming soon)
|
||||
@@ -331,6 +331,7 @@ Optional cloud integration (Spacedrive Cloud) is available for backup and remote
|
||||
## Documentation
|
||||
|
||||
- **[v2 Documentation](https://v2.spacedrive.com)** - Complete guides and API reference
|
||||
- **[Self-Hosting Guide](https://v2.spacedrive.com/overview/self-hosting)** - Deploy Spacedrive server
|
||||
- **[Whitepaper](whitepaper/spacedrive.pdf)** - Technical architecture (work in progress)
|
||||
- **[Contributing Guide](CONTRIBUTING.md)** - How to contribute
|
||||
- **[Architecture Docs](docs/core/architecture.md)** - Detailed system design
|
||||
|
||||
302
SERVER_RELEASE_SETUP.md
Normal file
302
SERVER_RELEASE_SETUP.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# Server Release Setup
|
||||
|
||||
This document explains the changes made to integrate Spacedrive server builds into the release workflow.
|
||||
|
||||
## Overview
|
||||
|
||||
The server app (`sd-server`) is now built and released in two formats:
|
||||
1. **Static binaries** - For systemd, bare metal, and custom deployments
|
||||
2. **Docker images** - For containerized deployments (Docker, Kubernetes, NAS systems)
|
||||
|
||||
Both are automatically built and published when a git tag is pushed (e.g., `v2.0.0-alpha.2`).
|
||||
|
||||
**Note:** The root `/Dockerfile` has been removed as it was redundant. The only Docker image for self-hosting is `apps/server/Dockerfile`, which builds the HTTP server with embedded daemon and full media processing support.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Release Workflow (`.github/workflows/release.yml`)
|
||||
|
||||
**Added: `server-build` job**
|
||||
|
||||
Builds static server binaries for:
|
||||
- `linux-x86_64` (Intel/AMD servers)
|
||||
- `linux-aarch64` (ARM servers, Raspberry Pi, AWS Graviton)
|
||||
|
||||
**Features:**
|
||||
- Full media processing support (`heif`, `ffmpeg` features enabled)
|
||||
- Cross-compilation for ARM using `gcc-aarch64-linux-gnu`
|
||||
- Checksums generated for each binary (SHA256)
|
||||
- Archives created as `.tar.gz` for easy distribution
|
||||
|
||||
**Artifacts uploaded:**
|
||||
- `sd-server-linux-x86_64.tar.gz`
|
||||
- `sd-server-linux-aarch64.tar.gz`
|
||||
|
||||
**Updated: `release` job**
|
||||
|
||||
- Added `server-build` to dependencies
|
||||
- Server artifacts now included in GitHub releases
|
||||
- Pattern updated to include `.tar.gz` files
|
||||
|
||||
### 2. Server Docker Workflow (`.github/workflows/server.yml`)
|
||||
|
||||
**Changed trigger:**
|
||||
- Old: `release: types: [published]`
|
||||
- New: `push: tags: ["v*"]`
|
||||
- Result: Docker images built at the same time as binaries
|
||||
|
||||
**Multi-arch support:**
|
||||
- Old: `amd64` only
|
||||
- New: `amd64` + `arm64`
|
||||
- Uses QEMU for ARM cross-compilation
|
||||
|
||||
**Fixed paths:**
|
||||
- Old: `context: ./apps/server/docker` (incorrect)
|
||||
- New: `context: .` (repo root)
|
||||
- Old: `containerfiles: ./apps/server/docker/Dockerfile` (incorrect)
|
||||
- New: `containerfiles: ./apps/server/Dockerfile` (correct)
|
||||
|
||||
**Image tagging:**
|
||||
- Git tags (e.g., `v2.0.0-alpha.2`) → `ghcr.io/spacedriveapp/spacedrive/server:v2.0.0-alpha.2` + `latest`
|
||||
- Non-tagged commits → `ghcr.io/spacedriveapp/spacedrive/server:<commit-sha>` + `staging`
|
||||
|
||||
### 3. Server Dockerfile (`apps/server/Dockerfile`)
|
||||
|
||||
**Added media processing dependencies:**
|
||||
|
||||
Builder stage:
|
||||
- `cmake`, `nasm` - Build tools for native dependencies
|
||||
- `libavcodec-dev`, `libavformat-dev`, `libavutil-dev`, `libswscale-dev` - FFmpeg dev libraries
|
||||
- `libheif-dev` - HEIF image format support
|
||||
|
||||
Runtime stage:
|
||||
- Changed from `distroless/cc` to `debian:bookworm-slim`
|
||||
- Installed runtime libraries: `libavcodec59`, `libavformat59`, `libavutil57`, `libswscale6`, `libheif1`
|
||||
- Created `spacedrive` user (UID 1000) for security
|
||||
|
||||
**Enabled features in build:**
|
||||
```dockerfile
|
||||
cargo build --release -p sd-server --features sd-core/heif,sd-core/ffmpeg
|
||||
```
|
||||
|
||||
This enables:
|
||||
- Video thumbnail generation
|
||||
- Audio transcription
|
||||
- HEIF/HEIC image support
|
||||
- All media processing capabilities
|
||||
|
||||
## Release Process
|
||||
|
||||
### Automated Release (Recommended)
|
||||
|
||||
1. **Tag a release:**
|
||||
```bash
|
||||
git tag v2.0.0-alpha.2
|
||||
git push origin v2.0.0-alpha.2
|
||||
```
|
||||
|
||||
2. **GitHub Actions automatically:**
|
||||
- Builds server binaries (x86_64 + ARM)
|
||||
- Builds desktop apps (macOS + Linux)
|
||||
- Builds Docker images (amd64 + arm64)
|
||||
- Creates draft GitHub release with all artifacts
|
||||
|
||||
3. **Review and publish:**
|
||||
- Go to GitHub Releases
|
||||
- Edit the draft release
|
||||
- Add release notes
|
||||
- Publish
|
||||
|
||||
### Manual Testing
|
||||
|
||||
**Test static binary build:**
|
||||
```bash
|
||||
# From project root
|
||||
cargo build --release -p sd-server --features sd-core/heif,sd-core/ffmpeg
|
||||
|
||||
# Test locally
|
||||
./target/release/sd-server --data-dir /tmp/sd-test
|
||||
```
|
||||
|
||||
**Test Docker build:**
|
||||
```bash
|
||||
# From project root
|
||||
docker build -f apps/server/Dockerfile -t sd-server-test .
|
||||
|
||||
# Run locally
|
||||
docker run -p 8080:8080 -e SD_AUTH=admin:test sd-server-test
|
||||
```
|
||||
|
||||
**Test multi-arch Docker build:**
|
||||
```bash
|
||||
docker buildx create --use
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
-f apps/server/Dockerfile \
|
||||
-t sd-server-multiarch \
|
||||
.
|
||||
```
|
||||
|
||||
## Deployment Options
|
||||
|
||||
### Option 1: Static Binary (systemd)
|
||||
|
||||
```bash
|
||||
# Download from GitHub release
|
||||
wget https://github.com/spacedriveapp/spacedrive/releases/download/v2.0.0-alpha.2/sd-server-linux-x86_64.tar.gz
|
||||
tar -xzf sd-server-linux-x86_64.tar.gz
|
||||
|
||||
# Verify checksum
|
||||
sha256sum -c sd-server-linux-x86_64.sha256
|
||||
|
||||
# Install
|
||||
sudo mv sd-server-linux-x86_64 /usr/local/bin/sd-server
|
||||
sudo chmod +x /usr/local/bin/sd-server
|
||||
|
||||
# Create systemd service
|
||||
sudo nano /etc/systemd/system/spacedrive.service
|
||||
```
|
||||
|
||||
Example systemd unit:
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Spacedrive Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=spacedrive
|
||||
Environment="DATA_DIR=/var/lib/spacedrive"
|
||||
Environment="SD_AUTH=admin:your-secure-password"
|
||||
ExecStart=/usr/local/bin/sd-server --data-dir /var/lib/spacedrive
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
### Option 2: Docker
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name spacedrive \
|
||||
-p 8080:8080 \
|
||||
-p 7373:7373 \
|
||||
-v spacedrive-data:/data \
|
||||
-e SD_AUTH=admin:password \
|
||||
ghcr.io/spacedriveapp/spacedrive/server:latest
|
||||
```
|
||||
|
||||
### Option 3: Docker Compose
|
||||
|
||||
Use the provided `docker-compose.yml` in `apps/server/`:
|
||||
|
||||
```bash
|
||||
cd apps/server
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Or create your own:
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
spacedrive:
|
||||
image: ghcr.io/spacedriveapp/spacedrive/server:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "7373:7373"
|
||||
volumes:
|
||||
- spacedrive-data:/data
|
||||
- /mnt/storage:/storage:ro # Optional: mount storage
|
||||
environment:
|
||||
SD_AUTH: "admin:your-password"
|
||||
TZ: "America/New_York"
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
spacedrive-data:
|
||||
```
|
||||
|
||||
## Architecture Support
|
||||
|
||||
| Platform | Binary | Docker |
|
||||
|----------|--------|--------|
|
||||
| Linux x86_64 (Intel/AMD) | ✅ | ✅ |
|
||||
| Linux ARM64 (Raspberry Pi, AWS Graviton) | ✅ | ✅ |
|
||||
| macOS | ❌ (desktop app only) | ❌ |
|
||||
| Windows | ❌ (desktop app only) | ❌ |
|
||||
|
||||
## Verify Release Artifacts
|
||||
|
||||
After a release is created, verify these files exist:
|
||||
|
||||
**Server binaries:**
|
||||
- `sd-server-linux-x86_64.tar.gz`
|
||||
- `sd-server-linux-aarch64.tar.gz`
|
||||
|
||||
**Desktop apps:**
|
||||
- `Spacedrive_<version>_aarch64.dmg` (macOS ARM)
|
||||
- `Spacedrive_<version>_amd64.deb` (Linux)
|
||||
- `dist.tar.xz` (frontend assets)
|
||||
|
||||
**Docker images:**
|
||||
Check ghcr.io:
|
||||
```bash
|
||||
docker pull ghcr.io/spacedriveapp/spacedrive/server:v2.0.0-alpha.2
|
||||
docker pull ghcr.io/spacedriveapp/spacedrive/server:latest
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Potential Enhancements
|
||||
|
||||
1. **Add Windows server binary** - Build `sd-server.exe` for Windows Server deployments
|
||||
2. **Package formats** - Create `.deb` and `.rpm` packages for easier installation
|
||||
3. **ARM macOS** - Server binary for macOS (though desktop app is preferred)
|
||||
4. **Static linking** - Fully static binaries using `musl` for maximum compatibility
|
||||
5. **Checksums in release notes** - Auto-generate checksums table in release description
|
||||
|
||||
### Documentation Updates Needed
|
||||
|
||||
- [x] Add "Self-Hosting Guide" to docs (`docs/overview/self-hosting.mdx`)
|
||||
- [x] Update main README with server deployment links
|
||||
- [ ] Create TrueNAS app manifest for one-click install
|
||||
- [ ] Write Unraid template
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build fails with "media features not found"
|
||||
|
||||
The workflow now automatically includes `sd-core/heif` and `sd-core/ffmpeg` features. If this fails:
|
||||
- Check native dependencies are installed (cmake, nasm, FFmpeg dev packages)
|
||||
- Verify setup-system action runs successfully
|
||||
|
||||
### Docker image size too large
|
||||
|
||||
Current runtime image uses `debian:bookworm-slim` (~80-100MB base) plus runtime libraries.
|
||||
|
||||
To reduce size:
|
||||
- Consider Alpine Linux base (smaller but more complex dependencies)
|
||||
- Use distroless and manually copy .so files (more brittle)
|
||||
- Current approach prioritizes reliability over size
|
||||
|
||||
### Multi-arch build timeout
|
||||
|
||||
ARM builds can be slow via QEMU emulation. Options:
|
||||
- Use native ARM runners (more expensive)
|
||||
- Build in parallel jobs
|
||||
- Cache build artifacts more aggressively
|
||||
|
||||
### Permission denied in container
|
||||
|
||||
The container runs as user `spacedrive` (UID 1000). If mounting volumes:
|
||||
```bash
|
||||
# Fix permissions
|
||||
sudo chown -R 1000:1000 /path/to/data
|
||||
```
|
||||
|
||||
Or run as root (not recommended):
|
||||
```bash
|
||||
docker run --user root ...
|
||||
```
|
||||
@@ -11,7 +11,14 @@ RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/
|
||||
git \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
ca-certificates
|
||||
ca-certificates \
|
||||
cmake \
|
||||
nasm \
|
||||
libavcodec-dev \
|
||||
libavformat-dev \
|
||||
libavutil-dev \
|
||||
libswscale-dev \
|
||||
libheif-dev
|
||||
|
||||
# Install Rust
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
|
||||
@@ -28,17 +35,32 @@ COPY core ./core
|
||||
COPY crates ./crates
|
||||
COPY apps/server ./apps/server
|
||||
|
||||
# Build server (RPC only, no assets)
|
||||
# Build server with media processing features
|
||||
RUN --mount=type=cache,target=/root/.cargo/registry \
|
||||
--mount=type=cache,target=/root/.cargo/git \
|
||||
--mount=type=cache,target=/build/target \
|
||||
cargo build --release -p sd-server && \
|
||||
cargo build --release -p sd-server --features sd-core/heif,sd-core/ffmpeg && \
|
||||
cp target/release/sd-server /usr/local/bin/sd-server
|
||||
|
||||
#--
|
||||
# Runtime image
|
||||
#--
|
||||
FROM gcr.io/distroless/cc-debian12:nonroot
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
# Install runtime dependencies only
|
||||
RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \
|
||||
apt-get update && apt-get install -y \
|
||||
libssl3 \
|
||||
ca-certificates \
|
||||
libavcodec59 \
|
||||
libavformat59 \
|
||||
libavutil57 \
|
||||
libswscale6 \
|
||||
libheif1 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user
|
||||
RUN useradd -m -u 1000 spacedrive
|
||||
|
||||
# Copy binary
|
||||
COPY --from=builder /usr/local/bin/sd-server /usr/bin/sd-server
|
||||
@@ -58,7 +80,7 @@ EXPOSE 7373
|
||||
VOLUME ["/data"]
|
||||
|
||||
# Run as non-root user
|
||||
USER nonroot:nonroot
|
||||
USER spacedrive:spacedrive
|
||||
|
||||
# Start server
|
||||
ENTRYPOINT ["/usr/bin/sd-server"]
|
||||
|
||||
@@ -36,7 +36,7 @@ impl ActionManager {
|
||||
|
||||
// Check if confirmation is required
|
||||
match validation_result {
|
||||
super::ValidationResult::Success => {
|
||||
super::ValidationResult::Success { .. } => {
|
||||
// Proceed with execution
|
||||
}
|
||||
super::ValidationResult::RequiresConfirmation(_request) => {
|
||||
@@ -95,7 +95,7 @@ impl ActionManager {
|
||||
|
||||
// Check if confirmation is required
|
||||
match validation_result {
|
||||
super::ValidationResult::Success => {
|
||||
super::ValidationResult::Success { .. } => {
|
||||
// Proceed with execution
|
||||
}
|
||||
super::ValidationResult::RequiresConfirmation(_request) => {
|
||||
|
||||
@@ -22,7 +22,11 @@ pub mod receipt;
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ValidationResult {
|
||||
/// The action is valid and can proceed without user interaction.
|
||||
Success,
|
||||
Success {
|
||||
/// Optional metadata for rich UI display (strategy info, file counts, etc.)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
metadata: Option<serde_json::Value>,
|
||||
},
|
||||
/// The action is valid, but requires user confirmation to proceed.
|
||||
RequiresConfirmation(ConfirmationRequest),
|
||||
}
|
||||
@@ -64,7 +68,7 @@ pub trait CoreAction: Send + Sync + 'static {
|
||||
) -> impl std::future::Future<
|
||||
Output = Result<ValidationResult, crate::infra::action::error::ActionError>,
|
||||
> + Send {
|
||||
async { Ok(ValidationResult::Success) }
|
||||
async { Ok(ValidationResult::Success { metadata: None }) }
|
||||
}
|
||||
|
||||
/// Resolve a user confirmation choice (optional)
|
||||
@@ -112,7 +116,7 @@ pub trait LibraryAction: Send + Sync + 'static {
|
||||
) -> impl std::future::Future<
|
||||
Output = Result<ValidationResult, crate::infra::action::error::ActionError>,
|
||||
> + Send {
|
||||
async { Ok(ValidationResult::Success) }
|
||||
async { Ok(ValidationResult::Success { metadata: None }) }
|
||||
}
|
||||
|
||||
/// Resolve a user confirmation choice (optional)
|
||||
|
||||
@@ -321,10 +321,10 @@ impl LibraryAction for FileCopyAction {
|
||||
"strategy": strategy_metadata,
|
||||
"file_count": file_count,
|
||||
"total_bytes": total_bytes,
|
||||
"conflicts": conflicts.iter().map(|c| {
|
||||
"conflicts": conflicts.iter().map(|(source, dest)| {
|
||||
json!({
|
||||
"source": c.to_string_lossy(),
|
||||
"destination": c.to_string_lossy(),
|
||||
"source": source.to_string_lossy(),
|
||||
"destination": dest.to_string_lossy(),
|
||||
})
|
||||
}).collect::<Vec<_>>(),
|
||||
"is_fast_operation": strategy_metadata.is_fast_operation,
|
||||
@@ -357,7 +357,9 @@ impl LibraryAction for FileCopyAction {
|
||||
|
||||
// If it's a fast operation with no conflicts, return success with metadata
|
||||
// Frontend can use this to decide whether to show a modal or auto-proceed
|
||||
Ok(ValidationResult::Success)
|
||||
Ok(ValidationResult::Success {
|
||||
metadata: Some(metadata),
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_confirmation(&mut self, choice_index: usize) -> Result<(), ActionError> {
|
||||
@@ -474,8 +476,8 @@ impl FileCopyAction {
|
||||
Ok((count, size))
|
||||
}
|
||||
|
||||
/// Check for all file conflicts and return list of conflicting paths
|
||||
async fn check_for_conflicts_detailed(&self) -> Result<Vec<PathBuf>, ActionError> {
|
||||
/// Check for all file conflicts and return list of conflicting (source, destination) pairs
|
||||
async fn check_for_conflicts_detailed(&self) -> Result<Vec<(PathBuf, PathBuf)>, ActionError> {
|
||||
let mut conflicts = Vec::new();
|
||||
|
||||
let dest_path = match self.destination.as_local_path() {
|
||||
@@ -501,7 +503,7 @@ impl FileCopyAction {
|
||||
|
||||
// Check if this would conflict
|
||||
if actual_dest.exists() {
|
||||
conflicts.push(actual_dest);
|
||||
conflicts.push((source_path.to_path_buf(), actual_dest));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,7 +514,7 @@ impl FileCopyAction {
|
||||
/// Check if any destination files would cause conflicts (legacy method)
|
||||
async fn check_for_conflicts(&self) -> Result<Option<PathBuf>, ActionError> {
|
||||
let conflicts = self.check_for_conflicts_detailed().await?;
|
||||
Ok(conflicts.into_iter().next())
|
||||
Ok(conflicts.into_iter().next().map(|(_, dest)| dest))
|
||||
}
|
||||
|
||||
/// Generate a unique destination path by appending a number if the original exists
|
||||
|
||||
@@ -915,6 +915,13 @@ impl ToGenericProgress for CopyProgress {
|
||||
progress = progress.with_current_path(path.clone());
|
||||
}
|
||||
|
||||
// Add strategy metadata for UI display
|
||||
if let Some(ref strategy_metadata) = self.strategy_metadata {
|
||||
progress = progress.with_metadata(serde_json::json!({
|
||||
"strategy": strategy_metadata
|
||||
}));
|
||||
}
|
||||
|
||||
progress
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ impl LibraryAction for CreateFolderAction {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ValidationResult::Success)
|
||||
Ok(ValidationResult::Success { metadata: None })
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
|
||||
@@ -85,7 +85,7 @@ impl LibraryAction for FileDeleteAction {
|
||||
});
|
||||
}
|
||||
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ impl LibraryAction for FileRenameAction {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(ValidationResult::Success)
|
||||
Ok(ValidationResult::Success { metadata: None })
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
|
||||
@@ -75,7 +75,7 @@ impl LibraryAction for IndexingAction {
|
||||
message: errors.join("; "),
|
||||
});
|
||||
}
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
|
||||
@@ -77,7 +77,7 @@ impl CoreAction for LibraryCreateAction {
|
||||
message: "Library name cannot be empty".to_string(),
|
||||
});
|
||||
}
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ impl CoreAction for LibraryOpenAction {
|
||||
});
|
||||
}
|
||||
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ impl LibraryAction for LibraryRenameAction {
|
||||
});
|
||||
}
|
||||
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ impl LibraryAction for LocationAddAction {
|
||||
// Check for duplicate locations
|
||||
// TODO: Implement proper duplicate detection for both Physical and Cloud paths
|
||||
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ impl LibraryAction for EnableIndexingAction {
|
||||
});
|
||||
}
|
||||
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ impl LibraryAction for LocationImportAction {
|
||||
});
|
||||
}
|
||||
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
|
||||
async fn execute(
|
||||
|
||||
@@ -235,7 +235,7 @@ impl LibraryAction for LocationTriggerJobAction {
|
||||
return Err(ActionError::LocationNotFound(self.input.location_id));
|
||||
}
|
||||
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ impl LibraryAction for LocationUpdateAction {
|
||||
return Err(ActionError::LocationNotFound(self.input.id));
|
||||
}
|
||||
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ impl CoreAction for LibrarySyncSetupAction {
|
||||
});
|
||||
}
|
||||
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ impl LibrarySyncSetupAction {
|
||||
);
|
||||
}
|
||||
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
|
||||
/// Execute ShareLocalLibrary action - share local library to remote device
|
||||
|
||||
@@ -113,7 +113,7 @@ impl LibraryAction for AddGroupAction {
|
||||
_library: &std::sync::Arc<crate::library::Library>,
|
||||
_context: std::sync::Arc<CoreContext>,
|
||||
) -> Result<crate::infra::action::ValidationResult, ActionError> {
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ impl LibraryAction for AddItemAction {
|
||||
_library: &std::sync::Arc<crate::library::Library>,
|
||||
_context: std::sync::Arc<CoreContext>,
|
||||
) -> Result<crate::infra::action::ValidationResult, ActionError> {
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ impl LibraryAction for SpaceCreateAction {
|
||||
_library: &std::sync::Arc<crate::library::Library>,
|
||||
_context: std::sync::Arc<CoreContext>,
|
||||
) -> Result<crate::infra::action::ValidationResult, ActionError> {
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ impl LibraryAction for SpaceDeleteAction {
|
||||
_library: &std::sync::Arc<crate::library::Library>,
|
||||
_context: std::sync::Arc<CoreContext>,
|
||||
) -> Result<crate::infra::action::ValidationResult, ActionError> {
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ impl LibraryAction for DeleteGroupAction {
|
||||
_library: &std::sync::Arc<crate::library::Library>,
|
||||
_context: std::sync::Arc<CoreContext>,
|
||||
) -> Result<crate::infra::action::ValidationResult, ActionError> {
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ impl LibraryAction for DeleteItemAction {
|
||||
_library: &std::sync::Arc<crate::library::Library>,
|
||||
_context: std::sync::Arc<CoreContext>,
|
||||
) -> Result<crate::infra::action::ValidationResult, ActionError> {
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ impl LibraryAction for ReorderGroupsAction {
|
||||
_library: &std::sync::Arc<crate::library::Library>,
|
||||
_context: std::sync::Arc<CoreContext>,
|
||||
) -> Result<crate::infra::action::ValidationResult, ActionError> {
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ impl LibraryAction for ReorderItemsAction {
|
||||
_library: &std::sync::Arc<crate::library::Library>,
|
||||
_context: std::sync::Arc<CoreContext>,
|
||||
) -> Result<crate::infra::action::ValidationResult, ActionError> {
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ impl LibraryAction for SpaceUpdateAction {
|
||||
_library: &std::sync::Arc<crate::library::Library>,
|
||||
_context: std::sync::Arc<CoreContext>,
|
||||
) -> Result<crate::infra::action::ValidationResult, ActionError> {
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ impl LibraryAction for UpdateGroupAction {
|
||||
_library: &std::sync::Arc<crate::library::Library>,
|
||||
_context: std::sync::Arc<CoreContext>,
|
||||
) -> Result<crate::infra::action::ValidationResult, ActionError> {
|
||||
Ok(crate::infra::action::ValidationResult::Success)
|
||||
Ok(crate::infra::action::ValidationResult::Success { metadata: None })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
spacedrive:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: spacedrive:latest
|
||||
container_name: spacedrive-daemon
|
||||
restart: unless-stopped
|
||||
|
||||
# Data persistence
|
||||
volumes:
|
||||
- spacedrive-data:/data
|
||||
# Optional: mount host directories to index
|
||||
# - /path/to/your/files:/mnt/files:ro
|
||||
|
||||
# Environment variables
|
||||
environment:
|
||||
- SPACEDRIVE_DATA_DIR=/data
|
||||
# Optional: Set instance name
|
||||
# - SPACEDRIVE_INSTANCE=default
|
||||
|
||||
# Optional: Expose ports when API is enabled
|
||||
# ports:
|
||||
# - "8080:8080"
|
||||
|
||||
# Health check
|
||||
healthcheck:
|
||||
test: ["CMD", "sd-cli", "status"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
spacedrive-data:
|
||||
driver: local
|
||||
@@ -57,6 +57,7 @@
|
||||
"icon": "compass",
|
||||
"pages": [
|
||||
"overview/get-started",
|
||||
"overview/self-hosting",
|
||||
"overview/backup-photos-ios",
|
||||
"overview/manage-libraries",
|
||||
"overview/add-index-locations"
|
||||
|
||||
317
docs/overview/self-hosting.mdx
Normal file
317
docs/overview/self-hosting.mdx
Normal file
@@ -0,0 +1,317 @@
|
||||
---
|
||||
title: Self-Hosting Spacedrive
|
||||
description: Deploy Spacedrive server for remote access and headless operation
|
||||
sidebarTitle: Self-Hosting
|
||||
---
|
||||
|
||||
Self-hosting Spacedrive means running the server component on hardware you control. The server provides HTTP access to your libraries, enabling remote connections from desktop and mobile apps. Unlike the desktop app which requires a GUI, the server runs headless on Linux systems, NAS devices, or cloud instances.
|
||||
|
||||
The server embeds the full Spacedrive daemon with media processing capabilities. It exposes RPC endpoints over HTTP with optional basic authentication. Your data stays on your infrastructure while remaining accessible from any device with network access.
|
||||
|
||||
## Installation Methods
|
||||
|
||||
### Static Binary
|
||||
|
||||
Download the latest release binary for your platform. This method works on any Linux system and requires no container runtime.
|
||||
|
||||
```bash
|
||||
# Download and extract
|
||||
wget https://github.com/spacedriveapp/spacedrive/releases/latest/download/sd-server-linux-x86_64.tar.gz
|
||||
tar -xzf sd-server-linux-x86_64.tar.gz
|
||||
|
||||
# Verify checksum
|
||||
sha256sum -c sd-server-linux-x86_64.sha256
|
||||
|
||||
# Install to system
|
||||
sudo mv sd-server-linux-x86_64 /usr/local/bin/sd-server
|
||||
sudo chmod +x /usr/local/bin/sd-server
|
||||
```
|
||||
|
||||
For ARM systems like Raspberry Pi, use `sd-server-linux-aarch64.tar.gz` instead.
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
Docker images are available for both x86_64 and ARM64 architectures. The image includes all media processing dependencies and runs as a non-root user.
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name spacedrive \
|
||||
-p 8080:8080 \
|
||||
-p 7373:7373 \
|
||||
-v spacedrive-data:/data \
|
||||
-e SD_AUTH=admin:your-secure-password \
|
||||
ghcr.io/spacedriveapp/spacedrive/server:latest
|
||||
```
|
||||
|
||||
The server listens on port 8080 for HTTP traffic and port 7373 for peer-to-peer connections. Mount additional volumes to index existing storage.
|
||||
|
||||
### Docker Compose
|
||||
|
||||
For production deployments, use the provided compose file in the repository at `apps/server/docker-compose.yml`. This includes health checks and restart policies.
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
spacedrive:
|
||||
image: ghcr.io/spacedriveapp/spacedrive/server:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "7373:7373"
|
||||
volumes:
|
||||
- spacedrive-data:/data
|
||||
- /mnt/media:/media:ro
|
||||
environment:
|
||||
SD_AUTH: "admin:your-password"
|
||||
TZ: "America/New_York"
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
spacedrive-data:
|
||||
```
|
||||
|
||||
## System Service Setup
|
||||
|
||||
Running the server as a systemd service ensures it starts on boot and restarts on failure.
|
||||
|
||||
Create a service file at `/etc/systemd/system/spacedrive.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Spacedrive Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=spacedrive
|
||||
Group=spacedrive
|
||||
Environment="DATA_DIR=/var/lib/spacedrive"
|
||||
Environment="SD_AUTH=admin:your-secure-password"
|
||||
ExecStart=/usr/local/bin/sd-server --data-dir /var/lib/spacedrive
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Create the user and data directory, then enable the service:
|
||||
|
||||
```bash
|
||||
sudo useradd -r -s /bin/false spacedrive
|
||||
sudo mkdir -p /var/lib/spacedrive
|
||||
sudo chown spacedrive:spacedrive /var/lib/spacedrive
|
||||
sudo systemctl enable --now spacedrive
|
||||
```
|
||||
|
||||
Check status with `sudo systemctl status spacedrive`.
|
||||
|
||||
## Configuration
|
||||
|
||||
The server accepts configuration via environment variables or command-line flags.
|
||||
|
||||
**Authentication** is controlled by `SD_AUTH`. The format supports multiple users separated by commas: `user1:pass1,user2:pass2`. Set to `disabled` to disable authentication, though this is not recommended for network-accessible deployments.
|
||||
|
||||
**Data directory** defaults to `/data` in Docker or a temporary directory in development. In production, always set `DATA_DIR` to a persistent location. The server creates library databases, thumbnails, and logs in this directory.
|
||||
|
||||
**Port configuration** defaults to 8080. Change with `--port` flag or `PORT` environment variable. The P2P port (7373) is fixed and required for device sync.
|
||||
|
||||
<Warning>
|
||||
Production deployments must set `SD_AUTH`. The server refuses to start without authentication unless explicitly disabled. This prevents accidental exposure of your libraries.
|
||||
</Warning>
|
||||
|
||||
## Network Access
|
||||
|
||||
The server binds to all interfaces by default, making it accessible on your local network. For internet access, place it behind a reverse proxy with TLS termination.
|
||||
|
||||
### Nginx Example
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name spacedrive.example.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/spacedrive.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/spacedrive.example.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Caddy Example
|
||||
|
||||
Caddy handles TLS automatically with Let's Encrypt:
|
||||
|
||||
```
|
||||
spacedrive.example.com {
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
```
|
||||
|
||||
## NAS Deployment
|
||||
|
||||
Network-attached storage devices are ideal for Spacedrive server. The server indexes existing media while providing remote access.
|
||||
|
||||
### TrueNAS SCALE
|
||||
|
||||
Install via the Apps interface using Docker. Map your pools as read-only volumes to prevent accidental modification during indexing.
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /mnt/pool1/photos:/photos:ro
|
||||
- /mnt/pool1/videos:/videos:ro
|
||||
- /mnt/pool2/documents:/documents:ro
|
||||
```
|
||||
|
||||
After deployment, create locations in Spacedrive pointing to these mount points. The server indexes files without moving them.
|
||||
|
||||
### Synology NAS
|
||||
|
||||
Use Container Manager to deploy the Docker image. Configure port forwarding and volume mounts through the UI. Ensure the container has read access to your shared folders.
|
||||
|
||||
### Unraid
|
||||
|
||||
Add a custom container template with the image `ghcr.io/spacedriveapp/spacedrive/server:latest`. Map your shares as additional paths in the container configuration.
|
||||
|
||||
## Storage Considerations
|
||||
|
||||
The server creates three types of data in the data directory:
|
||||
|
||||
**Libraries** contain SQLite databases with file indexes, tags, and metadata. Each library is a separate `.sdlibrary` directory. These are small, typically under 100MB even for millions of files.
|
||||
|
||||
**Sidecars** include thumbnails, previews, and extracted text. Size depends on media processing settings. Budget 1-5% of your total media size for thumbnails.
|
||||
|
||||
**Logs** rotate automatically but can grow large during initial indexing. The server keeps the last 10 log files, typically under 100MB total.
|
||||
|
||||
Plan for library growth over time. A library indexing 1 million files uses approximately 500MB for the database and 10-50GB for sidecars, depending on media density.
|
||||
|
||||
<Info>
|
||||
The server never modifies your original files. Indexing is read-only. File operations like copy and move require explicit user action through connected clients.
|
||||
</Info>
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
Self-hosted servers face different threats than desktop apps. Follow these guidelines for secure deployment.
|
||||
|
||||
**Always use authentication** when the server is network-accessible. HTTP basic auth is sufficient for home networks but consider stronger methods for internet-facing deployments.
|
||||
|
||||
**Enable TLS** for any internet access. Let's Encrypt provides free certificates through Caddy, Nginx, or Traefik. The server itself does not handle TLS, rely on reverse proxies.
|
||||
|
||||
**Limit exposure** with firewall rules. Only open port 8080 to trusted networks or VPN connections. The P2P port (7373) requires UDP ingress for device sync but can be restricted to known peers.
|
||||
|
||||
**Run as non-root** user. The Docker image and systemd example both use dedicated users with minimal permissions. Never run the server as root.
|
||||
|
||||
**Regular backups** of the data directory protect against corruption. The SQLite databases can be backed up while running using the `.backup` command or by copying the entire data directory when the server is stopped.
|
||||
|
||||
## Connecting Clients
|
||||
|
||||
Desktop and mobile apps can connect to self-hosted servers by configuring the server URL in settings.
|
||||
|
||||
The URL format is `http://your-server:8080` or `https://spacedrive.example.com` if using a reverse proxy. Clients authenticate using credentials set in `SD_AUTH`.
|
||||
|
||||
P2P sync works between the server and other devices without additional configuration. The server appears as a device in your library and participates in sync like any other device.
|
||||
|
||||
## Monitoring
|
||||
|
||||
The server provides a health check endpoint at `/health` that returns HTTP 200 when operational. Use this for monitoring tools or container orchestration health probes.
|
||||
|
||||
Logs are written to stdout and the data directory under `logs/`. Set `RUST_LOG` environment variable to `debug` for detailed output during troubleshooting.
|
||||
|
||||
**Key metrics to monitor:**
|
||||
|
||||
Disk usage in the data directory grows with library size. Alert when free space drops below 20% to prevent database corruption.
|
||||
|
||||
Memory usage typically ranges from 100-500MB depending on active jobs. Spike during intensive operations like thumbnail generation.
|
||||
|
||||
Network bandwidth peaks during initial sync with new devices. Ongoing sync uses minimal bandwidth after the first synchronization.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Server won't start:**
|
||||
|
||||
Check that the data directory exists and is writable by the server user. Verify no other process is using port 8080 with `lsof -i :8080`. Review logs for specific errors.
|
||||
|
||||
**Authentication failures:**
|
||||
|
||||
Ensure `SD_AUTH` format is correct. Test credentials with curl: `curl -u admin:password http://localhost:8080/health`. Browser authentication dialogs require exact username and password.
|
||||
|
||||
**Clients can't connect:**
|
||||
|
||||
Verify the server is accessible from the client network. Test with `curl http://server-ip:8080/health`. Check firewall rules on both server and client networks. Ensure reverse proxy configuration preserves authentication headers.
|
||||
|
||||
**Media processing fails:**
|
||||
|
||||
The server requires FFmpeg and libheif for video thumbnails and HEIF images. Docker images include these by default. Binary installations on minimal systems may need manual installation of these libraries.
|
||||
|
||||
**Sync not working:**
|
||||
|
||||
UDP port 7373 must be accessible for P2P connections. Check NAT and firewall rules. The server attempts hole-punching for NAT traversal but may fall back to relay servers if direct connection fails.
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
The server handles multiple concurrent operations through the job system. Performance depends on storage speed, available memory, and indexing load.
|
||||
|
||||
**Indexing performance** is limited by storage I/O. SSDs provide 10-50x faster indexing than spinning disks. Network storage over gigabit ethernet typically indexes at 50-100MB/s for metadata reads.
|
||||
|
||||
**Thumbnail generation** is CPU-bound. Disable thumbnail generation for video files if CPU usage is a concern. Image thumbnails are fast but video processing can use significant resources.
|
||||
|
||||
**Database optimization** happens automatically through SQLite's auto-vacuum and WAL mode. Large libraries benefit from periodic `VACUUM` operations when the server is idle.
|
||||
|
||||
**Memory allocation** can be limited using systemd or Docker resource constraints. The server operates efficiently with 512MB minimum, though 2GB provides better performance for large libraries.
|
||||
|
||||
## Backup Strategy
|
||||
|
||||
Back up the data directory regularly to prevent data loss. The recommended approach depends on your infrastructure.
|
||||
|
||||
**Systemd deployments** can use rsync or similar tools while the server is running. SQLite WAL mode ensures consistent reads during backup.
|
||||
|
||||
**Docker deployments** should back up named volumes. Stop the container, copy the volume contents, then restart. Alternatively, use volume backup tools that handle running containers.
|
||||
|
||||
**Cloud backups** of library databases are small enough for frequent uploads. Sidecar data can be regenerated from source files if needed, reducing backup size.
|
||||
|
||||
Test restore procedures periodically. A backup is only useful if you can restore it successfully.
|
||||
|
||||
## Migration
|
||||
|
||||
Moving an existing library to a new server requires copying the data directory and adjusting location paths.
|
||||
|
||||
Copy the entire data directory to the new server. Start the server and verify the library loads. Update location paths if the mount points differ on the new system.
|
||||
|
||||
File content identity ensures files are recognized even if paths change. The server re-indexes locations with new paths but preserves tags, collections, and metadata.
|
||||
|
||||
## Updates
|
||||
|
||||
The server receives updates through new Docker image tags or binary releases. Always review release notes before updating to understand breaking changes.
|
||||
|
||||
**Docker updates:**
|
||||
|
||||
Pull the new image and recreate the container. Data persists in volumes across container replacements.
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/spacedriveapp/spacedrive/server:latest
|
||||
docker stop spacedrive
|
||||
docker rm spacedrive
|
||||
# Run docker run command with new image
|
||||
```
|
||||
|
||||
**Binary updates:**
|
||||
|
||||
Download the new release, verify checksums, and replace the binary. Restart the systemd service.
|
||||
|
||||
```bash
|
||||
sudo systemctl stop spacedrive
|
||||
sudo mv sd-server-new /usr/local/bin/sd-server
|
||||
sudo systemctl start spacedrive
|
||||
```
|
||||
|
||||
Database migrations run automatically on first start after update. Check logs to confirm successful migration.
|
||||
|
||||
## Support
|
||||
|
||||
Report issues on [GitHub](https://github.com/spacedriveapp/spacedrive/issues) with server logs and deployment details. Join [Discord](https://discord.gg/gTaF2Z44f5) for community support and deployment advice.
|
||||
@@ -98,6 +98,7 @@ export function JobManagerPopover({ className }: JobManagerPopoverProps) {
|
||||
pause={pause}
|
||||
resume={resume}
|
||||
cancel={cancel}
|
||||
getSpeedHistory={getSpeedHistory}
|
||||
/>
|
||||
)}
|
||||
</Popover>
|
||||
@@ -111,6 +112,7 @@ function JobManagerPopoverContent({
|
||||
pause,
|
||||
resume,
|
||||
cancel,
|
||||
getSpeedHistory,
|
||||
}: {
|
||||
jobs: any[];
|
||||
showOnlyRunning: boolean;
|
||||
@@ -118,6 +120,7 @@ function JobManagerPopoverContent({
|
||||
pause: (jobId: string) => Promise<void>;
|
||||
resume: (jobId: string) => Promise<void>;
|
||||
cancel: (jobId: string) => Promise<void>;
|
||||
getSpeedHistory: (jobId: string) => import("./hooks/useJobs").SpeedSample[];
|
||||
}) {
|
||||
const filteredJobs = showOnlyRunning
|
||||
? jobs.filter((job) => job.status === "running" || job.status === "paused")
|
||||
|
||||
@@ -19,7 +19,7 @@ interface JobCardProps {
|
||||
onPause?: (jobId: string) => void;
|
||||
onResume?: (jobId: string) => void;
|
||||
onCancel?: (jobId: string) => void;
|
||||
getSpeedHistory: (jobId: string) => SpeedSample[];
|
||||
getSpeedHistory?: (jobId: string) => SpeedSample[];
|
||||
}
|
||||
|
||||
export function JobCard({ job, onPause, onResume, onCancel, getSpeedHistory }: JobCardProps) {
|
||||
@@ -143,7 +143,7 @@ export function JobCard({ job, onPause, onResume, onCancel, getSpeedHistory }: J
|
||||
|
||||
{/* Expanded details section */}
|
||||
<AnimatePresence>
|
||||
{isExpanded && isCopyJob && (
|
||||
{isExpanded && isCopyJob && getSpeedHistory && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: "auto", opacity: 1 }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useRef, useMemo } from "react";
|
||||
import { useState, useEffect, useRef, useMemo, useCallback } from "react";
|
||||
import { useLibraryQuery, useLibraryMutation, useSpacedriveClient } from "../../../contexts/SpacedriveContext";
|
||||
import type { JobListItem } from "../types";
|
||||
import { sounds } from "@sd/assets/sounds";
|
||||
@@ -211,10 +211,10 @@ export function useJobs() {
|
||||
const runningCount = jobs.filter((j) => j.status === "running").length;
|
||||
const pausedCount = jobs.filter((j) => j.status === "paused").length;
|
||||
|
||||
// Helper to get speed history for a job
|
||||
const getSpeedHistory = (jobId: string): SpeedSample[] => {
|
||||
// Helper to get speed history for a job (memoized to prevent re-creation)
|
||||
const getSpeedHistory = useCallback((jobId: string): SpeedSample[] => {
|
||||
return speedHistoryRef.current.get(jobId) || [];
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
jobs,
|
||||
|
||||
@@ -286,7 +286,7 @@ function DeviceCard({
|
||||
}: DeviceCardProps) {
|
||||
const deviceName = device?.name || "Unknown Device";
|
||||
const deviceIconSrc = device ? getDeviceIcon(device) : null;
|
||||
const { pause, resume } = useJobs();
|
||||
const { pause, resume, getSpeedHistory } = useJobs();
|
||||
|
||||
// Format hardware specs
|
||||
const cpuInfo = device?.cpu_model
|
||||
@@ -386,6 +386,7 @@ function DeviceCard({
|
||||
job={job}
|
||||
onPause={pause}
|
||||
onResume={resume}
|
||||
getSpeedHistory={getSpeedHistory}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user