Cross-platform cloud storage filesystem client built in Rust. On-demand file access, nested
.cascadeconfig with directory-walk precedence, offline pinning, policy-driven lifecycle management, P2P block sync, and multi-backend support. Uses native platform APIs (File Provider on macOS, ProjFS on Windows, FUSE on Linux) with NFS fallback — no kernel extensions required.
Rust (edition 2024) · Swift (macOS File Provider extension) · SQLite state · Tokio async runtime
Every cloud provider ships their own desktop client. They all share the same shape — pick a folder, sync it, hope your disk is bigger than the data. Google Drive for Desktop has a stream mode that loads files on demand, but it's macOS- and Windows-only, leaves you guessing what's actually local, and behaves badly offline. Dropbox, OneDrive, iCloud — same story, each with their own client, their own quirks, their own opinions about your filesystem layout.
rclone solves the cross-cloud unification well, but its mount story is per-OS fiddly: each platform needs its own filesystem driver, sometimes root, sometimes a separate install. The config is global; if you want different rules for Work/ than Personal/ you reach for a wrapper script.
Cascade is what happens when you want a single tool, with a single config language, that:
- Presents every backend — Google Drive, S3, the local filesystem — as one virtual tree under one mount point, mounted with the same shape on macOS, Linux, and Windows.
- Streams file content on demand. Directory listings work without downloading anything; only
open()triggers a fetch. - Uses the OS's native filesystem APIs — FSKit on macOS (15.4+), FUSE on Linux, WebDAV via the built-in
WebClienton Windows — with no kernel extension, no admin escalation, no third-party driver. - Takes its rules from
.cascadefiles scattered through the tree (gitignore-style precedence), so behaviour can vary per directory without restarting anything. - Mirrors the actual Drive web UI —
My Drive,Shared drives,Shared with me,Bin— instead of pretending every account is one flat folder.
The bet is that one filesystem client that works the same on every laptop is more useful than five vendor clients plus a folder full of rclone scripts.
- Rust toolchain pinned in
rust-toolchain.toml(currently 1.96.0, edition 2024).rustupinstalls it automatically on first build.- rustup (recommended) — the project pins its Rust toolchain in
rust-toolchain.toml. Install rustup from https://rustup.rs/ so local builds, the pre-push hook, and CI all use the same compiler.
- rustup (recommended) — the project pins its Rust toolchain in
- macOS: Xcode Command Line Tools (for Swift File Provider and FSKit extensions). FSKit requires macOS 15.4+ (Sequoia).
- Linux:
libfuse3runtime libraries for the FUSE presenter (apt install libfuse3-devon Debian/Ubuntu,yum install fuse3-develon RHEL/Fedora). NFS fallback additionally needs root to bind a privileged port. - Windows: the built-in
WebClientservice for the WebDAV mount. It ships with Windows but is often set to manual start —sc config WebClient start= auto(in an elevated shell) makes it come up automatically. A native ProjFS presenter (presenter-projfs) is implemented and tried first bycascade start— it serves directory browsing and on-demand file reads through an engine-backed content provider — with WebDAV as the fallback if ProjFS cannot start (for example when the Client-ProjFS optional feature is disabled).
Pre-built binaries are published on each release from GitHub Releases.
-
macOS and Linux (Homebrew):
brew install Mearman/cascade/cascade
The
Formula/cascade.rbbottle covers bothaarch64andx86_64on macOS and Linux. -
Linux (direct download): grab
cascade-aarch64-linux.tar.gzorcascade-x86_64-linux.tar.gzfrom the release, extract, and placecascadeon yourPATH:curl -L https://github.com/Mearman/cascade/releases/latest/download/cascade-x86_64-linux.tar.gz | tar -xz install -m 0755 cascade ~/.local/bin/
-
Windows (Scoop):
scoop bucket add cascade https://github.com/Mearman/scoop-cascade scoop install cascade
-
Windows (direct download): grab
cascade-x86_64-windows.zipfrom the release, extract, and placecascade.exeon yourPATH. -
From source (any platform): see Build below.
cargo build --release
# Specific presenter
cargo build --release --features presenter-nfs
cargo build --release --features presenter-fileprovider # macOS only
# Including Swift extensions (macOS)
cargo build --release
xcodebuild -project swift/CascadeFileProvider.xcodeproj -scheme CascadeFileProviderHost -configuration Release -destination "platform=macOS" build
cd swift/CascadeFSKit && xcodebuildThe Makefile wraps the common workflows:
make release # cargo build --release
make build # cargo build (debug)
make start # build then run the daemon
make stop # stop a running daemon
make dev # cargo watch with debug logging
make debug # run the release binary with RUST_LOG=debugcascade backend add gdrive --name personal
cascade start
cascade status
cascade pin Documents/Accounts/
cascade stopcascade service manages the daemon as an OS background service. The default scope is per-user and requires no administrator rights: a launchd LaunchAgent on macOS, a systemd --user unit on Linux, and a logon Scheduled Task on Windows.
cascade service install # write the service definition and register it
cascade service start # start the registered service
cascade service status # show whether the service is registered and running
cascade service stop # stop the service
cascade service uninstall # deregister the service and remove its definitionThe scope is selected in this order: an explicit --user or --system flag, then inference from the session (an interactive GUI desktop session picks the user scope; a headless host picks the system scope), then — only when there is both a GUI desktop and a terminal — a prompt that defaults to the user scope. The chosen scope and the reason for it are always printed. The --system scope installs a machine-wide service: on Linux it writes a systemd system unit (requires root); on macOS and Windows it errors clearly — a system-scoped service in session 0 cannot drive File Provider, FSKit, ProjFS, or WebDAV, which all require a user session, and this is a documented platform limitation rather than a missing feature.
If you installed via Homebrew, brew services start cascade works out of the box — the formula ships a service block that delegates to cascade start.
The daemon exits cleanly with a log message when no backends are configured, so a freshly-installed service does not crash-loop before cascade backend add has been run.
cargo test --workspace # all unit tests
cargo test --test integration # integration tests (require mock backend)
cargo test -p backend-gdrive # single crate
cargo clippy --workspace # lint
cargo fmt --check # format checkThe design is documented in full at docs/design.md. This section covers the high-level structure.
┌──────────────────────────────────────────────────────────┐
│ Platform Layer (per-OS) │
│ macOS: File Provider · FSKit (15.4+) · WebDAV · NFS │
│ Linux: FUSE · NFS (root) │
│ Windows: ProjFS · WebDAV via WebClient │
│ Universal fallback: NFS server · WebDAV server │
└────────────────────┬─────────────────────────────────────┘
│ VfsPresenter trait
┌────────────────────▼─────────────────────────────────────┐
│ Cascade Engine (Rust) │
│ VFS Tree · .cascade config walk · Cache Manager │
│ Backend trait · Expression Evaluator · P2P Engine (BEP) │
└──────────────────────────────────────────────────────────┘
cascade start tries the platform-preferred presenters in order and falls back as each one fails: on macOS that's FSKit → WebDAV → NFS; on Linux it's FUSE → NFS; on Windows it's ProjFS → WebDAV (mounted via net use * against the built-in WebClient service). The Windows ProjFS presenter implements the full callback table — directory enumeration, placeholder info, file-name queries, on-demand reads, notifications, and cancellation — and serves file contents through an engine-backed content provider. WebDAV remains the fallback for when ProjFS cannot start (for example when the Client-ProjFS optional feature is disabled on the machine).
Communication between the platform layer and the engine uses a Unix domain socket with a length-prefixed JSON protocol, shared by the CLI, the macOS File Provider and FSKit extensions, and any future GUI.
crates/
engine/ VFS tree, backend trait, cache manager, sync, state DB
cascade-config/ .cascade parsing (4 formats), merge, directory walk
expr/ Conditional expression parser (PEG via pest) and evaluator
p2p/ BEP protocol, peer discovery (LAN, gossip, announce, Mainline DHT), block store
cascade-announce-wire/ Announce-server wire contract: signed-candidate types, HMAC write auth, wasm-safe handler
backend-gdrive/ Google Drive (Drive API v3, OAuth2 device code)
backend-s3/ S3-compatible
backend-local/ Local filesystem (adopt-and-sync)
backend-p2p/ P2P-only content-addressed store (no cloud authority; blocks local, metadata in SQLite)
presenter-nfs/ NFSv3 server
presenter-fuse/ Linux FUSE presenter
presenter-webdav/ WebDAV server presenter (cross-platform)
presenter-fileprovider/ macOS File Provider bridge (Rust side)
presenter-fskit/ macOS FSKit bridge (Rust side, macOS 15.4+)
presenter-projfs/ Windows ProjFS presenter
cascade/ Binary crate (CLI entry point and daemon)
relay-server/ Opaque byte-pipe relay (binary): pairs two WebSocket clients by session ID for WAN NAT traversal, HMAC-gated, never inspects payload
swift/
CascadeFileProvider/ macOS File Provider extension
CascadeFSKit/ macOS FSKit extension (15.4+)
workers/
announce/ Stateless announce-server Cloudflare Worker (workers-rs, KV soft state)
Integration tests live inside each crate's tests/ directory rather than at workspace root.
Backendtrait — every cloud provider and the local filesystem implement this. The engine never sees provider-specific APIs. Each backend crate exposescreate_backend(config) -> Result<Box<dyn Backend>>.VfsTree— composes multiple backends into a single tree, routed by longest-prefix match. Cross-backend moves trigger download + upload + delete.VfsPresentertrait — platform-agnostic interface for presenting the VFS to the OS. Compile-time selection: FSKit on macOS (with WebDAV and NFS fallbacks), FUSE on Linux (with NFS fallback), ProjFS on Windows with WebDAV via WebClient as fallback, NFS as universal fallback..cascadeconfig walk — like.gitignore: files in each directory layer with child-overrides-parent precedence. Four formats (gitignore-style, TOML, YAML, JSON) all deserialise toCascadeConfig.- Expression language — PEG grammar evaluated against
EvalContext(file, device, disk, network, power, time, peer). Used for conditional rules in.cascadefiles. - P2P engine — based on Syncthing's BEP v1. Sits between VFS and cache as an optimisation layer, not as a backend. Cloud remains the authority for cloud-backed folders.
- Node management plane — a trusted device administers another over the authenticated peer connection. Authority is modelled as capability grants (a verb over a scope) held on the managed node; the
ManageRequest/ManageResponseBEP frames carry the full command set — status read, pin/unpin, cache evict/warm, config push, policy set, backend add/remove, daemon restart/stop, and grant delegation/revocation — and dispatch into the same handlers the local CLI drives, gated by per-command authorisation and an append-only audit log. The dangerous verbs (backend, lifecycle, grant administration) are never satisfied by a node-wide grant and must be granted explicitly for a folder scope, and a delegated grant must be a subset of authority the caller can itself exercise.cascade grant add|list|revoke|auditadministers the capabilities a node confers;cascade remote <device-id>drives a target reached over the discovery and connectivity stack. On top of the on-node grant list sits the signed capability-token model:cascade token issue|revoke|listmints and revokes tokens signed by the issuing node's real device key, which the bearer carries and presents with--token <file>oncascade remote. The node verifies the signature, expiry, and revocation list, then authorises the carried grant through the same path an on-node grant takes. Delegated tokens form bounded chains — each hop can only narrow authority, never widen it — and a token's expiry is clamped to its parent's, so a delegate never outlives the authority it derived from.
SQLite at ~/.config/cascade/state.db. Tables: files, backends, pin_rules, lifecycle_policies, config_cache, sync_cursors, p2p_peers, p2p_block_index, grants, manage_audit. Full schema in docs/design.md.
- Rust edition 2024 with
async_traitfor the Backend and VfsPresenter traits. - Error handling with
anyhowfor applications andthiserrorfor library crate error types. - Serde for all serialisation (JSON wire protocol, TOML/YAML/JSON config, SQLite bridge types).
- Platform context is injected via traits (
PlatformContext), not pulled from global APIs. Each OS provides its own implementation behind the same contract. - Backend crates are self-contained: each exposes exactly one
create_backendfunction and implements theBackendtrait. - Config merge semantics differ by concern: ignore rules and pins accumulate, lifecycle policies are child-first first-match-wins, cache settings are nearest-wins, device config is root-only.
- Strict workspace lints in
Cargo.toml: pedantic, nursery, and cargo Clippy groups are denied, plusunwrap_used,expect_used,indexing_slicing,string_slice, andunsafe_code. New code must satisfy these without#[allow]escapes.
- Linux FUSE runs without root. The FUSE presenter mounts as the calling user via
fusermount3. NFS fallback is different — binding a privileged port for NFS still needssudo, so if FUSE is unavailable (missinglibfuse3, no/dev/fuse) and you don't want to escalate, the daemon will fail rather than silently downgrade. - Windows mounts go via WebDAV and the WebClient service.
cascade startrunsnet use * http://localhost:<port>/which lets the OS pick the next free drive letter. IfWebClientisn't running the mount fails immediately; start it once withsc start WebClientor set it to auto-start (see Prerequisites). - Google Drive auth tokens are stored per platform. macOS and Linux:
$XDG_CONFIG_HOME/cascade/gdrive-tokens/<account>.json, falling back to~/.config/cascade/.... Windows:%APPDATA%\cascade\gdrive-tokens\<account>.json, with the default NTFS ACLs restricting access to the current user. - NFS cache mode controls write support, typed as the
NfsCacheModeenum (off/minimal/full).offis read-only — writes are refused withNFS3ERR_ROFS/NFS4ERR_ROFS.minimal(default) is write-capable with minimal disk usage;fullcaches everything eagerly. Write-capable modes implement the full NFSv3 and NFSv4 write procedure set:WRITE,CREATE,SETATTR(size/mtime),MKDIR,REMOVE,RMDIR,RENAME, andCOMMIT. All write procedures route through the same backend operations (upload,update,create_dir,delete,rename) as the WebDAV presenter. - P2P exposure is one posture, not a pile of flags. A single
DiscoveryReach(lan-only/private/public) governs how far a device reaches for peers, defaulting toprivate— LAN discovery, gossip, hole punch, and peer relay across a trusted mesh, with nothing published to any global directory. Each discovery source self-activates only when the posture permits its level and it has what it needs (a bound listener, a configured server). Global publication to the Mainline DHT and announce servers is opt-in via thepublicposture; the DHT's bootstrap set is always present (empty falls back to the built-in public nodes), so disabling the DHT is a posture choice, not missing config. Rendezvous-by-presence is a complementary live-pairing path: two peers register under the same rendezvous key at the same instant, and theRendezvousBroker— a distinct in-memory component hosted alongside the relay server'sSessionRegistry, not the same registry — exchanges their candidate sets and hole-punch agreements so they can connect directly; no durable state remains once either side disconnects. It activates under thepublicposture only and requires a configured rendezvous endpoint. - Google Drive rate limits — ~10,000 requests per 100 seconds per user. The backend uses a token-bucket rate limiter and batch requests where possible.
- Cross-backend moves are not atomic — they download from source, upload to destination, then delete the original. A failure partway through can leave duplicates.
- P2P block sizes are adaptive: 128KB for files under 250MB, 512KB for 250MB–1GB, 1MB for files over 1GB. This is not configurable per-file.
- Conflict copies are never auto-deleted. The losing version is renamed with the device name and date (e.g.
report (work-laptop 2026-05-27).conflict). P2P-only folders use last-write-wins per-block. - Shadowing in nested mounts: if a child mount point exists in the parent backend with files, the child takes over and the parent's files are hidden (not deleted). Removing the child reveals them.
- Multiple
.cascadeformats in one directory is allowed — they merge in deterministic order: gitignore-style → TOML → YAML → JSON, with last-writer-wins for scalar settings. - Device identity is derived from a self-generated TLS certificate (SHA-256 of cert, base32-encoded). All P2P connections are TLS-encrypted and authenticated by device ID.
| Phase | Scope |
|---|---|
| v1 | NFS mount + .cascade (ignore only) + single backend (read-only, Google Drive) |
| v2 | Pinning + lifecycle + cache manager |
| v3 | Write-back + multi-backend + nested mounts + conflict resolution |
| v4 | Conditional rules (expressions + context providers) |
| v5 | macOS File Provider presenter (Swift extension), implemented |
| v6 | Adopt existing directories (local backend, adopt-and-sync, adopt-in-place) |
| v7 | P2P block sharing (LAN) |
| v8 | Windows native ProjFS presenter, implemented (Linux FUSE delivered earlier) |
| v9 | Full P2P (WAN discovery, NAT traversal), implemented |
| v10 | Node management plane (capability grants, remote administration over BEP), implemented |
| v11 | OS background service (`cascade service install |
Full timeline estimates, dependency list, and reference implementations in docs/design.md.
- Design specification —
docs/design.md: core types, Backend trait definition, VFS tree implementation,.cascadeparser details, expression grammar, NFS/FUSE/File Provider presenter internals, state database schema, wire protocol, Google Drive backend details. - Deployment guide —
docs/deployment.md: deploying the announce Worker and relay server for WAN peer discovery and NAT traversal. - Reference implementations — rclone (NFS, VFS caching, Google Drive), Syncthing (BEP, peer discovery, NAT traversal), go-nfs (NFSv3/XDR), WinFSP (Windows virtual filesystem).