1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.locate;
10 isMLocate = lib.hasPrefix "mlocate" cfg.package.name;
11 isPLocate = lib.hasPrefix "plocate" cfg.package.name;
12in
13{
14 imports = [
15 (lib.mkRenamedOptionModule [ "services" "locate" "period" ] [ "services" "locate" "interval" ])
16 (lib.mkRenamedOptionModule [ "services" "locate" "locate" ] [ "services" "locate" "package" ])
17 (lib.mkRemovedOptionModule [ "services" "locate" "includeStore" ] "Use services.locate.prunePaths")
18 (lib.mkRemovedOptionModule [ "services" "locate" "localuser" ]
19 "The services.locate.localuser option has been removed because support for findutils locate has been removed."
20 )
21 ];
22
23 options.services.locate = {
24 enable = lib.mkOption {
25 type = lib.types.bool;
26 default = false;
27 description = ''
28 If enabled, NixOS will periodically update the database of
29 files used by the {command}`locate` command.
30 '';
31 };
32
33 package = lib.mkPackageOption pkgs [ "plocate" ] {
34 example = "mlocate";
35 };
36
37 interval = lib.mkOption {
38 type = lib.types.str;
39 default = "02:15";
40 example = "hourly";
41 description = ''
42 Update the locate database at this interval. Updates by
43 default at 2:15 AM every day.
44
45 The format is described in
46 {manpage}`systemd.time(7)`.
47
48 To disable automatic updates, set to `"never"`
49 and run {command}`updatedb` manually.
50 '';
51 };
52
53 extraFlags = lib.mkOption {
54 type = lib.types.listOf lib.types.str;
55 default = [ ];
56 description = ''
57 Extra flags to pass to {command}`updatedb`.
58 '';
59 };
60
61 output = lib.mkOption {
62 type = lib.types.path;
63 default = "/var/cache/locatedb";
64 description = ''
65 The database file to build.
66 '';
67 };
68
69 pruneFS = lib.mkOption {
70 type = lib.types.listOf lib.types.str;
71 default = [
72 "afs"
73 "anon_inodefs"
74 "auto"
75 "autofs"
76 "bdev"
77 "binfmt"
78 "binfmt_misc"
79 "ceph"
80 "cgroup"
81 "cgroup2"
82 "cifs"
83 "coda"
84 "configfs"
85 "cramfs"
86 "cpuset"
87 "curlftpfs"
88 "debugfs"
89 "devfs"
90 "devpts"
91 "devtmpfs"
92 "ecryptfs"
93 "eventpollfs"
94 "exofs"
95 "futexfs"
96 "ftpfs"
97 "fuse"
98 "fusectl"
99 "fusesmb"
100 "fuse.ceph"
101 "fuse.glusterfs"
102 "fuse.gvfsd-fuse"
103 "fuse.mfs"
104 "fuse.rclone"
105 "fuse.rozofs"
106 "fuse.sshfs"
107 "gfs"
108 "gfs2"
109 "hostfs"
110 "hugetlbfs"
111 "inotifyfs"
112 "iso9660"
113 "jffs2"
114 "lustre"
115 "lustre_lite"
116 "misc"
117 "mfs"
118 "mqueue"
119 "ncpfs"
120 "nfs"
121 "NFS"
122 "nfs4"
123 "nfsd"
124 "nnpfs"
125 "ocfs"
126 "ocfs2"
127 "pipefs"
128 "proc"
129 "ramfs"
130 "rpc_pipefs"
131 "securityfs"
132 "selinuxfs"
133 "sfs"
134 "shfs"
135 "smbfs"
136 "sockfs"
137 "spufs"
138 "sshfs"
139 "subfs"
140 "supermount"
141 "sysfs"
142 "tmpfs"
143 "tracefs"
144 "ubifs"
145 "udev"
146 "udf"
147 "usbfs"
148 "vboxsf"
149 "vperfctrfs"
150 ];
151 description = ''
152 Which filesystem types to exclude from indexing
153 '';
154 };
155
156 prunePaths = lib.mkOption {
157 type = lib.types.listOf lib.types.path;
158 default = [
159 "/tmp"
160 "/var/tmp"
161 "/var/cache"
162 "/var/lock"
163 "/var/run"
164 "/var/spool"
165 "/nix/store"
166 "/nix/var/log/nix"
167 ];
168 description = ''
169 Which paths to exclude from indexing
170 '';
171 };
172
173 pruneNames = lib.mkOption {
174 type = lib.types.listOf lib.types.str;
175 default = [
176 ".bzr"
177 ".cache"
178 ".git"
179 ".hg"
180 ".svn"
181 ];
182 defaultText = lib.literalMD ''
183 `[ ".bzr" ".cache" ".git" ".hg" ".svn" ]`, if
184 supported by the locate implementation (i.e. mlocate or plocate).
185 '';
186 description = ''
187 Directory components which should exclude paths containing them from indexing
188 '';
189 };
190
191 pruneBindMounts = lib.mkOption {
192 type = lib.types.bool;
193 default = false;
194 description = ''
195 Whether not to index bind mounts
196 '';
197 };
198
199 };
200
201 config = lib.mkIf cfg.enable {
202 users.groups = lib.mkMerge [
203 (lib.mkIf isMLocate { mlocate = { }; })
204 (lib.mkIf isPLocate { plocate = { }; })
205 ];
206
207 security.wrappers =
208 let
209 common = {
210 owner = "root";
211 permissions = "u+rx,g+x,o+x";
212 setgid = true;
213 setuid = false;
214 };
215 mlocate = lib.mkIf isMLocate {
216 group = "mlocate";
217 source = "${cfg.package}/bin/locate";
218 };
219 plocate = lib.mkIf isPLocate {
220 group = "plocate";
221 source = "${cfg.package}/bin/plocate";
222 };
223 in
224 {
225 locate = lib.mkMerge [
226 common
227 mlocate
228 plocate
229 ];
230 plocate = lib.mkIf isPLocate (
231 lib.mkMerge [
232 common
233 plocate
234 ]
235 );
236 };
237
238 environment = {
239 # write /etc/updatedb.conf for manual calls to `updatedb`
240 etc."updatedb.conf".text = ''
241 PRUNEFS="${lib.concatStringsSep " " cfg.pruneFS}"
242 PRUNENAMES="${lib.concatStringsSep " " cfg.pruneNames}"
243 PRUNEPATHS="${lib.concatStringsSep " " cfg.prunePaths}"
244 PRUNE_BIND_MOUNTS="${if cfg.pruneBindMounts then "yes" else "no"}"
245 '';
246
247 systemPackages = [ cfg.package ];
248 };
249
250 systemd.services.update-locatedb = {
251 description = "Update Locate Database";
252
253 # mlocate's updatedb takes flags via a configuration file or
254 # on the command line, but not by environment variable.
255 script =
256 let
257 toFlags =
258 x: lib.optional (cfg.${x} != [ ]) "--${lib.toLower x} '${lib.concatStringsSep " " cfg.${x}}'";
259 args = lib.concatLists (
260 map toFlags [
261 "pruneFS"
262 "pruneNames"
263 "prunePaths"
264 ]
265 );
266 in
267 ''
268 exec ${cfg.package}/bin/updatedb \
269 --output ${toString cfg.output} ${lib.concatStringsSep " " args} \
270 --prune-bind-mounts ${if cfg.pruneBindMounts then "yes" else "no"} \
271 ${lib.concatStringsSep " " cfg.extraFlags}
272 '';
273 serviceConfig = {
274 CapabilityBoundingSet = "CAP_DAC_READ_SEARCH CAP_CHOWN";
275 Nice = 19;
276 IOSchedulingClass = "idle";
277 IPAddressDeny = "any";
278 LockPersonality = true;
279 MemoryDenyWriteExecute = true;
280 NoNewPrivileges = true;
281 PrivateTmp = "yes";
282 PrivateDevices = true;
283 PrivateNetwork = "yes";
284 ProtectClock = true;
285 ProtectControlGroups = true;
286 ProtectHostname = true;
287 RestrictAddressFamilies = "AF_UNIX";
288 RestrictNamespaces = true;
289 RestrictRealtime = true;
290 RestrictSUIDSGID = true;
291 ReadOnlyPaths = "/";
292 # Use dirOf cfg.output because mlocate creates temporary files next to
293 # the actual database. We could specify and create them as well,
294 # but that would make this quite brittle when they change something.
295 # NOTE: If /var/cache does not exist, this leads to the misleading error message:
296 # update-locatedb.service: Failed at step NAMESPACE spawning …/update-locatedb-start: No such file or directory
297 ReadWritePaths = dirOf cfg.output;
298 SystemCallArchitectures = "native";
299 SystemCallFilter = "@system-service @chown";
300 };
301 };
302
303 systemd.timers.update-locatedb = lib.mkIf (cfg.interval != "never") {
304 description = "Update timer for locate database";
305 partOf = [ "update-locatedb.service" ];
306 wantedBy = [ "timers.target" ];
307 timerConfig = {
308 OnCalendar = cfg.interval;
309 Persistent = true;
310 };
311 };
312 };
313
314 meta.maintainers = with lib.maintainers; [ SuperSandro2000 ];
315}