nixos/preload: init
[NixPkgs.git] / nixos / lib / utils.nix
blobe618cf2f861a33b0edd11b79026789644e8b1cac
1 { lib, config, pkgs }: with lib;
3 rec {
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   '';
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;
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                                   };
35       a' = normalise a;
36       b' = normalise b;
38     in hasPrefix a'.mountPoint b'.device
39     || hasPrefix a'.mountPoint b'.mountPoint
40     || any (hasPrefix a'.mountPoint) b'.depends;
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)));
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);
72   # Quotes a list of arguments into a single string for use in a Exec*
73   # line.
74   escapeSystemdExecArgs = concatMapStringsSep " " escapeSystemdExecArg;
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;
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.
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));
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
127      { _secret = "/path/to/secret" }
129      in the attrset replaced with the contents of the file
130      "/path/to/secret" in the output JSON.
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.
136      Example:
137        If the file "/path/to/secret" contains the string
138        "topsecretpassword1234",
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"
156        would generate a snippet that, when run, outputs the following
157        JSON file at "/path/to/output.json":
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";
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
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     '';
212   /* Remove packages of packagesToRemove from packages, based on their names.
213      Relies on package names and has quadratic complexity so use with caution!
215      Type:
216        removePackagesByName :: [package] -> [package] -> [package]
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;
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   };