grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / tasks / filesystems.nix
blob5c95cd3d451ef4caca28bcfed4c87313ff32f7ea
1 { config, lib, pkgs, utils, ... }@moduleArgs:
3 with lib;
4 with utils;
6 let
8   addCheckDesc = desc: elemType: check: types.addCheck elemType check
9     // { description = "${elemType.description} (with check: ${desc})"; };
11   isNonEmpty = s: (builtins.match "[ \t\n]*" s) == null;
12   nonEmptyStr = addCheckDesc "non-empty" types.str isNonEmpty;
14   fileSystems' = toposort fsBefore (attrValues config.fileSystems);
16   fileSystems = if fileSystems' ? result
17                 then # use topologically sorted fileSystems everywhere
18                      fileSystems'.result
19                 else # the assertion below will catch this,
20                      # but we fall back to the original order
21                      # anyway so that other modules could check
22                      # their assertions too
23                      (attrValues config.fileSystems);
25   specialFSTypes = [ "proc" "sysfs" "tmpfs" "ramfs" "devtmpfs" "devpts" ];
27   nonEmptyWithoutTrailingSlash = addCheckDesc "non-empty without trailing slash" types.str
28     (s: isNonEmpty s && (builtins.match ".+/" s) == null);
30   coreFileSystemOpts = { name, config, ... }: {
32     options = {
33       mountPoint = mkOption {
34         example = "/mnt/usb";
35         type = nonEmptyWithoutTrailingSlash;
36         description = "Location of the mounted file system.";
37       };
39       stratis.poolUuid = lib.mkOption {
40         type = types.uniq (types.nullOr types.str);
41         description = ''
42           UUID of the stratis pool that the fs is located in
43         '';
44         example = "04c68063-90a5-4235-b9dd-6180098a20d9";
45         default = null;
46       };
48       device = mkOption {
49         default = null;
50         example = "/dev/sda";
51         type = types.nullOr nonEmptyStr;
52         description = "Location of the device.";
53       };
55       fsType = mkOption {
56         default = "auto";
57         example = "ext3";
58         type = nonEmptyStr;
59         description = "Type of the file system.";
60       };
62       options = mkOption {
63         default = [ "defaults" ];
64         example = [ "data=journal" ];
65         description = "Options used to mount the file system.";
66         type = types.nonEmptyListOf nonEmptyStr;
67       };
69       depends = mkOption {
70         default = [ ];
71         example = [ "/persist" ];
72         type = types.listOf nonEmptyWithoutTrailingSlash;
73         description = ''
74           List of paths that should be mounted before this one. This filesystem's
75           {option}`device` and {option}`mountPoint` are always
76           checked and do not need to be included explicitly. If a path is added
77           to this list, any other filesystem whose mount point is a parent of
78           the path will be mounted before this filesystem. The paths do not need
79           to actually be the {option}`mountPoint` of some other filesystem.
80         '';
81       };
83     };
85     config = {
86       mountPoint = mkDefault name;
87       device = mkIf (elem config.fsType specialFSTypes) (mkDefault config.fsType);
88     };
90   };
92   fileSystemOpts = { config, ... }: {
94     options = {
96       label = mkOption {
97         default = null;
98         example = "root-partition";
99         type = types.nullOr nonEmptyStr;
100         description = "Label of the device (if any).";
101       };
103       autoFormat = mkOption {
104         default = false;
105         type = types.bool;
106         description = ''
107           If the device does not currently contain a filesystem (as
108           determined by {command}`blkid`), then automatically
109           format it with the filesystem type specified in
110           {option}`fsType`.  Use with caution.
111         '';
112       };
114       formatOptions = mkOption {
115         visible = false;
116         type = types.unspecified;
117         default = null;
118       };
120       autoResize = mkOption {
121         default = false;
122         type = types.bool;
123         description = ''
124           If set, the filesystem is grown to its maximum size before
125           being mounted. (This is typically the size of the containing
126           partition.) This is currently only supported for ext2/3/4
127           filesystems that are mounted during early boot.
128         '';
129       };
131       noCheck = mkOption {
132         default = false;
133         type = types.bool;
134         description = "Disable running fsck on this filesystem.";
135       };
137     };
139     config.options = let
140       inInitrd = utils.fsNeededForBoot config;
141     in mkMerge [
142       (mkIf config.autoResize [ "x-systemd.growfs" ])
143       (mkIf config.autoFormat [ "x-systemd.makefs" ])
144       (mkIf (utils.fsNeededForBoot config) [ "x-initrd.mount" ])
145       (mkIf
146         # With scripted stage 1, depends is implemented by sorting 'config.system.build.fileSystems'
147         (lib.length config.depends > 0 && (inInitrd -> moduleArgs.config.boot.initrd.systemd.enable))
148         (
149           map (
150             x: "x-systemd.requires-mounts-for=${optionalString inInitrd "/sysroot"}${x}"
151           ) config.depends
152         )
153       )
154     ];
156   };
158   # Makes sequence of `specialMount device mountPoint options fsType` commands.
159   # `systemMount` should be defined in the sourcing script.
160   makeSpecialMounts = mounts:
161     pkgs.writeText "mounts.sh" (concatMapStringsSep "\n" (mount: ''
162       specialMount "${mount.device}" "${mount.mountPoint}" "${concatStringsSep "," mount.options}" "${mount.fsType}"
163     '') mounts);
165   makeFstabEntries =
166     let
167       fsToSkipCheck = [
168         "none"
169         "auto"
170         "overlay"
171         "iso9660"
172         "bindfs"
173         "udf"
174         "btrfs"
175         "zfs"
176         "tmpfs"
177         "bcachefs"
178         "nfs"
179         "nfs4"
180         "nilfs2"
181         "vboxsf"
182         "squashfs"
183         "glusterfs"
184         "apfs"
185         "9p"
186         "cifs"
187         "prl_fs"
188         "vmhgfs"
189       ] ++ lib.optionals (!config.boot.initrd.checkJournalingFS) [
190         "ext3"
191         "ext4"
192         "reiserfs"
193         "xfs"
194         "jfs"
195         "f2fs"
196       ];
197       isBindMount = fs: builtins.elem "bind" fs.options;
198       skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck || isBindMount fs;
199       # https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
200       escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
201     in fstabFileSystems: { }: concatMapStrings (fs:
202       (if fs.device != null then escape fs.device
203          else if fs.label != null then "/dev/disk/by-label/${escape fs.label}"
204          else throw "No device specified for mount point ‘${fs.mountPoint}’.")
205       + " " + escape fs.mountPoint
206       + " " + fs.fsType
207       + " " + escape (builtins.concatStringsSep "," fs.options)
208       + " 0 " + (if skipCheck fs then "0" else if fs.mountPoint == "/" then "1" else "2")
209       + "\n"
210     ) fstabFileSystems;
212     initrdFstab = pkgs.writeText "initrd-fstab" (makeFstabEntries (filter utils.fsNeededForBoot fileSystems) { });
218   ###### interface
220   options = {
222     fileSystems = mkOption {
223       default = {};
224       example = literalExpression ''
225         {
226           "/".device = "/dev/hda1";
227           "/data" = {
228             device = "/dev/hda2";
229             fsType = "ext3";
230             options = [ "data=journal" ];
231           };
232           "/bigdisk".label = "bigdisk";
233         }
234       '';
235       type = types.attrsOf (types.submodule [coreFileSystemOpts fileSystemOpts]);
236       description = ''
237         The file systems to be mounted.  It must include an entry for
238         the root directory (`mountPoint = "/"`).  Each
239         entry in the list is an attribute set with the following fields:
240         `mountPoint`, `device`,
241         `fsType` (a file system type recognised by
242         {command}`mount`; defaults to
243         `"auto"`), and `options`
244         (the mount options passed to {command}`mount` using the
245         {option}`-o` flag; defaults to `[ "defaults" ]`).
247         Instead of specifying `device`, you can also
248         specify a volume label (`label`) for file
249         systems that support it, such as ext2/ext3 (see {command}`mke2fs -L`).
250       '';
251     };
253     system.fsPackages = mkOption {
254       internal = true;
255       default = [ ];
256       description = "Packages supplying file system mounters and checkers.";
257     };
259     boot.supportedFilesystems = mkOption {
260       default = { };
261       example = lib.literalExpression ''
262         {
263           btrfs = true;
264           zfs = lib.mkForce false;
265         }
266       '';
267       type = types.coercedTo
268         (types.listOf types.str)
269         (enabled: lib.listToAttrs (map (fs: lib.nameValuePair fs true) enabled))
270         (types.attrsOf types.bool);
271       description = ''
272         Names of supported filesystem types, or an attribute set of file system types
273         and their state. The set form may be used together with `lib.mkForce` to
274         explicitly disable support for specific filesystems, e.g. to disable ZFS
275         with an unsupported kernel.
276       '';
277     };
279     boot.specialFileSystems = mkOption {
280       default = {};
281       type = types.attrsOf (types.submodule coreFileSystemOpts);
282       internal = true;
283       description = ''
284         Special filesystems that are mounted very early during boot.
285       '';
286     };
288     boot.devSize = mkOption {
289       default = "5%";
290       example = "32m";
291       type = types.str;
292       description = ''
293         Size limit for the /dev tmpfs. Look at mount(8), tmpfs size option,
294         for the accepted syntax.
295       '';
296     };
298     boot.devShmSize = mkOption {
299       default = "50%";
300       example = "256m";
301       type = types.str;
302       description = ''
303         Size limit for the /dev/shm tmpfs. Look at mount(8), tmpfs size option,
304         for the accepted syntax.
305       '';
306     };
308     boot.runSize = mkOption {
309       default = "25%";
310       example = "256m";
311       type = types.str;
312       description = ''
313         Size limit for the /run tmpfs. Look at mount(8), tmpfs size option,
314         for the accepted syntax.
315       '';
316     };
317   };
320   ###### implementation
322   config = {
324     assertions = let
325       ls = sep: concatMapStringsSep sep (x: x.mountPoint);
326       resizableFSes = [
327         "ext3"
328         "ext4"
329         "btrfs"
330         "xfs"
331       ];
332       notAutoResizable = fs: fs.autoResize && !(builtins.elem fs.fsType resizableFSes);
333     in [
334       { assertion = ! (fileSystems' ? cycle);
335         message = "The ‘fileSystems’ option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}";
336       }
337       { assertion = ! (any notAutoResizable fileSystems);
338         message = let
339           fs = head (filter notAutoResizable fileSystems);
340         in ''
341           Mountpoint '${fs.mountPoint}': 'autoResize = true' is not supported for 'fsType = "${fs.fsType}"'
342           ${optionalString (fs.fsType == "auto") "fsType has to be explicitly set and"}
343           only the following support it: ${lib.concatStringsSep ", " resizableFSes}.
344         '';
345       }
346       {
347         assertion = ! (any (fs: fs.formatOptions != null) fileSystems);
348         message = let
349           fs = head (filter (fs: fs.formatOptions != null) fileSystems);
350         in ''
351           'fileSystems.<name>.formatOptions' has been removed, since
352           systemd-makefs does not support any way to provide formatting
353           options.
354         '';
355       }
356     ];
358     # Export for use in other modules
359     system.build.fileSystems = fileSystems;
360     system.build.earlyMountScript = makeSpecialMounts (toposort fsBefore (attrValues config.boot.specialFileSystems)).result;
362     boot.supportedFilesystems = map (fs: fs.fsType) fileSystems;
364     # Add the mount helpers to the system path so that `mount' can find them.
365     system.fsPackages = [ pkgs.dosfstools ];
367     environment.systemPackages = with pkgs; [ fuse3 fuse ] ++ config.system.fsPackages;
369     environment.etc.fstab.text =
370       let
371         swapOptions = sw: concatStringsSep "," (
372           sw.options
373           ++ optional (sw.priority != null) "pri=${toString sw.priority}"
374           ++ optional (sw.discardPolicy != null) "discard${optionalString (sw.discardPolicy != "both") "=${toString sw.discardPolicy}"}"
375         );
376       in ''
377         # This is a generated file.  Do not edit!
378         #
379         # To make changes, edit the fileSystems and swapDevices NixOS options
380         # in your /etc/nixos/configuration.nix file.
381         #
382         # <file system> <mount point>   <type>  <options>       <dump>  <pass>
384         # Filesystems.
385         ${makeFstabEntries fileSystems {}}
387         # Swap devices.
388         ${flip concatMapStrings config.swapDevices (sw:
389             "${sw.realDevice} none swap ${swapOptions sw}\n"
390         )}
391       '';
393     boot.initrd.systemd.storePaths = [initrdFstab];
394     boot.initrd.systemd.managerEnvironment.SYSTEMD_SYSROOT_FSTAB = initrdFstab;
395     boot.initrd.systemd.services.initrd-parse-etc.environment.SYSTEMD_SYSROOT_FSTAB = initrdFstab;
397     # Provide a target that pulls in all filesystems.
398     systemd.targets.fs =
399       { description = "All File Systems";
400         wants = [ "local-fs.target" "remote-fs.target" ];
401       };
403     systemd.services = {
404     # Mount /sys/fs/pstore for evacuating panic logs and crashdumps from persistent storage onto the disk using systemd-pstore.
405     # This cannot be done with the other special filesystems because the pstore module (which creates the mount point) is not loaded then.
406         "mount-pstore" = {
407           serviceConfig = {
408             Type = "oneshot";
409             # skip on kernels without the pstore module
410             ExecCondition = "${pkgs.kmod}/bin/modprobe -b pstore";
411             ExecStart = pkgs.writeShellScript "mount-pstore.sh" ''
412               set -eu
413               # if the pstore module is builtin it will have mounted the persistent store automatically. it may also be already mounted for other reasons.
414               ${pkgs.util-linux}/bin/mountpoint -q /sys/fs/pstore || ${pkgs.util-linux}/bin/mount -t pstore -o nosuid,noexec,nodev pstore /sys/fs/pstore
415               # wait up to 1.5 seconds for the backend to be registered and the files to appear. a systemd path unit cannot detect this happening; and succeeding after a restart would not start dependent units.
416               TRIES=15
417               while [ "$(cat /sys/module/pstore/parameters/backend)" = "(null)" ]; do
418                 if (( $TRIES )); then
419                   sleep 0.1
420                   TRIES=$((TRIES-1))
421                 else
422                   echo "Persistent Storage backend was not registered in time." >&2
423                   break
424                 fi
425               done
426             '';
427             RemainAfterExit = true;
428           };
429           unitConfig = {
430             ConditionVirtualization = "!container";
431             DefaultDependencies = false; # needed to prevent a cycle
432           };
433           before = [ "systemd-pstore.service" "shutdown.target" ];
434           conflicts = [ "shutdown.target" ];
435           wantedBy = [ "systemd-pstore.service" ];
436         };
437       };
439     systemd.tmpfiles.rules = [
440       "d /run/keys 0750 root ${toString config.ids.gids.keys}"
441       "z /run/keys 0750 root ${toString config.ids.gids.keys}"
442     ];
444     # Sync mount options with systemd's src/core/mount-setup.c: mount_table.
445     boot.specialFileSystems = {
446       "/proc" = { fsType = "proc"; options = [ "nosuid" "noexec" "nodev" ]; };
447       "/run" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=755" "size=${config.boot.runSize}" ]; };
448       "/dev" = { fsType = "devtmpfs"; options = [ "nosuid" "strictatime" "mode=755" "size=${config.boot.devSize}" ]; };
449       "/dev/shm" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=1777" "size=${config.boot.devShmSize}" ]; };
450       "/dev/pts" = { fsType = "devpts"; options = [ "nosuid" "noexec" "mode=620" "ptmxmode=0666" "gid=${toString config.ids.gids.tty}" ]; };
452       # To hold secrets that shouldn't be written to disk
453       "/run/keys" = { fsType = "ramfs"; options = [ "nosuid" "nodev" "mode=750" ]; };
454     } // optionalAttrs (!config.boot.isContainer) {
455       # systemd-nspawn populates /sys by itself, and remounting it causes all
456       # kinds of weird issues (most noticeably, waiting for host disk device
457       # nodes).
458       "/sys" = { fsType = "sysfs"; options = [ "nosuid" "noexec" "nodev" ]; };
459     };
461   };