{ config, pkgs, ... }:
with pkgs.lib;
let
usingSome = fsname: any (fs: fs.fsType == fsname) config.fileSystems;
usingSomeStage1 = fsname: any (fs: fs.fsType == fsname &&
(fs.mountPoint == "/" || fs.neededForBoot)) config.fileSystems;
usingBtrfs = usingSome "btrfs";
usingBtrfsStage1 = usingSomeStage1 "btrfs";
usingReiserfs = usingSome "reiserfs";
usingReiserfsStage1 = usingSomeStage1 "reiserfs";
# Packages that provide fsck backends.
fsPackages = [ pkgs.e2fsprogs pkgs.dosfstools ]
++ optional usingReiserfs pkgs.btrfsProgs
++ optional usingBtrfs pkgs.btrfsProgs;
fsKernelModules = optional usingBtrfsStage1 [ "btrfs" "crc32c" ]
++ optional usingReiserfsStage1 [ "reiserfs" ];
fsExtraUtilsCommands = optionalString usingBtrfsStage1 ''
cp -v ${pkgs.btrfsProgs}/bin/btrfsck $out/bin
cp -v ${pkgs.btrfsProgs}/bin/btrfs $out/bin
ln -sv btrfsck $out/bin/fsck.btrfs
'';
fsPostDeviceCommands = optionalString usingBtrfsStage1 ''
btrfs device scan
'';
in
{
###### interface
options = {
fileSystems = mkOption {
example = [
{ mountPoint = "/";
device = "/dev/hda1";
}
{ mountPoint = "/data";
device = "/dev/hda2";
fsType = "ext3";
options = "data=journal";
}
{ mountPoint = "/bigdisk";
label = "bigdisk";
}
];
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).
autocreate forces mountPoint to be created with
mkdir -p .
";
type = types.nullOr (types.list types.optionSet);
options = {
mountPoint = mkOption {
example = "/mnt/usb";
type = types.uniq types.string;
description = "
Location of the mounted the file system.
";
};
device = mkOption {
default = null;
example = "/dev/sda";
type = types.uniq (types.nullOr types.string);
description = "
Location of the device.
";
};
label = mkOption {
default = null;
example = "root-partition";
type = types.uniq (types.nullOr types.string);
description = "
Label of the device (if any).
";
};
fsType = mkOption {
default = "auto";
example = "ext3";
type = types.uniq types.string;
description = "
Type of the file system.
";
};
options = mkOption {
default = "defaults,relatime";
example = "data=journal";
type = types.string;
merge = pkgs.lib.concatStringsSep ",";
description = "Options used to mount the file system.";
};
autocreate = mkOption {
default = false;
type = types.bool;
description = "
Automatically create the mount point defined in
.
";
};
noCheck = mkOption {
default = false;
type = types.bool;
description = "Disable running fsck on this filesystem.";
};
};
};
system.sbin.mount = mkOption {
internal = true;
default = pkgs.utillinux;
description = "
Package containing mount and umount.
";
};
};
###### implementation
config = {
# Add the mount helpers to the system path so that `mount' can find them.
environment.systemPackages =
[ pkgs.ntfs3g pkgs.cifs_utils pkgs.nfsUtils pkgs.mountall ]
++ fsPackages;
environment.etc = singleton
{ source = pkgs.writeText "fstab"
''
# This is a generated file. Do not edit!
# Filesystems.
${flip concatMapStrings config.fileSystems (fs:
(if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}")
+ " " + fs.mountPoint
+ " " + fs.fsType
+ " " + fs.options
+ " 0"
+ " " + (if fs.fsType == "none" || fs.noCheck then "0" else
if fs.mountPoint == "/" then "1" else "2")
+ "\n"
)}
# Swap devices.
${flip concatMapStrings config.swapDevices (sw:
"${sw.device} none swap\n"
)}
'';
target = "fstab";
};
boot.initrd.extraUtilsCommands = fsExtraUtilsCommands;
boot.initrd.postDeviceCommands = fsPostDeviceCommands;
boot.initrd.kernelModules = fsKernelModules;
jobs.mountall =
{ startOn = "started udev"
# !!! The `started nfs-kernel-statd' condition shouldn't be
# here. The `nfs-kernel-statd' job should have a `starting
# mountall' condition. However, that doesn't work if
# `mountall' is restarted due to an apparent bug in Upstart:
# `mountall' hangs forever in the `start/starting' state.
+ optionalString config.services.nfsKernel.client.enable " and started nfs-kernel-statd";
task = true;
script =
''
exec > /dev/console 2>&1
echo "mounting filesystems..."
export PATH=${config.system.sbin.mount}/bin:${makeSearchPath "sbin" ([pkgs.utillinux] ++ fsPackages)}:$PATH
${optionalString usingBtrfs "${pkgs.btrfsProgs}/bin/btrfs device scan"}
${pkgs.mountall}/sbin/mountall
'';
};
# The `mount-failed' event is emitted synchronously, but we don't
# want `mountall' to wait for the emergency shell. So use this
# intermediate job to make the event asynchronous.
jobs.mount_failed =
{ name = "mount-failed";
task = true;
startOn = "mount-failed";
script =
''
[ -n "$MOUNTPOINT" ] || exit 0
start --no-wait emergency-shell \
DEVICE="$DEVICE" MOUNTPOINT="$MOUNTPOINT"
'';
};
# On an `ip-up' event, notify mountall so that it retries mounting
# remote filesystems.
jobs.mountall_ip_up =
{
name = "mountall-ip-up";
task = true;
startOn = "ip-up";
script =
''
${pkgs.procps}/bin/pkill -USR1 -u root mountall || true
'';
};
jobs.emergency_shell =
{ name = "emergency-shell";
task = true;
extraConfig = "console owner";
script =
''
[ -n "$MOUNTPOINT" ] || exit 0
exec < /dev/console > /dev/console 2>&1
cat <>>[0m
The filesystem \`$DEVICE' could not be mounted on \`$MOUNTPOINT'.
Please do one of the following:
- Repair the filesystem (\`fsck $DEVICE') and exit the emergency
shell to resume booting.
- Ignore any failed filesystems and continue booting by running
\`initctl emit filesystem'.
- Remove the failed filesystem from the system configuration in
/etc/nixos/configuration.nix and run \`nixos-rebuild switch'.
EOF
${pkgs.shadow}/bin/login root || false
initctl start --no-wait mountall
'';
};
};
}