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}