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