at 23.05-pre 8.9 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 replaceChars ["/"] ["-"] 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 replaceChars [ "%" "$" ] [ "%%" "$$" ] (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 = ''"${replaceChars [''"'' "\\"] [''\"'' "\\\\"] 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 in '' 181 if [[ -h '${output}' ]]; then 182 rm '${output}' 183 fi 184 185 inherit_errexit_enabled=0 186 shopt -pq inherit_errexit && inherit_errexit_enabled=1 187 shopt -s inherit_errexit 188 '' 189 + concatStringsSep 190 "\n" 191 (imap1 (index: name: '' 192 secret${toString index}=$(<'${secrets.${name}}') 193 export secret${toString index} 194 '') 195 (attrNames secrets)) 196 + "\n" 197 + "${pkgs.jq}/bin/jq >'${output}' " 198 + lib.escapeShellArg (concatStringsSep 199 " | " 200 (imap1 (index: name: ''${name} = $ENV.secret${toString index}'') 201 (attrNames secrets))) 202 + '' 203 <<'EOF' 204 ${builtins.toJSON set} 205 EOF 206 (( ! $inherit_errexit_enabled )) && shopt -u inherit_errexit 207 ''; 208 209 /* Remove packages of packagesToRemove from packages, based on their names. 210 Relies on package names and has quadratic complexity so use with caution! 211 212 Type: 213 removePackagesByName :: [package] -> [package] -> [package] 214 215 Example: 216 removePackagesByName [ nautilus file-roller ] [ file-roller totem ] 217 => [ nautilus ] 218 */ 219 removePackagesByName = packages: packagesToRemove: 220 let 221 namesToRemove = map lib.getName packagesToRemove; 222 in 223 lib.filter (x: !(builtins.elem (lib.getName x) namesToRemove)) packages; 224 225 systemdUtils = { 226 lib = import ./systemd-lib.nix { inherit lib config pkgs; }; 227 unitOptions = import ./systemd-unit-options.nix { inherit lib systemdUtils; }; 228 types = import ./systemd-types.nix { inherit lib systemdUtils pkgs; }; 229 }; 230}