Mount remote storage (S3, GCS, HDFS, Azure Blob, etc.) via FUSE.
Linux / macOS / Windows (WinFSP) / Kubernetes (CSI)
A high-performance FUSE mount for object storage and remote filesystems, written in Rust. Backed by Apache OpenDAL, supporting 13 storage backends with a unified caching, prefetching, and write-back pipeline.
- Single-file write cache — per-handle cache file with
WriteAtrandom write support, plus block-level read cache (8 MB) - Adaptive prefetcher with backpressure — chunk size doubles on sequential reads (up to 8 MB)
- Multi-chunk concurrent read —
Semaphore-bounded streams per FUSE read - Write-back queue with
fsyncsemantics +.dirtysidecar crash recovery - HDFS Kerberos — three backends (native / JNI / WebHDFS)
- WinFSP adapter for native Windows support
- Pure-Rust CSI driver for Kubernetes with Controller + Node + Identity services
- CRC64 integrity for disk cache
# S3
mntrs mount s3://my-bucket /mnt/s3 \
--opt region=us-east-1 \
--opt access-key=AKIA... \
--opt secret-key=...
# MinIO (self-signed CA)
mntrs mount s3://bucket /mnt/s3 \
--opt endpoint=https://minio.local:9000 \
--opt cacert=/etc/ca.crt
# HDFS (Kerberos via kinit)
kinit -kt /etc/security/keytabs/hdfs.keytab hdfs/namenode@REALM
mntrs mount hdfs://namenode:8020 /mnt/hdfs
# HDFS (Kerberos via options)
mntrs mount hdfs://namenode:8020 /mnt/hdfs \
--opt dfs.namenode.kerberos.principal=hdfs/_HOST@REALM \
--opt dfs.namenode.kerberos.keytab=/etc/krb5.keytab
# GCS
mntrs mount gs://my-bucket /mnt/gcs
# Local filesystem (passthrough)
mntrs mount fs:///data /mnt/fs
# Unmount
mntrs unmount /mnt/s3# From source (Rust 1.87+)
cargo install --path .
# Pre-built binaries (GitHub Releases, all platforms)
# https://github.com/your-org/mntrs/releases
# Docker
docker build -f csi/Dockerfile -t mntrs-csi .WinFSP 2.1+ must be installed. Then:
# Drive letter
mntrs mount s3://bucket X:
# Auto-assign
mntrs mount s3://bucket *
# NTFS directory
mntrs mount s3://bucket C:\mnt\s3| Scheme | Backend | Auth | Notes |
|---|---|---|---|
s3:// |
AWS S3 / MinIO / R2 / Ceph | AKID/SK or IAM | Full S3 API |
gs:// / gcs:// |
Google Cloud Storage | Service account | |
azblob:// |
Azure Blob Storage | Connection string / SAS | |
hdfs:// / hdfs-native:// |
HDFS (native Rust) | Kerberos via ccache | Default |
hdfs-jni:// |
HDFS (libhdfs JNI) | Kerberos via options | --features hdfs-jni |
webhdfs:// |
WebHDFS REST | Kerberos / SPENGO | HTTP gateway |
oss:// |
Alibaba OSS | AKID/SK | |
cos:// |
Tencent COS | AKID/SK | |
obs:// |
Huawei OBS | AKID/SK | |
b2:// |
Backblaze B2 | AKID/SK | |
vercel-blob:// |
Vercel Blob | Token | |
aliyun-drive:// |
Aliyun Drive | OAuth | |
fs:// / file:// |
Local filesystem | n/a | Passthrough |
memory:// / mem:// |
In-memory | n/a | Testing only |
All --opt key=value pairs are passed through to the backend. Common keys:
| Key | Description | Example |
|---|---|---|
endpoint |
Service endpoint | https://s3.custom.com |
access-key |
Access key | AKIA... |
secret-key |
Secret key | ... |
region |
Region | us-east-1 |
cacert / cert / key / pass |
TLS (curl-compatible) | mTLS supported |
insecure |
Skip cert verification | true |
dfs.namenode.kerberos.* |
HDFS Kerberos config | hdfs/_HOST@REALM |
# mTLS
mntrs mount s3://bucket /mnt \
--opt endpoint=https://s3.custom.com \
--opt cacert=/etc/ca.crt \
--opt cert=/etc/client.crt \
--opt key=/etc/client.key
# PKCS12
mntrs mount s3://bucket /mnt \
--opt cert=/etc/client.p12 --opt cert-type=P12 --opt pass=secret
# Self-signed
mntrs mount s3://bucket /mnt --opt insecureTwo modes:
Mode 1 — pre-authenticated (standard kinit):
kinit -kt /etc/security/keytabs/hdfs.keytab hdfs/namenode@REALM
mntrs mount hdfs://namenode:8020 /mnt/hdfs
# hdfs-native auto-detects principal from KRB5CCNAMEMode 2 — pass via options (hdfs-native):
mntrs mount hdfs://namenode:8020 /mnt/hdfs \
--opt dfs.namenode.kerberos.principal=hdfs/_HOST@REALM \
--opt dfs.namenode.kerberos.keytab=/etc/krb5.keytabMode 3 — JNI (requires Java + libhdfs):
cargo build --features hdfs-jni
mntrs mount hdfs-jni://namenode:8020 /mnt/hdfs \
--opt kerberos-ticket-cache-path=/tmp/krb5cc \
--opt user=hdfsThree-tier cache: memory → disk → remote. Block-level (8 MB) indexing. Disk cache survives restarts.
| Flag | CLI default | Code fallback | Effective default | Description |
|---|---|---|---|---|
--vfs-cache-max-size |
0 (off) |
none (post-#243) | 0 = no LRU |
Disk cache upper limit (LRU) |
--vfs-cache-min-free-space |
0 (off) |
none (post-#243) | 0 = no floor check |
Min free space before eviction |
--vfs-cache-max-age |
3600s | — | 3600s | Max cache file age (shadow — see Durability) |
--vfs-cache-mode |
off |
— | off |
off / minimal / writes / full (shadow — see Durability) |
--vfs-cache-poll-interval |
60s | — | 60s | Stale-object poll interval (shadow — see Durability) |
--mem-limit |
256 MB | — | 256 MB | Memory cache upper limit |
--dir-cache-time |
10s | — | 10s | Directory listing TTL |
--attr-timeout |
5s | — | 5s | File attribute TTL (kernel) — bumped 1s→5s (#469) so the #467 FUSE_READDIRPLUS_AUTO cap actually materializes |
--stat-cache-ttl |
1s | — | 1s | Stat TTL (mntrs internal) |
--type-cache-ttl |
1s | — | 1s | File-type cache TTL |
--no-modtime |
false | — | false | Disable mtime read/write |
--use-server-modtime |
false | — | false | Use server-side mtime (vs local cache) |
--no-implicit-dir |
false | — | false | Disable S3 implicit dir fallback |
--direct-io |
false | — | false | Bypass kernel page cache, direct FUSE access |
--vfs-handle-caching |
0s | — | 0s | Keep file handles open after last close for reuse |
--vfs-write-back |
5s | — | 5s | Max time before dirty file is uploaded |
--write-back-cache |
false | — | false | Opt-in. FUSE kernel write-back cache. Off by default — daemon's write() is called per writeback segment under default. When enabled, the kernel buffers writes and daemon's write handler is skipped for multi-page files (3 known bugs + stress 01/05 fail under unconditional WRITEBACK_CACHE — see docs/durability.md). |
Issue #243:
--vfs-cache-max-sizeand--vfs-cache-min-free-spaceboth have CLI default0(= "off") but historically the code path fell back to 1 GiB / 100 MiB when the field was 0. Post-#243.2/3 the0value is honored literally (seesrc/lib.rsfor the new behavior). If you want a 1 GiB cap, pass--vfs-cache-max-size 1024explicitly.
Disk cache: write uses file-level cache ({hash} hash name), read checks file-level first then block-level ({hash}_{block}.block). Recoverable on restart.
| Flag | CLI default | Effective default | Description |
|---|---|---|---|
--vfs-read-chunk-size |
128 MiB (134217728) | 128 MiB | Initial read chunk size |
--vfs-read-chunk-size-limit |
0 (off) | 128 MiB (fallback) | Chunk doubling ceiling |
--vfs-read-chunk-streams |
1 | 1 | Concurrent read streams (per FUSE read) |
--vfs-read-ahead |
131072 | 131072 | Bytes prefetched past EOF |
--async-read |
false | false | Async reads (FUSE kernel) |
--vfs-fast-fingerprint |
false | false | Fast change detection (size+mtime) |
--vfs-read-wait |
1s | 1s | Sequential read wait threshold |
--vfs-write-wait |
1s | 1s | Sequential write wait threshold |
Adaptive chunk reader: chunk size doubles on sequential reads, resets to 128 KB on seek. Up to 8 MB cap.
Prefetcher with backpressure: 4 in-flight chunks max per file. Replaces naive thread::spawn with bounded PartQueue.
Multi-chunk concurrent: --vfs-read-chunk-streams=4 fetches 4 S3 parts in parallel via tokio::Semaphore for a single FUSE read.
4/6 leading, 1/6 tie, 1/6 behind (recoverable by matching --stat-cache-ttl=300).
The macOS variant lives at bench/run_all_mac.sh. See
docs/benchmark_macos.md for methodology and the rclone
auto-detect path. No CI workflow runs it — see issue #304
for the GH runner macFUSE kext limitation.
Local write cache with async write-back (5s default delay). Crash-safe.
# Write
echo "hello" > /mnt/s3/file.txt # cached + async write-back
cat /mnt/s3/file.txt # served from cache (hot)
# Sync
sync /mnt/s3/file.txt # fdatasync → cache file durable on local diskMechanisms:
- Write-back queue with exponential backoff (5 attempts; cycle cap 10 at 60s cooldown)
- fdatasync on flush/release before the FUSE reply (
Issue #34— local-durability half) .dirtysidecar for crash recovery (scanned on mount init; left on disk for retry-exhausted paths)PendingUploadHookupdates inode size/mtime after successful upload- Retry-cycle counter (4th tuple field,
Issue #53) prevents silent re-enqueue drops - Multipart upload via
op.writer()auto-chunks >5 GB - CRC64 integrity for disk cache
For the full durability model — including the rclone-compat shadow
fields (--vfs-cache-mode, --vfs-cache-max-age,
--vfs-cache-poll-interval, --poll-interval, --vfs-refresh)
that are accepted on the CLI but not yet implemented — see
docs/durability.md.
mntrs mount s3://bucket /mnt/s3 --daemon
mntrs mount s3://bucket /mnt/s3 --daemon --daemon-waitmntrs install systemd generates a systemd user service template. Restart=always + ExecStopPost lazy unmount for crash-safe operation.
| Flag | Description |
|---|---|
--vfs-noapple-double |
Filter ._* and .DS_Store files (Time Machine) |
--vfs-noapple-xattr |
Filter com.apple.* xattrs |
--mount-case-insensitive |
OS-level case-insensitive mount |
Native support via winfsp = "0.13". Conditional compilation (#[cfg(windows)]).
# Drive letter (recommended)
mntrs mount s3://bucket X:
# Auto-assign
mntrs mount s3://bucket *
# NTFS directory
mntrs mount s3://bucket C:\mnt\s3CI tested on Windows with 31 WinFSP integration tests (covering mount/unmount lifecycle, write/read roundtrip, list/create/delete/rename, setattr/truncate, statfs, nested directories, large-file reads, unicode + NFC normalization, symlink create/get/rename/delete, dirty-cache lifecycle, readdir paging, getattr/statfs cache coalescing, volume flush, and mount-internal scheme variants).
csi/mntrs-csi/ — Pure Rust CSI driver (tonic 0.12).
kubectl apply -f csi/deploy/kubernetes/1.20/StorageClass + PVC example:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: mntrs-s3
provisioner: csi-mntrs
parameters:
storage: "s3://my-bucket"
prefix: "k8s-pv"
--opt s3-endpoint=http://minio:9000
reclaimPolicy: Retain
volumeBindingMode: Immediate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-data
spec:
storageClassName: mntrs-s3
accessModes: [ReadWriteMany]
resources:
requests: { storage: 1Gi }CSI services:
- Identity:
GetPluginInfo/GetPluginCapabilities/Probe - Controller:
CreateVolume/DeleteVolume(real implementation) - Node:
NodeStageVolume/NodePublishVolume/NodeUnstageVolume/NodeUnpublishVolumewith per-volume cache dir, write-back wait, and lazy unmount
src/
├── lib.rs # MntrsFs core + fuser impl (Linux/macOS)
├── main.rs # CLI entry
├── path.rs # Cross-platform path normalization
├── prefetcher.rs # PartQueue + backpressure
├── writeback.rs # Async write-back + CRC64 + PendingUploadHook
├── cmd/
│ ├── mod.rs
│ ├── mount.rs # Multi-backend routing + TLS + daemon
│ ├── unmount.rs # Unmount (lazy for safety)
│ ├── list.rs # List active mounts
│ └── install.rs # Systemd template generator
└── core_fs/
├── mod.rs # CoreFilesystem trait
├── fuser.rs # FuserAdapter (Linux/macOS)
└── winfsp.rs # WinfspAdapter (Windows)
csi/mntrs-csi/
├── Cargo.toml
├── build.rs # protoc + tonic_build
├── src/
│ ├── main.rs # 4 CSI services + lifecycle
│ └── csi.rs # Generated protobuf
└── csi/deploy/kubernetes/ # K8s manifests
Data flow (FUSE read):
FUSE read(ino, offset, size)
↓
1. inodes cache hit? → make_attr (fast path)
↓ miss
2. attr_cache hit? → make_attr
↓ miss
3. network stat() → attr_cache.insert
↓
4. cache fd (write handle still open)? → read from fd → return
↓ miss
5. mem_cache[(ino, block_idx)]? → return block
↓ miss
6. file-level disk cache → mem_cache insert → return
↓ miss
7. block-level disk cache (CRC64 verify) → mem_cache insert → return
↓ miss
8. prefetcher PartQueue pop → return chunk
↓ miss
9. multi-chunk fetch (Semaphore N streams) → disk + mem insert
# Build
cargo build --release
# Test (all 50+ tests)
cargo test --workspace
cargo nextest run --workspace # 30-50% faster
# Lint
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --check
# Backend-specific builds
cargo build --features hdfs-jni # HDFS via libhdfs
# CSI plugin
cargo build --package mntrs-csi --release
# Benchmarks
cargo bench # micro-benchmarks
./bench/run_all.sh # vs rclone (MinIO, Linux)
./bench/run_all_mac.sh # macOS variant (manual, see docs/benchmark_macos.md)| Workflow | Environment | Scope |
|---|---|---|
CI |
Linux | Build + test + clippy + fmt |
CI - Windows |
Windows | WinFSP + release build + 31 mount integration tests |
CI - macOS |
macOS | macFUSE + build + test |
Integration Tests |
Linux | S3 / HDFS / memory mount tests + HDFS Kerberos auth |
CSI Integration Test |
Linux (k3s) | CSI driver e2e with HDFS backend |
CSI e2e |
Linux (k3s) | CSI driver e2e with S3 (MinIO) backend |
Benchmark |
Linux | vs rclone performance (MinIO) |
| macOS bench (manual) | macOS developer | bench/run_all_mac.sh (issue #304 — no GH runner support) |
| Component | Requirement |
|---|---|
| Rust | 1.87+ (edition 2024) |
| Linux | FUSE 3 (libfuse3-dev fuse3) |
| macOS | macFUSE 4+ |
| Windows | WinFSP 2.1+ |
| Kubernetes | 1.20+ (external-provisioner) |
| HDFS-JNI | Java 11+, libhdfs3 |
| protoc | For CSI builds |
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.