36 inherit (lib.strings) toJSON normalizePath escapeC;
42 # Copy configuration files to avoid having the entire sources in the system closure
43 copyFile = filePath: pkgs.runCommand (builtins.unsafeDiscardStringContext (baseNameOf filePath)) {} ''
47 # Check whenever fileSystem is needed for boot. NOTE: Make sure
48 # pathsNeededForBoot is closed under the parent relationship, i.e. if /a/b/c
49 # is in the list, put /a and /a/b in as well.
50 pathsNeededForBoot = [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/var/lib/nixos" "/etc" "/usr" ];
51 fsNeededForBoot = fs: fs.neededForBoot || elem fs.mountPoint pathsNeededForBoot;
53 # Check whenever `b` depends on `a` as a fileSystem
56 # normalisePath adds a slash at the end of the path if it didn't already
59 # The reason slashes are added at the end of each path is to prevent `b`
60 # from accidentally depending on `a` in cases like
61 # a = { mountPoint = "/aaa"; ... }
62 # b = { device = "/aaaa"; ... }
63 # Here a.mountPoint *is* a prefix of b.device even though a.mountPoint is
64 # *not* a parent of b.device. If we add a slash at the end of each string,
65 # though, this is not a problem: "/aaa/" is not a prefix of "/aaaa/".
66 normalisePath = path: "${path}${optionalString (!(hasSuffix "/" path)) "/"}";
67 normalise = mount: mount // { device = normalisePath (toString mount.device);
68 mountPoint = normalisePath mount.mountPoint;
69 depends = map normalisePath mount.depends;
75 in hasPrefix a'.mountPoint b'.device
76 || hasPrefix a'.mountPoint b'.mountPoint
77 || any (hasPrefix a'.mountPoint) b'.depends;
79 # Escape a path according to the systemd rules. FIXME: slow
80 # The rules are described in systemd.unit(5) as follows:
81 # 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.
82 # 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".
83 escapeSystemdPath = s: let
84 replacePrefix = p: r: s: (if (hasPrefix p s) then r + (removePrefix p s) else s);
85 trim = s: removeSuffix "/" (removePrefix "/" s);
86 normalizedPath = normalizePath s;
88 replaceStrings ["/"] ["-"]
89 (replacePrefix "." (escapeC ["."] ".")
90 (escapeC (stringToCharacters " !\"#$%&'()*+,;<=>=@[\\]^`{|}~-")
91 (if normalizedPath == "/" then normalizedPath else trim normalizedPath)));
93 # Quotes an argument for use in Exec* service lines.
94 # systemd accepts "-quoted strings with escape sequences, toJSON produces
96 # Additionally we escape % to disallow expansion of % specifiers. Any lone ;
97 # in the input will be turned it ";" and thus lose its special meaning.
98 # Every $ is escaped to $$, this makes it unnecessary to disable environment
99 # substitution for the directive.
100 escapeSystemdExecArg = arg:
102 s = if isPath arg then "${arg}"
103 else if isString arg then arg
104 else if isInt arg || isFloat arg || isDerivation arg then toString arg
105 else throw "escapeSystemdExecArg only allows strings, paths, numbers and derivations";
107 replaceStrings [ "%" "$" ] [ "%%" "$$" ] (toJSON s);
109 # Quotes a list of arguments into a single string for use in a Exec*
111 escapeSystemdExecArgs = concatMapStringsSep " " escapeSystemdExecArg;
113 # Returns a system path for a given shell package
115 if types.shellPackage.check shell then
116 "/run/current-system/sw${shell.shellPath}"
117 else if types.package.check shell then
118 throw "${shell} is not a shell package"
122 /* Recurse into a list or an attrset, searching for attrs named like
123 the value of the "attr" parameter, and return an attrset where the
124 names are the corresponding jq path where the attrs were found and
125 the values are the values of the attrs.
128 recursiveGetAttrWithJqPrefix {
131 irrelevant = "not interesting";
134 ignored = "ignored attr";
137 _secret = "/path/to/secret";
142 } "_secret" -> { ".example[1].relevant.secret" = "/path/to/secret"; }
144 recursiveGetAttrWithJqPrefix = item: attr: mapAttrs (_name: set: set.${attr}) (recursiveGetAttrsetWithJqPrefix item attr);
146 /* Similar to `recursiveGetAttrWithJqPrefix`, but returns the whole
147 attribute set containing `attr` instead of the value of `attr` in
151 recursiveGetAttrsetWithJqPrefix {
154 irrelevant = "not interesting";
157 ignored = "ignored attr";
160 _secret = "/path/to/secret";
166 } "_secret" -> { ".example[1].relevant.secret" = { _secret = "/path/to/secret"; quote = true; }; }
168 recursiveGetAttrsetWithJqPrefix = item: attr:
170 recurse = prefix: item:
171 if item ? ${attr} then
172 nameValuePair prefix item
173 else if isDerivation item then []
174 else if isAttrs item then
177 escapedName = ''"${replaceStrings [''"'' "\\"] [''\"'' "\\\\"] name}"'';
179 recurse (prefix + "." + escapedName) item.${name}) (attrNames item)
180 else if isList item then
181 imap0 (index: item: recurse (prefix + "[${toString index}]") item) item
184 in listToAttrs (flatten (recurse "" item));
186 /* Takes an attrset and a file path and generates a bash snippet that
187 outputs a JSON file at the file path with all instances of
189 { _secret = "/path/to/secret" }
191 in the attrset replaced with the contents of the file
192 "/path/to/secret" in the output JSON.
194 When a configuration option accepts an attrset that is finally
195 converted to JSON, this makes it possible to let the user define
196 arbitrary secret values.
199 If the file "/path/to/secret" contains the string
200 "topsecretpassword1234",
202 genJqSecretsReplacementSnippet {
205 irrelevant = "not interesting";
208 ignored = "ignored attr";
211 _secret = "/path/to/secret";
216 } "/path/to/output.json"
218 would generate a snippet that, when run, outputs the following
219 JSON file at "/path/to/output.json":
224 "irrelevant": "not interesting"
227 "ignored": "ignored attr",
229 "secret": "topsecretpassword1234"
235 The attribute set { _secret = "/path/to/secret"; } can contain extra
236 options, currently it accepts the `quote = true|false` option.
238 If `quote = true` (default behavior), the content of the secret file will
239 be quoted as a string and embedded. Otherwise, if `quote = false`, the
240 content of the secret file will be parsed to JSON and then embedded.
243 If the file "/path/to/secret" contains the JSON document:
246 { "a": "topsecretpassword1234" },
247 { "b": "topsecretpassword5678" }
250 genJqSecretsReplacementSnippet {
253 irrelevant = "not interesting";
256 ignored = "ignored attr";
259 _secret = "/path/to/secret";
265 } "/path/to/output.json"
267 would generate a snippet that, when run, outputs the following
268 JSON file at "/path/to/output.json":
273 "irrelevant": "not interesting"
276 "ignored": "ignored attr",
279 { "a": "topsecretpassword1234" },
280 { "b": "topsecretpassword5678" }
287 genJqSecretsReplacementSnippet = genJqSecretsReplacementSnippet' "_secret";
289 # Like genJqSecretsReplacementSnippet, but allows the name of the
290 # attr which identifies the secret to be changed.
291 genJqSecretsReplacementSnippet' = attr: set: output:
293 secretsRaw = recursiveGetAttrsetWithJqPrefix set attr;
294 # Set default option values
295 secrets = mapAttrs (_name: set: {
297 } // set) secretsRaw;
298 stringOrDefault = str: def: if str == "" then def else str;
300 if [[ -h '${output}' ]]; then
304 inherit_errexit_enabled=0
305 shopt -pq inherit_errexit && inherit_errexit_enabled=1
306 shopt -s inherit_errexit
310 (imap1 (index: name: ''
311 secret${toString index}=$(<'${secrets.${name}.${attr}}')
312 export secret${toString index}
316 + "${pkgs.jq}/bin/jq >'${output}' "
317 + escapeShellArg (stringOrDefault
320 (imap1 (index: name: ''${name} = ($ENV.secret${toString index}${optionalString (!secrets.${name}.quote) " | fromjson"})'')
321 (attrNames secrets)))
327 (( ! $inherit_errexit_enabled )) && shopt -u inherit_errexit
330 /* Remove packages of packagesToRemove from packages, based on their names.
331 Relies on package names and has quadratic complexity so use with caution!
334 removePackagesByName :: [package] -> [package] -> [package]
337 removePackagesByName [ nautilus file-roller ] [ file-roller totem ]
340 removePackagesByName = packages: packagesToRemove:
342 namesToRemove = map getName packagesToRemove;
344 filter (x: !(elem (getName x) namesToRemove)) packages;
347 lib = import ./systemd-lib.nix { inherit lib config pkgs utils; };
348 unitOptions = import ./systemd-unit-options.nix { inherit lib systemdUtils; };
349 types = import ./systemd-types.nix { inherit lib systemdUtils pkgs; };
351 units = import ./systemd-network-units.nix { inherit lib systemdUtils; };