{ config, lib, pkgs, utils, ... }: with lib; with utils; let fileSystems' = toposort fsBefore (attrValues config.fileSystems); fileSystems = if fileSystems' ? "result" then # use topologically sorted fileSystems everywhere fileSystems'.result else # the assertion below will catch this, # but we fall back to the original order # anyway so that other modules could check # their assertions too (attrValues config.fileSystems); prioOption = prio: optionalString (prio != null) " pri=${toString prio}"; specialFSTypes = [ "proc" "sysfs" "tmpfs" "devtmpfs" "devpts" ]; coreFileSystemOpts = { name, config, ... }: { options = { mountPoint = mkOption { example = "/mnt/usb"; type = types.str; description = "Location of the mounted the file system."; }; device = mkOption { default = null; example = "/dev/sda"; type = types.nullOr types.str; description = "Location of the device."; }; fsType = mkOption { default = "auto"; example = "ext3"; type = types.str; description = "Type of the file system."; }; options = mkOption { default = [ "defaults" ]; example = [ "data=journal" ]; description = "Options used to mount the file system."; type = types.listOf types.str; }; }; config = { mountPoint = mkDefault name; device = mkIf (elem config.fsType specialFSTypes) (mkDefault config.fsType); }; }; fileSystemOpts = { config, ... }: { options = { label = mkOption { default = null; example = "root-partition"; type = types.nullOr types.str; description = "Label of the device (if any)."; }; autoFormat = mkOption { default = false; type = types.bool; description = '' If the device does not currently contain a filesystem (as determined by blkid, then automatically format it with the filesystem type specified in . Use with caution. ''; }; formatOptions = mkOption { default = ""; type = types.str; description = '' If option is set specifies extra options passed to mkfs. ''; }; autoResize = mkOption { default = false; type = types.bool; description = '' If set, the filesystem is grown to its maximum size before being mounted. (This is typically the size of the containing partition.) This is currently only supported for ext2/3/4 filesystems that are mounted during early boot. ''; }; noCheck = mkOption { default = false; type = types.bool; description = "Disable running fsck on this filesystem."; }; }; config = { options = mkIf config.autoResize [ "x-nixos.autoresize" ]; # -F needed to allow bare block device without partitions formatOptions = mkIf ((builtins.substring 0 3 config.fsType) == "ext") (mkDefault "-F"); }; }; # Makes sequence of `specialMount device mountPoint options fsType` commands. # `systemMount` should be defined in the sourcing script. makeSpecialMounts = mounts: pkgs.writeText "mounts.sh" (concatMapStringsSep "\n" (mount: '' specialMount "${mount.device}" "${mount.mountPoint}" "${concatStringsSep "," mount.options}" "${mount.fsType}" '') mounts); in { ###### interface options = { fileSystems = mkOption { default = {}; example = literalExample '' { "/".device = "/dev/hda1"; "/data" = { device = "/dev/hda2"; fsType = "ext3"; options = [ "data=journal" ]; }; "/bigdisk".label = "bigdisk"; } ''; type = types.loaOf (types.submodule [coreFileSystemOpts fileSystemOpts]); description = '' The file systems to be mounted. It must include an entry for the root directory (mountPoint = "/"). Each entry in the list is an attribute set with the following fields: mountPoint, device, fsType (a file system type recognised by mount; defaults to "auto"), and options (the mount options passed to mount using the flag; defaults to [ "defaults" ]). Instead of specifying device, you can also specify a volume label (label) for file systems that support it, such as ext2/ext3 (see mke2fs -L). ''; }; system.fsPackages = mkOption { internal = true; default = [ ]; description = "Packages supplying file system mounters and checkers."; }; boot.supportedFilesystems = mkOption { default = [ ]; example = [ "btrfs" ]; type = types.listOf types.str; description = "Names of supported filesystem types."; }; boot.specialFileSystems = mkOption { default = {}; type = types.loaOf (types.submodule coreFileSystemOpts); internal = true; description = '' Special filesystems that are mounted very early during boot. ''; }; }; ###### implementation config = { assertions = let ls = sep: concatMapStringsSep sep (x: x.mountPoint); in [ { assertion = ! (fileSystems' ? "cycle"); message = "The ‘fileSystems’ option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}"; } ]; # Export for use in other modules system.build.fileSystems = fileSystems; system.build.earlyMountScript = makeSpecialMounts (toposort fsBefore (attrValues config.boot.specialFileSystems)).result; boot.supportedFilesystems = map (fs: fs.fsType) fileSystems; # Add the mount helpers to the system path so that `mount' can find them. system.fsPackages = [ pkgs.dosfstools ]; environment.systemPackages = [ pkgs.fuse ] ++ config.system.fsPackages; environment.etc.fstab.text = let fsToSkipCheck = [ "none" "btrfs" "zfs" "tmpfs" "nfs" "vboxsf" ]; skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck; in '' # This is a generated file. Do not edit! # # To make changes, edit the fileSystems and swapDevices NixOS options # in your /etc/nixos/configuration.nix file. # Filesystems. ${concatMapStrings (fs: (if fs.device != null then fs.device else if fs.label != null then "/dev/disk/by-label/${fs.label}" else throw "No device specified for mount point ‘${fs.mountPoint}’.") + " " + fs.mountPoint + " " + fs.fsType + " " + builtins.concatStringsSep "," fs.options + " 0" + " " + (if skipCheck fs then "0" else if fs.mountPoint == "/" then "1" else "2") + "\n" ) fileSystems} # Swap devices. ${flip concatMapStrings config.swapDevices (sw: "${sw.realDevice} none swap${prioOption sw.priority}\n" )} ''; # Provide a target that pulls in all filesystems. systemd.targets.fs = { description = "All File Systems"; wants = [ "local-fs.target" "remote-fs.target" ]; }; # Emit systemd services to format requested filesystems. systemd.services = let formatDevice = fs: let mountPoint' = "${escapeSystemdPath fs.mountPoint}.mount"; device' = escapeSystemdPath fs.device; device'' = "${device}.device"; in nameValuePair "mkfs-${device'}" { description = "Initialisation of Filesystem ${fs.device}"; wantedBy = [ mountPoint' ]; before = [ mountPoint' "systemd-fsck@${device'}.service" ]; requires = [ device'' ]; after = [ device'' ]; path = [ pkgs.utillinux ] ++ config.system.fsPackages; script = '' if ! [ -e "${fs.device}" ]; then exit 1; fi # FIXME: this is scary. The test could be more robust. type=$(blkid -p -s TYPE -o value "${fs.device}" || true) if [ -z "$type" ]; then echo "creating ${fs.fsType} filesystem on ${fs.device}..." mkfs.${fs.fsType} ${fs.formatOptions} "${fs.device}" fi ''; unitConfig.RequiresMountsFor = [ "${dirOf fs.device}" ]; unitConfig.DefaultDependencies = false; # needed to prevent a cycle serviceConfig.Type = "oneshot"; }; in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems)); # Sync mount options with systemd's src/core/mount-setup.c: mount_table. boot.specialFileSystems = { "/proc" = { fsType = "proc"; options = [ "nosuid" "noexec" "nodev" ]; }; "/sys" = { fsType = "sysfs"; options = [ "nosuid" "noexec" "nodev" ]; }; "/run" = { fsType = "tmpfs"; options = [ "nodev" "strictatime" "mode=755" "size=${config.boot.runSize}" ]; }; "/dev" = { fsType = "devtmpfs"; options = [ "nosuid" "strictatime" "mode=755" "size=${config.boot.devSize}" ]; }; "/dev/shm" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=1777" "size=${config.boot.devShmSize}" ]; }; "/dev/pts" = { fsType = "devpts"; options = [ "nosuid" "noexec" "mode=620" "gid=${toString config.ids.gids.tty}" ]; }; }; }; }