at 18.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 = let 119 defaultFormatOptions = 120 # -F needed to allow bare block device without partitions 121 if (builtins.substring 0 3 config.fsType) == "ext" then "-F" 122 # -q needed for non-interactive operations 123 else if config.fsType == "jfs" then "-q" 124 # (same here) 125 else if config.fsType == "reiserfs" then "-q" 126 else null; 127 in { 128 options = mkIf config.autoResize [ "x-nixos.autoresize" ]; 129 formatOptions = mkIf (defaultFormatOptions != null) (mkDefault defaultFormatOptions); 130 }; 131 132 }; 133 134 # Makes sequence of `specialMount device mountPoint options fsType` commands. 135 # `systemMount` should be defined in the sourcing script. 136 makeSpecialMounts = mounts: 137 pkgs.writeText "mounts.sh" (concatMapStringsSep "\n" (mount: '' 138 specialMount "${mount.device}" "${mount.mountPoint}" "${concatStringsSep "," mount.options}" "${mount.fsType}" 139 '') mounts); 140 141in 142 143{ 144 145 ###### interface 146 147 options = { 148 149 fileSystems = mkOption { 150 default = {}; 151 example = literalExample '' 152 { 153 "/".device = "/dev/hda1"; 154 "/data" = { 155 device = "/dev/hda2"; 156 fsType = "ext3"; 157 options = [ "data=journal" ]; 158 }; 159 "/bigdisk".label = "bigdisk"; 160 } 161 ''; 162 type = types.loaOf (types.submodule [coreFileSystemOpts fileSystemOpts]); 163 description = '' 164 The file systems to be mounted. It must include an entry for 165 the root directory (<literal>mountPoint = "/"</literal>). Each 166 entry in the list is an attribute set with the following fields: 167 <literal>mountPoint</literal>, <literal>device</literal>, 168 <literal>fsType</literal> (a file system type recognised by 169 <command>mount</command>; defaults to 170 <literal>"auto"</literal>), and <literal>options</literal> 171 (the mount options passed to <command>mount</command> using the 172 <option>-o</option> flag; defaults to <literal>[ "defaults" ]</literal>). 173 174 Instead of specifying <literal>device</literal>, you can also 175 specify a volume label (<literal>label</literal>) for file 176 systems that support it, such as ext2/ext3 (see <command>mke2fs 177 -L</command>). 178 ''; 179 }; 180 181 system.fsPackages = mkOption { 182 internal = true; 183 default = [ ]; 184 description = "Packages supplying file system mounters and checkers."; 185 }; 186 187 boot.supportedFilesystems = mkOption { 188 default = [ ]; 189 example = [ "btrfs" ]; 190 type = types.listOf types.str; 191 description = "Names of supported filesystem types."; 192 }; 193 194 boot.specialFileSystems = mkOption { 195 default = {}; 196 type = types.loaOf (types.submodule coreFileSystemOpts); 197 internal = true; 198 description = '' 199 Special filesystems that are mounted very early during boot. 200 ''; 201 }; 202 203 }; 204 205 206 ###### implementation 207 208 config = { 209 210 assertions = let 211 ls = sep: concatMapStringsSep sep (x: x.mountPoint); 212 in [ 213 { assertion = ! (fileSystems' ? "cycle"); 214 message = "The fileSystems option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}"; 215 } 216 ]; 217 218 # Export for use in other modules 219 system.build.fileSystems = fileSystems; 220 system.build.earlyMountScript = makeSpecialMounts (toposort fsBefore (attrValues config.boot.specialFileSystems)).result; 221 222 boot.supportedFilesystems = map (fs: fs.fsType) fileSystems; 223 224 # Add the mount helpers to the system path so that `mount' can find them. 225 system.fsPackages = [ pkgs.dosfstools ]; 226 227 environment.systemPackages = with pkgs; [ fuse3 fuse ] ++ config.system.fsPackages; 228 229 environment.etc.fstab.text = 230 let 231 fsToSkipCheck = [ "none" "bindfs" "btrfs" "zfs" "tmpfs" "nfs" "vboxsf" "glusterfs" ]; 232 skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck; 233 in '' 234 # This is a generated file. Do not edit! 235 # 236 # To make changes, edit the fileSystems and swapDevices NixOS options 237 # in your /etc/nixos/configuration.nix file. 238 239 # Filesystems. 240 ${concatMapStrings (fs: 241 (if fs.device != null then fs.device 242 else if fs.label != null then "/dev/disk/by-label/${fs.label}" 243 else throw "No device specified for mount point ${fs.mountPoint}.") 244 + " " + fs.mountPoint 245 + " " + fs.fsType 246 + " " + builtins.concatStringsSep "," fs.options 247 + " 0" 248 + " " + (if skipCheck fs then "0" else 249 if fs.mountPoint == "/" then "1" else "2") 250 + "\n" 251 ) fileSystems} 252 253 # Swap devices. 254 ${flip concatMapStrings config.swapDevices (sw: 255 "${sw.realDevice} none swap${prioOption sw.priority}\n" 256 )} 257 ''; 258 259 # Provide a target that pulls in all filesystems. 260 systemd.targets.fs = 261 { description = "All File Systems"; 262 wants = [ "local-fs.target" "remote-fs.target" ]; 263 }; 264 265 # Emit systemd services to format requested filesystems. 266 systemd.services = 267 let 268 269 formatDevice = fs: 270 let 271 mountPoint' = "${escapeSystemdPath fs.mountPoint}.mount"; 272 device' = escapeSystemdPath fs.device; 273 device'' = "${device'}.device"; 274 in nameValuePair "mkfs-${device'}" 275 { description = "Initialisation of Filesystem ${fs.device}"; 276 wantedBy = [ mountPoint' ]; 277 before = [ mountPoint' "systemd-fsck@${device'}.service" ]; 278 requires = [ device'' ]; 279 after = [ device'' ]; 280 path = [ pkgs.utillinux ] ++ config.system.fsPackages; 281 script = 282 '' 283 if ! [ -e "${fs.device}" ]; then exit 1; fi 284 # FIXME: this is scary. The test could be more robust. 285 type=$(blkid -p -s TYPE -o value "${fs.device}" || true) 286 if [ -z "$type" ]; then 287 echo "creating ${fs.fsType} filesystem on ${fs.device}..." 288 mkfs.${fs.fsType} ${fs.formatOptions} "${fs.device}" 289 fi 290 ''; 291 unitConfig.RequiresMountsFor = [ "${dirOf fs.device}" ]; 292 unitConfig.DefaultDependencies = false; # needed to prevent a cycle 293 serviceConfig.Type = "oneshot"; 294 }; 295 296 in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems)); 297 298 # Sync mount options with systemd's src/core/mount-setup.c: mount_table. 299 boot.specialFileSystems = { 300 "/proc" = { fsType = "proc"; options = [ "nosuid" "noexec" "nodev" ]; }; 301 "/run" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=755" "size=${config.boot.runSize}" ]; }; 302 "/dev" = { fsType = "devtmpfs"; options = [ "nosuid" "strictatime" "mode=755" "size=${config.boot.devSize}" ]; }; 303 "/dev/shm" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=1777" "size=${config.boot.devShmSize}" ]; }; 304 "/dev/pts" = { fsType = "devpts"; options = [ "nosuid" "noexec" "mode=620" "ptmxmode=0666" "gid=${toString config.ids.gids.tty}" ]; }; 305 306 # To hold secrets that shouldn't be written to disk (generally used for NixOps, harmless elsewhere) 307 "/run/keys" = { fsType = "ramfs"; options = [ "nosuid" "nodev" "mode=750" "gid=${toString config.ids.gids.keys}" ]; }; 308 } // optionalAttrs (!config.boot.isContainer) { 309 # systemd-nspawn populates /sys by itself, and remounting it causes all 310 # kinds of weird issues (most noticeably, waiting for host disk device 311 # nodes). 312 "/sys" = { fsType = "sysfs"; options = [ "nosuid" "noexec" "nodev" ]; }; 313 }; 314 315 }; 316 317}