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