2014-04-14 16:26:48 +02:00
|
|
|
|
{ config, lib, pkgs, utils, ... }:
|
2009-10-12 18:36:19 +02:00
|
|
|
|
|
2014-04-14 16:26:48 +02:00
|
|
|
|
with lib;
|
2012-10-12 23:32:36 +02:00
|
|
|
|
with utils;
|
2006-12-21 15:22:40 +01:00
|
|
|
|
|
2012-03-15 14:54:23 +01:00
|
|
|
|
let
|
|
|
|
|
|
2017-02-15 13:22:48 +01:00
|
|
|
|
addCheckDesc = desc: elemType: check: types.addCheck elemType check
|
|
|
|
|
// { description = "${elemType.description} (with check: ${desc})"; };
|
|
|
|
|
nonEmptyStr = addCheckDesc "non-empty" types.str
|
|
|
|
|
(x: x != "" && ! (all (c: c == " " || c == "\t") (stringToCharacters x)));
|
|
|
|
|
|
2015-11-25 20:09:09 +01:00
|
|
|
|
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);
|
2012-11-02 18:02:12 +01:00
|
|
|
|
|
2015-09-24 18:13:14 +02:00
|
|
|
|
prioOption = prio: optionalString (prio != null) " pri=${toString prio}";
|
2013-05-13 23:42:55 +02:00
|
|
|
|
|
2016-09-17 12:43:37 +02:00
|
|
|
|
specialFSTypes = [ "proc" "sysfs" "tmpfs" "ramfs" "devtmpfs" "devpts" ];
|
2016-08-27 12:29:38 +02:00
|
|
|
|
|
2016-08-31 15:45:19 +02:00
|
|
|
|
coreFileSystemOpts = { name, config, ... }: {
|
2012-11-02 18:02:12 +01:00
|
|
|
|
|
|
|
|
|
options = {
|
|
|
|
|
|
|
|
|
|
mountPoint = mkOption {
|
|
|
|
|
example = "/mnt/usb";
|
2017-02-15 13:22:48 +01:00
|
|
|
|
type = nonEmptyStr;
|
2012-11-02 18:02:12 +01:00
|
|
|
|
description = "Location of the mounted the file system.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
device = mkOption {
|
|
|
|
|
default = null;
|
|
|
|
|
example = "/dev/sda";
|
2017-02-15 13:22:48 +01:00
|
|
|
|
type = types.nullOr nonEmptyStr;
|
2012-11-02 18:02:12 +01:00
|
|
|
|
description = "Location of the device.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fsType = mkOption {
|
|
|
|
|
default = "auto";
|
|
|
|
|
example = "ext3";
|
2017-02-15 13:22:48 +01:00
|
|
|
|
type = nonEmptyStr;
|
2012-11-02 18:02:12 +01:00
|
|
|
|
description = "Type of the file system.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
options = mkOption {
|
2015-10-21 19:37:14 +02:00
|
|
|
|
default = [ "defaults" ];
|
|
|
|
|
example = [ "data=journal" ];
|
2012-11-02 18:02:12 +01:00
|
|
|
|
description = "Options used to mount the file system.";
|
2017-02-15 13:22:48 +01:00
|
|
|
|
type = types.listOf nonEmptyStr;
|
2016-09-01 11:18:33 +02:00
|
|
|
|
};
|
2012-11-02 18:02:12 +01:00
|
|
|
|
|
2016-08-31 15:45:19 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config = {
|
|
|
|
|
mountPoint = mkDefault name;
|
|
|
|
|
device = mkIf (elem config.fsType specialFSTypes) (mkDefault config.fsType);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fileSystemOpts = { config, ... }: {
|
|
|
|
|
|
|
|
|
|
options = {
|
|
|
|
|
|
|
|
|
|
label = mkOption {
|
|
|
|
|
default = null;
|
|
|
|
|
example = "root-partition";
|
2017-02-15 13:22:48 +01:00
|
|
|
|
type = types.nullOr nonEmptyStr;
|
2016-08-31 15:45:19 +02:00
|
|
|
|
description = "Label of the device (if any).";
|
|
|
|
|
};
|
|
|
|
|
|
2012-11-02 18:02:12 +01:00
|
|
|
|
autoFormat = mkOption {
|
|
|
|
|
default = false;
|
|
|
|
|
type = types.bool;
|
|
|
|
|
description = ''
|
|
|
|
|
If the device does not currently contain a filesystem (as
|
|
|
|
|
determined by <command>blkid</command>, then automatically
|
|
|
|
|
format it with the filesystem type specified in
|
|
|
|
|
<option>fsType</option>. Use with caution.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2015-10-04 03:14:53 +02:00
|
|
|
|
formatOptions = mkOption {
|
|
|
|
|
default = "";
|
|
|
|
|
type = types.str;
|
|
|
|
|
description = ''
|
|
|
|
|
If <option>autoFormat</option> option is set specifies
|
|
|
|
|
extra options passed to mkfs.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2015-09-24 18:13:14 +02:00
|
|
|
|
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.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2012-11-02 18:02:12 +01:00
|
|
|
|
noCheck = mkOption {
|
|
|
|
|
default = false;
|
|
|
|
|
type = types.bool;
|
|
|
|
|
description = "Disable running fsck on this filesystem.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
2017-10-12 04:35:31 +02:00
|
|
|
|
config = let
|
|
|
|
|
defaultFormatOptions =
|
|
|
|
|
# -F needed to allow bare block device without partitions
|
|
|
|
|
if (builtins.substring 0 3 config.fsType) == "ext" then "-F"
|
|
|
|
|
# -q needed for non-interactive operations
|
|
|
|
|
else if config.fsType == "jfs" then "-q"
|
|
|
|
|
# (same here)
|
|
|
|
|
else if config.fsType == "reiserfs" then "-q"
|
|
|
|
|
else null;
|
|
|
|
|
in {
|
2016-03-14 17:24:36 +01:00
|
|
|
|
options = mkIf config.autoResize [ "x-nixos.autoresize" ];
|
2017-10-12 04:35:31 +02:00
|
|
|
|
formatOptions = mkIf (defaultFormatOptions != null) (mkDefault defaultFormatOptions);
|
2012-11-02 18:02:12 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
2016-08-27 12:29:38 +02:00
|
|
|
|
# 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);
|
|
|
|
|
|
2012-03-15 14:54:23 +01:00
|
|
|
|
in
|
2012-10-12 23:32:36 +02:00
|
|
|
|
|
2009-10-12 18:36:19 +02:00
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
###### interface
|
2009-05-28 01:14:38 +02:00
|
|
|
|
|
|
|
|
|
options = {
|
|
|
|
|
|
2009-06-11 18:03:57 +02:00
|
|
|
|
fileSystems = mkOption {
|
2013-11-18 16:26:39 +01:00
|
|
|
|
default = {};
|
2016-06-19 09:02:24 +02:00
|
|
|
|
example = literalExample ''
|
|
|
|
|
{
|
|
|
|
|
"/".device = "/dev/hda1";
|
|
|
|
|
"/data" = {
|
|
|
|
|
device = "/dev/hda2";
|
|
|
|
|
fsType = "ext3";
|
|
|
|
|
options = [ "data=journal" ];
|
|
|
|
|
};
|
|
|
|
|
"/bigdisk".label = "bigdisk";
|
|
|
|
|
}
|
|
|
|
|
'';
|
2016-08-31 15:45:19 +02:00
|
|
|
|
type = types.loaOf (types.submodule [coreFileSystemOpts fileSystemOpts]);
|
2012-03-09 15:37:58 +01:00
|
|
|
|
description = ''
|
2009-05-28 01:14:38 +02:00
|
|
|
|
The file systems to be mounted. It must include an entry for
|
2013-10-28 13:36:45 +01:00
|
|
|
|
the root directory (<literal>mountPoint = "/"</literal>). Each
|
2009-05-28 01:14:38 +02:00
|
|
|
|
entry in the list is an attribute set with the following fields:
|
|
|
|
|
<literal>mountPoint</literal>, <literal>device</literal>,
|
|
|
|
|
<literal>fsType</literal> (a file system type recognised by
|
|
|
|
|
<command>mount</command>; defaults to
|
2013-10-28 13:36:45 +01:00
|
|
|
|
<literal>"auto"</literal>), and <literal>options</literal>
|
2009-05-28 01:14:38 +02:00
|
|
|
|
(the mount options passed to <command>mount</command> using the
|
2015-10-21 19:37:14 +02:00
|
|
|
|
<option>-o</option> flag; defaults to <literal>[ "defaults" ]</literal>).
|
2009-05-28 01:14:38 +02:00
|
|
|
|
|
|
|
|
|
Instead of specifying <literal>device</literal>, you can also
|
|
|
|
|
specify a volume label (<literal>label</literal>) for file
|
|
|
|
|
systems that support it, such as ext2/ext3 (see <command>mke2fs
|
|
|
|
|
-L</command>).
|
2012-03-09 15:37:58 +01:00
|
|
|
|
'';
|
2009-05-28 01:14:38 +02:00
|
|
|
|
};
|
2011-09-14 20:20:50 +02:00
|
|
|
|
|
2012-03-09 15:37:58 +01:00
|
|
|
|
system.fsPackages = mkOption {
|
2009-05-28 01:14:38 +02:00
|
|
|
|
internal = true;
|
2012-03-09 15:37:58 +01:00
|
|
|
|
default = [ ];
|
|
|
|
|
description = "Packages supplying file system mounters and checkers.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
boot.supportedFilesystems = mkOption {
|
|
|
|
|
default = [ ];
|
|
|
|
|
example = [ "btrfs" ];
|
2015-06-15 18:18:46 +02:00
|
|
|
|
type = types.listOf types.str;
|
2012-03-09 15:37:58 +01:00
|
|
|
|
description = "Names of supported filesystem types.";
|
|
|
|
|
};
|
|
|
|
|
|
2016-08-31 15:45:19 +02:00
|
|
|
|
boot.specialFileSystems = mkOption {
|
|
|
|
|
default = {};
|
|
|
|
|
type = types.loaOf (types.submodule coreFileSystemOpts);
|
|
|
|
|
internal = true;
|
|
|
|
|
description = ''
|
|
|
|
|
Special filesystems that are mounted very early during boot.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2009-05-28 01:14:38 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2009-10-12 18:36:19 +02:00
|
|
|
|
###### implementation
|
2009-05-28 01:14:38 +02:00
|
|
|
|
|
2009-10-12 18:36:19 +02:00
|
|
|
|
config = {
|
2009-05-28 01:14:38 +02:00
|
|
|
|
|
2015-11-25 20:09:09 +01:00
|
|
|
|
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;
|
2016-08-31 15:45:19 +02:00
|
|
|
|
system.build.earlyMountScript = makeSpecialMounts (toposort fsBefore (attrValues config.boot.specialFileSystems)).result;
|
2015-11-25 20:09:09 +01:00
|
|
|
|
|
2012-11-02 18:02:12 +01:00
|
|
|
|
boot.supportedFilesystems = map (fs: fs.fsType) fileSystems;
|
2012-03-09 15:37:58 +01:00
|
|
|
|
|
2009-10-12 18:36:19 +02:00
|
|
|
|
# Add the mount helpers to the system path so that `mount' can find them.
|
2012-03-09 15:37:58 +01:00
|
|
|
|
system.fsPackages = [ pkgs.dosfstools ];
|
2012-10-12 23:32:36 +02:00
|
|
|
|
|
2017-08-19 18:50:53 +02:00
|
|
|
|
environment.systemPackages = with pkgs; [ fuse3 fuse ] ++ config.system.fsPackages;
|
2011-09-14 20:20:50 +02:00
|
|
|
|
|
2013-02-03 14:12:49 +01:00
|
|
|
|
environment.etc.fstab.text =
|
2014-12-29 02:53:37 +01:00
|
|
|
|
let
|
2017-04-06 13:35:25 +02:00
|
|
|
|
fsToSkipCheck = [ "none" "bindfs" "btrfs" "zfs" "tmpfs" "nfs" "vboxsf" "glusterfs" ];
|
2014-12-29 02:53:37 +01:00
|
|
|
|
skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck;
|
2018-09-07 23:49:38 +02:00
|
|
|
|
# https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
|
2019-02-03 14:33:31 +01:00
|
|
|
|
escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
|
2014-12-29 02:53:37 +01:00
|
|
|
|
in ''
|
2013-02-03 14:12:49 +01:00
|
|
|
|
# This is a generated file. Do not edit!
|
2016-08-24 20:37:21 +02:00
|
|
|
|
#
|
|
|
|
|
# To make changes, edit the fileSystems and swapDevices NixOS options
|
|
|
|
|
# in your /etc/nixos/configuration.nix file.
|
2013-02-03 14:12:49 +01:00
|
|
|
|
|
|
|
|
|
# Filesystems.
|
2015-11-25 20:09:09 +01:00
|
|
|
|
${concatMapStrings (fs:
|
2018-09-07 23:49:38 +02:00
|
|
|
|
(if fs.device != null then escape fs.device
|
|
|
|
|
else if fs.label != null then "/dev/disk/by-label/${escape fs.label}"
|
2014-07-30 13:10:03 +02:00
|
|
|
|
else throw "No device specified for mount point ‘${fs.mountPoint}’.")
|
2018-09-07 23:49:38 +02:00
|
|
|
|
+ " " + escape fs.mountPoint
|
2013-02-03 14:12:49 +01:00
|
|
|
|
+ " " + fs.fsType
|
2015-10-21 19:37:14 +02:00
|
|
|
|
+ " " + builtins.concatStringsSep "," fs.options
|
2013-02-03 14:12:49 +01:00
|
|
|
|
+ " 0"
|
2014-12-29 02:53:37 +01:00
|
|
|
|
+ " " + (if skipCheck fs then "0" else
|
2013-02-03 14:12:49 +01:00
|
|
|
|
if fs.mountPoint == "/" then "1" else "2")
|
|
|
|
|
+ "\n"
|
2016-08-31 15:45:19 +02:00
|
|
|
|
) fileSystems}
|
2013-02-03 14:12:49 +01:00
|
|
|
|
|
|
|
|
|
# Swap devices.
|
|
|
|
|
${flip concatMapStrings config.swapDevices (sw:
|
2015-10-18 20:20:46 +02:00
|
|
|
|
"${sw.realDevice} none swap${prioOption sw.priority}\n"
|
2013-02-03 14:12:49 +01:00
|
|
|
|
)}
|
|
|
|
|
'';
|
2010-06-04 16:22:11 +02:00
|
|
|
|
|
2012-10-12 21:08:44 +02:00
|
|
|
|
# Provide a target that pulls in all filesystems.
|
2013-01-16 12:33:18 +01:00
|
|
|
|
systemd.targets.fs =
|
2012-10-12 21:08:44 +02:00
|
|
|
|
{ description = "All File Systems";
|
|
|
|
|
wants = [ "local-fs.target" "remote-fs.target" ];
|
|
|
|
|
};
|
|
|
|
|
|
2012-10-12 23:32:36 +02:00
|
|
|
|
# Emit systemd services to format requested filesystems.
|
2013-01-16 12:33:18 +01:00
|
|
|
|
systemd.services =
|
2012-10-12 23:32:36 +02:00
|
|
|
|
let
|
|
|
|
|
|
|
|
|
|
formatDevice = fs:
|
|
|
|
|
let
|
2015-11-25 20:09:43 +01:00
|
|
|
|
mountPoint' = "${escapeSystemdPath fs.mountPoint}.mount";
|
|
|
|
|
device' = escapeSystemdPath fs.device;
|
2016-09-30 15:03:33 +02:00
|
|
|
|
device'' = "${device'}.device";
|
2012-10-12 23:32:36 +02:00
|
|
|
|
in nameValuePair "mkfs-${device'}"
|
|
|
|
|
{ description = "Initialisation of Filesystem ${fs.device}";
|
2015-11-25 20:09:43 +01:00
|
|
|
|
wantedBy = [ mountPoint' ];
|
|
|
|
|
before = [ mountPoint' "systemd-fsck@${device'}.service" ];
|
|
|
|
|
requires = [ device'' ];
|
|
|
|
|
after = [ device'' ];
|
2012-10-12 23:32:36 +02:00
|
|
|
|
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.
|
2012-05-16 02:03:44 +02:00
|
|
|
|
type=$(blkid -p -s TYPE -o value "${fs.device}" || true)
|
|
|
|
|
if [ -z "$type" ]; then
|
|
|
|
|
echo "creating ${fs.fsType} filesystem on ${fs.device}..."
|
2015-10-04 03:14:53 +02:00
|
|
|
|
mkfs.${fs.fsType} ${fs.formatOptions} "${fs.device}"
|
2012-05-16 02:03:44 +02:00
|
|
|
|
fi
|
2012-10-12 23:32:36 +02:00
|
|
|
|
'';
|
|
|
|
|
unitConfig.RequiresMountsFor = [ "${dirOf fs.device}" ];
|
|
|
|
|
unitConfig.DefaultDependencies = false; # needed to prevent a cycle
|
|
|
|
|
serviceConfig.Type = "oneshot";
|
|
|
|
|
};
|
2010-06-07 14:15:55 +02:00
|
|
|
|
|
2012-11-02 18:02:12 +01:00
|
|
|
|
in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems));
|
2006-12-21 15:22:40 +01:00
|
|
|
|
|
2016-08-27 12:29:38 +02:00
|
|
|
|
# Sync mount options with systemd's src/core/mount-setup.c: mount_table.
|
2016-08-31 15:45:19 +02:00
|
|
|
|
boot.specialFileSystems = {
|
2016-08-27 12:29:38 +02:00
|
|
|
|
"/proc" = { fsType = "proc"; options = [ "nosuid" "noexec" "nodev" ]; };
|
2017-02-18 18:06:09 +01:00
|
|
|
|
"/run" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=755" "size=${config.boot.runSize}" ]; };
|
2016-08-27 12:29:38 +02:00
|
|
|
|
"/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}" ]; };
|
2017-08-30 01:50:29 +02:00
|
|
|
|
"/dev/pts" = { fsType = "devpts"; options = [ "nosuid" "noexec" "mode=620" "ptmxmode=0666" "gid=${toString config.ids.gids.tty}" ]; };
|
2016-09-17 12:43:37 +02:00
|
|
|
|
|
|
|
|
|
# To hold secrets that shouldn't be written to disk (generally used for NixOps, harmless elsewhere)
|
|
|
|
|
"/run/keys" = { fsType = "ramfs"; options = [ "nosuid" "nodev" "mode=750" "gid=${toString config.ids.gids.keys}" ]; };
|
2016-09-07 01:55:26 +02:00
|
|
|
|
} // optionalAttrs (!config.boot.isContainer) {
|
|
|
|
|
# systemd-nspawn populates /sys by itself, and remounting it causes all
|
|
|
|
|
# kinds of weird issues (most noticeably, waiting for host disk device
|
|
|
|
|
# nodes).
|
|
|
|
|
"/sys" = { fsType = "sysfs"; options = [ "nosuid" "noexec" "nodev" ]; };
|
2016-08-27 12:29:38 +02:00
|
|
|
|
};
|
|
|
|
|
|
2009-03-06 13:27:33 +01:00
|
|
|
|
};
|
2009-08-11 23:10:33 +02:00
|
|
|
|
|
2006-12-21 15:22:40 +01:00
|
|
|
|
}
|