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"