IMPORTANT CAVEAT
BETA RELEASE - FOR TESTING PURPOSES
This version is made public for beta testing. There's a thorough test suite that tries to cover all data loss scenarios but it's too early to claim that none might exit. This is still pre-release software.
What this means:
- ✅ Core FUSE operations work correctly
- ✅ Data loss bugs identified in testing have been fixed
- ✅ 307 tests pass, including data loss regression tests
- ⚠️ Not yet tested extensively in real-world scenarios
- ⚠️ Performance optimizations still in progress
- ⚠️ Desktop client integration not implemented
- ⚠️ No offline mode support
Please test on non-critical data and report issues. Do not use as your primary file access method until a stable release is announced. Or, if you do, have backups handy.
The name of this project describes its functionality: "Nextcloud FUSE". The project is in no way endorsed by or otherwise associated with Nextcloud GmbH. Nextcloud trademarks are used strictly to describe the project. If Nextcloud, when the project reaches suitable maturity, decide that they would like to host the project the author will happily hand it over.
With all that said - here's some README content:
Nextcloud FUSE
FUSE-based virtual filesystem for Nextcloud on Linux.
Overview
This project provides a true virtual filesystem for Nextcloud on Linux using FUSE (Filesystem in Userspace). It enables on-demand access to Nextcloud files without requiring a full sync, with intelligent local caching for performance.
Status
Beta release - Core functionality complete and tested. Suitable for testing with non-critical data. All identified data loss bugs have been fixed. See the test suite for details on what's been verified.
Features
- ✅ On-demand file access (download only when accessed)
- ✅ Block-level caching (4MB blocks by default)
- ✅ Write-back caching with async upload
- ✅ Core FUSE operations (getattr, readdir, open, read, write, create, mkdir, unlink, rmdir, rename)
- ✅ Metadata cache with TTL
- ✅ Upload manager with retry logic and conflict detection
- ✅ Advanced FUSE operations (truncate, chmod, utimens via setattr)
- ✅ SIGTERM handling and graceful shutdown with fusermount3 unmount
- ✅ SQLite WAL mode for concurrent access
- ✅ Native chunked upload protocol (per-block uploads via MKCOL + PUT + MOVE assembly)
- ✅ Parallel block downloads with semaphore-bounded concurrency
- ✅ Read-ahead prefetch on cache miss
- ✅ SQLite persistence for upload session resume
- ✅ Comprehensive test suite (307 tests)
- ❌ symlink/readlink/xattr (return ENOSYS — correct for cloud storage)
- ❌ Desktop client integration (IPC not implemented)
- ❌ Pin states support
- ❌ Offline mode
Architecture
User Applications → Linux Kernel (FUSE) → nextcloudfs (Rust) → Nextcloud Server
↓
Cache (Block + Metadata)
Building
Prerequisites
Runtime Dependencies
-
libfuse3 >= 3.0
-
FUSE configuration: uncomment
user_allow_otherin/etc/fuse.confThe filesystem mounts with
allow_otherso that applications running under different user IDs (file managers, browsers, etc.) can access the mounted files. Without this, the FUSE kernel module rejects the mount unlessuser_allow_otheris enabled in/etc/fuse.conf:sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf
Build Dependencies
- Rust >= 1.70
- Cargo >= 1.70
Build
# Clone the repository
git clone https://codeberg.org/troed/nextcloud-fuse.git
cd nextcloud-fuse
# Build in debug mode
cargo build
# Build in release mode
cargo build --release
# Install locally
cargo install --path .
The binary will be at target/release/nextcloudfs.
Usage
Standalone Mode
# Mount a Nextcloud folder
./target/release/nextcloudfs \
--server http://localhost:8888 \
--username testuser \
--password MySecurePassword456! \
--remote-path /fuse-test/ \
/mnt/nextcloud
# Unmount
fusermount3 -u /mnt/nextcloud
Configuration
Configuration supports three mechanisms with the following precedence (highest to lowest):
- CLI arguments (with environment variable fallback via clap)
- TOML configuration file (
~/.config/nextcloud-fuse/config.tomlor custom path) - Built-in defaults
CLI Arguments
All CLI arguments support environment variable fallback using the NEXTCLOUD_FUSE_* prefix:
# Server and authentication
--server URL # NEXTCLOUD_FUSE_SERVER
--username USER # NEXTCLOUD_FUSE_USERNAME
--password PASS # NEXTCLOUD_FUSE_PASSWORD
--token TOKEN # NEXTCLOUD_FUSE_TOKEN
# Mount configuration
--remote-path PATH # NEXTCLOUD_FUSE_REMOTE_PATH (default: /)
--mount-point PATH # Required, no env var fallback
# Configuration file
--config PATH # NEXTCLOUD_FUSE_CONFIG
# Logging
--log-level LEVEL # NEXTCLOUD_FUSE_LOG_LEVEL (default: info)
# Options: trace, debug, info, warn, error
--log-format FORMAT # NEXTCLOUD_FUSE_LOG_FORMAT (default: text)
# Options: text, json, compact
# Debugging
--verbose # Enable verbose output (no env var)
# IPC (Inter-Process Communication)
--no-ipc # NEXTCLOUD_FUSE_NO_IPC
--ipc-socket PATH # NEXTCLOUD_FUSE_IPC_SOCKET
Configuration File
Location: ~/.config/nextcloud-fuse/config.toml (or custom path via --config)
All fields are optional. Example:
# Server connection
server_url = "https://nextcloud.example.com"
username = "user"
password = "app-password"
# token = "bearer-token" # Alternative to password
remote_path = "/Documents"
# Cache settings
cache_dir = "/home/user/.cache/nextcloud-fuse"
cache_size = "10GB" # Supports: "10GB", "500MB", "100KB", or raw bytes
block_size = "4MB"
metadata_ttl = 30 # Seconds
# Performance
parallel_downloads = 4
prefetch = true
# Logging
log_level = "info"
log_format = "text"
debug = false
# IPC
no_ipc = false
# ipc_socket = "/tmp/nextcloud-fuse.sock"
Environment Variables
All environment variables use the NEXTCLOUD_FUSE_* prefix:
export NEXTCLOUD_FUSE_SERVER="https://nextcloud.example.com"
export NEXTCLOUD_FUSE_USERNAME="user"
export NEXTCLOUD_FUSE_PASSWORD="app-password"
export NEXTCLOUD_FUSE_REMOTE_PATH="/Documents"
export NEXTCLOUD_FUSE_LOG_LEVEL="debug"
Precedence Example
# Config file sets cache_size = "5GB"
# Environment variable sets NEXTCLOUD_FUSE_CACHE_SIZE="10GB"
# CLI argument sets --cache-size "20GB"
# Result: cache_size = "20GB" (CLI wins)
Note: Environment variables are handled by clap's env feature for CLI arguments, not separately. The NEXTCLOUD_FUSE_* env vars provide fallback values when CLI arguments are not provided.
Project Structure
nextcloud-fuse/
├── src/ # Rust source code
│ ├── main.rs # Entry point, CLI, mount lifecycle
│ ├── lib.rs # Library root
│ ├── config.rs # Configuration
│ ├── logging.rs # Logging
│ ├── error.rs # Error types
│ ├── fuse_operations.rs # FUSE operations
│ ├── upload.rs # Upload manager
│ ├── api/ # Nextcloud API client
│ │ ├── client.rs
│ │ ├── webdav.rs
│ │ ├── xml.rs
│ │ ├── error.rs
│ │ └── path.rs
│ ├── cache/ # Caching implementation
│ │ ├── block_cache.rs
│ │ └── metadata_cache.rs
│ └── utils/ # Utilities
│ ├── mod.rs
│ └── path.rs
├── tests/ # Test suite
│ ├── unit/ # Unit tests
│ ├── integration/ # Integration tests
│ ├── reliability/ # Reliability tests
│ ├── test_block_boundaries.rs # Block boundary crossing tests
│ └── test_concurrent_ops.rs # Concurrent FUSE operation tests
├── Cargo.toml # Rust dependencies
└── README.md # This file
Testing
Unit Tests (101 tests)
Unit tests cover configuration, errors, logging, utilities, metadata cache, block cache, API client, chunked upload sessions, and session SQLite persistence. They require no external services.
# Run all unit tests
cargo test --lib
# Run specific test
cargo test test_name
Component Tests (82 tests)
Auto-discovered test targets in tests/*.rs covering configuration parsing, error handling, file reading/writing logic, FUSE skeleton, logging, metadata cache, block boundary crossing, concurrent operations, and utilities. They require no external services.
# Run all component tests (no server required)
cargo test --test test_block_boundaries --test test_concurrent_ops --test test_config --test test_errors --test test_file_reading --test test_file_writing --test test_fuse_skeleton --test test_logging --test test_metadata_cache --test test_utils
# Or run a single test
cargo test --test test_block_boundaries
Integration Tests (22 tests)
Integration tests exercise FUSE mount/unmount lifecycle, file read/write, random-access I/O, crash recovery, and real-API interactions. They require a running Nextcloud instance (see Test Instance).
# Run all integration tests
make nc-start
cargo test --test integration
Reliability Tests (97 tests)
A comprehensive reliability test suite verifies data integrity under concurrent access, server crashes, and edge cases. All tests require a running Nextcloud instance (see Test Instance).
Test Groups:
| Group | Tests | Description |
|---|---|---|
harness |
5 | Unit tests for the test harness infrastructure |
test_cache_behavior |
15 | Block cache insert/get, TTL expiration, multiple blocks, stats |
test_cache_data_loss |
4 | SQLite blindness, block index/file offset mismatch, deadlock fixes |
test_concurrent_ops |
6 | Concurrent reads, writes, directory creates, deletes, mixed operations |
test_data_integrity |
13 | Edge cases: empty files, null bytes, unicode, special chars, binary, very long filenames |
test_data_loss_scenarios |
5 | Upload failure state, cache restart, overwrite atomicity, rapid create/delete |
test_directory_ops |
10 | Create, rename, move, rmdir, deep hierarchies, many files |
test_file_ops |
10 | Create/read small/medium/large files, overwrite, delete, read exact bytes |
test_mount_ops |
10 | Harness setup, getattr, readdir, cleanup, statfs, timestamps |
test_server_crash |
10 | Crash during write/delete/rename/mkdir, multiple crashes, recovery verification |
test_upload_pipeline |
9 | Full upload, chunked fallback, idempotency, multi-block, stats tracking |
Execution Model:
Tests run in two phases to avoid interference:
- Phase 1 (parallel): All non-crash tests run concurrently with
RUST_TEST_THREADS=4 - Phase 2 (sequential): Crash tests run single-threaded since they kill and restart the container
# Run all reliability tests (requires Nextcloud instance)
make test-reliability
# Run only non-crash tests (parallel)
RUST_TEST_THREADS=4 cargo test --test reliability -- test_cache_behavior test_cache_data_loss test_concurrent_ops test_data_integrity test_data_loss_scenarios test_directory_ops test_file_ops test_mount_ops test_upload_pipeline harness --include-ignored
# Run only crash tests (sequential)
RUST_TEST_THREADS=1 cargo test --test reliability -- test_server_crash --include-ignored
# Run a single test
make test-single TEST=test_concurrent_reads_same_file
Crash Test Mechanism:
The ServerCrashSimulator in the test harness (tests/reliability/harness.rs) uses podman kill + podman start to simulate server crashes. After each crash recovery, the harness re-applies a config override to ensure Nextcloud stays in "installed" state (the container image does not persist config changes). This override is written to /var/www/html/config/upgrade-disable-web.config.php via podman exec.
# Check test compilation
make test-check
# Start the test instance
make nc-start
Test Results (Current)
Unit tests (src/) 101 (no server required)
Component tests (no server): 77
├─ Config/Errors/Utils/Logging/Skeleton 34
├─ File reading 10
├─ File writing (writes, no API calls) 4
├─ Metadata cache 21
├─ Block boundary crossing 5
└─ Concurrent operations 3
Component tests (require server): 5 (create/mkdir/rmdir)
Integration tests (tests/integration) 22 (require server)
Reliability tests (tests/reliability) 97 (require server)
Total (no server): 178
Total (with server): 302
Test Instance
A rootless Podman-based test instance is configured for safe testing:
- Container:
nextcloud-fuse-test - Port: 8888 (HTTP)
- Test User:
testuser/MySecurePassword456! - Admin User:
admin/admin123 - Test Path:
/fuse-test/
Config Persistence: The container image regenerates config.php on each restart, removing the installed flag. To work around this, the test harness writes a config override to /var/www/html/config/upgrade-disable-web.config.php after each crash recovery. This file is loaded by Nextcloud's .config.php merging mechanism and provides the installed, secret, passwordsalt, instanceid, and database settings.
License
Creative Commons Zero - See LICENSE file.