{ config, pkgs, ... }:
with pkgs.lib;
let
fstab = 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.fsType == "btrfs" || 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"
)}
'';
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.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
.
'';
};
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.
'';
};
noCheck = mkOption {
default = false;
type = types.bool;
description = "Disable running fsck on this filesystem.";
};
};
};
system.fsPackages = mkOption {
internal = true;
default = [ ];
description = "Packages supplying file system mounters and checkers.";
};
boot.supportedFilesystems = mkOption {
default = [ ];
example = [ "btrfs" ];
type = types.list types.string;
description = "Names of supported filesystem types.";
};
boot.initrd.supportedFilesystems = mkOption {
default = [ ];
example = [ "btrfs" ];
type = types.list types.string;
description = "Names of supported filesystem types in the initial ramdisk.";
};
boot.ttyEmergency = mkOption {
default =
if pkgs.stdenv.isArm
then "ttyS0" # presumably an embedded platform such as a plug
else "tty1";
description = ''
The tty that will be stopped in case an emergency shell is spawned
at boot.
'';
};
};
###### implementation
config = {
boot.supportedFilesystems =
map (fs: fs.fsType) config.fileSystems;
boot.initrd.supportedFilesystems =
map (fs: fs.fsType)
(filter (fs: fs.mountPoint == "/" || fs.neededForBoot) config.fileSystems);
# Add the mount helpers to the system path so that `mount' can find them.
system.fsPackages = [ pkgs.dosfstools ];
environment.systemPackages =
[ pkgs.ntfs3g pkgs.cifs_utils pkgs.mountall ]
++ config.system.fsPackages;
environment.etc = singleton
{ source = fstab;
target = "fstab";
};
jobs.mountall =
{ startOn = "started udev or config-changed";
task = true;
path = [ pkgs.utillinux pkgs.mountall ] ++ config.system.fsPackages;
console = "output";
preStart =
''
# Ensure that this job is restarted when fstab changed:
# ${fstab}
echo "mounting filesystems..."
# Format devices.
${flip concatMapStrings config.fileSystems (fs: optionalString fs.autoFormat ''
if [ -e "${fs.device}" ]; then
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.device}"
fi
fi
'')}
# Create missing mount points. Note that this won't work
# if the mount point is under another mount point.
${flip concatMapStrings config.fileSystems (fs: optionalString fs.autocreate ''
mkdir -p -m 0755 '${fs.mountPoint}'
'')}
# Create missing swapfiles.
# FIXME: support changing the size of existing swapfiles.
${flip concatMapStrings config.swapDevices (sw: optionalString (sw.size != null) ''
if [ ! -e "${sw.device}" -a -e "$(dirname "${sw.device}")" ]; then
# FIXME: use ‘fallocate’ on filesystems that support it.
dd if=/dev/zero of="${sw.device}" bs=1M count=${toString sw.size}
mkswap ${sw.device}
fi
'')}
'';
daemonType = "daemon";
exec = "mountall --daemon";
};
# 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" =
{ task = true;
startOn = "mount-failed";
restartIfChanged = false;
script =
''
# Don't start the emergency shell if the X server is
# running. The user won't see it, and the "console owner"
# stanza breaks VT switching and causes the X server to go
# to 100% CPU time.
status="$(status xserver || true)"
[[ "$status" =~ start/ ]] && exit 0
stop ${config.boot.ttyEmergency} || true
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" =
{
task = true;
startOn = "ip-up";
restartIfChanged = false;
script =
''
# Send USR1 to the mountall process. Can't use "pkill
# mountall" here because that has a race condition: we may
# accidentally send USR1 to children of mountall (such as
# fsck) just before they do execve().
status="$(status mountall)"
if [[ "$status" =~ "start/running, process "([0-9]+) ]]; then
pid=''${BASH_REMATCH[1]}
echo "sending USR1 to $pid..."
kill -USR1 "$pid"
fi
'';
};
jobs."emergency-shell" =
{ task = true;
restartIfChanged = false;
console = "owner";
script =
''
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
'';
};
};
}