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) | ✅ |
| Office document locking (Nextcloud Office/Collabora ↔ local LibreOffice, no conflicted copies) | ❌ | ✅ locks a document on the server while LibreOffice, Office, Vim or Kate has it open, keeps the lock through saves, and shows files someone else has open in Collabora as read-only | |
| 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.
The .rpm packages the same client as the .deb (GUI tray app, daemon, GNOME search provider, CR3 thumbnailer, Nautilus extension, and the KF6 Dolphin plugin + ServiceMenu). It is built from source, so the build host needs cargo, pnpm, rpm-build and every BuildRequires listed in packaging/ncrs.spec. A bare opensuse/tumbleweed container does not have those installed — install them first (.github/workflows/ci.yml has the exact zypper install line CI uses):
bash scripts/build-rpm.sh # -> dist/ncrs-<version>-0.<arch>.rpmOn a host whose toolchain is not installed as RPMs — rustup's cargo, or an npm-installed pnpm — rpmbuild cannot see them and stops with Failed build dependencies:. Install the -devel packages the spec needs (fuse3-devel, gtk3-devel, webkitgtk3-devel, kf6-kio-devel, librsvg-devel, plus cmake/qt6-base-devel/kf6-kcoreaddons-devel/kf6-extra-cmake-modules), then build with --nodeps:
bash scripts/build-rpm.sh --nodeps--nodeps skips only rpmbuild's BuildRequires check; the libraries must still be present or the compile fails. CI installs the distro toolchain and does not use it.
Install it. The locally built artifact is not GPG-signed, so zypper warns that the package is unsigned and prompts for confirmation; pass --allow-unsigned-rpm to skip that prompt:
sudo zypper in ./dist/ncrs-*.rpmThe GUI tray app autostarts at login via /etc/xdg/autostart/es.rgon.ncrs.desktop, exactly like the .deb. The headless alternative is systemctl --user enable --now ncrs.service, which runs the daemon without the GUI; the two coexist (see Provisioning below).
The Nautilus sync-state emblems need python-nautilus. On Leap 16 it may not be in the OSS repo — enable the Package Hub for your release and install it from there if zypper cannot find it. The Dolphin integration ships inside the RPM, so it needs nothing extra.
On openSUSE Leap 16 the tray icon also needs libayatana-appindicator3-1, which is Package Hub–only (not in OSS/Update). Enable the Package Hub and install it to get the tray icon. It is a Recommends on Leap 16, not a hard dependency, so a stock install succeeds without it and the GUI runs — you just get no tray icon.
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/<user>/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_coreOn Linux 6.9+ the kernel can serve reads of fully-cached files straight from the
cache copy. Registering those files needs CAP_SYS_ADMIN, which only the small
ncrs-passthrough helper gets (the packages install it at
/usr/libexec/ncrs/ncrs-passthrough with cap_sys_admin=ep); the ncrs
service itself runs with no capabilities, so it mounts through fusermount3.
The service spawns the helper per mount and falls back to buffered reads when it
is missing or lacks the capability (the GUI settings and the IPC
PASSTHROUGH_STATUS then report it as unavailable). To try it on a development build, cap the helper next to your
binary — never ncrs itself:
cargo build -p ncrs_core -p ncrs_passthrough
sudo setcap cap_sys_admin=ep target/debug/ncrs-passthrough(NCRS_PASSTHROUGH_HELPER=/path/to/ncrs-passthrough overrides where the service
looks for it.)
rm ~/.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 cached copy after uploading a file you edited (default on; cleared by the cache size limit, auto-purge and "purge local cache")auto_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)



