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