Trying it out
This page walks you through the current functional state, step by step. It is written to be followed even if you have never built a Rust program before. It is also honest about what works today and what does not yet — see the Roadmap for the plan.
There are two ways to read this page:
-
Just trying it out — you want to see your own Nextcloud show up as a normal folder on your Linux machine. Follow Part A from top to bottom.
-
Developing / testing the engine — you want to run the test suite, lint, or build the docs. Jump to For developers.
Part A — See your Nextcloud as a folder
|
First, the one idea everything depends on: mounting
Once it is mounted, that folder behaves like any other folder:
There is no separate "app view" and no "terminal-only mode". The moment it is mounted, it is just a folder, everywhere on your system. When you unmount it, the folder becomes empty again and your files live only in the cloud. Files are fetched from the server the first time you open them (nothing is copied down in advance), and your edits are uploaded back. |
Do the steps in order. Steps 1–3 you do once; steps 4–7 are the everyday use.
Step 1 — Prepare your Linux (install build tools)
Because wusel is currently run from source, your machine needs a C compiler
and linker (Rust uses the system linker), plus the FUSE library that makes the
"folder" trick possible. If these are missing you will see errors such as
error: linker 'cc' not found — that is this step, not a bug.
Run the block for your distribution.
Debian / Ubuntu / Mint (anything using apt):
sudo apt update
sudo apt install build-essential pkg-config libfuse3-dev fuse3
Fedora / RHEL / Rocky / Alma (anything using dnf):
sudo dnf group install development-tools # C compiler, linker, make (works on dnf4 and dnf5)
sudo dnf install pkgconf-pkg-config fuse3-devel fuse3
What each package is for
-
build-essential(apt) / thedevelopment-toolsgroup (dnf) — the C compiler and linker Rust needs to turn code into a program. Without them:linker 'cc' not found. -
pkg-config/pkgconf-pkg-config— lets the build find the FUSE library. -
libfuse3-dev/fuse3-devel— the FUSE headers, needed while building. -
fuse3— the FUSE runtime (fusermount3), needed while running the mount.
Step 2 — Install mise and turn it on
This project pins its exact toolchain (a specific Rust version, Node for the
docs, …) with a tool called mise. You do not install or manage Rust
yourself: mise reads the project’s mise.toml and puts precisely the pinned
versions on your PATH — but only after you activate it.
|
Activation is the step people miss. Installing mise only drops the |
-
Install the mise program once (official guide: mise.jdx.dev):
curl https://mise.run | sh -
Activate it in your shell. Add the line for your shell to its startup file (most Linux systems use bash):
# bash: echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc # zsh: echo 'eval "$(~/.local/bin/mise activate zsh)"' >> ~/.zshrc -
Close the terminal and open a new one so activation takes effect, then check it worked:
mise --version
Step 3 — Get the code and build it
Clone the public repository and let mise fetch the pinned toolchain:
git clone https://github.com/itbh-at/wusel.git
cd wusel
mise trust # allow this project's pinned tools (needed once per checkout)
mise install # download the pinned Rust (first time: a few minutes)
| If you only want to use Wusel on Fedora rather than build it, skip this whole page and install the packaged RPM instead — Installation. |
mise trust is required because mise refuses to load a project’s tool versions
until you confirm you trust that project — without it you would again see
"command not found". After mise install, cargo and friends resolve to the
project’s pinned versions automatically whenever you are inside this directory —
no mise exec -- prefix needed.
|
Still getting
cargo: command not found?Then mise is installed but not activated (Step 2), or you have not run
|
Step 4 — Log in to your Nextcloud
This uses Nextcloud’s official "Login Flow": it prints a URL, you approve the
login in your browser, and an app password is stored locally — in your OS
keyring by default; if no keyring is usable it falls back to the 0600 file
~/.config/wusel/credentials.json (readable only by you), which otherwise
holds just non-secret metadata (server + login name). You can revoke the app
password anytime from Nextcloud’s security settings.
cargo run -p wusel -- login https://cloud.example.org
Open the URL it prints, confirm in the browser, and wait for:
✓ Logged in as <user> on <server>.
|
How to read these
cargo run … commandsYou are running Wusel straight from its source code, so every command has a build wrapper in front of the actual wusel command. Take the line above:
So everything after |
Step 5 — Mount it
Now make your cloud appear at ~/Wusel.
|
You will need two terminals
The mount command keeps running for as long as your Nextcloud is mounted — it does not return to the prompt, and that is correct. Think of it like starting a server: it stays in the foreground and holds that terminal. So:
Do not close Terminal 1 while you are using your files — closing it unmounts the folder. |
Terminal 1 — start the mount and leave it running:
cargo run -p wusel --features fuse -- mount
|
Why
--features fuseCompared with You only need |
Notice: no path is given. With no path, wusel mounts to its default,
~/Wusel, and creates that folder for you if it does not exist. (You can
pass an explicit path — … — mount ~/somewhere-else — but you rarely need to.)
The first build with --features fuse compiles the FUSE part and can take a
minute. When it is up, Terminal 1 will sit there quietly — that is the mount
doing its job.
Step 6 — Use your files
Now switch to Terminal 2, or just open your graphical file manager.
In the file manager: open your home folder and go into Wusel. Your cloud tree is there. Double-click a file to open it — it is fetched from the server on first open.
In Terminal 2:
ls -l ~/Wusel # your Nextcloud tree; read-only shares show up as r--
cat ~/Wusel/Some.txt # streamed live from the server
echo "a new line" >> ~/Wusel/Some.txt # your edit uploads to Nextcloud
It is read-write. Create, edit, mkdir, rename and delete through the folder,
and the changes sync to Nextcloud. Read-only shares reject writes (you get a
permission error). If two sides changed the same file, your version is saved
beside the server’s as a … (conflicted copy …) rather than lost. Editor and
office scratch files — vim swap files, LibreOffice/MS Office lock files, *.tmp,
backups — are kept purely local and never clutter your cloud.
Online-only by default, cached on use. Nothing is copied down in advance, so a folder of huge files costs no disk until you touch them. When you open a file its read is served live at once, and the whole file is pulled into a local cache in the background — so it (and re-reads of it) become local. The cache is bounded by a configurable size with LRU eviction, so it never grows without limit. To keep specific files permanently offline (exempt from eviction, always synced), pin them (see Pin files/folders offline).
Live updates. Change, add or delete a file on the server (the web UI, another
device) and — with the notify_push
app installed on your Nextcloud — the mount reflects it within moments, including
live in an open file-manager window (files appear and disappear without a manual
refresh). Without notify_push, it catches up within a short revalidation
interval.
|
If the mount feels busy in the background
A desktop file indexer (GNOME Tracker, KDE Baloo) or thumbnailer scans folders you browse and reads every file — and since opening a file caches it, that would mean a background download storm (and cache churn) you did not ask for. By default Wusel protects against this: the mount root exposes synthetic,
local-only Caveats: KDE Baloo ignores these markers — exclude |
Step 7 — Stop (unmount)
When you are done, go to Terminal 1 (the one running the mount) and press
Ctrl+C. That unmounts ~/Wusel cleanly; the folder goes empty and
your files live only in the cloud again.
If the folder is ever left in a stale state (for example because Terminal 1 was killed abruptly), force the unmount:
fusermount3 -u ~/Wusel
Install Wusel as a real program
Part A ran Wusel straight from source with cargo run — convenient for a first
look, but it rebuilds each time and only works inside the project directory. Once
you are happy with it, compile a single optimized binary and put it on your
PATH. Then wusel is just a normal program you can run from anywhere, with
no cargo and no mise involved.
Build the release binary (from inside the project directory):
cargo build -p wusel --features fuse --release
This produces one self-contained executable at target/release/wusel. It is an
ordinary native Linux program: it does not need cargo, mise, or the source tree
to run — only the fuse3 runtime library you already installed in Step 1.
Now copy it onto your PATH. System-wide is the most reliable, because
/usr/local/bin is always on the PATH (it needs sudo):
sudo install -Dm755 target/release/wusel /usr/local/bin/wusel
Or install it just for your user, without sudo (this assumes ~/.local/bin is
on your PATH, which it is on most desktop distributions):
install -Dm755 target/release/wusel ~/.local/bin/wusel
From now on it is a plain command:
wusel # prints the list of commands
wusel login https://cloud.example.org # log in
wusel mount # mount ~/Wusel
Notice what disappeared compared with Part A: no cargo run, no --features
fuse, no --. Those were all cargo build-time scaffolding. The installed binary
already has mounting compiled in and there is no cargo to separate arguments from,
so the command is simply wusel mount — exactly as its own help writes it.
|
Rebuilding and re-copying ( |
Keep it mounted automatically (systemd service)
Everything in Part A is manual: the mount lives only as long as Terminal 1 is open, and it is gone after a reboot or logout. That is perfect for trying things out.
If instead you want your ~/Wusel to be there permanently — mounted in the
background, and again automatically after every login/reboot, with no terminal to
keep open — install it as a systemd user service. This uses the installed
wusel from the previous section:
wusel service enable # mount now, and at every login, in the background
wusel service status # is it running?
journalctl --user -u wusel@default -f # live logs
wusel service disable # stop doing that
|
Run |
mount (Part A) |
service enable |
|---|---|
Runs in the foreground; holds a terminal |
Runs in the background; no terminal needed |
Stops when you press Ctrl+C or close the terminal |
Keeps running; survives logout and reboot |
Good for trying it out |
Good for actually living with it |
service enable writes and enables a systemd user unit for you — no editing
unit files or running systemctl by hand. (It needs a systemd user session.) If
you installed the Fedora RPM instead of building from
source, the unit is already there as
/usr/lib/systemd/user/wusel@.service — use systemctl --user enable --now
wusel@default and skip service enable entirely.
Configuration is optional
|
You do not need a config file to complete Part A. With no config at all,
|
If you do want to change something, create ~/.config/wusel/config.toml. Every
key is optional:
[mount]
# Override the mountpoint used by `mount` with no path.
# Give an ABSOLUTE path — "~" is not expanded here, it would be taken literally
# and create a directory actually named "~".
point = "/home/you/Wusel"
[cache]
max_size = "5GB" # blob budget; "0"/"unlimited" = no limit
max_age = "30d" # drop blobs unused this long; "0" = never
[sync]
revalidate_secs = 30 # re-list a directory older than this (no-push fallback)
push_floor_secs = 5 # min seconds between push-triggered re-lists of one directory
text_merge = false # true = try a 3-way text merge on conflict before a conflicted copy
# What happens when a PINNED file goes out of date on the server:
# manual = emblem only ask = notify when more files go stale (default)
# auto = fetch it, but only over an unmetered connection; anything less
# certain than "not metered" falls back to ask
refresh_pinned = "ask"
# What an OUTDATED offline file serves when you OPEN it — a different question
# from the one above, which is about fetching unasked in the background:
# newest = always the current version (default)
# newest-unmetered = the current one, unless the connection is metered
# offline = the copy that is here; you refresh when you mean to
# While an outdated copy is what gets served, the file is READ-ONLY — see the
# concurrency page for why editing it would lose the newer version silently.
open_pinned = "newest"
# ephemeral editor/OS files kept purely local (never uploaded). Setting this
# REPLACES the built-in default; omit it to keep the default set.
ignore_patterns = [".*.sw?", "4913", "*~", ".~lock.*#", "~$*", ".#*", "*.tmp", ".goutputstream-*", ".DS_Store", "Thumbs.db"]
[state]
# Where the metadata database goes. Normally derived from XDG_STATE_HOME; set it
# only to override, including to overrule the automatic move off a network
# filesystem (NFS/CIFS), where SQLite cannot lock reliably. A path you name here
# is always used — and warned about if it is on such a filesystem.
db_path = "/var/tmp/wusel/state.sqlite"
[tls]
ca_cert = "/etc/wusel/my-ca.pem" # trust a private / self-signed CA
insecure = false # true = disable TLS verification (DANGER, testing only)
[auth]
keyring = false # default true (OS keyring); false = keep the app password in the 0600 file (see below)
[desktop]
exclude_from_indexers = true # default; false = let GNOME Tracker index the mount
Self-hosting with a self-signed certificate? Point tls.ca_cert at your CA
certificate, or set tls.insecure = true for a throwaway test (it logs a loud
warning).
Storing the app password in the OS keyring
By default the app password lives in your desktop keyring (it is a revocable
app password, not your account password); the credentials file then holds only
non-secret metadata (server + login name). To keep it in the 0600 file
instead, set [auth] keyring = false, or log in once with
wusel login --keyring false <server-url>.
This is fail-soft on purpose — the keyring is never allowed to break the tool:
-
If the keyring cannot be written and verified at login, Wusel silently keeps the password in the
0600file (it tells you it did). -
If a keyring-stored password cannot be read later (the keyring is locked, or its service is not running — common on headless/SSH boxes), you get a clear message telling you to unlock it, re-run
login, or setkeyring = false— never a cryptic failure.
The keyring works unattended for a desktop user because the login keyring is
unlocked at graphical login and Wusel runs as a systemd user service in that
session. On a server without a graphical login, set keyring = false (the file).
On macOS: run it in a Linux container
FUSE is a Linux kernel feature, so the mount is Linux-only (native macOS support is far-future, experimental work — a File Provider frontend, not FUSE). On a Mac, do the mount inside the project’s podman container, which already has the build tools from Step 1:
mise run fuse-shell # a Linux shell with /dev/fuse
# then, inside the container:
cargo run -p wusel --features fuse -- mount /mnt/nc
ls -l /mnt/nc
fusermount3 -u /mnt/nc
|
If mounting inside the container fails with a permission error, re-run
|
For developers
The rest of this page is for working on Wusel rather than just using it. None
of it needs a FUSE mount; it runs natively (including on macOS). The one
exception is the real mount end-to-end test, which needs a Linux FUSE driver
(mise run fuse-test, in the podman container).
Tests, type-check, lint
mise run check # type-check the workspace except wusel-fuse (no binary produced)
mise run test # build + run the native tests (wusel-core, wusel, wusel-desktop, wusel-mock)
mise run clippy # lint, failing on any warning (-D warnings)
mise run fmt-check # formatting is enforced
mise run fuse-test # real FUSE mount e2e against wusel-mock (Linux container)
The native tests exercise the platform-independent engine (wusel-core) and the CLI
(wusel without the fuse feature) — no FUSE driver needed. They include our own
Rust mock server (wusel-mock, run in-process) with end-to-end coverage of the
WebDAV client, the write buffer, conflict handling, deferred create, pinning,
the ignore list, background revalidation and the ETag sync walk, plus a
notify_push and a self-signed-TLS end-to-end. See Development
for the testing strategy.
The CLI surface
cargo run -p wusel # prints the usage: login / mount / service / accounts
Commands: login, mount, service enable|disable|status, accounts,
account remove, pin/pins/unpin, desktop, cache clear,
search-provider. A --account NAME selects a profile; without it the
implicit default account is used.
Pin files/folders offline
Pinning keeps a file or directory available offline: it is downloaded now and never evicted, so it stays readable with the network gone. It needs no mount — it writes straight into the cache.
| A pin is not yet a live copy. If the file changes on the server, the pinned copy is marked stale and the new content is fetched on the next read — which does touch the network. Until that read happens, going offline leaves you with the old version. Refreshing pinned files proactively is a roadmap item. |
cargo run -p wusel -- pin Documents # a directory (recursively)
cargo run -p wusel -- pin Notes.txt # a single file
cargo run -p wusel -- pin # no path = the whole account
cargo run -p wusel -- pins # list pins
cargo run -p wusel -- unpin Documents # release (blobs become evictable)
Directory pins cover their whole subtree; oversized files are cached too (a pin
overrides the size budget). Add --account NAME for a named profile.
Pins are kept in ~/.config/wusel/pins.json, next to the configuration and
not in the state database — they are what you said, not something we fetched.
So cache clear leaves them alone, they survive a rebuilt or relocated
database, and they travel with a roaming home directory. Their files download
again on first use after a clear.
Multiple accounts
A single-account user can ignore this. To add a second Nextcloud:
cargo run -p wusel -- --account work login https://work.example.org
cargo run -p wusel --features fuse -- --account work mount # → ~/Wusel-work
cargo run -p wusel -- accounts # list configured accounts
cargo run -p wusel -- account remove work # remove a named profile (server untouched)
Each account is fully isolated (credentials, state, cache, mountpoint). Mountpoints may not overlap; mounting the same server+user twice is warned.
What does NOT work yet
-
File-manager integration on non-GNOME desktops (KDE Dolphin, Nemo, Thunar); GNOME/Nautilus emblems + context menus, GNOME Shell search and native notifications work.
-
Proactive refresh of pinned files when the server copy changes (see the note under Pin files/folders offline).
-
Refinements: fully concurrent content reads (the FUSE loop is single-threaded), and WebDAV LOCK (advisory locking). Emblems do refresh live, but a background hydration only pushes the refresh once the download finishes — see File-manager integration.