at 24.05-pre 9.1 kB view raw
1{ lib, config, pkgs }: with lib; 2 3rec { 4 5 # Copy configuration files to avoid having the entire sources in the system closure 6 copyFile = filePath: pkgs.runCommand (builtins.unsafeDiscardStringContext (builtins.baseNameOf filePath)) {} '' 7 cp ${filePath} $out 8 ''; 9 10 # Check whenever fileSystem is needed for boot. NOTE: Make sure 11 # pathsNeededForBoot is closed under the parent relationship, i.e. if /a/b/c 12 # is in the list, put /a and /a/b in as well. 13 pathsNeededForBoot = [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/var/lib/nixos" "/etc" "/usr" ]; 14 fsNeededForBoot = fs: fs.neededForBoot || elem fs.mountPoint pathsNeededForBoot; 15 16 # Check whenever `b` depends on `a` as a fileSystem 17 fsBefore = a: b: 18 let 19 # normalisePath adds a slash at the end of the path if it didn't already 20 # have one. 21 # 22 # The reason slashes are added at the end of each path is to prevent `b` 23 # from accidentally depending on `a` in cases like 24 # a = { mountPoint = "/aaa"; ... } 25 # b = { device = "/aaaa"; ... } 26 # Here a.mountPoint *is* a prefix of b.device even though a.mountPoint is 27 # *not* a parent of b.device. If we add a slash at the end of each string, 28 # though, this is not a problem: "/aaa/" is not a prefix of "/aaaa/". 29 normalisePath = path: "${path}${optionalString (!(hasSuffix "/" path)) "/"}"; 30 normalise = mount: mount // { device = normalisePath (toString mount.device); 31 mountPoint = normalisePath mount.mountPoint; 32 depends = map normalisePath mount.depends; 33 }; 34 35 a' = normalise a; 36 b' = normalise b; 37 38 in hasPrefix a'.mountPoint b'.device 39 || hasPrefix a'.mountPoint b'.mountPoint 40 || any (hasPrefix a'.mountPoint) b'.depends; 41 42 # Escape a path according to the systemd rules. FIXME: slow 43 # The rules are described in systemd.unit(5) as follows: 44 # The escaping algorithm operates as follows: given a string, any "/" character is replaced by "-", and all other characters which are not ASCII alphanumerics, ":", "_" or "." are replaced by C-style "\x2d" escapes. In addition, "." is replaced with such a C-style escape when it would appear as the first character in the escaped string. 45 # When the input qualifies as absolute file system path, this algorithm is extended slightly: the path to the root directory "/" is encoded as single dash "-". In addition, any leading, trailing or duplicate "/" characters are removed from the string before transformation. Example: /foo//bar/baz/ becomes "foo-bar-baz". 46 escapeSystemdPath = s: let 47 replacePrefix = p: r: s: (if (hasPrefix p s) then r + (removePrefix p s) else s); 48 trim = s: removeSuffix "/" (removePrefix "/" s); 49 normalizedPath = strings.normalizePath s; 50 in 51 replaceStrings ["/"] ["-"] 52 (replacePrefix "." (strings.escapeC ["."] ".") 53 (strings.escapeC (stringToCharacters " !\"#$%&'()*+,;<=>=@[\\]^`{|}~-") 54 (if normalizedPath == "/" then normalizedPath else trim normalizedPath))); 55 56 # Quotes an argument for use in Exec* service lines. 57 # systemd accepts "-quoted strings with escape sequences, toJSON produces 58 # a subset of these. 59 # Additionally we escape % to disallow expansion of % specifiers. Any lone ; 60 # in the input will be turned it ";" and thus lose its special meaning. 61 # Every $ is escaped to $$, this makes it unnecessary to disable environment 62 # substitution for the directive. 63 escapeSystemdExecArg = arg: 64 let 65 s = if builtins.isPath arg then "${arg}" 66 else if builtins.isString arg then arg 67 else if builtins.isInt arg || builtins.isFloat arg then toString arg 68 else throw "escapeSystemdExecArg only allows strings, paths and numbers"; 69 in 70 replaceStrings [ "%" "$" ] [ "%%" "$$" ] (builtins.toJSON s); 71 72 # Quotes a list of arguments into a single string for use in a Exec* 73 # line. 74 escapeSystemdExecArgs = concatMapStringsSep " " escapeSystemdExecArg; 75 76 # Returns a system path for a given shell package 77 toShellPath = shell: 78 if types.shellPackage.check shell then 79 "/run/current-system/sw${shell.shellPath}" 80 else if types.package.check shell then 81 throw "${shell} is not a shell package" 82 else 83 shell; 84 85 /* Recurse into a list or an attrset, searching for attrs named like 86 the value of the "attr" parameter, and return an attrset where the 87 names are the corresponding jq path where the attrs were found and 88 the values are the values of the attrs. 89 90 Example: 91 recursiveGetAttrWithJqPrefix { 92 example = [ 93 { 94 irrelevant = "not interesting"; 95 } 96 { 97 ignored = "ignored attr"; 98 relevant = { 99 secret = { 100 _secret = "/path/to/secret"; 101 }; 102 }; 103 } 104 ]; 105 } "_secret" -> { ".example[1].relevant.secret" = "/path/to/secret"; } 106 */ 107 recursiveGetAttrWithJqPrefix = item: attr: 108 let 109 recurse = prefix: item: 110 if item ? ${attr} then 111 nameValuePair prefix item.${attr} 112 else if isAttrs item then 113 map (name: 114 let 115 escapedName = ''"${replaceStrings [''"'' "\\"] [''\"'' "\\\\"] name}"''; 116 in 117 recurse (prefix + "." + escapedName) item.${name}) (attrNames item) 118 else if isList item then 119 imap0 (index: item: recurse (prefix + "[${toString index}]") item) item 120 else 121 []; 122 in listToAttrs (flatten (recurse "" item)); 123 124 /* Takes an attrset and a file path and generates a bash snippet that 125 outputs a JSON file at the file path with all instances of 126 127 { _secret = "/path/to/secret" } 128 129 in the attrset replaced with the contents of the file 130 "/path/to/secret" in the output JSON. 131 132 When a configuration option accepts an attrset that is finally 133 converted to JSON, this makes it possible to let the user define 134 arbitrary secret values. 135 136 Example: 137 If the file "/path/to/secret" contains the string 138 "topsecretpassword1234", 139 140 genJqSecretsReplacementSnippet { 141 example = [ 142 { 143 irrelevant = "not interesting"; 144 } 145 { 146 ignored = "ignored attr"; 147 relevant = { 148 secret = { 149 _secret = "/path/to/secret"; 150 }; 151 }; 152 } 153 ]; 154 } "/path/to/output.json" 155 156 would generate a snippet that, when run, outputs the following 157 JSON file at "/path/to/output.json": 158 159 { 160 "example": [ 161 { 162 "irrelevant": "not interesting" 163 }, 164 { 165 "ignored": "ignored attr", 166 "relevant": { 167 "secret": "topsecretpassword1234" 168 } 169 } 170 ] 171 } 172 */ 173 genJqSecretsReplacementSnippet = genJqSecretsReplacementSnippet' "_secret"; 174 175 # Like genJqSecretsReplacementSnippet, but allows the name of the 176 # attr which identifies the secret to be changed. 177 genJqSecretsReplacementSnippet' = attr: set: output: 178 let 179 secrets = recursiveGetAttrWithJqPrefix set attr; 180 stringOrDefault = str: def: if str == "" then def else str; 181 in '' 182 if [[ -h '${output}' ]]; then 183 rm '${output}' 184 fi 185 186 inherit_errexit_enabled=0 187 shopt -pq inherit_errexit && inherit_errexit_enabled=1 188 shopt -s inherit_errexit 189 '' 190 + concatStringsSep 191 "\n" 192 (imap1 (index: name: '' 193 secret${toString index}=$(<'${secrets.${name}}') 194 export secret${toString index} 195 '') 196 (attrNames secrets)) 197 + "\n" 198 + "${pkgs.jq}/bin/jq >'${output}' " 199 + lib.escapeShellArg (stringOrDefault 200 (concatStringsSep 201 " | " 202 (imap1 (index: name: ''${name} = $ENV.secret${toString index}'') 203 (attrNames secrets))) 204 ".") 205 + '' 206 <<'EOF' 207 ${builtins.toJSON set} 208 EOF 209 (( ! $inherit_errexit_enabled )) && shopt -u inherit_errexit 210 ''; 211 212 /* Remove packages of packagesToRemove from packages, based on their names. 213 Relies on package names and has quadratic complexity so use with caution! 214 215 Type: 216 removePackagesByName :: [package] -> [package] -> [package] 217 218 Example: 219 removePackagesByName [ nautilus file-roller ] [ file-roller totem ] 220 => [ nautilus ] 221 */ 222 removePackagesByName = packages: packagesToRemove: 223 let 224 namesToRemove = map lib.getName packagesToRemove; 225 in 226 lib.filter (x: !(builtins.elem (lib.getName x) namesToRemove)) packages; 227 228 systemdUtils = { 229 lib = import ./systemd-lib.nix { inherit lib config pkgs; }; 230 unitOptions = import ./systemd-unit-options.nix { inherit lib systemdUtils; }; 231 types = import ./systemd-types.nix { inherit lib systemdUtils pkgs; }; 232 network = { 233 units = import ./systemd-network-units.nix { inherit lib systemdUtils; }; 234 }; 235 }; 236}