Set up a Wusel development environment
This one is for people who want to change Wusel’s own code. If you only want to use it — to get your Nextcloud files as a folder — you want one of the three user tutorials instead.
By the end of this you will have built Wusel, run the whole test suite green, and mounted a working filesystem against a local mock server — no Nextcloud account needed.
This is the Linux path, and Linux is where Wusel is developed: it is the only platform where the mount runs. Everything below assumes a Linux machine.
| Working from a Mac? Everything except the mount builds and tests natively there, and the mount runs in a container. Develop on macOS covers it. You can stop reading this page. |
Step 1 — Build tools
Rust uses the system linker, and the mount needs the FUSE library. The test suite additionally wants a keyring daemon and a D-Bus launcher — the reason is in step 4.
RPM-based:
sudo dnf group install development-tools
sudo dnf install pkgconf-pkg-config fuse3-devel fuse3 gnome-keyring dbus-daemon
Debian-based:
sudo apt update
sudo apt install build-essential pkg-config libfuse3-dev fuse3 gnome-keyring dbus-bin
If you plan to touch the file-manager extension, add its development files too:
nautilus-devel glib2-devel gtk4-devel make (RPM-based) or
libnautilus-extension-dev libglib2.0-dev libgtk-4-dev make (Debian-based).
Step 2 — The toolchain, via mise
Every binary this project uses is pinned in mise.toml — the Rust version, Node
for the documentation, all of it. You never install Rust yourself.
curl https://mise.run | sh
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc # zsh: activate zsh, ~/.zshrc
Open a new terminal, then confirm:
mise --version
Activation is the step people miss. Installing mise only puts the
program on disk — it does not yet manage your PATH. Until you activate it,
cargo is "command not found" even though Rust is installed, and you have to
prefix everything with mise exec --.
|
Step 3 — Clone and build
git clone https://github.com/itbh-at/wusel.git
cd wusel
mise install
mise run check
mise install fetches the pinned toolchain; the first run takes a few minutes.
mise run check is a cargo check over the engine and the CLI. When it comes
back clean, your environment is correct.
Step 4 — Run the test suite
mise run test
Everything should pass. If it stops immediately complaining about a missing
program, that is step 1’s gnome-keyring or dbus-daemon.
That is not a bare cargo test. On Linux it runs inside a private D-Bus session
with an empty, unlocked keyring under a temporary data directory. Two reasons,
and both matter the first time you touch credential code:
-
The credential tests answer the same on your unlocked GNOME desktop as in a bare container, instead of quietly depending on what your machine happens to have.
-
No test can reach your login keyring. The product and the tests naturally pick the same account key, and the tests used to overwrite the real one.
The suite refuses to run rather than testing less than it claims. That is deliberate: a green run has to mean the thing was checked.
Step 5 — Mount against the mock server
You do not need a Nextcloud to run a mount. wusel-mock serves an ordinary
directory as a Nextcloud-style user root.
In one terminal, make a directory with something in it and serve it:
mkdir -p /tmp/fake-cloud && echo hello > /tmp/fake-cloud/Some.txt
mise run mock -- --root /tmp/fake-cloud
The mock serves the user alice at http://127.0.0.1:8080 and accepts any
password. It has no Login Flow v2, so wusel login cannot run against it;
instead, write the credentials file that login would have written. Point the
XDG directories at a scratch location first, so this stays apart from any real
account you have.
In a second terminal:
export XDG_CONFIG_HOME=/tmp/wusel-dev/config XDG_STATE_HOME=/tmp/wusel-dev/state XDG_CACHE_HOME=/tmp/wusel-dev/cache
mkdir -p "$XDG_CONFIG_HOME/wusel"
printf '{"server":"http://127.0.0.1:8080","loginName":"alice","appPassword":"x","in_keyring":false}\n' \
> "$XDG_CONFIG_HOME/wusel/credentials.json"
chmod 600 "$XDG_CONFIG_HOME/wusel/credentials.json"
Then build with the mount compiled in, and mount:
mise run build-fuse
cargo run -p wusel --features fuse -- mount /tmp/mnt
--features fuse is a build switch: the mount is not compiled in by default,
which is what keeps the project buildable where there is no FUSE. Everything
after -- is handed to Wusel rather than to cargo.
In a third terminal:
ls -l /tmp/mnt
cat /tmp/mnt/Some.txt
echo "written through the mount" >> /tmp/mnt/Some.txt
cat /tmp/fake-cloud/Some.txt
The last line proves the round trip: your write went through FUSE, through the engine, over WebDAV, and landed in the directory the mock serves.
Press kbd:[Ctrl+C] in the mount’s terminal to unmount. If it is ever left stale,
fusermount3 -u /tmp/mnt.
Because the mock mirrors a real directory, staging a test case is echo and
rm in /tmp/fake-cloud rather than writing XML. Markers in a name provoke
behaviour that is otherwise hard to arrange:
-
*.fail-once— the firstPUTanswers 500, then it succeeds. -
*.fail-perm— everyPUTanswers 403. -
.no-etag—PUTandMOVEanswer without anETag. -
.lost-once.— the firstPUTlands, then answers 504. -
*.relist-fails(a directory) — after the firstPUTinto it, its next listing answers 503, once. -
.proxy-403— a chunked upload assembles, but its finalMOVEanswers 403.
Step 6 — The full mount test
mise run fuse-test
This mounts the whole stack against the in-process mock and drives it through
the kernel — ls, cat, stat, statfs, unmount. The same run covers
wusel-desktop’s D-Bus and notify-hook path, lints `wusel-fuse, and checks the
network-home relocation. It runs in a podman container with /dev/fuse, which
is why it is not part of mise run test — and why it needs podman installed.
What to run before you push
mise run fmt-check
mise run headers-check
mise run shellcheck
mise run clippy
mise run check
mise run test
mise run build-fuse
These seven are exactly what CI runs, through the same mise tasks and the same pinned toolchain — there is no separate CI script that can drift.
mise run setup-hooks makes git do this for you: a pre-commit hook formats
what you commit, and a pre-push hook runs the gate above — with fuse-test
in place of build-fuse — before a branch is pushed.
Two more exist for when they are relevant: mise run e2e-local drives the whole
thing against a real Nextcloud in a container, and mise run docs-diagrams
re-renders the documentation diagrams (their SVGs are committed, and the build
checks their hashes, so forgetting this is caught rather than silently shipping
a stale picture).