Wick

A standalone Elixir library for building FUSE userspace filesystems on the BEAM — without libfuse bindings or a native event loop. The only native code is a minimal syscall NIF; everything above the file descriptor (frame parsing, protocol encoding, your filesystem logic) is ordinary supervised Elixir.

Two layers:

Installation

Add wick to your list of dependencies in mix.exs:

def deps do
[{:wick, "~> 0.1.0"}]
end

Wick compiles a small Rust NIF via Rustler, so a Rust toolchain must be available at build time.

Writing a filesystem

A FUSE server is an event loop: mount, wait for a readiness notification, read a request frame, decode it, write a reply, re-arm, repeat. The kernel's first request is always INIT, and nothing else works until you answer it.

The Writing a filesystem guide builds a complete read-only filesystem from scratch and is the best place to start. The primitive below shows the raw transport and codec call sequence those servers are built from.

Mount and serve

{:ok, handle} =
Wick.Fusermount.mount(
"/tmp/my-mount",
["fsname=demo", "subtype=demo", "default_permissions"]
)
:ok = Wick.Native.select_read(handle)
receive do
{:select, ^handle, :undefined, :ready_input} ->
{:ok, request_bytes} = Wick.Native.read_frame(handle)
{:ok, op, header, request} = Wick.Protocol.decode_request(request_bytes)
# ... build a reply struct for `op` ...
response_bytes = Wick.Protocol.encode_response(header.unique, reply, 0)
:ok = Wick.Native.write_frame(handle, response_bytes)
end
:ok = Wick.Fusermount.unmount("/tmp/my-mount")

Wick.Fusermount.mount/2 calls into a NIF that uses posix_spawn(3) to run fusermount3 with one end of a socketpair(2) inherited as fd 3, then receives the resulting /dev/fuse fd via SCM_RIGHTS. Wick.Fusermount.unmount/1 invokes fusermount3 -u via an Erlang Port so the BEAM's child-process management reaps the helper without colliding with SIGCHLD = SIG_IGN.

See Wick.Native, Wick.Fusermount, and Wick.Protocol for full documentation.

Tests without /dev/fuse

CI hosts that lack FUSE support can still exercise the transport:

{:ok, {read_fd, write_fd}} = Wick.Native.pipe_pair()

returns a non-blocking pipe pair wrapped in the same resource type, so the select_read / read_frame / write_frame path can be driven end-to-end. Tests that exercise Wick.Fusermount.mount/2 are tagged :fuse and skipped on hosts where /dev/fuse is not available.

GitHub Mirror

Eventually, Forgejo will support fully federated operation, but for now there's a mirror of this repository on GitHub - feel free to open issues and PRs there.

Licence

Apache-2.0 — see LICENSE for details.