Lots of changes.

This commit is contained in:
niten 2022-04-26 18:39:53 -07:00
parent c40aba6133
commit 9edae4a39c
6 changed files with 269 additions and 240 deletions

View File

@ -103,14 +103,13 @@ in {
networking.firewall.allowedTCPPorts = networking.firewall.allowedTCPPorts =
mkIf (cfg.ssh != null) [ cfg.ssh.listen-port ]; mkIf (cfg.ssh != null) [ cfg.ssh.listen-port ];
environment.systemPackages = with pkgs; let environment.systemPackages = with pkgs;
let
gitea-admin = writeShellScriptBin "gitea-admin" '' gitea-admin = writeShellScriptBin "gitea-admin" ''
TMP=$(mktemp -d /tmp/gitea-XXXXXXXX) TMP=$(mktemp -d /tmp/gitea-XXXXXXXX)
${gitea}/bin/gitea --custom-path ${cfg.state-dir}/custom --config ${cfg.state-dir}/custom/conf/app.ini --work-path $TMP $@ ${gitea}/bin/gitea --custom-path ${cfg.state-dir}/custom --config ${cfg.state-dir}/custom/conf/app.ini --work-path $TMP $@
''; '';
in [ in [ gitea-admin ];
gitea-admin
];
services = { services = {
gitea = { gitea = {

View File

@ -11,8 +11,7 @@ let
join-lines = lib.concatStringsSep "\n"; join-lines = lib.concatStringsSep "\n";
strip-ext = filename: strip-ext = filename: head (builtins.match "^(.+)[.][^.]+$" filename);
head (builtins.match "^(.+)[.][^.]+$" filename);
userDatabaseOpts = { database, ... }: { userDatabaseOpts = { database, ... }: {
options = { options = {
@ -109,11 +108,11 @@ let
makeNetworksEntry = networks: join-lines (map makeEntry networks); makeNetworksEntry = networks: join-lines (map makeEntry networks);
makeLocalUserPasswordEntries = users: networks: let makeLocalUserPasswordEntries = users: networks:
let
network-entries = user: db: network-entries = user: db:
join-lines join-lines
(map (network: "hostssl ${db} ${user} ${network} md5") (map (network: "hostssl ${db} ${user} ${network} md5") networks);
networks);
in join-lines (mapAttrsToList (user: opts: in join-lines (mapAttrsToList (user: opts:
join-lines (map (db: '' join-lines (map (db: ''
local ${db} ${user} md5 local ${db} ${user} md5
@ -220,7 +219,7 @@ in {
cleanup-tasks = mkOption { cleanup-tasks = mkOption {
type = listOf str; type = listOf str;
description = "List of actions to take during shutdown of the service."; description = "List of actions to take during shutdown of the service.";
default = []; default = [ ];
}; };
systemd-target = mkOption { systemd-target = mkOption {
@ -254,8 +253,7 @@ in {
ensurePermissions = { "DATABASE ${database}" = "ALL PRIVILEGES"; }; ensurePermissions = { "DATABASE ${database}" = "ALL PRIVILEGES"; };
}) opts.users)) cfg.databases))); }) opts.users)) cfg.databases)));
settings = let settings = let ssl-enabled = cfg.ssl-certificate != null;
ssl-enabled = cfg.ssl-certificate != null;
in { in {
krb_server_keyfile = mkIf (cfg.keytab != null) cfg.keytab; krb_server_keyfile = mkIf (cfg.keytab != null) cfg.keytab;
@ -286,8 +284,8 @@ in {
systemd = { systemd = {
tmpfiles.rules = optional (cfg.state-directory != null) (let tmpfiles.rules = optional (cfg.state-directory != null)
user = config.systemd.services.postgresql.serviceConfig.User; (let user = config.systemd.services.postgresql.serviceConfig.User;
in "d ${cfg.state-directory} 0700 ${user} - - -"); in "d ${cfg.state-directory} 0700 ${user} - - -");
targets.${strip-ext cfg.systemd-target} = { targets.${strip-ext cfg.systemd-target} = {
@ -296,14 +294,12 @@ in {
}; };
paths = let paths = let
user-password-files = mapAttrsToList user-password-files =
(user: userOpts: userOpts.password-file) mapAttrsToList (user: userOpts: userOpts.password-file) cfg.users;
cfg.users;
in { in {
postgresql-password-watcher = mkIf (length user-password-files > 0) { postgresql-password-watcher = mkIf (length user-password-files > 0) {
wantedBy = [ "default.target" ]; wantedBy = [ "default.target" ];
description = description = "Reset all user passwords if any changes occur.";
"Reset all user passwords if any changes occur.";
pathConfig = { pathConfig = {
PathChanged = user-password-files; PathChanged = user-password-files;
Unit = "postgresql-password-setter.service"; Unit = "postgresql-password-setter.service";
@ -368,6 +364,10 @@ in {
# ${pkgs.coreutils}/bin/chgrp ${cfg.socket-group} ${cfg.socket-directory}/.s.PGSQL* # ${pkgs.coreutils}/bin/chgrp ${cfg.socket-group} ${cfg.socket-directory}/.s.PGSQL*
# ''; # '';
# Wait a bit before starting dependent services, to let postgres finish initializing
serviceConfig.ExecStartPost =
mkAfter [ "${pkgs.coreutils}/bin/sleep 10" ];
postStop = concatStringsSep "\n" cfg.cleanup-tasks; postStop = concatStringsSep "\n" cfg.cleanup-tasks;
}; };
@ -382,8 +382,8 @@ in {
allow-user-login = user: "ALTER ROLE ${user} WITH LOGIN;"; allow-user-login = user: "ALTER ROLE ${user} WITH LOGIN;";
extra-settings-sql = pkgs.writeText "settings.sql" '' extra-settings-sql = pkgs.writeText "settings.sql" ''
${concatStringsSep "\n" ${concatStringsSep "\n" (map allow-user-login
(map allow-user-login (mapAttrsToList (key: val: key) cfg.users))} (mapAttrsToList (key: val: key) cfg.users))}
${usersAccessSql cfg.users} ${usersAccessSql cfg.users}
''; '';
in pkgs.writeShellScript "postgresql-finalizer.sh" '' in pkgs.writeShellScript "postgresql-finalizer.sh" ''

View File

@ -1,4 +1,4 @@
{ config, lib, pkgs, ... } @ toplevel: { config, lib, pkgs, ... }@toplevel:
# NOTE: To get DNS records: # NOTE: To get DNS records:
# pdnsutil --config-dir=... show-zone <domain> # pdnsutil --config-dir=... show-zone <domain>
@ -38,28 +38,33 @@ let
include-dir=${runtime-dir} include-dir=${runtime-dir}
''; '';
make-pgpass-file = user: target-file: let make-pgpass-file = user: target-file:
db = cfg.database; let db = cfg.database;
in pkgs.writeShellScript "genenrate-pgpass-file.sh" '' in pkgs.writeShellScript "genenrate-pgpass-file.sh" ''
touch ${target-file} touch ${target-file}
chown ${user} ${target-file} chown ${user} ${target-file}
chmod 700 ${target-file} chmod 700 ${target-file}
PASSWORD=$(cat ${db.password-file}) PASSWORD=$(cat ${db.password-file})
echo "${db.host}:${toString db.port}:${db.database}:${db.user}:__PASSWORD__" | sed "s/__PASSWORD__/$PASSWORD/" > ${target-file} echo "${db.host}:${
toString db.port
}:${db.database}:${db.user}:__PASSWORD__" | sed "s/__PASSWORD__/$PASSWORD/" > ${target-file}
''; '';
mkRecord = name: type: content: { mkRecord = name: type: content: { inherit name type content; };
inherit name type content;
};
initialize-domain-sql = domain: let initialize-domain-sql = domain:
let
domain-name = domain.domain; domain-name = domain.domain;
host-ip = pkgs.lib.network.host-ipv4 config hostname; host-ip = pkgs.lib.network.host-ipv4 config hostname;
ipv6-net = net: (builtins.match ":" net) != null; ipv6-net = net: (builtins.match ":" net) != null;
ipv4-net = net: !(ipv6-net net); ipv4-net = net: !(ipv6-net net);
domain-records = [ domain-records = [
(mkRecord domain-name "SOA" "ns1.${domain-name} hostmaster.${domain-name} ${toString config.instance.build-timestamp} 10800 3600 1209600 3600") (mkRecord domain-name "SOA"
(mkRecord "_dmark.${domain-name}" "TXT" ''"v=DMARC1; p=reject; rua=mailto:${domain.admin}; ruf=mailto:${domain.admin}; fo=1;"'') "ns1.${domain-name} hostmaster.${domain-name} ${
toString config.instance.build-timestamp
} 10800 3600 1209600 3600")
(mkRecord "_dmark.${domain-name}" "TXT" ''
"v=DMARC1; p=reject; rua=mailto:${domain.admin}; ruf=mailto:${domain.admin}; fo=1;"'')
(mkRecord domain-name "NS" "ns1.${domain-name}") (mkRecord domain-name "NS" "ns1.${domain-name}")
(mkRecord domain-name "TXT" (let (mkRecord domain-name "TXT" (let
networks = config.instance.local-networks; networks = config.instance.local-networks;
@ -69,21 +74,22 @@ let
in ''"v=spf1 mx ${networks-string} -all"'')) in ''"v=spf1 mx ${networks-string} -all"''))
(mkRecord "ns1.${domain-name}" "A" host-ip) (mkRecord "ns1.${domain-name}" "A" host-ip)
(mkRecord domain-name "A" host-ip) (mkRecord domain-name "A" host-ip)
] ++ ] ++ (optional (domain.gssapi-realm != null)
(optional (domain.gssapi-realm != null) (mkRecord "_kerberos.${domain-name}" "TXT" ''"domain.gssapi-realm"''))
(mkRecord "_kerberos.${domain-name}" "TXT" ''"domain.gssapi-realm"'')) ++ ++ (mapAttrsToList (alias: target: mkRecord alias "CNAME" target)
(mapAttrsToList (alias: target: mkRecord alias "CNAME" target)
domain.aliases); domain.aliases);
records-strings = map (record: records-strings = map (record:
"INSERT INTO records (domain_id, name, type, content) SELECT id, '${record.name}', '${record.type}', '${record.content}' FROM domains WHERE name='${domain-name}';") "INSERT INTO records (domain_id, name, type, content) SELECT id, '${record.name}', '${record.type}', '${record.content}' FROM domains WHERE name='${domain-name}';")
domain-records; domain-records;
in pkgs.writeText "initialize-${domain-name}.sql" '' in pkgs.writeText "initialize-${domain-name}.sql" ''
INSERT INTO domains (name, master, type, notified_serial) VALUES ('${domain-name}', '${host-ip}', 'MASTER', '${toString config.instance.build-timestamp}'); INSERT INTO domains (name, master, type, notified_serial) VALUES ('${domain-name}', '${host-ip}', 'MASTER', '${
toString config.instance.build-timestamp
}');
${concatStringsSep "\n" records-strings} ${concatStringsSep "\n" records-strings}
''; '';
initialize-domain-script = domain: let initialize-domain-script = domain:
domain-name = domain.domain; let domain-name = domain.domain;
in pkgs.writeShellScript "initialize-${domain-name}.sh" '' in pkgs.writeShellScript "initialize-${domain-name}.sh" ''
if [ "$( psql -tAc "SELECT id FROM domains WHERE name='${domain-name}'" )" ]; then if [ "$( psql -tAc "SELECT id FROM domains WHERE name='${domain-name}'" )" ]; then
logger "${domain-name} already initialized, skipping" logger "${domain-name} already initialized, skipping"
@ -111,7 +117,7 @@ let
aliases = mkOption { aliases = mkOption {
type = attrsOf str; type = attrsOf str;
description = "Map of alias to authoritative hostname for this domain."; description = "Map of alias to authoritative hostname for this domain.";
default = {}; default = { };
}; };
gssapi-realm = mkOption { gssapi-realm = mkOption {
@ -152,7 +158,7 @@ in {
listen-v6-addresses = mkOption { listen-v6-addresses = mkOption {
type = listOf str; type = listOf str;
description = "List of IPv6 addresses on which to listen."; description = "List of IPv6 addresses on which to listen.";
default = []; default = [ ];
}; };
domains = mkOption { domains = mkOption {
@ -238,12 +244,18 @@ in {
systemd = let systemd = let
pgpass-file = "${runtime-dir}/pgpass"; pgpass-file = "${runtime-dir}/pgpass";
initialize-jobs = mapAttrs' (_: domain: let initialize-jobs = mapAttrs' (_: domain:
domain-name = domain.domain; let domain-name = domain.domain;
in nameValuePair "powerdns-initialize-${domain-name}" { in nameValuePair "powerdns-initialize-${domain-name}" {
description = "Initialize the ${domain-name} domain"; description = "Initialize the ${domain-name} domain";
requires = [ "powerdns-initialize-db.service" "powerdns-generate-pgpass.service" ]; requires = [
after = [ "powerdns-initialize-db.service" "powerdns-generate-pgpass.service" ]; "powerdns-initialize-db.service"
"powerdns-generate-pgpass.service"
];
after = [
"powerdns-initialize-db.service"
"powerdns-generate-pgpass.service"
];
requiredBy = [ "powerdns.service" ]; requiredBy = [ "powerdns.service" ];
wantedBy = [ "powerdns.service" ]; wantedBy = [ "powerdns.service" ];
before = [ "powerdns.service" ]; before = [ "powerdns.service" ];
@ -252,18 +264,14 @@ in {
PGUSER = cfg.database.user; PGUSER = cfg.database.user;
PGDATABASE = cfg.database.database; PGDATABASE = cfg.database.database;
PGPORT = toString cfg.database.port; PGPORT = toString cfg.database.port;
PGSSLMODE= "require"; PGSSLMODE = "require";
PGPASSFILE = pgpass-file; PGPASSFILE = pgpass-file;
}; };
path = with pkgs; [ postgresql util-linux ]; path = with pkgs; [ postgresql util-linux ];
serviceConfig = { serviceConfig = { ExecStart = initialize-domain-script domain; };
ExecStart = initialize-domain-script domain;
};
}) cfg.domains; }) cfg.domains;
in { in {
tmpfiles.rules = [ tmpfiles.rules = [ "d ${runtime-dir} 0750 ${cfg.user} ${cfg.group} - -" ];
"d ${runtime-dir} 0750 ${cfg.user} ${cfg.group} - -"
];
services = initialize-jobs // { services = initialize-jobs // {
powerdns-generate-pgpass = { powerdns-generate-pgpass = {
@ -285,7 +293,7 @@ in {
PGUSER = cfg.database.user; PGUSER = cfg.database.user;
PGDATABASE = cfg.database.database; PGDATABASE = cfg.database.database;
PGPORT = toString cfg.database.port; PGPORT = toString cfg.database.port;
PGSSLMODE= "require"; PGSSLMODE = "require";
PGPASSFILE = pgpass-file; PGPASSFILE = pgpass-file;
}; };
serviceConfig = { serviceConfig = {
@ -297,16 +305,20 @@ in {
psql -f ${pkgs.powerdns}/share/doc/pdns/schema.pgsql.sql psql -f ${pkgs.powerdns}/share/doc/pdns/schema.pgsql.sql
fi fi
''; '';
# Wait until posgresql is available before starting
ExecStartPre =
pkgs.writeShellScript "ensure-postgresql-running.sh" ''
while [ ! "$( psql -tAc "SELECT 1" )" ]; do
${pkgs.coreutils}/bin/sleep 3
done
'';
}; };
}; };
powerdns = { powerdns = {
description = "PowerDNS nameserver."; description = "PowerDNS nameserver.";
requires = [ "powerdns-config-generator.service" ]; requires = [ "powerdns-config-generator.service" ];
after = [ after = [ "network.target" "powerdns-config-generator.service" ];
"network.target"
"powerdns-config-generator.service"
];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
path = with pkgs; [ powerdns postgresql util-linux ]; path = with pkgs; [ powerdns postgresql util-linux ];
serviceConfig = { serviceConfig = {
@ -318,7 +330,8 @@ in {
PASSWORD=$( cat ${cfg.database.password-file} | tr -d '\n') PASSWORD=$( cat ${cfg.database.password-file} | tr -d '\n')
sed -e "s/__PASSWORD__/$PASSWORD/" ${gpgsql-template} > $TARGET sed -e "s/__PASSWORD__/$PASSWORD/" ${gpgsql-template} > $TARGET
''; '';
ExecStart = pkgs.writeShellScript "launch-powerdns.sh" (concatStringsSep " " [ ExecStart = pkgs.writeShellScript "launch-powerdns.sh"
(concatStringsSep " " [
"${pkgs.powerdns}/bin/pdns_server" "${pkgs.powerdns}/bin/pdns_server"
"--setuid=${cfg.user}" "--setuid=${cfg.user}"
"--setgid=${cfg.group}" "--setgid=${cfg.group}"
@ -329,9 +342,7 @@ in {
"--config-dir=${pdns-config-dir}" "--config-dir=${pdns-config-dir}"
]); ]);
ExecStartPost = pkgs.writeShellScript "powerdns-secure-zones.sh" ExecStartPost = pkgs.writeShellScript "powerdns-secure-zones.sh"
(concatStringsSep "\n" (concatStringsSep "\n" (mapAttrsToList (_: domain: ''
(mapAttrsToList
(_: domain: ''
DNSINFO=$(${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} show-zone ${domain.domain}) DNSINFO=$(${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} show-zone ${domain.domain})
if [[ "x$DNSINFO" =~ "xNo such zone in the database" ]]; then if [[ "x$DNSINFO" =~ "xNo such zone in the database" ]]; then
logger "zone ${domain.domain} does not exist in powerdns database" logger "zone ${domain.domain} does not exist in powerdns database"
@ -345,8 +356,7 @@ in {
logger "not securing zone ${domain.domain} in powerdns database" logger "not securing zone ${domain.domain} in powerdns database"
fi fi
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} rectify-zone ${domain.domain} ${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} rectify-zone ${domain.domain}
'') '') cfg.domains));
cfg.domains));
}; };
}; };
}; };

View File

@ -1,8 +1,7 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
with lib; with lib;
let let cfg = config.fudo.metrics.prometheus;
cfg = config.fudo.metrics.prometheus;
in { in {
@ -19,10 +18,10 @@ in {
postfix = [ "postfix._metrics._tcp.my-domain.com" ]; postfix = [ "postfix._metrics._tcp.my-domain.com" ];
}; };
default = { default = {
dovecot = []; dovecot = [ ];
node = []; node = [ ];
postfix = []; postfix = [ ];
rspamd = []; rspamd = [ ];
}; };
}; };
@ -31,14 +30,12 @@ in {
description = '' description = ''
A map of exporter type to a list of host:ports from which to collect metrics. A map of exporter type to a list of host:ports from which to collect metrics.
''; '';
example = { example = { node = [ "my-host.my-domain:1111" ]; };
node = [ "my-host.my-domain:1111" ];
};
default = { default = {
dovecot = []; dovecot = [ ];
node = []; node = [ ];
postfix = []; postfix = [ ];
rspamd = []; rspamd = [ ];
}; };
}; };
@ -47,7 +44,7 @@ in {
description = '' description = ''
A list of explicit <host:port> docker targets from which to gather node data. A list of explicit <host:port> docker targets from which to gather node data.
''; '';
default = []; default = [ ];
}; };
push-url = mkOption { push-url = mkOption {
@ -86,6 +83,15 @@ in {
"d ${cfg.state-directory} 0700 ${config.systemd.services.prometheus.serviceConfig.User} - - -" "d ${cfg.state-directory} 0700 ${config.systemd.services.prometheus.serviceConfig.User} - - -"
]; ];
fileSystems =
let state-dir = "/var/lib/${config.services.prometheus.stateDir}";
in mkIf (cfg.state-directory != state-dir) {
${state-dir} = {
device = cfg.state-directory;
options = [ "bind" ];
};
};
services.nginx = { services.nginx = {
enable = true; enable = true;
recommendedOptimisation = true; recommendedOptimisation = true;
@ -93,17 +99,18 @@ in {
virtualHosts = { virtualHosts = {
"${cfg.hostname}" = { "${cfg.hostname}" = {
enableACME = ! cfg.private-network; enableACME = !cfg.private-network;
forceSSL = ! cfg.private-network; forceSSL = !cfg.private-network;
locations."/" = { locations."/" = {
proxyPass = "http://127.0.0.1:9090"; proxyPass = "http://127.0.0.1:9090";
extraConfig = let extraConfig = let local-networks = config.instance.local-networks;
local-networks = config.instance.local-networks;
in "${optionalString ((length local-networks) > 0) in "${optionalString ((length local-networks) > 0)
(concatStringsSep "\n" (concatStringsSep "\n"
(map (network: "allow ${network};") local-networks)) + "\ndeny all;"}"; (map (network: "allow ${network};") local-networks)) + ''
deny all;''}";
}; };
}; };
}; };
@ -124,19 +131,22 @@ in {
honor_labels = false; honor_labels = false;
scheme = if cfg.private-network then "http" else "https"; scheme = if cfg.private-network then "http" else "https";
metrics_path = "/metrics/${type}"; metrics_path = "/metrics/${type}";
dns_sd_configs = if (hasAttr type cfg.service-discovery-dns) then dns_sd_configs = if (hasAttr type cfg.service-discovery-dns) then [{
[ { names = cfg.service-discovery-dns.${type}; } ] else []; names = cfg.service-discovery-dns.${type};
static_configs = if (hasAttr type cfg.static-targets) then }] else
[ { targets = cfg.static-targets.${type}; } ] else []; [ ];
static_configs = if (hasAttr type cfg.static-targets) then [{
targets = cfg.static-targets.${type};
}] else
[ ];
}; };
in map make-job ["docker" "node" "dovecot" "postfix" "rspamd"]; in map make-job [ "docker" "node" "dovecot" "postfix" "rspamd" ];
pushgateway = { pushgateway = {
enable = if (cfg.push-url != null) then true else false; enable = if (cfg.push-url != null) then true else false;
web = { web = {
external-url = if cfg.push-url == null then external-url =
cfg.push-address else if cfg.push-url == null then cfg.push-address else cfg.push-url;
cfg.push-url;
listen-address = cfg.push-address; listen-address = cfg.push-address;
}; };
}; };

View File

@ -16,7 +16,8 @@ let
decrypt-script = { secret-name, source-file, target-host, target-file decrypt-script = { secret-name, source-file, target-host, target-file
, host-master-key, user, group, permissions }: , host-master-key, user, group, permissions }:
pkgs.writeShellScript "decrypt-fudo-secret-${target-host}-${secret-name}.sh" '' pkgs.writeShellScript
"decrypt-fudo-secret-${target-host}-${secret-name}.sh" ''
rm -f ${target-file} rm -f ${target-file}
touch ${target-file} touch ${target-file}
chown ${user}:${group} ${target-file} chown ${user}:${group} ${target-file}
@ -24,27 +25,33 @@ let
# NOTE: silly hack because sometimes age leaves a blank line # NOTE: silly hack because sometimes age leaves a blank line
# Only include lines with at least one non-space character # Only include lines with at least one non-space character
SRC=$(mktemp fudo-secret-${target-host}-${secret-name}.XXXXXXXX) SRC=$(mktemp fudo-secret-${target-host}-${secret-name}.XXXXXXXX)
cat ${encrypt-on-disk { cat ${
encrypt-on-disk {
inherit secret-name source-file target-host; inherit secret-name source-file target-host;
target-pubkey = host-master-key.public-key; target-pubkey = host-master-key.public-key;
}} | grep "[^ ]" > $SRC }
} | grep "[^ ]" > $SRC
age -d -i ${host-master-key.key-path} -o ${target-file} $SRC age -d -i ${host-master-key.key-path} -o ${target-file} $SRC
rm -f $SRC rm -f $SRC
''; '';
secret-service = target-host: secret-name: secret-service = target-host: secret-name:
{ source-file, target-file, user, group, permissions, ... }: { { source-file, target-file, user, group, permissions, ... }: {
description = "decrypt secret ${secret-name} for ${target-host}."; description =
"decrypt secret ${secret-name} at ${target-host}:${target-file}.";
wantedBy = [ cfg.secret-target ]; wantedBy = [ cfg.secret-target ];
before = [ cfg.secret-target ]; before = [ cfg.secret-target ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "simple";
ExecStart = let RemainAfterExit = true;
host-master-key = config.fudo.hosts.${target-host}.master-key; ExecStart =
let host-master-key = config.fudo.hosts.${target-host}.master-key;
in decrypt-script { in decrypt-script {
inherit secret-name source-file target-host target-file host-master-key inherit secret-name source-file target-host target-file
user group permissions; host-master-key user group permissions;
}; };
ExecStop = pkgs.writeShellScript "fudo-remove-${secret-name}-secret.sh"
"rm -f ${target-file}";
}; };
path = [ pkgs.age ]; path = [ pkgs.age ];
}; };
@ -52,8 +59,10 @@ let
secretOpts = { name, ... }: { secretOpts = { name, ... }: {
options = with types; { options = with types; {
source-file = mkOption { source-file = mkOption {
type = path; # CAREFUL: this will copy the file to nixstore...keep on deploy host type =
description = "File from which to load the secret. If unspecified, a random new password will be generated."; path; # CAREFUL: this will copy the file to nixstore...keep on deploy host
description =
"File from which to load the secret. If unspecified, a random new password will be generated.";
default = "${generate-secret name}/passwd"; default = "${generate-secret name}/passwd";
}; };
@ -72,7 +81,7 @@ let
group = mkOption { group = mkOption {
type = str; type = str;
description = "Group (on target host) to which the file will belong."; description = "Group (on target host) to which the file will belong.";
default = "nogroup"; default = "root";
}; };
permissions = mkOption { permissions = mkOption {
@ -84,7 +93,7 @@ let
metadata = mkOption { metadata = mkOption {
type = attrsOf anything; type = attrsOf anything;
description = "Arbitrary metadata associated with this secret."; description = "Arbitrary metadata associated with this secret.";
default = {}; default = { };
}; };
service = mkOption { service = mkOption {
@ -99,7 +108,8 @@ let
in filter (user: (builtins.match "^nixbld[0-9]{1,2}$" user) != null) in filter (user: (builtins.match "^nixbld[0-9]{1,2}$" user) != null)
usernames; usernames;
generate-secret = name: pkgs.stdenv.mkDerivation { generate-secret = name:
pkgs.stdenv.mkDerivation {
name = "${name}-generated-passwd"; name = "${name}-generated-passwd";
phases = [ "installPhase" ]; phases = [ "installPhase" ];
@ -122,7 +132,8 @@ in {
options.fudo.secrets = with types; { options.fudo.secrets = with types; {
enable = mkOption { enable = mkOption {
type = bool; type = bool;
description = "Include secrets in the build (disable when secrets are unavailable)"; description =
"Include secrets in the build (disable when secrets are unavailable)";
default = true; default = true;
}; };
@ -175,9 +186,7 @@ in {
config = mkIf cfg.enable { config = mkIf cfg.enable {
users.groups = { users.groups = {
${cfg.secret-group} = { ${cfg.secret-group} = { members = cfg.secret-users ++ nix-build-users; };
members = cfg.secret-users ++ nix-build-users;
};
}; };
systemd = let systemd = let
@ -189,21 +198,20 @@ in {
{ }; { };
host-secret-services = let host-secret-services = let
head-or-null = lst: if (lst == []) then null else head lst; head-or-null = lst: if (lst == [ ]) then null else head lst;
strip-service = service-name: strip-service = service-name:
head-or-null head-or-null (builtins.match "^(.+)[.]service$" service-name);
(builtins.match "^(.+)[.]service$" service-name);
in mapAttrs' (secret: secretOpts: in mapAttrs' (secret: secretOpts:
(nameValuePair (strip-service secretOpts.service) (nameValuePair (strip-service secretOpts.service)
(secret-service hostname secret secretOpts))) host-secrets; (secret-service hostname secret secretOpts))) host-secrets;
trace-all = obj: builtins.trace obj obj; trace-all = obj: builtins.trace obj obj;
host-secret-paths = mapAttrsToList host-secret-paths = mapAttrsToList (secret: secretOpts:
(secret: secretOpts:
let perms = if secretOpts.group != "nobody" then "550" else "500"; let perms = if secretOpts.group != "nobody" then "550" else "500";
in "d ${dirOf secretOpts.target-file} ${perms} ${secretOpts.user} ${secretOpts.group} - -") in "d ${
host-secrets; dirOf secretOpts.target-file
} ${perms} ${secretOpts.user} ${secretOpts.group} - -") host-secrets;
build-secret-paths = build-secret-paths =
map (path: "d '${path}' - root ${cfg.secret-group} - -") map (path: "d '${path}' - root ${cfg.secret-group} - -")
@ -231,7 +239,8 @@ in {
strip-ext = filename: head (builtins.match "^(.+)[.]target$" filename); strip-ext = filename: head (builtins.match "^(.+)[.]target$" filename);
in { in {
${strip-ext cfg.secret-target} = { ${strip-ext cfg.secret-target} = {
description = "Target indicating that all Fudo secrets are available."; description =
"Target indicating that all Fudo secrets are available.";
wantedBy = [ "default.target" ]; wantedBy = [ "default.target" ];
}; };
}; };

View File

@ -2,33 +2,34 @@
with pkgs.lib; with pkgs.lib;
let let
head-or-null = lst: if (lst == []) then null else head lst; head-or-null = lst: if (lst == [ ]) then null else head lst;
is-regular-file = filename: type: type == "regular" || type == "link"; is-regular-file = filename: type: type == "regular" || type == "link";
regular-files = path: filterAttrs is-regular-file (builtins.readDir path); regular-files = path: filterAttrs is-regular-file (builtins.readDir path);
matches-ext = ext: filename: type: (builtins.match ".+[.]${ext}$" filename) != null; matches-ext = ext: filename: type:
(builtins.match ".+[.]${ext}$" filename) != null;
is-nix-file = matches-ext "nix"; is-nix-file = matches-ext "nix";
strip-ext = ext: filename: head-or-null (builtins.match "(.+)[.]${ext}$" filename); strip-ext = ext: filename:
head-or-null (builtins.match "(.+)[.]${ext}$" filename);
get-ext = filename: head-or-null (builtins.match "^.+[.](.+)$" filename); get-ext = filename: head-or-null (builtins.match "^.+[.](.+)$" filename);
hostname-from-file = filename: strip-ext "nix"; hostname-from-file = filename: strip-ext "nix";
nix-files = path:
attrNames
(filterAttrs is-nix-file
(filterAttrs is-regular-file
(builtins.readDir path)));
basename-to-file-map = path: let ext-files = ext: path:
files = nix-files path; attrNames (filterAttrs (matches-ext ext)
(filterAttrs is-regular-file (builtins.readDir path)));
nix-files = ext-files "nix";
basename-to-file-map = path:
let files = nix-files path;
in listToAttrs in listToAttrs
(map (file: (map (file: nameValuePair (strip-ext "nix" file) (path + "/${file}"))
nameValuePair (strip-ext "nix" file)
(path + "/${file}"))
files); files);
import-by-basename = path: import-by-basename = path:
mapAttrs (attr: attr-file: import attr-file) mapAttrs (attr: attr-file: import attr-file) (basename-to-file-map path);
(basename-to-file-map path);
list-nix-files = nix-files; list-nix-files = nix-files;
in { in {
inherit basename-to-file-map import-by-basename list-nix-files; inherit strip-ext basename-to-file-map import-by-basename list-nix-files
ext-files;
} }