{ config, lib, pkgs, ... }: with lib; let all-secrets = config.fudo.secrets; encrypt-on-disk = name: { target-host, source-file }: pkgs.stdenv.mkDerivation { name = "${name}-secret"; phases = "installPhase"; buildInputs = [ pkgs.age ]; installPhase = let key = config.fudo.hosts.${target-host}.ssh-pubkey; in '' age -a -r "${key}" -o $out ${source-file} ''; }; decrypt-script = name: { source-file, target-host, target-file, decrypt-key, user, group , permissions }: pkgs.writeShellScript "decrypt-fudo-secret-${name}.sh" '' rm -rf ${target-file} age -d -i ${decrypt-key} -o ${target-file} ${ encrypt-on-disk name { inherit source-file target-host; } } chown ${user}:${group} ${target-file} chmod ${permissions} ${target-file} ''; secret-service = name: { source-file, target-host, target-file, user, group, permissions , key-type ? "ed25519" }: { description = "decrypt secret ${name} for ${target-host}."; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; ExecStartPre = pkgs.writeShellScript "prepare-${name}-secret-dir.sh" '' TARGET_DIR=$(dirname ${target-file}) if [[ ! -d "$TARGET_DIR" ]]; then mkdir -p "$TARGET_DIR" fi ''; ExecStop = pkgs.writeShellScript "clear-${name}-secret.sh" '' rm -f ${target-file} ''; ExecStart = let decrypt-keys = filter (key: key.type == key-type) config.services.openssh.hostKeys; decrypt-key = head (map (key: key.path) decrypt-keys); in decrypt-script name { inherit source-file target-host target-file decrypt-key user group permissions; }; }; path = [ pkgs.age ]; }; secretOpts = { ... }: { options = with types; { source-file = mkOption { type = path; description = "File from which to load the secret."; }; target-host = mkOption { type = str; description = "Host to which the secret belongs (determins SSH key to encrypt)."; }; target-file = mkOption { type = str; description = "Target file on the host; the secret will be decrypted to this file."; }; user = mkOption { type = str; description = "User (on target host) to which the file will belong."; }; group = mkOption { type = str; description = "Group (on target host) to which the file will belong."; default = "nogroup"; }; permissions = mkOption { type = str; description = "Permissions to set on the target file."; default = "0400"; }; }; }; in { options.fudo.secrets = with types; mkOption { type = attrsOf (submodule secretOpts); description = "Map of secrets to secret config."; default = { }; }; config = { systemd.services = let hostname = config.instance.hostname; host-secrets = filterAttrs (secret: secretOpts: secretOpts.target-host == hostname) all-secrets; in mapAttrs' (secret: secretOpts: (nameValuePair "fudo-secret-${secret}" (secret-service secret secretOpts))) host-secrets; }; }