1{ config, lib, pkgs, utils, ... }:
2
3with lib;
4with utils;
5
6let
7
8 fileSystems' = toposort fsBefore (attrValues config.fileSystems);
9
10 fileSystems = if fileSystems' ? "result"
11 then # use topologically sorted fileSystems everywhere
12 fileSystems'.result
13 else # the assertion below will catch this,
14 # but we fall back to the original order
15 # anyway so that other modules could check
16 # their assertions too
17 (attrValues config.fileSystems);
18
19 prioOption = prio: optionalString (prio != null) " pri=${toString prio}";
20
21 specialFSTypes = [ "proc" "sysfs" "tmpfs" "devtmpfs" "devpts" ];
22
23 coreFileSystemOpts = { name, config, ... }: {
24
25 options = {
26
27 mountPoint = mkOption {
28 example = "/mnt/usb";
29 type = types.str;
30 description = "Location of the mounted the file system.";
31 };
32
33 device = mkOption {
34 default = null;
35 example = "/dev/sda";
36 type = types.nullOr types.str;
37 description = "Location of the device.";
38 };
39
40 fsType = mkOption {
41 default = "auto";
42 example = "ext3";
43 type = types.str;
44 description = "Type of the file system.";
45 };
46
47 options = mkOption {
48 default = [ "defaults" ];
49 example = [ "data=journal" ];
50 description = "Options used to mount the file system.";
51 type = types.listOf types.str;
52 };
53
54 };
55
56 config = {
57 mountPoint = mkDefault name;
58 device = mkIf (elem config.fsType specialFSTypes) (mkDefault config.fsType);
59 };
60
61 };
62
63 fileSystemOpts = { config, ... }: {
64
65 options = {
66
67 label = mkOption {
68 default = null;
69 example = "root-partition";
70 type = types.nullOr types.str;
71 description = "Label of the device (if any).";
72 };
73
74 autoFormat = mkOption {
75 default = false;
76 type = types.bool;
77 description = ''
78 If the device does not currently contain a filesystem (as
79 determined by <command>blkid</command>, then automatically
80 format it with the filesystem type specified in
81 <option>fsType</option>. Use with caution.
82 '';
83 };
84
85 formatOptions = mkOption {
86 default = "";
87 type = types.str;
88 description = ''
89 If <option>autoFormat</option> option is set specifies
90 extra options passed to mkfs.
91 '';
92 };
93
94 autoResize = mkOption {
95 default = false;
96 type = types.bool;
97 description = ''
98 If set, the filesystem is grown to its maximum size before
99 being mounted. (This is typically the size of the containing
100 partition.) This is currently only supported for ext2/3/4
101 filesystems that are mounted during early boot.
102 '';
103 };
104
105 noCheck = mkOption {
106 default = false;
107 type = types.bool;
108 description = "Disable running fsck on this filesystem.";
109 };
110
111 };
112
113 config = {
114 options = mkIf config.autoResize [ "x-nixos.autoresize" ];
115
116 # -F needed to allow bare block device without partitions
117 formatOptions = mkIf ((builtins.substring 0 3 config.fsType) == "ext") (mkDefault "-F");
118 };
119
120 };
121
122 # Makes sequence of `specialMount device mountPoint options fsType` commands.
123 # `systemMount` should be defined in the sourcing script.
124 makeSpecialMounts = mounts:
125 pkgs.writeText "mounts.sh" (concatMapStringsSep "\n" (mount: ''
126 specialMount "${mount.device}" "${mount.mountPoint}" "${concatStringsSep "," mount.options}" "${mount.fsType}"
127 '') mounts);
128
129in
130
131{
132
133 ###### interface
134
135 options = {
136
137 fileSystems = mkOption {
138 default = {};
139 example = literalExample ''
140 {
141 "/".device = "/dev/hda1";
142 "/data" = {
143 device = "/dev/hda2";
144 fsType = "ext3";
145 options = [ "data=journal" ];
146 };
147 "/bigdisk".label = "bigdisk";
148 }
149 '';
150 type = types.loaOf (types.submodule [coreFileSystemOpts fileSystemOpts]);
151 description = ''
152 The file systems to be mounted. It must include an entry for
153 the root directory (<literal>mountPoint = "/"</literal>). Each
154 entry in the list is an attribute set with the following fields:
155 <literal>mountPoint</literal>, <literal>device</literal>,
156 <literal>fsType</literal> (a file system type recognised by
157 <command>mount</command>; defaults to
158 <literal>"auto"</literal>), and <literal>options</literal>
159 (the mount options passed to <command>mount</command> using the
160 <option>-o</option> flag; defaults to <literal>[ "defaults" ]</literal>).
161
162 Instead of specifying <literal>device</literal>, you can also
163 specify a volume label (<literal>label</literal>) for file
164 systems that support it, such as ext2/ext3 (see <command>mke2fs
165 -L</command>).
166 '';
167 };
168
169 system.fsPackages = mkOption {
170 internal = true;
171 default = [ ];
172 description = "Packages supplying file system mounters and checkers.";
173 };
174
175 boot.supportedFilesystems = mkOption {
176 default = [ ];
177 example = [ "btrfs" ];
178 type = types.listOf types.str;
179 description = "Names of supported filesystem types.";
180 };
181
182 boot.specialFileSystems = mkOption {
183 default = {};
184 type = types.loaOf (types.submodule coreFileSystemOpts);
185 internal = true;
186 description = ''
187 Special filesystems that are mounted very early during boot.
188 '';
189 };
190
191 };
192
193
194 ###### implementation
195
196 config = {
197
198 assertions = let
199 ls = sep: concatMapStringsSep sep (x: x.mountPoint);
200 in [
201 { assertion = ! (fileSystems' ? "cycle");
202 message = "The ‘fileSystems’ option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}";
203 }
204 ];
205
206 # Export for use in other modules
207 system.build.fileSystems = fileSystems;
208 system.build.earlyMountScript = makeSpecialMounts (toposort fsBefore (attrValues config.boot.specialFileSystems)).result;
209
210 boot.supportedFilesystems = map (fs: fs.fsType) fileSystems;
211
212 # Add the mount helpers to the system path so that `mount' can find them.
213 system.fsPackages = [ pkgs.dosfstools ];
214
215 environment.systemPackages = [ pkgs.fuse ] ++ config.system.fsPackages;
216
217 environment.etc.fstab.text =
218 let
219 fsToSkipCheck = [ "none" "btrfs" "zfs" "tmpfs" "nfs" "vboxsf" ];
220 skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck;
221 in ''
222 # This is a generated file. Do not edit!
223 #
224 # To make changes, edit the fileSystems and swapDevices NixOS options
225 # in your /etc/nixos/configuration.nix file.
226
227 # Filesystems.
228 ${concatMapStrings (fs:
229 (if fs.device != null then fs.device
230 else if fs.label != null then "/dev/disk/by-label/${fs.label}"
231 else throw "No device specified for mount point ‘${fs.mountPoint}’.")
232 + " " + fs.mountPoint
233 + " " + fs.fsType
234 + " " + builtins.concatStringsSep "," fs.options
235 + " 0"
236 + " " + (if skipCheck fs then "0" else
237 if fs.mountPoint == "/" then "1" else "2")
238 + "\n"
239 ) fileSystems}
240
241 # Swap devices.
242 ${flip concatMapStrings config.swapDevices (sw:
243 "${sw.realDevice} none swap${prioOption sw.priority}\n"
244 )}
245 '';
246
247 # Provide a target that pulls in all filesystems.
248 systemd.targets.fs =
249 { description = "All File Systems";
250 wants = [ "local-fs.target" "remote-fs.target" ];
251 };
252
253 # Emit systemd services to format requested filesystems.
254 systemd.services =
255 let
256
257 formatDevice = fs:
258 let
259 mountPoint' = "${escapeSystemdPath fs.mountPoint}.mount";
260 device' = escapeSystemdPath fs.device;
261 device'' = "${device}.device";
262 in nameValuePair "mkfs-${device'}"
263 { description = "Initialisation of Filesystem ${fs.device}";
264 wantedBy = [ mountPoint' ];
265 before = [ mountPoint' "systemd-fsck@${device'}.service" ];
266 requires = [ device'' ];
267 after = [ device'' ];
268 path = [ pkgs.utillinux ] ++ config.system.fsPackages;
269 script =
270 ''
271 if ! [ -e "${fs.device}" ]; then exit 1; fi
272 # FIXME: this is scary. The test could be more robust.
273 type=$(blkid -p -s TYPE -o value "${fs.device}" || true)
274 if [ -z "$type" ]; then
275 echo "creating ${fs.fsType} filesystem on ${fs.device}..."
276 mkfs.${fs.fsType} ${fs.formatOptions} "${fs.device}"
277 fi
278 '';
279 unitConfig.RequiresMountsFor = [ "${dirOf fs.device}" ];
280 unitConfig.DefaultDependencies = false; # needed to prevent a cycle
281 serviceConfig.Type = "oneshot";
282 };
283
284 in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems));
285
286 # Sync mount options with systemd's src/core/mount-setup.c: mount_table.
287 boot.specialFileSystems = {
288 "/proc" = { fsType = "proc"; options = [ "nosuid" "noexec" "nodev" ]; };
289 "/sys" = { fsType = "sysfs"; options = [ "nosuid" "noexec" "nodev" ]; };
290 "/run" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=755" "size=${config.boot.runSize}" ]; };
291 "/dev" = { fsType = "devtmpfs"; options = [ "nosuid" "strictatime" "mode=755" "size=${config.boot.devSize}" ]; };
292 "/dev/shm" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=1777" "size=${config.boot.devShmSize}" ]; };
293 "/dev/pts" = { fsType = "devpts"; options = [ "nosuid" "noexec" "mode=620" "gid=${toString config.ids.gids.tty}" ]; };
294 };
295
296 };
297
298}