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