at 22.05-pre 5.6 kB view raw
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}