1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 inInitrd = config.boot.initrd.supportedFilesystems.nfs or false;
8
9 nfsStateDir = "/var/lib/nfs";
10
11 rpcMountpoint = "${nfsStateDir}/rpc_pipefs";
12
13 format = pkgs.formats.ini {};
14
15 idmapdConfFile = format.generate "idmapd.conf" cfg.idmapd.settings;
16
17 # merge parameters from services.nfs.server
18 nfsConfSettings =
19 optionalAttrs (cfg.server.nproc != null) {
20 nfsd.threads = cfg.server.nproc;
21 } // optionalAttrs (cfg.server.hostName != null) {
22 nfsd.host= cfg.hostName;
23 } // optionalAttrs (cfg.server.mountdPort != null) {
24 mountd.port = cfg.server.mountdPort;
25 } // optionalAttrs (cfg.server.statdPort != null) {
26 statd.port = cfg.server.statdPort;
27 } // optionalAttrs (cfg.server.lockdPort != null) {
28 lockd.port = cfg.server.lockdPort;
29 lockd.udp-port = cfg.server.lockdPort;
30 };
31
32 nfsConfDeprecated = cfg.extraConfig + ''
33 [nfsd]
34 threads=${toString cfg.server.nproc}
35 ${optionalString (cfg.server.hostName != null) "host=${cfg.server.hostName}"}
36 ${cfg.server.extraNfsdConfig}
37
38 [mountd]
39 ${optionalString (cfg.server.mountdPort != null) "port=${toString cfg.server.mountdPort}"}
40
41 [statd]
42 ${optionalString (cfg.server.statdPort != null) "port=${toString cfg.server.statdPort}"}
43
44 [lockd]
45 ${optionalString (cfg.server.lockdPort != null) ''
46 port=${toString cfg.server.lockdPort}
47 udp-port=${toString cfg.server.lockdPort}
48 ''}
49 '';
50
51 nfsConfFile =
52 if cfg.settings != {}
53 then format.generate "nfs.conf" (recursiveUpdate nfsConfSettings cfg.settings)
54 else pkgs.writeText "nfs.conf" nfsConfDeprecated;
55
56 requestKeyConfFile = pkgs.writeText "request-key.conf" ''
57 create id_resolver * * ${pkgs.nfs-utils}/bin/nfsidmap -t 600 %k %d
58 '';
59
60 cfg = config.services.nfs;
61
62in
63
64{
65 ###### interface
66
67 options = {
68 services.nfs = {
69 idmapd.settings = mkOption {
70 type = format.type;
71 default = {};
72 description = ''
73 libnfsidmap configuration. Refer to
74 <https://linux.die.net/man/5/idmapd.conf>
75 for details.
76 '';
77 example = literalExpression ''
78 {
79 Translation = {
80 GSS-Methods = "static,nsswitch";
81 };
82 Static = {
83 "root/hostname.domain.com@REALM.COM" = "root";
84 };
85 }
86 '';
87 };
88 settings = mkOption {
89 type = format.type;
90 default = {};
91 description = ''
92 General configuration for NFS daemons and tools.
93 See nfs.conf(5) and related man pages for details.
94 '';
95 example = literalExpression ''
96 {
97 mountd.manage-gids = true;
98 }
99 '';
100 };
101 extraConfig = mkOption {
102 type = types.lines;
103 default = "";
104 description = ''
105 Extra nfs-utils configuration.
106 '';
107 };
108 };
109 };
110
111 ###### implementation
112
113 config = mkIf (config.boot.supportedFilesystems.nfs or config.boot.supportedFilesystems.nfs4 or false) {
114
115 warnings =
116 (optional (cfg.extraConfig != "") ''
117 `services.nfs.extraConfig` is deprecated. Use `services.nfs.settings` instead.
118 '') ++ (optional (cfg.server.extraNfsdConfig != "") ''
119 `services.nfs.server.extraNfsdConfig` is deprecated. Use `services.nfs.settings` instead.
120 '');
121 assertions = [{
122 assertion = cfg.settings != {} -> cfg.extraConfig == "" && cfg.server.extraNfsdConfig == "";
123 message = "`services.nfs.settings` cannot be used together with `services.nfs.extraConfig` and `services.nfs.server.extraNfsdConfig`.";
124 }];
125
126 services.rpcbind.enable = true;
127
128 services.nfs.idmapd.settings = {
129 General = mkMerge [
130 { Pipefs-Directory = rpcMountpoint; }
131 (mkIf (config.networking.domain != null) { Domain = config.networking.domain; })
132 ];
133 Mapping = {
134 Nobody-User = "nobody";
135 Nobody-Group = "nogroup";
136 };
137 Translation = {
138 Method = "nsswitch";
139 };
140 };
141
142 system.fsPackages = [ pkgs.nfs-utils ];
143
144 boot.initrd.kernelModules = mkIf inInitrd [ "nfs" ];
145
146 systemd.packages = [ pkgs.nfs-utils ];
147
148 environment.systemPackages = [ pkgs.keyutils ];
149
150 environment.etc = {
151 "idmapd.conf".source = idmapdConfFile;
152 "nfs.conf".source = nfsConfFile;
153 "request-key.conf".source = requestKeyConfFile;
154 };
155
156 systemd.services.nfs-blkmap =
157 { restartTriggers = [ nfsConfFile ];
158 };
159
160 systemd.targets.nfs-client =
161 { wantedBy = [ "multi-user.target" "remote-fs.target" ];
162 };
163
164 systemd.services.nfs-idmapd =
165 { restartTriggers = [ idmapdConfFile ];
166 };
167
168 systemd.services.nfs-mountd =
169 { restartTriggers = [ nfsConfFile ];
170 enable = mkDefault false;
171 };
172
173 systemd.services.nfs-server =
174 { restartTriggers = [ nfsConfFile ];
175 enable = mkDefault false;
176 };
177
178 systemd.services.auth-rpcgss-module =
179 {
180 unitConfig.ConditionPathExists = [ "" "/etc/krb5.keytab" ];
181 };
182
183 systemd.services.rpc-gssd =
184 { restartTriggers = [ nfsConfFile ];
185 unitConfig.ConditionPathExists = [ "" "/etc/krb5.keytab" ];
186 };
187
188 systemd.services.rpc-statd =
189 { restartTriggers = [ nfsConfFile ];
190
191 preStart =
192 ''
193 mkdir -p /var/lib/nfs/{sm,sm.bak}
194 '';
195 };
196
197 };
198}