NFS Server

This integration provides a wrapper around the upstream nixpkgs module to create and manage a NFS server declaratively, providing some options to configure features such as: - define default options to set on all exports - define exports declaratively - subnet/ip based ACLs to selectively allow access

NFS exports are generated by bind-mounting an existing directory to an /export directory

Example Configurations

Basic Setup

This shows a simple NFS server with a single export.

provision.fs.nfs.server = {
  enable = true;
  # base directory for export directory
  exportDir = "/export";
  firewall.enable = true; # open firewall for interfaces defined in `interfaces`
  firewall.interfaces = [ "eth1" ]; # interface to open firewall to
  # adds bind-mounts for /path -> /export/path
  default.addToFilesystem = true;
  # default export options to add to all exports
  default.export.options = {
    rw = true;
    insecure = true;
    subtree_check = true;
    nohide = true;
    async = true;
  };
  exports = {
    # Currently required to add root export
    "/" = {
      exportPath = "/export";
      export.options.fsid = 0;
      # allow to all hosts
      subnets."*" = { };
    };
    # Media share available at server:/media
    "/media" = {
      hostPath = "/media";
      # allow to two networks
      subnets = {
        "10.8.0.0/24" = { };
        "192.168.0.5/32" = { };
      };
    };
  };
};

You can then connect to the server by running

# on samba server
nix shell nixpkgs#nfs-utils
mount.nfs localhost:/media /mnt

# on another machine accessible via network on eth0
mount.nfs <samba-ip>:/media /mnt

Subnet + IP based Access Control

You can also define shares in the provision.fs.nfs.server.subnets option.

Example Configuration

users.users.media.uid = 3000;
users.users.media.isSystemUser = true;
users.groups.media.gid = 3000;

provision.fs.nfs.server = {
  enable = true;
  default.addToFilesystem = true;
  default.export.options = {
    rw = true;
    insecure = true;
    subtree_check = true;
    nohide = true;
    async = true;
  };
  subnets = {
    mydevices = {
      subnet = "10.77.1.0/24";
      paths = [
        "/media"
        "/pictures"
        "/documents"
        "/backups"
      ];
    };
    lan = {
      subnet = "192.168.1.0/24";
      paths = [ "/media" ];
    };
    phone = {
      subnet = "192.168.1.7/32";
      paths = [
        "/media"
        "/pictures"
        "/documents"
        "/backups"
      ];
    };
    thinclient = {
      subnet = "192.168.1.88/32";
      paths = [
        "/documents"
        "/backups"
      ];
    };
  };
  exports = {
    # Currently required to add root export
    "/" = {
      exportPath = "/export";
      export.options.fsid = 0;
      # allow to all hosts
      subnets."*" = { };
    };
    "/pictures".export.options = {
      anonuid = config.users.users.media.uid;
      anongid = config.users.users.media.uid;
    };
    "/media".export.options = {
      anonuid = config.users.users.media.uid;
      anongid = config.users.users.media.uid;
    };
    "/documents".export.options = {
      anonuid = 2000;
      anongid = 2000;
    };
  };
};

The generates an /etc/exports like:

/export	*(rw,insecure,subtree_check,nohide,async,fsid=0)
/export/backups	10.77.1.0/24(rw,insecure,subtree_check,nohide,async,rw,insecure,subtree_check,nohide,async) 192.168.1.7/32(rw,insecure,subtree_check,nohide,async,rw,insecure,subtree_check,nohide,async) 192.168.1.88/32(rw,insecure,subtree_check,nohide,async,rw,insecure,subtree_check,nohide,async)
/export/documents	10.77.1.0/24(rw,insecure,subtree_check,nohide,async,rw,insecure,subtree_check,nohide,async,anonuid=2000,anongid=2000) 192.168.1.7/32(rw,insecure,subtree_check,nohide,async,rw,insecure,subtree_check,nohide,async,anonuid=2000,anongid=2000) 192.168.1.88/32(rw,insecure,subtree_check,nohide,async,rw,insecure,subtree_check,nohide,async,anonuid=2000,anongid=2000)
/export/media	10.77.1.0/24(rw,insecure,subtree_check,nohide,async,rw,insecure,subtree_check,nohide,async,anonuid=3000,anongid=3000) 192.168.1.0/24(rw,insecure,subtree_check,nohide,async,rw,insecure,subtree_check,nohide,async,anonuid=3000,anongid=3000) 192.168.1.7/32(rw,insecure,subtree_check,nohide,async,rw,insecure,subtree_check,nohide,async,anonuid=3000,anongid=3000)
/export/pictures	10.77.1.0/24(rw,insecure,subtree_check,nohide,async,rw,insecure,subtree_check,nohide,async,anonuid=3000,anongid=3000) 192.168.1.7/32(rw,insecure,subtree_check,nohide,async,rw,insecure,subtree_check,nohide,async,anonuid=3000,anongid=3000)

Test Configuration

The server + client integrations are tested in tests/nfs/basic.nix

Troubleshooting

The below is a miscellaneous list of tools you can use to debug issues, or comments on configurations:

  • run exportfs to list current exports
  • some logs may be available at nfs-server.service