1pkgs: with pkgs.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, e.g. /dev/xyzzy
43 # becomes dev-xyzzy. FIXME: slow.
44 escapeSystemdPath = s:
45 replaceChars ["/" "-" " "] ["-" "\\x2d" "\\x20"]
46 (removePrefix "/" s);
47
48 # Returns a system path for a given shell package
49 toShellPath = shell:
50 if types.shellPackage.check shell then
51 "/run/current-system/sw${shell.shellPath}"
52 else if types.package.check shell then
53 throw "${shell} is not a shell package"
54 else
55 shell;
56
57 /* Recurse into a list or an attrset, searching for attrs named like
58 the value of the "attr" parameter, and return an attrset where the
59 names are the corresponding jq path where the attrs were found and
60 the values are the values of the attrs.
61
62 Example:
63 recursiveGetAttrWithJqPrefix {
64 example = [
65 {
66 irrelevant = "not interesting";
67 }
68 {
69 ignored = "ignored attr";
70 relevant = {
71 secret = {
72 _secret = "/path/to/secret";
73 };
74 };
75 }
76 ];
77 } "_secret" -> { ".example[1].relevant.secret" = "/path/to/secret"; }
78 */
79 recursiveGetAttrWithJqPrefix = item: attr:
80 let
81 recurse = prefix: item:
82 if item ? ${attr} then
83 nameValuePair prefix item.${attr}
84 else if isAttrs item then
85 map (name: recurse (prefix + "." + name) item.${name}) (attrNames item)
86 else if isList item then
87 imap0 (index: item: recurse (prefix + "[${toString index}]") item) item
88 else
89 [];
90 in listToAttrs (flatten (recurse "" item));
91
92 /* Takes an attrset and a file path and generates a bash snippet that
93 outputs a JSON file at the file path with all instances of
94
95 { _secret = "/path/to/secret" }
96
97 in the attrset replaced with the contents of the file
98 "/path/to/secret" in the output JSON.
99
100 When a configuration option accepts an attrset that is finally
101 converted to JSON, this makes it possible to let the user define
102 arbitrary secret values.
103
104 Example:
105 If the file "/path/to/secret" contains the string
106 "topsecretpassword1234",
107
108 genJqSecretsReplacementSnippet {
109 example = [
110 {
111 irrelevant = "not interesting";
112 }
113 {
114 ignored = "ignored attr";
115 relevant = {
116 secret = {
117 _secret = "/path/to/secret";
118 };
119 };
120 }
121 ];
122 } "/path/to/output.json"
123
124 would generate a snippet that, when run, outputs the following
125 JSON file at "/path/to/output.json":
126
127 {
128 "example": [
129 {
130 "irrelevant": "not interesting"
131 },
132 {
133 "ignored": "ignored attr",
134 "relevant": {
135 "secret": "topsecretpassword1234"
136 }
137 }
138 ]
139 }
140 */
141 genJqSecretsReplacementSnippet = genJqSecretsReplacementSnippet' "_secret";
142
143 # Like genJqSecretsReplacementSnippet, but allows the name of the
144 # attr which identifies the secret to be changed.
145 genJqSecretsReplacementSnippet' = attr: set: output:
146 let
147 secrets = recursiveGetAttrWithJqPrefix set attr;
148 in ''
149 if [[ -h '${output}' ]]; then
150 rm '${output}'
151 fi
152 ''
153 + concatStringsSep
154 "\n"
155 (imap1 (index: name: "export secret${toString index}=$(<'${secrets.${name}}')")
156 (attrNames secrets))
157 + "\n"
158 + "${pkgs.jq}/bin/jq >'${output}' '"
159 + concatStringsSep
160 " | "
161 (imap1 (index: name: ''${name} = $ENV.secret${toString index}'')
162 (attrNames secrets))
163 + ''
164 ' <<'EOF'
165 ${builtins.toJSON set}
166 EOF
167 '';
168}