{ pkgs, lib, config, ... }: let pass_subdir = ""; readPassword = password: "pass show '${pass_subdir}/${password.path}'" + ( if password.selector == null then " | head -n1" else (if password.selector == "@" then "" else " | tail -n +2 | yq -r '.${password.selector}'") ); readPasswordFile = password: (readPassword password) + (lib.strings.optionalString (password.transform != null) "| ${password.transform}"); passwords = builtins.attrValues config.vivarium.passwords; passwordFiles = builtins.attrValues config.vivarium.passwordFiles; generatePassword = password: '' if [ ! -f "$PASSWORD_STORE_DIR/${password.path}.gpg" ] then ${password.generator} | pass insert -m "${password.path}" fi ''; ensurePassword = password: (lib.optionalString (password.generator != null) (generatePassword password)) + '' ${readPassword password} > /dev/null ''; pipeSubstitute = k: v: " | K='${k}' V=\"$(${v})\" awk '{ gsub (ENVIRON[\"K\"], ENVIRON[\"V\"]); print }'"; renderPasswordFile = passwordFile: "echo ${lib.strings.escapeShellArg passwordFile.text} ${lib.strings.concatStrings (map (password: pipeSubstitute password.variable (readPasswordFile password)) (lib.attrsets.attrValues passwordFile.passwords))}"; installPasswordFile = passwordFile: '' sudo mkdir -p "${builtins.dirOf passwordFile.path}" ${renderPasswordFile passwordFile} | sudo tee ${passwordFile.path} > /dev/null ''; fixPermissionsPasswordFile = passwordFile: '' sudo chown ${passwordFile.owner}:${passwordFile.group} ${passwordFile.path} sudo chmod ${passwordFile.mode} ${passwordFile.path} ''; in { config = { system.extraSystemBuilderCmds = '' ln -s ${pkgs.writeShellApplication { name = "generate-passwords"; text = '' test -d "$PASSWORD_STORE_DIR" '' + lib.strings.concatLines (builtins.map ensurePassword passwords); }}/bin/generate-passwords $out/bin/generate-passwords ln -s ${pkgs.writeShellApplication { name = "install-passwords"; text = lib.strings.concatStrings (builtins.map installPasswordFile passwordFiles); }}/bin/install-passwords $out/bin/install-passwords ln -s ${pkgs.writeShellApplication { name = "fix-permissions-passwords"; text = lib.strings.concatStrings (builtins.map fixPermissionsPasswordFile passwordFiles); }}/bin/fix-permissions-passwords $out/bin/fix-permissions-passwords ''; vivarium.passwords = let passwordTypes = lib.lists.flatten (map (f: builtins.attrValues f.passwords) passwordFiles); password = passwordType: { ${passwordType.path} = { inherit (passwordType) selector generator; }; }; passwords = map password passwordTypes; in lib.attrsets.mergeAttrsList passwords; }; options = { # Using vivarium because that's where it's from, and we don't want it in home manager's frogeye # TODO Make this cleaner, merge the two, somehow vivarium = let defaultvar = "@PASSWORD@"; passwordTypeCommon = { selector = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "If unset, selects the first line. If '@', select everything. If any other value, will parse the password metadata as YML and use selector (yq)."; }; generator = lib.mkOption { type = lib.types.nullOr lib.types.str; default = "${lib.getExe pkgs.pwgen} -s 32"; description = "Command to generate the password. Won't work when selector is set to read metadata."; }; }; hostConfig = config; passwordTypeFile = { name, config, ... }: { options = passwordTypeCommon // { variable = lib.mkOption { type = lib.types.str; default = name; description = "String in the template that will be substituted by the actual password"; }; transform = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "Shell command to transform the password with before substitution"; }; path = lib.mkOption { type = lib.types.str; description = "Path to the password store entry"; }; }; }; in { passwords = lib.mkOption { default = { }; type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: { options = passwordTypeCommon // { path = lib.mkOption { type = lib.types.str; default = name; description = "Path to the password store entry"; }; }; })); }; passwordFiles = lib.mkOption { default = { }; type = lib.types.attrsOf (lib.types.submodule ({ name, config, ... }: { options = { path = lib.mkOption { type = lib.types.str; default = name; description = "Where to place the file."; }; mode = lib.mkOption { type = lib.types.str; default = "0400"; description = "Unix permission"; }; owner = lib.mkOption { type = lib.types.str; default = "root"; description = "Owner of the secret file"; }; group = lib.mkOption { type = lib.types.str; default = "root"; description = "Group of the secret file"; }; text = lib.mkOption { type = lib.types.str; default = defaultvar; description = "Content of the template used to make the file. Exclusive with `template`."; }; passwords = lib.mkOption { default = lib.optionalAttrs (config.password != null) { ${defaultvar} = config.password; }; type = lib.types.attrsOf (lib.types.submodule passwordTypeFile); description = "Paths to passwords that will substitute the variables in the template. Exclusive with `password`"; }; password = lib.mkOption { type = lib.types.submodule ({ ... }@args: passwordTypeFile (args // { name = defaultvar; })); description = "Path to password that will substitute '@PASSWORD@' in the template. Exclusive with `passwords`."; }; }; })); }; }; }; }