Unofficial Nextcloud Desktop client with proper VFS support on *nix. Rewritten from scratch in rust. Designed to perform well in installations with a very large amount of shared files/media etc (where cloud storage >> local storage), as well as in a smaller cloud.
![]() |
![]() |
![]() |
![]() |
NOTE: personal info has been edited out from the screenshots, in real life it looks normal :)
| Feature | Nextcloud Desktop | GNOME Integration/GVfs | ncRS |
|---|---|---|---|
| Real Virtual Filesystem | ❌ (Experimental, bad approach which doesn't work with shell/file pickers etc) | ✅ (remote only) | ✅ |
| Streaming Download Support (play large 4K videos at network-rate) | ❌ | ❌ (yes, but throughput is lower) | ✅ |
| Nextcloud Notifications | ✅ | ❌ | ✅ |
| Nautilus integration: syncing/downloaded/shared | ✅ (partial) | N/A since it's only online | ✅ |
| Instant local access | ✅ | ❌ | ✅ |
| Fast time to first use (usable right after setup, no bulk download first) | ❌ (must fully download the entire selected folder tree before anything is usable) | ✅ | ✅ |
| Zero-copy cache reads (kernel serves the cached file directly, no extra copy through the app) | ✅ (not really a fair comparison — it isn't virtual, so every file is a permanent full local copy, always "cached" by definition) | ❌ | ✅ (Linux 6.9+, via kernel FUSE passthrough) |
| Nextcloud-generated thumbnails | ❌ | ❌ | ✅ (even for RAW images!) |
| Local cached file pruning | ❌ | ❌ | ✅ intelligently keeps local copies of often-used files |
| Dynamically cache files/keep part locally | ❌ | ❌ (No internet = no files) | ✅ |
| No path conflicts | ❌ (may emit sync errors) | ✅ | ✅ No dumb 'Some files could not be synced' |
| Maps nextcloud permissions to filesystem permissions | ❌ | ❌ (will error, but doesn't first display it to the user) | ✅ |
| HPB Support/Sync speed | ✅ NC HPB | ❌ | ✅ NC HPB |
| QUIC/HTTP3 Support | ❌ | ❌ | ✅ |
| Syncing multiple OS paths -> different folders within Nextcloud | ✅ | ❌ | ❌ (everything mounted under the Nextcloud path) |
Server-side recommendation: If you have shell access to your Nextcloud server, you shall enable background thumbnail pre-generation with occ preview:pre-generate.
This makes the server generate thumbnails during idle time rather than on-demand, which would eliminate the congestion entirely for directories that have been indexed.
We avoid local thumbnail generation so that entire files don't have to be downloaded for the local thumbnailer to run.
Coding agents are not polite to network filesystems. To find a binary or a config file they happily run find / -name protoc, rg pattern ~ or du -sh ~/*. Those commands walk into ~/Nextcloud, and every directory they enter becomes a WebDAV request to your server. Claude Code does not kill a long command: after 2 minutes it moves it to the background, where a single find / can crawl your whole Nextcloud for hours (we measured ~20 listings/s and 20,000 folders in 20 minutes). Such a crawl can overload the server into returning 500 errors. ncrs defends itself: it backs off from a failing server, and it paces any single program that crawls folders it hasn't cached (Settings → Performance → Slow down folder crawlers, on by default at 10 folders/s). Still, the cheapest request is the one never sent.
Keep agents off the mount by adding the bundled guard hook to your user-level Claude Code settings. It refuses find/rg/grep -r/du/fd/ls -R/tree walks, and Glob/Grep tool searches, that would descend into the mount from / or ~. It allows them when they stay on one filesystem (find / -xdev, rg --one-file-system, du -x), prune the mount, or target a narrower directory. The refusal message tells the agent how to rewrite the command.
mkdir -p ~/.claude/hooks
curl -fsSLo ~/.claude/hooks/block-root-walks.py \
https://raw.githubusercontent.com/rgon/ncrsDesktop/master/scripts/claude-code/block-root-walks.py
# Merge the hook into ~/.claude/settings.json (keeps your existing settings):
jq '.hooks.PreToolUse = ((.hooks.PreToolUse // []) + [{"matcher":"Bash|Glob|Grep","hooks":[{"type":"command","command":"python3 ~/.claude/hooks/block-root-walks.py","timeout":10}]}])' \
~/.claude/settings.json > /tmp/claude-settings.json && mv /tmp/claude-settings.json ~/.claude/settings.jsonThe hook guards ~/Nextcloud by default. To guard other mount points, set NCRS_WALK_GUARD_MOUNTS=/path/one:/path/two. Also consider adding a line like "never recurse from / or ~ without -xdev; ~/Nextcloud is a network mount" to your ~/.claude/CLAUDE.md. Other agents need their own equivalent, such as a command deny-list or rules file.
Download and install the .deb file from the /releases page. You may simply double click the .deb to install it with your OS's package manager.
You may directly set it up using the GUI, following the interactive 'Authorize Device'-type login from the web browser. This issues a revocable Nextcloud app password, which is saved in your OS keyring (GNOME Keyring/KWallet).
The recommended configuration options are the defaults. However, you may modify them via the cogwheel icon in the application menu.
ncrs-gui writes its configuration to a yaml file, for easy view and editing.
You may edit this file to configure it non-interactively, which is especially useful for remote/fleet deployment.
Create the config file — run the daemon once to generate the skeleton, then fill it in:
cargo run -p ncrs_core # exits immediately, writes ~/.config/ncrs/config.yaml
nano ~/.config/ncrs/config.yamlThe file looks like this. You shall use an app password rather than the user's main password:
url: https://cloud.example.com/remote.php/dav/files/YOUR_USERNAME/
username: youruser
password: xxxx-xxxx-xxxx-xxxx # app password (optional) or fetched/saved from the system keyring
mount_point: /home/you/ncrs
user: youruserThe .deb (built by scripts/build-deb.sh will allow fleet config. For
- Config is per-user at
~/.config/ncrs/config.yaml(XDG; there is no system-wide config). A template ships at/usr/share/doc/ncrs/config.yaml.example, or generate one withncrs --print-default-config. - Push per-user config files with your config-management tool (e.g. Ansible
templateto each user's~/.config/ncrs/config.yaml). You may pre-fill/etc/skel/.config/ncrs/config.yamlso new accounts start provisioned. If setting the password on the config file, always use per-user app passwords or anauth_command— never a shared credential. - The GUI tray app autostarts at login via
/etc/xdg/autostart/es.rgon.ncrs.desktopand runs the systemctl service. - Headless alternative:
systemctl --user enable --now ncrs.serviceruns the daemon without the GUI. The two coexist: when the GUI starts and finds the service already serving the IPC socket, it attaches as a client — mirroring sync state, errors, and transfers in the tray and forwarding pause/resume — instead of mounting a second time. Quitting an attached tray will unmount it, however.
Download the .deb and manually install it
cd Downloads
sudo apt install ./ncrs_*_amd64.deb
And open it from your applications list or the terminal ncrs-gui & disown
sudo apt-get install fuse3 libfuse3-dev libxdo-devThe Rust toolchain: https://rustup.rs/
For the Nautilus extension (optional):
sudo apt-get install python3-nautilusGUI + daemon (the normal way):
./runui.shThis starts the Tauri tray app; the daemon mounts WebDAV at your configured mount_point automatically.
It will also install the shell integrations etc: ./shell_integration/file-managers/nautilus/install.sh to show sync-state emblems (cloud = remote-only, tick = local) on files in the mount.
File-browser support is modular: the service detects which browsers are installed (Nautilus, Dolphin, Nemo) and applies each one's profile (indexer exclusion, thumbnails, type detection). Profiles can be toggled from the GUI's File browsers settings or with ncrs-ctl integrations / ncrs-ctl integration-set <id> on|off|auto. Adapters talk to the service over the protocol in shell_integration/file-managers/PROTOCOL.md; the Dolphin adapter lives in shell_integration/file-managers/dolphin/.
Downloaded files are cached in ~/.cache/ncrs/.
Daemon only (headless / for systemd):
RUST_LOG=info cargo run -p ncrs_corerm ~/.local/share/nautilus-python/extensions/syncstate.py
nautilus -qUnit + integration tests (no server needed — integration tests skip gracefully):
cargo test
python3 -m unittest discover -s shell_integration/file-managers/nautilus -vEnd-to-end tests against a real WebDAV server (requires Docker):
docker compose -f docker/docker-compose.yml up -d
WEBDAV_TEST_URL=http://localhost:8888 cargo test -p ncrs_core --test integration_test
docker compose -f docker/docker-compose.yml downPackage build + verification (what CI runs; --container needs Docker or Podman):
./scripts/build-deb.sh # options: --version, --arch, --out-dir, --skip-gui, --skip-build
./scripts/test-deb.sh --container # metadata, contents, desktop entries, clean-install smoke testncrs stores locally-available files under ~/.cache/ncrs/<server-hash>/:
| Directory | Purpose | Survives restart | User-controlled |
|---|---|---|---|
kept/ |
Files explicitly pinned via "Keep Locally" | Yes | Yes (KEEP/EVICT) |
cache/ |
Files auto-downloaded during reads | Yes (validated on boot) | No (may be evicted) |
The <server-hash> component is derived from the WebDAV URL to allow multiple server configurations.
Additional metadata files in the root:
dir_cache.json-- cached directory listings (etags + entries)file_cache.json-- tracks which files are cached/kept locally (remote path, etag, kept flag)journal.bin-- offline mutation journal for pending uploads/deletes/renames
Config options (in ~/.config/ncrs/config.yaml):
auto_keep_locally_modified_files: true-- keep a local copy after uploading a file you editedauto_keep_cached_files: true-- promote read-cached files to kept automaticallycache_max_size_bytes: 34359738368-- max size forcache/directory (default 32 GB, 0 = unlimited). Oldest-accessed files are evicted first.cache_auto_purge_days: 10-- auto-delete cached files not accessed in N days (default 10, 0 = disabled)cache_streamed_reads: true-- promote fully-streamed files to disk cacheread_ahead_bytes: 67108864-- read-ahead window for streaming reads (default 64 MB)cache_cleanup_interval_secs: 3600-- how often to run cache pruning (default 3600 = 1 hour)dir_cache_max_stale_mins: 15-- before showing a directory whose cached listing is older than this, ask the server whether it changed, so the first listing is already current (default 15, 0 = disabled). An unchanged directory costs one small etag request. Only applies while push notifications are down; while they work, a 24-hour backstop applies.keep_paths: ["/Documents", "/Photos"]-- remote paths to auto-keep locally on startup (default empty)



