CI Licence

Tahr

A standalone Elixir library for building NFSv3 file servers natively on the BEAM — no kernel NFS server, no NIFs, no out-of-tree Rust. If you can implement a behaviour, you can serve NFS from an Elixir application.

The whole stack is here, from the wire up:

  • Tahr.XDR — codec for every ONC RPC and NFSv3 data structure
  • Tahr.RPC — the ONC RPC framework (record marking, programs, TCP transport)
  • Tahr.Mount — the MOUNT protocol, against a Tahr.Mount.Backend behaviour
  • Tahr.NFSv3 — the NFSv3 procedures, against a Tahr.NFSv3.Backend behaviour

Implement the two backend behaviours over your storage and any NFS client built into Linux, macOS, or Windows can mount it.

The library follows the same split-of-concerns pattern as firkin (S3) and davy (WebDAV): protocol library here, storage-specific backend elsewhere.

Installation

Add tahr to your dependencies:

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

Quick start

Implement the two backend behaviours over your storage:

Then wire the protocol handlers and the RPC listener into your supervision tree:

programs = %{
  100_005 => %{3 => Tahr.Mount.Handler.with_backend(MyApp.MountBackend)},
  100_003 => %{3 => Tahr.NFSv3.Handler.with_backend(MyApp.NFSBackend)}
}

children = [
  {Tahr.RPC.Server, port: 2049, programs: programs}
]

Supervisor.start_link(children, strategy: :one_for_one)

The portmapper (program 100000) is always registered so that NFS clients can discover the server, and the NFSv3 mapping is announced automatically on whatever port is bound.

Features

  • Pure BEAM, from the wire up — XDR (RFC 4506), ONC RPC v2 with record marking and AUTH_SYS, MOUNT v3, and the full NFSv3 procedure set (RFC 1813)
  • Pluggable storage — two behaviours separate the protocol from your storage layer
  • Streaming reads — READ replies stream to the socket as iolists; files are never materialised into a single binary
  • One process per connection — slow clients don't affect anyone else, and the listener drains in-flight RPCs on shutdown
  • Batteries includedTahr.InMemory, a read-write reference backend, and mix tahr.serve to mount it locally

Try it locally

mix tahr.serve

# in another terminal
sudo mount -t nfs -o vers=3,tcp,port=2049,mountport=2049 \
  127.0.0.1:/export /mnt/nfs
ls -l /mnt/nfs

Tutorials

Step-by-step guides to building a backend, included in the generated docs (mix docs):

Building and testing

mix deps.get
mix compile
mix test

Licence

Apache-2.0