Formatting

This commit is contained in:
niten 2023-10-11 12:03:07 -07:00
parent 1e5cdcba79
commit 5079561573

View File

@ -1,108 +1,107 @@
{ config, lib, pkgs, ... } @ toplevel: { config, lib, pkgs, ... }@toplevel:
with lib; with lib;
let let
hostname = config.instance.hostname; hostname = config.instance.hostname;
domainOpts = { name, ... }: let domainOpts = { name, ... }:
domain = name; let domain = name;
in { in {
options = with types; { options = with types; {
admin-email = mkOption { admin-email = mkOption {
type = str; type = str;
description = "Domain administrator email."; description = "Domain administrator email.";
default = "admin@${domain}"; default = "admin@${domain}";
}; };
extra-domains = mkOption { extra-domains = mkOption {
type = listOf str; type = listOf str;
description = "List of domains to add to this certificate."; description = "List of domains to add to this certificate.";
default = []; default = [ ];
}; };
local-copies = let local-copies = let
localCopyOpts = { name, ... }: let localCopyOpts = { name, ... }:
copy = name; let copy = name;
in { in {
options = with types; let options = with types;
target-path = "/run/ssl-certificates/${domain}/${copy}"; let target-path = "/run/ssl-certificates/${domain}/${copy}";
in { in {
user = mkOption { user = mkOption {
type = str; type = str;
description = "User to which this copy belongs."; description = "User to which this copy belongs.";
}; };
group = mkOption { group = mkOption {
type = nullOr str; type = nullOr str;
description = "Group to which this copy belongs."; description = "Group to which this copy belongs.";
default = null; default = null;
}; };
service = mkOption { service = mkOption {
type = str; type = str;
description = "systemd job to copy certs."; description = "systemd job to copy certs.";
default = "fudo-acme-${domain}-${copy}-certs.service"; default = "fudo-acme-${domain}-${copy}-certs.service";
}; };
certificate = mkOption { certificate = mkOption {
type = str; type = str;
description = "Full path to the local copy certificate."; description = "Full path to the local copy certificate.";
default = "${target-path}/cert.pem"; default = "${target-path}/cert.pem";
}; };
full-certificate = mkOption { full-certificate = mkOption {
type = str; type = str;
description = "Full path to the local copy certificate."; description = "Full path to the local copy certificate.";
default = "${target-path}/fullchain.pem"; default = "${target-path}/fullchain.pem";
}; };
chain = mkOption { chain = mkOption {
type = str; type = str;
description = "Full path to the local copy certificate."; description = "Full path to the local copy certificate.";
default = "${target-path}/chain.pem"; default = "${target-path}/chain.pem";
}; };
private-key = mkOption { private-key = mkOption {
type = str; type = str;
description = "Full path to the local copy certificate."; description = "Full path to the local copy certificate.";
default = "${target-path}/key.pem"; default = "${target-path}/key.pem";
}; };
dependent-services = mkOption { dependent-services = mkOption {
type = listOf str; type = listOf str;
description = "List of systemd services depending on this copy."; description =
default = [ ]; "List of systemd services depending on this copy.";
}; default = [ ];
};
part-of = mkOption {
type = listOf str; part-of = mkOption {
description = "List of systemd targets to which this copy belongs."; type = listOf str;
default = [ ]; description =
}; "List of systemd targets to which this copy belongs.";
}; default = [ ];
};
};
};
in mkOption {
type = attrsOf (submodule localCopyOpts);
description = "Map of copies to make for use by services.";
default = { };
}; };
in mkOption {
type = attrsOf (submodule localCopyOpts);
description = "Map of copies to make for use by services.";
default = {};
}; };
}; };
};
head-or-null = lst: if (lst == []) then null else head lst; head-or-null = lst: if (lst == [ ]) then null else head lst;
rm-service-ext = filename: rm-service-ext = filename:
head-or-null (builtins.match "^(.+)\.service$" filename); head-or-null (builtins.match "^(.+).service$" filename);
concatMapAttrs = f: attrs: concatMapAttrs = f: attrs: foldr (a: b: a // b) { } (mapAttrsToList f attrs);
foldr (a: b: a // b) {} (mapAttrsToList f attrs);
cfg = config.fudo.acme; cfg = config.fudo.acme;
hasLocalDomains = hasAttr hostname cfg.host-domains; hasLocalDomains = hasAttr hostname cfg.host-domains;
localDomains = if hasLocalDomains then localDomains = if hasLocalDomains then cfg.host-domains.${hostname} else { };
cfg.host-domains.${hostname} else {};
optionalStringOr = str: default: optionalStringOr = str: default: if (str != null) then str else default;
if (str != null) then str else default;
in { in {
options.fudo.acme = with types; { options.fudo.acme = with types; {
@ -123,20 +122,20 @@ in {
}; };
config = { config = {
security.acme.certs = mapAttrs (domain: domainOpts: { security.acme.certs = mapAttrs (domain: domainOpts:
# email = domainOpts.admin-email; {
# webroot = cfg.challenge-path; # email = domainOpts.admin-email;
# group = "nginx"; # webroot = cfg.challenge-path;
# extraDomainNames = domainOpts.extra-domains; # group = "nginx";
}) localDomains; # extraDomainNames = domainOpts.extra-domains;
}) localDomains;
# Assume that if we're acquiring SSL certs, we have a real IP for the # Assume that if we're acquiring SSL certs, we have a real IP for the
# host. nginx must have an acme dir for security.acme to work. # host. nginx must have an acme dir for security.acme to work.
services.nginx = mkIf hasLocalDomains { services.nginx = mkIf hasLocalDomains {
enable = true; enable = true;
recommendedTlsSettings = true; recommendedTlsSettings = true;
virtualHosts = let virtualHosts = let server-path = "/.well-known/acme-challenge";
server-path = "/.well-known/acme-challenge";
in (mapAttrs (domain: domainOpts: { in (mapAttrs (domain: domainOpts: {
# THIS IS A HACK. Getting redundant paths. So if {domain} is configured # THIS IS A HACK. Getting redundant paths. So if {domain} is configured
# somewhere else, assume ACME is already set. # somewhere else, assume ACME is already set.
@ -152,7 +151,7 @@ in {
serverName = "_"; serverName = "_";
default = true; default = true;
locations = { locations = {
${server-path} = { "${server-path}" = {
root = cfg.challenge-path; root = cfg.challenge-path;
extraConfig = "auth_basic off;"; extraConfig = "auth_basic off;";
}; };
@ -167,76 +166,82 @@ in {
systemd = { systemd = {
tmpfiles = mkIf hasLocalDomains { tmpfiles = mkIf hasLocalDomains {
rules = let rules = let
copies = concatMapAttrs (domain: domainOpts: copies = concatMapAttrs (domain: domainOpts: domainOpts.local-copies)
domainOpts.local-copies) localDomains; localDomains;
perms = copyOpts: if (copyOpts.group != null) then "0550" else "0500"; perms = copyOpts: if (copyOpts.group != null) then "0550" else "0500";
copy-paths = mapAttrsToList (copy: copyOpts: copy-paths = mapAttrsToList (copy: copyOpts:
let let
dir-entry = copyOpts: file: "d \"${dirOf file}\" ${perms copyOpts} ${copyOpts.user} ${optionalStringOr copyOpts.group "-"} - -"; dir-entry = copyOpts: file:
''
d "${dirOf file}" ${perms copyOpts} ${copyOpts.user} ${
optionalStringOr copyOpts.group "-"
} - -'';
in map (dir-entry copyOpts) [ in map (dir-entry copyOpts) [
copyOpts.certificate copyOpts.certificate
copyOpts.full-certificate copyOpts.full-certificate
copyOpts.chain copyOpts.chain
copyOpts.private-key copyOpts.private-key
]) copies; ]) copies;
in (unique (concatMap (i: unique i) copy-paths)) ++ [ in (unique (concatMap (i: unique i) copy-paths))
"d \"${cfg.challenge-path}\" 755 acme nginx - -" ++ [ ''d "${cfg.challenge-path}" 755 acme nginx - -'' ];
];
}; };
services = concatMapAttrs (domain: domainOpts: services = concatMapAttrs (domain: domainOpts:
concatMapAttrs (copy: copyOpts: let concatMapAttrs (copy: copyOpts:
key-perms = copyOpts: if (copyOpts.group != null) then "0440" else "0400"; let
source = config.security.acme.certs.${domain}.directory; key-perms = copyOpts:
target = copyOpts.path; if (copyOpts.group != null) then "0440" else "0400";
owners = source = config.security.acme.certs.${domain}.directory;
if (copyOpts.group != null) then target = copyOpts.path;
owners = if (copyOpts.group != null) then
"${copyOpts.user}:${copyOpts.group}" "${copyOpts.user}:${copyOpts.group}"
else copyOpts.user; else
dirs = unique [ copyOpts.user;
(dirOf copyOpts.certificate) dirs = unique [
(dirOf copyOpts.full-certificate) (dirOf copyOpts.certificate)
(dirOf copyOpts.chain) (dirOf copyOpts.full-certificate)
(dirOf copyOpts.private-key) (dirOf copyOpts.chain)
]; (dirOf copyOpts.private-key)
install-certs = pkgs.writeShellScript "fudo-install-${domain}-${copy}-certs.sh" '' ];
${concatStringsSep "\n" (map (dir: '' install-certs =
mkdir -p ${dir} pkgs.writeShellScript "fudo-install-${domain}-${copy}-certs.sh" ''
chown ${owners} ${dir} ${concatStringsSep "\n" (map (dir: ''
'') dirs)} mkdir -p ${dir}
cp ${source}/cert.pem ${copyOpts.certificate} chown ${owners} ${dir}
chmod 0444 ${copyOpts.certificate} '') dirs)}
chown ${owners} ${copyOpts.certificate} cp ${source}/cert.pem ${copyOpts.certificate}
chmod 0444 ${copyOpts.certificate}
chown ${owners} ${copyOpts.certificate}
cp ${source}/full.pem ${copyOpts.full-certificate} cp ${source}/full.pem ${copyOpts.full-certificate}
chmod 0444 ${copyOpts.full-certificate} chmod 0444 ${copyOpts.full-certificate}
chown ${owners} ${copyOpts.full-certificate} chown ${owners} ${copyOpts.full-certificate}
cp ${source}/chain.pem ${copyOpts.chain} cp ${source}/chain.pem ${copyOpts.chain}
chmod 0444 ${copyOpts.chain} chmod 0444 ${copyOpts.chain}
chown ${owners} ${copyOpts.chain} chown ${owners} ${copyOpts.chain}
cp ${source}/key.pem ${copyOpts.private-key} cp ${source}/key.pem ${copyOpts.private-key}
chmod ${key-perms copyOpts} ${copyOpts.private-key} chmod ${key-perms copyOpts} ${copyOpts.private-key}
chown ${owners} ${copyOpts.private-key} chown ${owners} ${copyOpts.private-key}
''; '';
service-name = rm-service-ext copyOpts.service; service-name = rm-service-ext copyOpts.service;
in { in {
${service-name} = { ${service-name} = {
description = "Copy ${domain} ACME certs for ${copy}."; description = "Copy ${domain} ACME certs for ${copy}.";
after = [ "acme-${domain}.service" ]; after = [ "acme-${domain}.service" ];
before = copyOpts.dependent-services; before = copyOpts.dependent-services;
wantedBy = [ "multi-user.target" ] ++ copyOpts.dependent-services; wantedBy = [ "multi-user.target" ] ++ copyOpts.dependent-services;
partOf = copyOpts.part-of; partOf = copyOpts.part-of;
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
ExecStart = install-certs; ExecStart = install-certs;
RemainAfterExit = true; RemainAfterExit = true;
StandardOutput = "journal"; StandardOutput = "journal";
};
}; };
}; }) domainOpts.local-copies) localDomains;
}) domainOpts.local-copies) localDomains;
}; };
}; };
} }