diff options
author | nikstur <nikstur@outlook.com> | 2024-02-03 22:40:40 +0100 |
---|---|---|
committer | nikstur <nikstur@outlook.com> | 2024-02-07 22:13:00 +0100 |
commit | 1c1cfa073a80354fc2814265f1550bd537c01908 (patch) | |
tree | 650836b648e23995e81ad031d23633bf7750b068 /nixos/modules/tasks | |
parent | b87fbb96ad34b559e0c130572b09bc9b49a29b75 (diff) |
nixos/filesystems: init overlayfs
Diffstat (limited to 'nixos/modules/tasks')
-rw-r--r-- | nixos/modules/tasks/filesystems/overlayfs.nix | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/nixos/modules/tasks/filesystems/overlayfs.nix b/nixos/modules/tasks/filesystems/overlayfs.nix new file mode 100644 index 0000000000000..e71ef9ba62e9c --- /dev/null +++ b/nixos/modules/tasks/filesystems/overlayfs.nix @@ -0,0 +1,144 @@ +{ config, lib, pkgs, utils, ... }: + +let + # The scripted initrd contains some magic to add the prefix to the + # paths just in time, so we don't add it here. + sysrootPrefix = fs: + if config.boot.initrd.systemd.enable && (utils.fsNeededForBoot fs) then + "/sysroot" + else + ""; + + # Returns a service that creates the required directories before the mount is + # created. + preMountService = _name: fs: + let + prefix = sysrootPrefix fs; + + escapedMountpoint = utils.escapeSystemdPath (prefix + fs.mountPoint); + mountUnit = "${escapedMountpoint}.mount"; + + upperdir = prefix + fs.overlay.upperdir; + workdir = prefix + fs.overlay.workdir; + in + lib.mkIf (fs.overlay.upperdir != null) + { + "rw-${escapedMountpoint}" = { + requiredBy = [ mountUnit ]; + before = [ mountUnit ]; + unitConfig = { + DefaultDependencies = false; + RequiresMountsFor = "${upperdir} ${workdir}"; + }; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.coreutils}/bin/mkdir -p -m 0755 ${upperdir} ${workdir}"; + }; + }; + }; + + overlayOpts = { config, ... }: { + + options.overlay = { + + lowerdir = lib.mkOption { + type = with lib.types; nullOr (nonEmptyListOf (either str pathInStore)); + default = null; + description = lib.mdDoc '' + The list of path(s) to the lowerdir(s). + + To create a writable overlay, you MUST provide an upperdir and a + workdir. + + You can create a read-only overlay when you provide multiple (at + least 2!) lowerdirs and neither an upperdir nor a workdir. + ''; + }; + + upperdir = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = lib.mdDoc '' + The path to the upperdir. + + If this is null, a read-only overlay is created using the lowerdir. + + If you set this to some value you MUST also set `workdir`. + ''; + }; + + workdir = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = lib.mdDoc '' + The path to the workdir. + + This MUST be set if you set `upperdir`. + ''; + }; + + }; + + config = lib.mkIf (config.overlay.lowerdir != null) { + fsType = "overlay"; + device = lib.mkDefault "overlay"; + + options = + let + prefix = sysrootPrefix config; + + lowerdir = map (s: prefix + s) config.overlay.lowerdir; + upperdir = prefix + config.overlay.upperdir; + workdir = prefix + config.overlay.workdir; + in + [ + "lowerdir=${lib.concatStringsSep ":" lowerdir}" + ] ++ lib.optionals (config.overlay.upperdir != null) [ + "upperdir=${upperdir}" + "workdir=${workdir}" + ] ++ (map (s: "x-systemd.requires-mounts-for=${s}") lowerdir); + }; + + }; +in + +{ + + options = { + + # Merge the overlay options into the fileSystems option. + fileSystems = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule [ overlayOpts ]); + }; + + }; + + config = + let + overlayFileSystems = lib.filterAttrs (_name: fs: (fs.overlay.lowerdir != null)) config.fileSystems; + initrdFileSystems = lib.filterAttrs (_name: utils.fsNeededForBoot) overlayFileSystems; + userspaceFileSystems = lib.filterAttrs (_name: fs: (!utils.fsNeededForBoot fs)) overlayFileSystems; + in + { + + boot.initrd.availableKernelModules = lib.mkIf (initrdFileSystems != { }) [ "overlay" ]; + + assertions = lib.concatLists (lib.mapAttrsToList + (_name: fs: [ + { + assertion = (fs.overlay.upperdir == null) == (fs.overlay.workdir == null); + message = "You cannot define a `lowerdir` without a `workdir` and vice versa for mount point: ${fs.mountPoint}"; + } + { + assertion = (fs.overlay.lowerdir != null && fs.overlay.upperdir == null) -> (lib.length fs.overlay.lowerdir) >= 2; + message = "A read-only overlay (without an `upperdir`) requires at least 2 `lowerdir`s: ${fs.mountPoint}"; + } + ]) + config.fileSystems); + + boot.initrd.systemd.services = lib.mkMerge (lib.mapAttrsToList preMountService initrdFileSystems); + systemd.services = lib.mkMerge (lib.mapAttrsToList preMountService userspaceFileSystems); + + }; + +} |