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