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