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