at master 18 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 utils, 6 ... 7}@moduleArgs: 8let 9 inherit (lib) 10 any 11 attrValues 12 concatMapStrings 13 concatMapStringsSep 14 concatStringsSep 15 elem 16 filter 17 flip 18 head 19 literalExpression 20 mkDefault 21 mkEnableOption 22 mkIf 23 mkMerge 24 mkOption 25 optional 26 optionalAttrs 27 optionalString 28 toposort 29 types 30 ; 31 inherit (utils) fsBefore; 32 33 # https://wiki.archlinux.org/index.php/fstab#Filepath_spaces 34 escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string; 35 36 # A list of attrnames is coerced into an attrset of bools by 37 # setting the values to true. 38 attrNamesToTrue = types.coercedTo (types.listOf types.str) ( 39 enabledList: lib.genAttrs enabledList (_attrName: true) 40 ) (types.attrsOf types.bool); 41 42 addCheckDesc = 43 desc: elemType: check: 44 types.addCheck elemType check // { description = "${elemType.description} (with check: ${desc})"; }; 45 46 isNonEmpty = s: (builtins.match "[ \t\n]*" s) == null; 47 nonEmptyStr = addCheckDesc "non-empty" types.str isNonEmpty; 48 49 fileSystems' = toposort fsBefore (attrValues config.fileSystems); 50 51 fileSystems = 52 if fileSystems' ? result then 53 # use topologically sorted fileSystems everywhere 54 fileSystems'.result 55 else 56 # the assertion below will catch this, 57 # but we fall back to the original order 58 # anyway so that other modules could check 59 # their assertions too 60 (attrValues config.fileSystems); 61 62 specialFSTypes = [ 63 "proc" 64 "sysfs" 65 "tmpfs" 66 "ramfs" 67 "devtmpfs" 68 "devpts" 69 ]; 70 71 nonEmptyWithoutTrailingSlash = addCheckDesc "non-empty without trailing slash" types.str ( 72 s: isNonEmpty s && (builtins.match ".+/" s) == null 73 ); 74 75 coreFileSystemOpts = 76 { name, config, ... }: 77 { 78 79 options = { 80 enable = mkEnableOption "the filesystem mount" // { 81 default = true; 82 }; 83 84 mountPoint = mkOption { 85 example = "/mnt/usb"; 86 type = nonEmptyWithoutTrailingSlash; 87 default = name; 88 description = '' 89 Location where the file system will be mounted. 90 91 This is called `mountpoint` in {manpage}`mount(8)` and `fs_file` in {manpage}`fstab(5)` 92 ''; 93 }; 94 95 stratis.poolUuid = mkOption { 96 type = types.uniq (types.nullOr types.str); 97 description = '' 98 UUID of the stratis pool that the fs is located in 99 100 This is only relevant if you are using [stratis](https://stratis-storage.github.io/). 101 ''; 102 example = "04c68063-90a5-4235-b9dd-6180098a20d9"; 103 default = null; 104 }; 105 106 device = mkOption { 107 default = null; 108 example = "/dev/sda"; 109 type = types.nullOr nonEmptyStr; 110 description = '' 111 The device as passed to `mount`. 112 113 This can be any of: 114 115 - a filename of a block special device such as `/dev/sdc3` 116 - a tag such as `UUID=fdd68895-c307-4549-8c9c-90e44c71f5b7` 117 - (for bind mounts only) the source path 118 - something else depending on the {option}`fsType`. For example, `nfs` device may look like `knuth.cwi.nl:/dir` 119 120 This is called `device` in {manpage}`mount(8)` and `fs_spec` in {manpage}`fstab(5)`. 121 ''; 122 }; 123 124 fsType = mkOption { 125 default = "auto"; 126 example = "ext3"; 127 type = nonEmptyStr; 128 description = '' 129 Type of the file system. 130 131 This is the `fstype` passed to `-t` in the {manpage}`mount(8)` command, and is called `fs_vfstype` in {manpage}`fstab(5)`. 132 ''; 133 }; 134 135 options = mkOption { 136 default = [ "defaults" ]; 137 example = [ "data=journal" ]; 138 description = '' 139 Options used to mount the file system. 140 141 This is called `options` in {manpage}`mount(8)` and `fs_mntops` in {manpage}`fstab(5)` 142 143 Some options that can be used for all mounts are documented in {manpage}`mount(8)` under `FILESYSTEM-INDEPENDENT MOUNT OPTIONS`. 144 145 Options that systemd understands are documented in {manpage}`systemd.mount(5)` under `FSTAB`. 146 147 Each filesystem supports additional options, see the docs for that filesystem. 148 ''; 149 type = types.nonEmptyListOf nonEmptyStr; 150 }; 151 152 depends = mkOption { 153 default = [ ]; 154 example = [ "/persist" ]; 155 type = types.listOf nonEmptyWithoutTrailingSlash; 156 description = '' 157 List of paths that should be mounted before this one. This filesystem's 158 {option}`device` and {option}`mountPoint` are always 159 checked and do not need to be included explicitly. If a path is added 160 to this list, any other filesystem whose mount point is a parent of 161 the path will be mounted before this filesystem. The paths do not need 162 to actually be the {option}`mountPoint` of some other filesystem. 163 164 This is useful for mounts which require keys and/or configuration files residing on another filesystem. 165 ''; 166 }; 167 168 }; 169 170 config = { 171 device = mkIf (elem config.fsType specialFSTypes) (mkDefault config.fsType); 172 }; 173 174 }; 175 176 fileSystemOpts = 177 { config, ... }: 178 { 179 180 options = { 181 182 label = mkOption { 183 default = null; 184 example = "root-partition"; 185 type = types.nullOr nonEmptyStr; 186 description = '' 187 Label of the device. This simply sets {option}`device` to 188 `/dev/disk/by-id/''${label}`. Note that devices will not 189 have a label unless they contain a filesystem which 190 supports labels, such as ext4 or fat32. 191 ''; 192 }; 193 194 autoFormat = mkOption { 195 default = false; 196 type = types.bool; 197 description = '' 198 If the device does not currently contain a filesystem (as 199 determined by {command}`blkid`), then automatically 200 format it with the filesystem type specified in 201 {option}`fsType`. Use with caution. 202 ''; 203 }; 204 205 formatOptions = mkOption { 206 visible = false; 207 type = types.unspecified; 208 default = null; 209 }; 210 211 autoResize = mkOption { 212 default = false; 213 type = types.bool; 214 description = '' 215 If set, the filesystem is grown to its maximum size before 216 being mounted. (This is typically the size of the containing 217 partition.) This is currently only supported for ext2/3/4 218 filesystems that are mounted during early boot. 219 ''; 220 }; 221 222 noCheck = mkOption { 223 default = false; 224 type = types.bool; 225 description = "Disable running fsck on this filesystem."; 226 }; 227 228 }; 229 230 config.device = mkIf (config.label != null) (mkDefault "/dev/disk/by-label/${escape config.label}"); 231 232 config.options = 233 let 234 inInitrd = utils.fsNeededForBoot config; 235 in 236 mkMerge [ 237 (mkIf config.autoResize [ "x-systemd.growfs" ]) 238 (mkIf config.autoFormat [ "x-systemd.makefs" ]) 239 (mkIf (utils.fsNeededForBoot config) [ "x-initrd.mount" ]) 240 (mkIf 241 # With scripted stage 1, depends is implemented by sorting 'config.system.build.fileSystems' 242 (lib.length config.depends > 0 && (inInitrd -> moduleArgs.config.boot.initrd.systemd.enable)) 243 (map (x: "x-systemd.requires-mounts-for=${optionalString inInitrd "/sysroot"}${x}") config.depends) 244 ) 245 ]; 246 247 }; 248 249 # Makes sequence of `specialMount device mountPoint options fsType` commands. 250 # `systemMount` should be defined in the sourcing script. 251 makeSpecialMounts = 252 mounts: 253 pkgs.writeText "mounts.sh" ( 254 concatMapStringsSep "\n" (mount: '' 255 specialMount "${mount.device}" "${mount.mountPoint}" "${concatStringsSep "," mount.options}" "${mount.fsType}" 256 '') mounts 257 ); 258 259 makeFstabEntries = 260 let 261 fsToSkipCheck = [ 262 "none" 263 "auto" 264 "overlay" 265 "iso9660" 266 "bindfs" 267 "udf" 268 "btrfs" 269 "zfs" 270 "tmpfs" 271 "bcachefs" 272 "nfs" 273 "nfs4" 274 "nilfs2" 275 "vboxsf" 276 "squashfs" 277 "glusterfs" 278 "apfs" 279 "9p" 280 "cifs" 281 "prl_fs" 282 "vmhgfs" 283 ] 284 ++ lib.optionals (!config.boot.initrd.checkJournalingFS) [ 285 "ext3" 286 "ext4" 287 "reiserfs" 288 "xfs" 289 "jfs" 290 "f2fs" 291 ]; 292 isBindMount = fs: builtins.elem "bind" fs.options; 293 skipCheck = 294 fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck || isBindMount fs; 295 in 296 fstabFileSystems: 297 { }: 298 concatMapStrings ( 299 fs: 300 ( 301 if fs.device != null then 302 escape fs.device 303 else 304 throw "No device specified for mount point ${fs.mountPoint}." 305 ) 306 + " " 307 + escape fs.mountPoint 308 + " " 309 + fs.fsType 310 + " " 311 + escape (builtins.concatStringsSep "," fs.options) 312 + " 0 " 313 + ( 314 if skipCheck fs then 315 "0" 316 else if fs.mountPoint == "/" then 317 "1" 318 else 319 "2" 320 ) 321 + "\n" 322 ) fstabFileSystems; 323 324 initrdFstab = pkgs.writeText "initrd-fstab" ( 325 makeFstabEntries (filter utils.fsNeededForBoot fileSystems) { } 326 ); 327 328in 329 330{ 331 332 ###### interface 333 334 options = { 335 336 fileSystems = mkOption { 337 default = { }; 338 example = literalExpression '' 339 { 340 "/".device = "/dev/hda1"; 341 "/data" = { 342 device = "/dev/hda2"; 343 fsType = "ext3"; 344 options = [ "data=journal" ]; 345 }; 346 "/bigdisk".label = "bigdisk"; 347 } 348 ''; 349 type = types.attrsOf ( 350 types.submodule [ 351 coreFileSystemOpts 352 fileSystemOpts 353 ] 354 ); 355 apply = lib.filterAttrs (_: fs: fs.enable); 356 description = '' 357 The file systems to be mounted. It must include an entry for 358 the root directory (`mountPoint = "/"`). Each 359 entry in the list is an attribute set with the following fields: 360 `mountPoint`, `device`, 361 `fsType` (a file system type recognised by 362 {command}`mount`; defaults to 363 `"auto"`), and `options` 364 (the mount options passed to {command}`mount` using the 365 {option}`-o` flag; defaults to `[ "defaults" ]`). 366 367 Instead of specifying `device`, you can also 368 specify a volume label (`label`) for file 369 systems that support it, such as ext2/ext3 (see {command}`mke2fs -L`). 370 ''; 371 }; 372 373 system.fsPackages = mkOption { 374 internal = true; 375 default = [ ]; 376 description = "Packages supplying file system mounters and checkers."; 377 }; 378 379 boot.supportedFilesystems = mkOption { 380 default = { }; 381 example = literalExpression '' 382 { 383 btrfs = true; 384 zfs = lib.mkForce false; 385 } 386 ''; 387 type = attrNamesToTrue; 388 description = '' 389 Names of supported filesystem types, or an attribute set of file system types 390 and their state. The set form may be used together with `lib.mkForce` to 391 explicitly disable support for specific filesystems, e.g. to disable ZFS 392 with an unsupported kernel. 393 ''; 394 }; 395 396 boot.specialFileSystems = mkOption { 397 default = { }; 398 type = types.attrsOf (types.submodule coreFileSystemOpts); 399 apply = lib.filterAttrs (_: fs: fs.enable); 400 internal = true; 401 description = '' 402 Special filesystems that are mounted very early during boot. 403 ''; 404 }; 405 406 boot.devSize = mkOption { 407 default = "5%"; 408 example = "32m"; 409 type = types.str; 410 description = '' 411 Size limit for the /dev tmpfs. Look at {manpage}`mount(8)`, tmpfs size option, 412 for the accepted syntax. 413 ''; 414 }; 415 416 boot.devShmSize = mkOption { 417 default = "50%"; 418 example = "256m"; 419 type = types.str; 420 description = '' 421 Size limit for the /dev/shm tmpfs. Look at {manpage}`mount(8)`, tmpfs size option, 422 for the accepted syntax. 423 ''; 424 }; 425 426 boot.runSize = mkOption { 427 default = "25%"; 428 example = "256m"; 429 type = types.str; 430 description = '' 431 Size limit for the /run tmpfs. Look at {manpage}`mount(8)`, tmpfs size option, 432 for the accepted syntax. 433 ''; 434 }; 435 }; 436 437 ###### implementation 438 439 config = { 440 441 assertions = 442 let 443 ls = sep: concatMapStringsSep sep (x: x.mountPoint); 444 resizableFSes = [ 445 "ext3" 446 "ext4" 447 "btrfs" 448 "xfs" 449 ]; 450 notAutoResizable = fs: fs.autoResize && !(builtins.elem fs.fsType resizableFSes); 451 in 452 [ 453 { 454 assertion = !(fileSystems' ? cycle); 455 message = "The fileSystems option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}"; 456 } 457 { 458 assertion = !(any notAutoResizable fileSystems); 459 message = 460 let 461 fs = head (filter notAutoResizable fileSystems); 462 in 463 '' 464 Mountpoint '${fs.mountPoint}': 'autoResize = true' is not supported for 'fsType = "${fs.fsType}"' 465 ${optionalString (fs.fsType == "auto") "fsType has to be explicitly set and"} 466 only the following support it: ${lib.concatStringsSep ", " resizableFSes}. 467 ''; 468 } 469 { 470 assertion = !(any (fs: fs.formatOptions != null) fileSystems); 471 message = '' 472 'fileSystems.<name>.formatOptions' has been removed, since 473 systemd-makefs does not support any way to provide formatting 474 options. 475 ''; 476 } 477 ] 478 ++ lib.map (fs: { 479 assertion = fs.label != null -> fs.device == "/dev/disk/by-label/${escape fs.label}"; 480 message = '' 481 The filesystem with mount point ${fs.mountPoint} has its label and device set to inconsistent values: 482 label: ${toString fs.label} 483 device: ${toString fs.device} 484 'filesystems.<name>.label' and 'filesystems.<name>.device' are mutually exclusive. Please set only one. 485 ''; 486 }) fileSystems; 487 488 # Export for use in other modules 489 system.build.fileSystems = fileSystems; 490 system.build.earlyMountScript = makeSpecialMounts (toposort fsBefore ( 491 attrValues config.boot.specialFileSystems 492 )).result; 493 494 boot.supportedFilesystems = map (fs: fs.fsType) fileSystems; 495 496 # Add the mount helpers to the system path so that `mount' can find them. 497 system.fsPackages = [ pkgs.dosfstools ]; 498 499 environment.systemPackages = config.system.fsPackages; 500 501 environment.etc.fstab.text = 502 let 503 swapOptions = 504 sw: 505 concatStringsSep "," ( 506 sw.options 507 ++ optional (sw.priority != null) "pri=${toString sw.priority}" 508 ++ 509 optional (sw.discardPolicy != null) 510 "discard${optionalString (sw.discardPolicy != "both") "=${toString sw.discardPolicy}"}" 511 ); 512 in 513 '' 514 # This is a generated file. Do not edit! 515 # 516 # To make changes, edit the fileSystems and swapDevices NixOS options 517 # in your /etc/nixos/configuration.nix file. 518 # 519 # <file system> <mount point> <type> <options> <dump> <pass> 520 521 # Filesystems. 522 ${makeFstabEntries fileSystems { }} 523 524 ${optionalString (config.swapDevices != [ ]) "# Swap devices."} 525 ${flip concatMapStrings config.swapDevices (sw: "${sw.realDevice} none swap ${swapOptions sw}\n")} 526 ''; 527 528 boot.initrd.systemd.storePaths = [ initrdFstab ]; 529 boot.initrd.systemd.managerEnvironment.SYSTEMD_SYSROOT_FSTAB = initrdFstab; 530 boot.initrd.systemd.services.initrd-parse-etc.environment.SYSTEMD_SYSROOT_FSTAB = initrdFstab; 531 532 # Provide a target that pulls in all filesystems. 533 systemd.targets.fs = { 534 description = "All File Systems"; 535 wants = [ 536 "local-fs.target" 537 "remote-fs.target" 538 ]; 539 }; 540 541 systemd.tmpfiles.rules = [ 542 "d /run/keys 0750 root ${toString config.ids.gids.keys}" 543 "z /run/keys 0750 root ${toString config.ids.gids.keys}" 544 ]; 545 546 # Sync mount options with systemd's src/core/mount-setup.c: mount_table. 547 boot.specialFileSystems = { 548 "/proc" = { 549 fsType = "proc"; 550 options = [ 551 "nosuid" 552 "noexec" 553 "nodev" 554 ]; 555 }; 556 "/run" = { 557 fsType = "tmpfs"; 558 options = [ 559 "nosuid" 560 "nodev" 561 "strictatime" 562 "mode=755" 563 "size=${config.boot.runSize}" 564 ]; 565 }; 566 "/dev" = { 567 fsType = "devtmpfs"; 568 options = [ 569 "nosuid" 570 "strictatime" 571 "mode=755" 572 "size=${config.boot.devSize}" 573 ]; 574 }; 575 "/dev/shm" = { 576 fsType = "tmpfs"; 577 options = [ 578 "nosuid" 579 "nodev" 580 "strictatime" 581 "mode=1777" 582 "size=${config.boot.devShmSize}" 583 ]; 584 }; 585 "/dev/pts" = { 586 fsType = "devpts"; 587 options = [ 588 "nosuid" 589 "noexec" 590 "mode=620" 591 "ptmxmode=0666" 592 "gid=${toString config.ids.gids.tty}" 593 ]; 594 }; 595 596 # To hold secrets that shouldn't be written to disk 597 "/run/keys" = { 598 fsType = "ramfs"; 599 options = [ 600 "nosuid" 601 "nodev" 602 "mode=750" 603 ]; 604 }; 605 } 606 // optionalAttrs (!config.boot.isContainer) { 607 # systemd-nspawn populates /sys by itself, and remounting it causes all 608 # kinds of weird issues (most noticeably, waiting for host disk device 609 # nodes). 610 "/sys" = { 611 fsType = "sysfs"; 612 options = [ 613 "nosuid" 614 "noexec" 615 "nodev" 616 ]; 617 }; 618 }; 619 620 }; 621 622}