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 structureTahr.RPC— the ONC RPC framework (record marking, programs, TCP transport)Tahr.Mount— the MOUNT protocol, against aTahr.Mount.BackendbehaviourTahr.NFSv3— the NFSv3 procedures, against aTahr.NFSv3.Backendbehaviour
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"}]
endQuick start
Implement the two backend behaviours over your storage:
Tahr.Mount.Backend— export resolution (which clients may mount which paths)Tahr.NFSv3.Backend— filesystem operations (getattr, read, write, readdir, and friends)
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 included —
Tahr.InMemory, a read-write reference backend, andmix tahr.serveto 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