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