{ config, lib, pkgs, ... }: let inherit (lib) mapAttrs' nameValuePair mkMerge mkOption mkIf any types; hackterpolate = s: let parts = builtins.split "(\\\$\\{[^}]+})" s; unescape = s: builtins.fromJSON ''"${s}"''; strParts = lib.imap1 (i: v: if lib.mod i 2 == 0 then unescape (builtins.elemAt v 0) else v) parts; in lib.concatStrings strParts; cfg = config.my.acme; accountsEndpoints = mapAttrs' (name: value: nameValuePair "acme_account_${name}" { depends_on = [ "vault_mount.acme" ]; path = "acme/accounts/${name}"; disable_read = true; data_json = hackterpolate (builtins.toJSON value); }) cfg.accounts; rolesEndpoints = mapAttrs' (name: value: nameValuePair "acme_role_${name}" { depends_on = [ "vault_mount.acme" ]; path = "acme/roles/${name}"; disable_read = true; data_json = hackterpolate (builtins.toJSON value); }) cfg.roles; mkMergeIf = things: mkIf (any (t: t != { }) things) (mkMerge things); in { options = { my.acme.mountPoint = mkOption { default = "acme"; type = types.str; }; my.acme.accounts = mkOption { default = {}; type = (pkgs.formats.json { }).type; }; my.acme.roles = mkOption { default = {}; type = types.attrsOf (types.submodule ({ name, ... }: { options = { account = mkOption { type = types.str; default = name; }; allow_bare_domains = mkOption { type = types.bool; default = true; }; allow_subdomains = mkOption { type = types.bool; default = true; }; allowed_domains = mkOption { type = with types; listOf str; }; cache_for_ratio = mkOption { type = types.int; default = 20; }; disable_cache = mkOption { type = types.bool; default = false; }; }; })); }; }; config = { resource.vault_mount.acme = { path = config.my.acme.mountPoint; type = "acme"; max_lease_ttl_seconds = 90 * 86400; default_lease_ttl_seconds = 90 * 86400; }; resource.vault_generic_endpoint = mkMergeIf [ accountsEndpoints rolesEndpoints ]; }; }