1{ config, lib, pkgs, utils, ... }:
2
3with lib;
4with utils;
5
6let
7
8 fileSystems = attrValues config.fileSystems;
9
10 prioOption = prio: optionalString (prio != null) " pri=${toString prio}";
11
12 fileSystemOpts = { name, config, ... }: {
13
14 options = {
15
16 mountPoint = mkOption {
17 example = "/mnt/usb";
18 type = types.str;
19 description = "Location of the mounted the file system.";
20 };
21
22 device = mkOption {
23 default = null;
24 example = "/dev/sda";
25 type = types.nullOr types.str;
26 description = "Location of the device.";
27 };
28
29 label = mkOption {
30 default = null;
31 example = "root-partition";
32 type = types.nullOr types.str;
33 description = "Label of the device (if any).";
34 };
35
36 fsType = mkOption {
37 default = "auto";
38 example = "ext3";
39 type = types.str;
40 description = "Type of the file system.";
41 };
42
43 options = mkOption {
44 default = [ "defaults" ];
45 example = [ "data=journal" ];
46 description = "Options used to mount the file system.";
47 } // (if versionAtLeast lib.nixpkgsVersion "16.09" then {
48 type = types.listOf types.str;
49 } else {
50 type = types.either types.commas (types.listOf types.str);
51 apply = x: if isList x then x else lib.strings.splitString "," (builtins.trace "warning: passing a comma-separated string for filesystem options is deprecated; use a list of strings instead. This will become a hard error in 16.09." x);
52 });
53
54 autoFormat = mkOption {
55 default = false;
56 type = types.bool;
57 description = ''
58 If the device does not currently contain a filesystem (as
59 determined by <command>blkid</command>, then automatically
60 format it with the filesystem type specified in
61 <option>fsType</option>. Use with caution.
62 '';
63 };
64
65 formatOptions = mkOption {
66 default = "";
67 type = types.str;
68 description = ''
69 If <option>autoFormat</option> option is set specifies
70 extra options passed to mkfs.
71 '';
72 };
73
74 autoResize = mkOption {
75 default = false;
76 type = types.bool;
77 description = ''
78 If set, the filesystem is grown to its maximum size before
79 being mounted. (This is typically the size of the containing
80 partition.) This is currently only supported for ext2/3/4
81 filesystems that are mounted during early boot.
82 '';
83 };
84
85 noCheck = mkOption {
86 default = false;
87 type = types.bool;
88 description = "Disable running fsck on this filesystem.";
89 };
90
91 };
92
93 config = {
94 mountPoint = mkDefault name;
95 device = mkIf (config.fsType == "tmpfs") (mkDefault config.fsType);
96 options = mkIf config.autoResize "x-nixos.autoresize";
97
98 # -F needed to allow bare block device without partitions
99 formatOptions = mkIf ((builtins.substring 0 3 config.fsType) == "ext") (mkDefault "-F");
100 };
101
102 };
103
104in
105
106{
107
108 ###### interface
109
110 options = {
111
112 fileSystems = mkOption {
113 default = {};
114 example = {
115 "/".device = "/dev/hda1";
116 "/data" = {
117 device = "/dev/hda2";
118 fsType = "ext3";
119 options = [ "data=journal" ];
120 };
121 "/bigdisk".label = "bigdisk";
122 };
123 type = types.loaOf types.optionSet;
124 options = [ fileSystemOpts ];
125 description = ''
126 The file systems to be mounted. It must include an entry for
127 the root directory (<literal>mountPoint = "/"</literal>). Each
128 entry in the list is an attribute set with the following fields:
129 <literal>mountPoint</literal>, <literal>device</literal>,
130 <literal>fsType</literal> (a file system type recognised by
131 <command>mount</command>; defaults to
132 <literal>"auto"</literal>), and <literal>options</literal>
133 (the mount options passed to <command>mount</command> using the
134 <option>-o</option> flag; defaults to <literal>[ "defaults" ]</literal>).
135
136 Instead of specifying <literal>device</literal>, you can also
137 specify a volume label (<literal>label</literal>) for file
138 systems that support it, such as ext2/ext3 (see <command>mke2fs
139 -L</command>).
140 '';
141 };
142
143 system.fsPackages = mkOption {
144 internal = true;
145 default = [ ];
146 description = "Packages supplying file system mounters and checkers.";
147 };
148
149 boot.supportedFilesystems = mkOption {
150 default = [ ];
151 example = [ "btrfs" ];
152 type = types.listOf types.str;
153 description = "Names of supported filesystem types.";
154 };
155
156 };
157
158
159 ###### implementation
160
161 config = {
162
163 boot.supportedFilesystems = map (fs: fs.fsType) fileSystems;
164
165 # Add the mount helpers to the system path so that `mount' can find them.
166 system.fsPackages = [ pkgs.dosfstools ];
167
168 environment.systemPackages = [ pkgs.fuse ] ++ config.system.fsPackages;
169
170 environment.etc.fstab.text =
171 let
172 fsToSkipCheck = [ "none" "btrfs" "zfs" "tmpfs" "nfs" "vboxsf" ];
173 skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck;
174 in ''
175 # This is a generated file. Do not edit!
176
177 # Filesystems.
178 ${flip concatMapStrings fileSystems (fs:
179 (if fs.device != null then fs.device
180 else if fs.label != null then "/dev/disk/by-label/${fs.label}"
181 else throw "No device specified for mount point ‘${fs.mountPoint}’.")
182 + " " + fs.mountPoint
183 + " " + fs.fsType
184 + " " + builtins.concatStringsSep "," fs.options
185 + " 0"
186 + " " + (if skipCheck fs then "0" else
187 if fs.mountPoint == "/" then "1" else "2")
188 + "\n"
189 )}
190
191 # Swap devices.
192 ${flip concatMapStrings config.swapDevices (sw:
193 "${sw.realDevice} none swap${prioOption sw.priority}\n"
194 )}
195 '';
196
197 # Provide a target that pulls in all filesystems.
198 systemd.targets.fs =
199 { description = "All File Systems";
200 wants = [ "local-fs.target" "remote-fs.target" ];
201 };
202
203 # Emit systemd services to format requested filesystems.
204 systemd.services =
205 let
206
207 formatDevice = fs:
208 let
209 mountPoint' = escapeSystemdPath fs.mountPoint;
210 device' = escapeSystemdPath fs.device;
211 in nameValuePair "mkfs-${device'}"
212 { description = "Initialisation of Filesystem ${fs.device}";
213 wantedBy = [ "${mountPoint'}.mount" ];
214 before = [ "${mountPoint'}.mount" "systemd-fsck@${device'}.service" ];
215 requires = [ "${device'}.device" ];
216 after = [ "${device'}.device" ];
217 path = [ pkgs.utillinux ] ++ config.system.fsPackages;
218 script =
219 ''
220 if ! [ -e "${fs.device}" ]; then exit 1; fi
221 # FIXME: this is scary. The test could be more robust.
222 type=$(blkid -p -s TYPE -o value "${fs.device}" || true)
223 if [ -z "$type" ]; then
224 echo "creating ${fs.fsType} filesystem on ${fs.device}..."
225 mkfs.${fs.fsType} ${fs.formatOptions} "${fs.device}"
226 fi
227 '';
228 unitConfig.RequiresMountsFor = [ "${dirOf fs.device}" ];
229 unitConfig.DefaultDependencies = false; # needed to prevent a cycle
230 serviceConfig.Type = "oneshot";
231 };
232
233 in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems));
234
235 };
236
237}