{ config, lib, pkgs, ... }: with lib; let encrypt-on-disk = { secret-name, target-host, source-file }: pkgs.stdenv.mkDerivation { name = "${target-host}-${secret-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 = { secret-name, source-file, target-host, target-file , decrypt-key, user, group, permissions }: pkgs.writeShellScript "decrypt-fudo-secret-${target-host}-${secret-name}.sh" '' rm -rf ${target-file} age -d -i ${decrypt-key} -o ${target-file} ${ encrypt-on-disk { inherit secret-name source-file target-host; } } chown ${user}:${group} ${target-file} chmod ${permissions} ${target-file} ''; secret-service = target-host: secret-name: { source-file, target-file, user, group, permissions, key-type ? "ed25519" }: { description = "decrypt secret ${secret-name} for ${target-host}."; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; ExecStartPre = pkgs.writeShellScript "prepare-${target-host}-${secret-name}-secret-dir.sh" '' TARGET_DIR=$(dirname ${target-file}) if [[ ! -d "$TARGET_DIR" ]]; then mkdir -p "$TARGET_DIR" fi ''; ExecStop = pkgs.writeShellScript "clear-${secret-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 { inherit secret-name 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-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 (attrsOf (submodule secretOpts)); description = "Map of hosts, to secrets, to secret config."; default = { }; example = { my-host = { my-host-secret = { source-file = /path/to/file/on/this/host; target-file = "/target/path/on/host"; user = "some-user"; }; }; }; }; config = { systemd.services = let hostname = config.instance.hostname; host-secrets = config.fudo.secrets.${hostname}; in mapAttrs' (secret: secretOpts: (nameValuePair "fudo-secret-${hostname}-${secret}" (secret-service hostname secret secretOpts))) host-secrets; }; }