at 24.11-pre 4.4 kB view raw
1{ config, lib, pkgs, utils, ... }: 2 3let 4 # The scripted initrd contains some magic to add the prefix to the 5 # paths just in time, so we don't add it here. 6 sysrootPrefix = fs: 7 if config.boot.initrd.systemd.enable && (utils.fsNeededForBoot fs) then 8 "/sysroot" 9 else 10 ""; 11 12 # Returns a service that creates the required directories before the mount is 13 # created. 14 preMountService = _name: fs: 15 let 16 prefix = sysrootPrefix fs; 17 18 escapedMountpoint = utils.escapeSystemdPath (prefix + fs.mountPoint); 19 mountUnit = "${escapedMountpoint}.mount"; 20 21 upperdir = prefix + fs.overlay.upperdir; 22 workdir = prefix + fs.overlay.workdir; 23 in 24 lib.mkIf (fs.overlay.upperdir != null) 25 { 26 "rw-${escapedMountpoint}" = { 27 requiredBy = [ mountUnit ]; 28 before = [ mountUnit ]; 29 unitConfig = { 30 DefaultDependencies = false; 31 RequiresMountsFor = "${upperdir} ${workdir}"; 32 }; 33 serviceConfig = { 34 Type = "oneshot"; 35 ExecStart = "${pkgs.coreutils}/bin/mkdir -p -m 0755 ${upperdir} ${workdir}"; 36 }; 37 }; 38 }; 39 40 overlayOpts = { config, ... }: { 41 42 options.overlay = { 43 44 lowerdir = lib.mkOption { 45 type = with lib.types; nullOr (nonEmptyListOf (either str pathInStore)); 46 default = null; 47 description = '' 48 The list of path(s) to the lowerdir(s). 49 50 To create a writable overlay, you MUST provide an upperdir and a 51 workdir. 52 53 You can create a read-only overlay when you provide multiple (at 54 least 2!) lowerdirs and neither an upperdir nor a workdir. 55 ''; 56 }; 57 58 upperdir = lib.mkOption { 59 type = lib.types.nullOr lib.types.str; 60 default = null; 61 description = '' 62 The path to the upperdir. 63 64 If this is null, a read-only overlay is created using the lowerdir. 65 66 If you set this to some value you MUST also set `workdir`. 67 ''; 68 }; 69 70 workdir = lib.mkOption { 71 type = lib.types.nullOr lib.types.str; 72 default = null; 73 description = '' 74 The path to the workdir. 75 76 This MUST be set if you set `upperdir`. 77 ''; 78 }; 79 80 }; 81 82 config = lib.mkIf (config.overlay.lowerdir != null) { 83 fsType = "overlay"; 84 device = lib.mkDefault "overlay"; 85 86 options = 87 let 88 prefix = sysrootPrefix config; 89 90 lowerdir = map (s: prefix + s) config.overlay.lowerdir; 91 upperdir = prefix + config.overlay.upperdir; 92 workdir = prefix + config.overlay.workdir; 93 in 94 [ 95 "lowerdir=${lib.concatStringsSep ":" lowerdir}" 96 ] ++ lib.optionals (config.overlay.upperdir != null) [ 97 "upperdir=${upperdir}" 98 "workdir=${workdir}" 99 ] ++ (map (s: "x-systemd.requires-mounts-for=${s}") lowerdir); 100 }; 101 102 }; 103in 104 105{ 106 107 options = { 108 109 # Merge the overlay options into the fileSystems option. 110 fileSystems = lib.mkOption { 111 type = lib.types.attrsOf (lib.types.submodule [ overlayOpts ]); 112 }; 113 114 }; 115 116 config = 117 let 118 overlayFileSystems = lib.filterAttrs (_name: fs: (fs.overlay.lowerdir != null)) config.fileSystems; 119 initrdFileSystems = lib.filterAttrs (_name: utils.fsNeededForBoot) overlayFileSystems; 120 userspaceFileSystems = lib.filterAttrs (_name: fs: (!utils.fsNeededForBoot fs)) overlayFileSystems; 121 in 122 { 123 124 boot.initrd.availableKernelModules = lib.mkIf (initrdFileSystems != { }) [ "overlay" ]; 125 126 assertions = lib.concatLists (lib.mapAttrsToList 127 (_name: fs: [ 128 { 129 assertion = (fs.overlay.upperdir == null) == (fs.overlay.workdir == null); 130 message = "You cannot define a `lowerdir` without a `workdir` and vice versa for mount point: ${fs.mountPoint}"; 131 } 132 { 133 assertion = (fs.overlay.lowerdir != null && fs.overlay.upperdir == null) -> (lib.length fs.overlay.lowerdir) >= 2; 134 message = "A read-only overlay (without an `upperdir`) requires at least 2 `lowerdir`s: ${fs.mountPoint}"; 135 } 136 ]) 137 config.fileSystems); 138 139 boot.initrd.systemd.services = lib.mkMerge (lib.mapAttrsToList preMountService initrdFileSystems); 140 systemd.services = lib.mkMerge (lib.mapAttrsToList preMountService userspaceFileSystems); 141 142 }; 143 144}