diff --git a/modules/system/boot/systemd-unit-options.nix b/modules/system/boot/systemd-unit-options.nix
index 5707fb6f87e9..e3f0ec6f1d91 100644
--- a/modules/system/boot/systemd-unit-options.nix
+++ b/modules/system/boot/systemd-unit-options.nix
@@ -202,4 +202,51 @@ rec {
};
+ mountOptions = unitOptions // {
+
+ what = mkOption {
+ default = "";
+ example = "/dev/sda1";
+ type = types.uniq types.string;
+ description = "Absolute path of device node, file or other resource. (Mandatory)";
+ };
+
+ where = mkOption {
+ default = "";
+ example = "/mnt";
+ type = types.uniq types.string;
+ description = ''
+ Absolute path of a directory of the mount point.
+ Will be created if it doesn't exist. (Mandatory)
+ '';
+ };
+
+ type = mkOption {
+ default = "";
+ example = "ext4";
+ type = types.uniq types.string;
+ description = "File system type.";
+ };
+
+ options = mkOption {
+ default = "";
+ example = "noatime";
+ type = types.string;
+ merge = concatStringsSep ",";
+ description = "Options used to mount the file system.";
+ };
+
+ mountConfig = mkOption {
+ default = {};
+ example = { DirectoryMode = "0775"; };
+ type = types.attrs;
+ description = ''
+ Each attribute in this set specifies an option in the
+ [Mount] section of the unit. See
+ systemd.mount
+ 5 for details.
+ '';
+ };
+ };
+
}
\ No newline at end of file
diff --git a/modules/system/boot/systemd.nix b/modules/system/boot/systemd.nix
index a65c95e60f99..fbd28e6e836e 100644
--- a/modules/system/boot/systemd.nix
+++ b/modules/system/boot/systemd.nix
@@ -198,6 +198,19 @@ let
};
};
+ mountConfig = { name, config, ... }: {
+ config = {
+ mountConfig =
+ { What = config.what;
+ Where = config.where;
+ } // optionalAttrs (config.type != "") {
+ Type = config.type;
+ } // optionalAttrs (config.options != "") {
+ Options = config.options;
+ };
+ };
+ };
+
toOption = x:
if x == true then "true"
else if x == false then "false"
@@ -277,6 +290,29 @@ let
'';
};
+ # this is by no means the full escaping-logic systemd uses
+ # so feel free to extend this further.
+ mountName = path:
+ let escaped = replaceChars [ "-" " " "/" ]
+ [ "\x2d" "\x20" "-" ] (toString path);
+ in if (substring 0 1 escaped == "-")
+ then substring 1 (sub (stringLength escaped) 1) escaped
+ else escaped;
+
+ mountToUnit = name: def:
+ assert def.mountConfig.What != "";
+ assert def.mountConfig.Where != "";
+ { inherit (def) wantedBy enable;
+ text =
+ ''
+ [Unit]
+ ${attrsToSection def.unitConfig}
+
+ [Mount]
+ ${attrsToSection def.mountConfig}
+ '';
+ };
+
nixosUnits = mapAttrsToList makeUnit cfg.units;
units = pkgs.runCommand "units" { preferLocalBuild = true; }
@@ -387,6 +423,17 @@ in
description = "Definition of systemd socket units.";
};
+ boot.systemd.mounts = mkOption {
+ default = [];
+ type = types.listOf types.optionSet;
+ options = [ mountOptions unitConfig mountConfig ];
+ description = ''
+ Definition of systemd mount units.
+ This is a list instead of an attrSet, because systemd mandates the names to be derived from
+ the 'where' attribute.
+ '';
+ };
+
boot.systemd.defaultUnit = mkOption {
default = "multi-user.target";
type = types.uniq types.string;
@@ -501,7 +548,10 @@ in
{ "rescue.service".text = rescueService; }
// mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets
// mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
- // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets;
+ // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets
+ // listToAttrs (map
+ (v: let n = mountName v.where;
+ in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts);
system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled [
"CGROUPS" "AUTOFS4_FS" "DEVTMPFS"