Lots of changes.
This commit is contained in:
parent
c40aba6133
commit
9edae4a39c
|
@ -103,14 +103,13 @@ in {
|
|||
networking.firewall.allowedTCPPorts =
|
||||
mkIf (cfg.ssh != null) [ cfg.ssh.listen-port ];
|
||||
|
||||
environment.systemPackages = with pkgs; let
|
||||
gitea-admin = writeShellScriptBin "gitea-admin" ''
|
||||
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 $@
|
||||
'';
|
||||
in [
|
||||
gitea-admin
|
||||
];
|
||||
environment.systemPackages = with pkgs;
|
||||
let
|
||||
gitea-admin = writeShellScriptBin "gitea-admin" ''
|
||||
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 $@
|
||||
'';
|
||||
in [ gitea-admin ];
|
||||
|
||||
services = {
|
||||
gitea = {
|
||||
|
|
|
@ -11,8 +11,7 @@ let
|
|||
|
||||
join-lines = lib.concatStringsSep "\n";
|
||||
|
||||
strip-ext = filename:
|
||||
head (builtins.match "^(.+)[.][^.]+$" filename);
|
||||
strip-ext = filename: head (builtins.match "^(.+)[.][^.]+$" filename);
|
||||
|
||||
userDatabaseOpts = { database, ... }: {
|
||||
options = {
|
||||
|
@ -109,25 +108,25 @@ let
|
|||
|
||||
makeNetworksEntry = networks: join-lines (map makeEntry networks);
|
||||
|
||||
makeLocalUserPasswordEntries = users: networks: let
|
||||
network-entries = user: db:
|
||||
join-lines
|
||||
(map (network: "hostssl ${db} ${user} ${network} md5")
|
||||
networks);
|
||||
in join-lines (mapAttrsToList (user: opts:
|
||||
join-lines (map (db: ''
|
||||
local ${db} ${user} md5
|
||||
host ${db} ${user} 127.0.0.1/16 md5
|
||||
host ${db} ${user} ::1/128 md5
|
||||
${network-entries user db}
|
||||
'') (attrNames opts.databases))) (filterPasswordedUsers users));
|
||||
makeLocalUserPasswordEntries = users: networks:
|
||||
let
|
||||
network-entries = user: db:
|
||||
join-lines
|
||||
(map (network: "hostssl ${db} ${user} ${network} md5") networks);
|
||||
in join-lines (mapAttrsToList (user: opts:
|
||||
join-lines (map (db: ''
|
||||
local ${db} ${user} md5
|
||||
host ${db} ${user} 127.0.0.1/16 md5
|
||||
host ${db} ${user} ::1/128 md5
|
||||
${network-entries user db}
|
||||
'') (attrNames opts.databases))) (filterPasswordedUsers users));
|
||||
|
||||
userTableAccessSql = user: entity: access:
|
||||
"GRANT ${access} ON ${entity} TO ${user};";
|
||||
userDatabaseAccessSql = user: database: dbOpts: ''
|
||||
\c ${database}
|
||||
${join-lines
|
||||
(mapAttrsToList (userTableAccessSql user) dbOpts.entity-access)}
|
||||
(mapAttrsToList (userTableAccessSql user) dbOpts.entity-access)}
|
||||
'';
|
||||
userAccessSql = user: userOpts:
|
||||
join-lines (mapAttrsToList (userDatabaseAccessSql user) userOpts.databases);
|
||||
|
@ -220,7 +219,7 @@ in {
|
|||
cleanup-tasks = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of actions to take during shutdown of the service.";
|
||||
default = [];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
systemd-target = mkOption {
|
||||
|
@ -254,8 +253,7 @@ in {
|
|||
ensurePermissions = { "DATABASE ${database}" = "ALL PRIVILEGES"; };
|
||||
}) opts.users)) cfg.databases)));
|
||||
|
||||
settings = let
|
||||
ssl-enabled = cfg.ssl-certificate != null;
|
||||
settings = let ssl-enabled = cfg.ssl-certificate != null;
|
||||
in {
|
||||
krb_server_keyfile = mkIf (cfg.keytab != null) cfg.keytab;
|
||||
|
||||
|
@ -286,9 +284,9 @@ in {
|
|||
|
||||
systemd = {
|
||||
|
||||
tmpfiles.rules = optional (cfg.state-directory != null) (let
|
||||
user = config.systemd.services.postgresql.serviceConfig.User;
|
||||
in "d ${cfg.state-directory} 0700 ${user} - - -");
|
||||
tmpfiles.rules = optional (cfg.state-directory != null)
|
||||
(let user = config.systemd.services.postgresql.serviceConfig.User;
|
||||
in "d ${cfg.state-directory} 0700 ${user} - - -");
|
||||
|
||||
targets.${strip-ext cfg.systemd-target} = {
|
||||
description = "Postgresql and associated systemd services.";
|
||||
|
@ -296,14 +294,12 @@ in {
|
|||
};
|
||||
|
||||
paths = let
|
||||
user-password-files = mapAttrsToList
|
||||
(user: userOpts: userOpts.password-file)
|
||||
cfg.users;
|
||||
user-password-files =
|
||||
mapAttrsToList (user: userOpts: userOpts.password-file) cfg.users;
|
||||
in {
|
||||
postgresql-password-watcher = mkIf (length user-password-files > 0) {
|
||||
wantedBy = [ "default.target" ];
|
||||
description =
|
||||
"Reset all user passwords if any changes occur.";
|
||||
description = "Reset all user passwords if any changes occur.";
|
||||
pathConfig = {
|
||||
PathChanged = user-password-files;
|
||||
Unit = "postgresql-password-setter.service";
|
||||
|
@ -368,6 +364,10 @@ in {
|
|||
# ${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;
|
||||
};
|
||||
|
||||
|
@ -382,8 +382,8 @@ in {
|
|||
allow-user-login = user: "ALTER ROLE ${user} WITH LOGIN;";
|
||||
|
||||
extra-settings-sql = pkgs.writeText "settings.sql" ''
|
||||
${concatStringsSep "\n"
|
||||
(map allow-user-login (mapAttrsToList (key: val: key) cfg.users))}
|
||||
${concatStringsSep "\n" (map allow-user-login
|
||||
(mapAttrsToList (key: val: key) cfg.users))}
|
||||
${usersAccessSql cfg.users}
|
||||
'';
|
||||
in pkgs.writeShellScript "postgresql-finalizer.sh" ''
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{ config, lib, pkgs, ... } @ toplevel:
|
||||
{ config, lib, pkgs, ... }@toplevel:
|
||||
|
||||
# NOTE: To get DNS records:
|
||||
# pdnsutil --config-dir=... show-zone <domain>
|
||||
|
@ -38,61 +38,67 @@ let
|
|||
include-dir=${runtime-dir}
|
||||
'';
|
||||
|
||||
make-pgpass-file = user: target-file: let
|
||||
db = cfg.database;
|
||||
in pkgs.writeShellScript "genenrate-pgpass-file.sh" ''
|
||||
touch ${target-file}
|
||||
chown ${user} ${target-file}
|
||||
chmod 700 ${target-file}
|
||||
PASSWORD=$(cat ${db.password-file})
|
||||
echo "${db.host}:${toString db.port}:${db.database}:${db.user}:__PASSWORD__" | sed "s/__PASSWORD__/$PASSWORD/" > ${target-file}
|
||||
'';
|
||||
make-pgpass-file = user: target-file:
|
||||
let db = cfg.database;
|
||||
in pkgs.writeShellScript "genenrate-pgpass-file.sh" ''
|
||||
touch ${target-file}
|
||||
chown ${user} ${target-file}
|
||||
chmod 700 ${target-file}
|
||||
PASSWORD=$(cat ${db.password-file})
|
||||
echo "${db.host}:${
|
||||
toString db.port
|
||||
}:${db.database}:${db.user}:__PASSWORD__" | sed "s/__PASSWORD__/$PASSWORD/" > ${target-file}
|
||||
'';
|
||||
|
||||
mkRecord = name: type: content: {
|
||||
inherit name type content;
|
||||
};
|
||||
mkRecord = name: type: content: { inherit name type content; };
|
||||
|
||||
initialize-domain-sql = domain: let
|
||||
domain-name = domain.domain;
|
||||
host-ip = pkgs.lib.network.host-ipv4 config hostname;
|
||||
ipv6-net = net: (builtins.match ":" net) != null;
|
||||
ipv4-net = net: !(ipv6-net net);
|
||||
domain-records = [
|
||||
(mkRecord domain-name "SOA" "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 "TXT" (let
|
||||
networks = config.instance.local-networks;
|
||||
v4-nets = map (net: "ip4:${net}") (filter ipv4-net networks);
|
||||
v6-nets = map (net: "ip6:${net}") (filter ipv6-net networks);
|
||||
networks-string = concatStringsSep " " (v4-nets ++ v6-nets);
|
||||
in ''"v=spf1 mx ${networks-string} -all"''))
|
||||
(mkRecord "ns1.${domain-name}" "A" host-ip)
|
||||
(mkRecord domain-name "A" host-ip)
|
||||
] ++
|
||||
(optional (domain.gssapi-realm != null)
|
||||
(mkRecord "_kerberos.${domain-name}" "TXT" ''"domain.gssapi-realm"'')) ++
|
||||
(mapAttrsToList (alias: target: mkRecord alias "CNAME" target)
|
||||
domain.aliases);
|
||||
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}';")
|
||||
domain-records;
|
||||
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}');
|
||||
${concatStringsSep "\n" records-strings}
|
||||
'';
|
||||
initialize-domain-sql = domain:
|
||||
let
|
||||
domain-name = domain.domain;
|
||||
host-ip = pkgs.lib.network.host-ipv4 config hostname;
|
||||
ipv6-net = net: (builtins.match ":" net) != null;
|
||||
ipv4-net = net: !(ipv6-net net);
|
||||
domain-records = [
|
||||
(mkRecord domain-name "SOA"
|
||||
"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 "TXT" (let
|
||||
networks = config.instance.local-networks;
|
||||
v4-nets = map (net: "ip4:${net}") (filter ipv4-net networks);
|
||||
v6-nets = map (net: "ip6:${net}") (filter ipv6-net networks);
|
||||
networks-string = concatStringsSep " " (v4-nets ++ v6-nets);
|
||||
in ''"v=spf1 mx ${networks-string} -all"''))
|
||||
(mkRecord "ns1.${domain-name}" "A" host-ip)
|
||||
(mkRecord domain-name "A" host-ip)
|
||||
] ++ (optional (domain.gssapi-realm != null)
|
||||
(mkRecord "_kerberos.${domain-name}" "TXT" ''"domain.gssapi-realm"''))
|
||||
++ (mapAttrsToList (alias: target: mkRecord alias "CNAME" target)
|
||||
domain.aliases);
|
||||
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}';")
|
||||
domain-records;
|
||||
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
|
||||
}');
|
||||
${concatStringsSep "\n" records-strings}
|
||||
'';
|
||||
|
||||
initialize-domain-script = domain: let
|
||||
domain-name = domain.domain;
|
||||
in pkgs.writeShellScript "initialize-${domain-name}.sh" ''
|
||||
if [ "$( psql -tAc "SELECT id FROM domains WHERE name='${domain-name}'" )" ]; then
|
||||
logger "${domain-name} already initialized, skipping"
|
||||
exit 0
|
||||
else
|
||||
logger "initializing ${domain-name} in powerdns database"
|
||||
psql -f ${initialize-domain-sql domain}
|
||||
fi
|
||||
'';
|
||||
initialize-domain-script = domain:
|
||||
let domain-name = domain.domain;
|
||||
in pkgs.writeShellScript "initialize-${domain-name}.sh" ''
|
||||
if [ "$( psql -tAc "SELECT id FROM domains WHERE name='${domain-name}'" )" ]; then
|
||||
logger "${domain-name} already initialized, skipping"
|
||||
exit 0
|
||||
else
|
||||
logger "initializing ${domain-name} in powerdns database"
|
||||
psql -f ${initialize-domain-sql domain}
|
||||
fi
|
||||
'';
|
||||
|
||||
domainOpts = { name, ... }: {
|
||||
options = with types; {
|
||||
|
@ -111,7 +117,7 @@ let
|
|||
aliases = mkOption {
|
||||
type = attrsOf str;
|
||||
description = "Map of alias to authoritative hostname for this domain.";
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
|
||||
gssapi-realm = mkOption {
|
||||
|
@ -152,7 +158,7 @@ in {
|
|||
listen-v6-addresses = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of IPv6 addresses on which to listen.";
|
||||
default = [];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
domains = mkOption {
|
||||
|
@ -238,32 +244,34 @@ in {
|
|||
systemd = let
|
||||
pgpass-file = "${runtime-dir}/pgpass";
|
||||
|
||||
initialize-jobs = mapAttrs' (_: domain: let
|
||||
domain-name = domain.domain;
|
||||
in nameValuePair "powerdns-initialize-${domain-name}" {
|
||||
description = "Initialize the ${domain-name} domain";
|
||||
requires = [ "powerdns-initialize-db.service" "powerdns-generate-pgpass.service" ];
|
||||
after = [ "powerdns-initialize-db.service" "powerdns-generate-pgpass.service" ];
|
||||
requiredBy = [ "powerdns.service" ];
|
||||
wantedBy = [ "powerdns.service" ];
|
||||
before = [ "powerdns.service" ];
|
||||
environment = {
|
||||
PGHOST = cfg.database.host;
|
||||
PGUSER = cfg.database.user;
|
||||
PGDATABASE = cfg.database.database;
|
||||
PGPORT = toString cfg.database.port;
|
||||
PGSSLMODE= "require";
|
||||
PGPASSFILE = pgpass-file;
|
||||
};
|
||||
path = with pkgs; [ postgresql util-linux ];
|
||||
serviceConfig = {
|
||||
ExecStart = initialize-domain-script domain;
|
||||
};
|
||||
}) cfg.domains;
|
||||
initialize-jobs = mapAttrs' (_: domain:
|
||||
let domain-name = domain.domain;
|
||||
in nameValuePair "powerdns-initialize-${domain-name}" {
|
||||
description = "Initialize the ${domain-name} domain";
|
||||
requires = [
|
||||
"powerdns-initialize-db.service"
|
||||
"powerdns-generate-pgpass.service"
|
||||
];
|
||||
after = [
|
||||
"powerdns-initialize-db.service"
|
||||
"powerdns-generate-pgpass.service"
|
||||
];
|
||||
requiredBy = [ "powerdns.service" ];
|
||||
wantedBy = [ "powerdns.service" ];
|
||||
before = [ "powerdns.service" ];
|
||||
environment = {
|
||||
PGHOST = cfg.database.host;
|
||||
PGUSER = cfg.database.user;
|
||||
PGDATABASE = cfg.database.database;
|
||||
PGPORT = toString cfg.database.port;
|
||||
PGSSLMODE = "require";
|
||||
PGPASSFILE = pgpass-file;
|
||||
};
|
||||
path = with pkgs; [ postgresql util-linux ];
|
||||
serviceConfig = { ExecStart = initialize-domain-script domain; };
|
||||
}) cfg.domains;
|
||||
in {
|
||||
tmpfiles.rules = [
|
||||
"d ${runtime-dir} 0750 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
tmpfiles.rules = [ "d ${runtime-dir} 0750 ${cfg.user} ${cfg.group} - -" ];
|
||||
|
||||
services = initialize-jobs // {
|
||||
powerdns-generate-pgpass = {
|
||||
|
@ -285,7 +293,7 @@ in {
|
|||
PGUSER = cfg.database.user;
|
||||
PGDATABASE = cfg.database.database;
|
||||
PGPORT = toString cfg.database.port;
|
||||
PGSSLMODE= "require";
|
||||
PGSSLMODE = "require";
|
||||
PGPASSFILE = pgpass-file;
|
||||
};
|
||||
serviceConfig = {
|
||||
|
@ -297,16 +305,20 @@ in {
|
|||
psql -f ${pkgs.powerdns}/share/doc/pdns/schema.pgsql.sql
|
||||
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 = {
|
||||
description = "PowerDNS nameserver.";
|
||||
requires = [ "powerdns-config-generator.service" ];
|
||||
after = [
|
||||
"network.target"
|
||||
"powerdns-config-generator.service"
|
||||
];
|
||||
after = [ "network.target" "powerdns-config-generator.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = with pkgs; [ powerdns postgresql util-linux ];
|
||||
serviceConfig = {
|
||||
|
@ -318,35 +330,33 @@ in {
|
|||
PASSWORD=$( cat ${cfg.database.password-file} | tr -d '\n')
|
||||
sed -e "s/__PASSWORD__/$PASSWORD/" ${gpgsql-template} > $TARGET
|
||||
'';
|
||||
ExecStart = pkgs.writeShellScript "launch-powerdns.sh" (concatStringsSep " " [
|
||||
"${pkgs.powerdns}/bin/pdns_server"
|
||||
"--setuid=${cfg.user}"
|
||||
"--setgid=${cfg.group}"
|
||||
"--chroot=${runtime-dir}"
|
||||
"--daemon=no"
|
||||
"--guardian=no"
|
||||
"--write-pid=no"
|
||||
"--config-dir=${pdns-config-dir}"
|
||||
]);
|
||||
ExecStart = pkgs.writeShellScript "launch-powerdns.sh"
|
||||
(concatStringsSep " " [
|
||||
"${pkgs.powerdns}/bin/pdns_server"
|
||||
"--setuid=${cfg.user}"
|
||||
"--setgid=${cfg.group}"
|
||||
"--chroot=${runtime-dir}"
|
||||
"--daemon=no"
|
||||
"--guardian=no"
|
||||
"--write-pid=no"
|
||||
"--config-dir=${pdns-config-dir}"
|
||||
]);
|
||||
ExecStartPost = pkgs.writeShellScript "powerdns-secure-zones.sh"
|
||||
(concatStringsSep "\n"
|
||||
(mapAttrsToList
|
||||
(_: 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
|
||||
logger "zone ${domain.domain} does not exist in powerdns database"
|
||||
elif [[ "x$DNSINFO" =~ "xZone is not actively secured" ]]; then
|
||||
logger "securing zone ${domain.domain} in powerdns database"
|
||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
|
||||
elif [[ "x$DNSINFO" =~ "xNo keys for zone" ]]; then
|
||||
logger "securing zone ${domain.domain} in powerdns database"
|
||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
|
||||
else
|
||||
logger "not securing zone ${domain.domain} in powerdns database"
|
||||
fi
|
||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} rectify-zone ${domain.domain}
|
||||
'')
|
||||
cfg.domains));
|
||||
(concatStringsSep "\n" (mapAttrsToList (_: 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
|
||||
logger "zone ${domain.domain} does not exist in powerdns database"
|
||||
elif [[ "x$DNSINFO" =~ "xZone is not actively secured" ]]; then
|
||||
logger "securing zone ${domain.domain} in powerdns database"
|
||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
|
||||
elif [[ "x$DNSINFO" =~ "xNo keys for zone" ]]; then
|
||||
logger "securing zone ${domain.domain} in powerdns database"
|
||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
|
||||
else
|
||||
logger "not securing zone ${domain.domain} in powerdns database"
|
||||
fi
|
||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} rectify-zone ${domain.domain}
|
||||
'') cfg.domains));
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.metrics.prometheus;
|
||||
let cfg = config.fudo.metrics.prometheus;
|
||||
|
||||
in {
|
||||
|
||||
|
@ -19,26 +18,24 @@ in {
|
|||
postfix = [ "postfix._metrics._tcp.my-domain.com" ];
|
||||
};
|
||||
default = {
|
||||
dovecot = [];
|
||||
node = [];
|
||||
postfix = [];
|
||||
rspamd = [];
|
||||
dovecot = [ ];
|
||||
node = [ ];
|
||||
postfix = [ ];
|
||||
rspamd = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
static-targets = mkOption {
|
||||
type = attrsOf (listOf str);
|
||||
description = ''
|
||||
A map of exporter type to a list of host:ports from which to collect metrics.
|
||||
'';
|
||||
example = {
|
||||
node = [ "my-host.my-domain:1111" ];
|
||||
};
|
||||
A map of exporter type to a list of host:ports from which to collect metrics.
|
||||
'';
|
||||
example = { node = [ "my-host.my-domain:1111" ]; };
|
||||
default = {
|
||||
dovecot = [];
|
||||
node = [];
|
||||
postfix = [];
|
||||
rspamd = [];
|
||||
dovecot = [ ];
|
||||
node = [ ];
|
||||
postfix = [ ];
|
||||
rspamd = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -47,7 +44,7 @@ in {
|
|||
description = ''
|
||||
A list of explicit <host:port> docker targets from which to gather node data.
|
||||
'';
|
||||
default = [];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
push-url = mkOption {
|
||||
|
@ -86,6 +83,15 @@ in {
|
|||
"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 = {
|
||||
enable = true;
|
||||
recommendedOptimisation = true;
|
||||
|
@ -93,17 +99,18 @@ in {
|
|||
|
||||
virtualHosts = {
|
||||
"${cfg.hostname}" = {
|
||||
enableACME = ! cfg.private-network;
|
||||
forceSSL = ! cfg.private-network;
|
||||
enableACME = !cfg.private-network;
|
||||
forceSSL = !cfg.private-network;
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:9090";
|
||||
|
||||
extraConfig = let
|
||||
local-networks = config.instance.local-networks;
|
||||
extraConfig = let local-networks = config.instance.local-networks;
|
||||
in "${optionalString ((length local-networks) > 0)
|
||||
(concatStringsSep "\n"
|
||||
(map (network: "allow ${network};") local-networks)) + "\ndeny all;"}";
|
||||
(concatStringsSep "\n"
|
||||
(map (network: "allow ${network};") local-networks)) + ''
|
||||
|
||||
deny all;''}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -124,19 +131,22 @@ in {
|
|||
honor_labels = false;
|
||||
scheme = if cfg.private-network then "http" else "https";
|
||||
metrics_path = "/metrics/${type}";
|
||||
dns_sd_configs = if (hasAttr type cfg.service-discovery-dns) then
|
||||
[ { names = cfg.service-discovery-dns.${type}; } ] else [];
|
||||
static_configs = if (hasAttr type cfg.static-targets) then
|
||||
[ { targets = cfg.static-targets.${type}; } ] else [];
|
||||
dns_sd_configs = if (hasAttr type cfg.service-discovery-dns) then [{
|
||||
names = cfg.service-discovery-dns.${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 = {
|
||||
enable = if (cfg.push-url != null) then true else false;
|
||||
web = {
|
||||
external-url = if cfg.push-url == null then
|
||||
cfg.push-address else
|
||||
cfg.push-url;
|
||||
external-url =
|
||||
if cfg.push-url == null then cfg.push-address else cfg.push-url;
|
||||
listen-address = cfg.push-address;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -16,7 +16,8 @@ let
|
|||
|
||||
decrypt-script = { secret-name, source-file, target-host, target-file
|
||||
, 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}
|
||||
touch ${target-file}
|
||||
chown ${user}:${group} ${target-file}
|
||||
|
@ -24,27 +25,33 @@ let
|
|||
# NOTE: silly hack because sometimes age leaves a blank line
|
||||
# Only include lines with at least one non-space character
|
||||
SRC=$(mktemp fudo-secret-${target-host}-${secret-name}.XXXXXXXX)
|
||||
cat ${encrypt-on-disk {
|
||||
cat ${
|
||||
encrypt-on-disk {
|
||||
inherit secret-name source-file target-host;
|
||||
target-pubkey = host-master-key.public-key;
|
||||
}} | grep "[^ ]" > $SRC
|
||||
}
|
||||
} | grep "[^ ]" > $SRC
|
||||
age -d -i ${host-master-key.key-path} -o ${target-file} $SRC
|
||||
rm -f $SRC
|
||||
'';
|
||||
|
||||
secret-service = target-host: secret-name:
|
||||
{ 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 ];
|
||||
before = [ cfg.secret-target ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = let
|
||||
host-master-key = config.fudo.hosts.${target-host}.master-key;
|
||||
in decrypt-script {
|
||||
inherit secret-name source-file target-host target-file host-master-key
|
||||
user group permissions;
|
||||
};
|
||||
Type = "simple";
|
||||
RemainAfterExit = true;
|
||||
ExecStart =
|
||||
let host-master-key = config.fudo.hosts.${target-host}.master-key;
|
||||
in decrypt-script {
|
||||
inherit secret-name source-file target-host target-file
|
||||
host-master-key user group permissions;
|
||||
};
|
||||
ExecStop = pkgs.writeShellScript "fudo-remove-${secret-name}-secret.sh"
|
||||
"rm -f ${target-file}";
|
||||
};
|
||||
path = [ pkgs.age ];
|
||||
};
|
||||
|
@ -52,8 +59,10 @@ let
|
|||
secretOpts = { name, ... }: {
|
||||
options = with types; {
|
||||
source-file = mkOption {
|
||||
type = 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.";
|
||||
type =
|
||||
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";
|
||||
};
|
||||
|
||||
|
@ -72,7 +81,7 @@ let
|
|||
group = mkOption {
|
||||
type = str;
|
||||
description = "Group (on target host) to which the file will belong.";
|
||||
default = "nogroup";
|
||||
default = "root";
|
||||
};
|
||||
|
||||
permissions = mkOption {
|
||||
|
@ -84,7 +93,7 @@ let
|
|||
metadata = mkOption {
|
||||
type = attrsOf anything;
|
||||
description = "Arbitrary metadata associated with this secret.";
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
|
||||
service = mkOption {
|
||||
|
@ -97,32 +106,34 @@ let
|
|||
|
||||
nix-build-users = let usernames = attrNames config.users.users;
|
||||
in filter (user: (builtins.match "^nixbld[0-9]{1,2}$" user) != null)
|
||||
usernames;
|
||||
usernames;
|
||||
|
||||
generate-secret = name: pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-generated-passwd";
|
||||
generate-secret = name:
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-generated-passwd";
|
||||
|
||||
phases = [ "installPhase" ];
|
||||
phases = [ "installPhase" ];
|
||||
|
||||
buildInputs = with pkgs; [ pwgen ];
|
||||
buildInputs = with pkgs; [ pwgen ];
|
||||
|
||||
buildPhase = ''
|
||||
echo "${name}-${config.instance.build-timestamp}" >> file.txt
|
||||
pwgen --secure --symbols --num-passwords=1 --sha1=file.txt 40 > passwd
|
||||
rm -f file.txt
|
||||
'';
|
||||
buildPhase = ''
|
||||
echo "${name}-${config.instance.build-timestamp}" >> file.txt
|
||||
pwgen --secure --symbols --num-passwords=1 --sha1=file.txt 40 > passwd
|
||||
rm -f file.txt
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir $out
|
||||
mv passwd $out/passwd
|
||||
'';
|
||||
};
|
||||
installPhase = ''
|
||||
mkdir $out
|
||||
mv passwd $out/passwd
|
||||
'';
|
||||
};
|
||||
|
||||
in {
|
||||
options.fudo.secrets = with types; {
|
||||
enable = mkOption {
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -175,9 +186,7 @@ in {
|
|||
|
||||
config = mkIf cfg.enable {
|
||||
users.groups = {
|
||||
${cfg.secret-group} = {
|
||||
members = cfg.secret-users ++ nix-build-users;
|
||||
};
|
||||
${cfg.secret-group} = { members = cfg.secret-users ++ nix-build-users; };
|
||||
};
|
||||
|
||||
systemd = let
|
||||
|
@ -189,25 +198,24 @@ in {
|
|||
{ };
|
||||
|
||||
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:
|
||||
head-or-null
|
||||
(builtins.match "^(.+)[.]service$" service-name);
|
||||
head-or-null (builtins.match "^(.+)[.]service$" service-name);
|
||||
in mapAttrs' (secret: secretOpts:
|
||||
(nameValuePair (strip-service secretOpts.service)
|
||||
(secret-service hostname secret secretOpts))) host-secrets;
|
||||
|
||||
trace-all = obj: builtins.trace obj obj;
|
||||
|
||||
host-secret-paths = mapAttrsToList
|
||||
(secret: secretOpts:
|
||||
let perms = if secretOpts.group != "nobody" then "550" else "500";
|
||||
in "d ${dirOf secretOpts.target-file} ${perms} ${secretOpts.user} ${secretOpts.group} - -")
|
||||
host-secrets;
|
||||
host-secret-paths = mapAttrsToList (secret: secretOpts:
|
||||
let perms = if secretOpts.group != "nobody" then "550" else "500";
|
||||
in "d ${
|
||||
dirOf secretOpts.target-file
|
||||
} ${perms} ${secretOpts.user} ${secretOpts.group} - -") host-secrets;
|
||||
|
||||
build-secret-paths =
|
||||
map (path: "d '${path}' - root ${cfg.secret-group} - -")
|
||||
cfg.secret-paths;
|
||||
cfg.secret-paths;
|
||||
|
||||
in {
|
||||
tmpfiles.rules = host-secret-paths ++ build-secret-paths;
|
||||
|
@ -231,7 +239,8 @@ in {
|
|||
strip-ext = filename: head (builtins.match "^(.+)[.]target$" filename);
|
||||
in {
|
||||
${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" ];
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,33 +2,34 @@
|
|||
|
||||
with pkgs.lib;
|
||||
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";
|
||||
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";
|
||||
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);
|
||||
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
|
||||
files = nix-files path;
|
||||
in listToAttrs
|
||||
(map (file:
|
||||
nameValuePair (strip-ext "nix" file)
|
||||
(path + "/${file}"))
|
||||
ext-files = ext: 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
|
||||
(map (file: nameValuePair (strip-ext "nix" file) (path + "/${file}"))
|
||||
files);
|
||||
|
||||
import-by-basename = path:
|
||||
mapAttrs (attr: attr-file: import attr-file)
|
||||
(basename-to-file-map path);
|
||||
mapAttrs (attr: attr-file: import attr-file) (basename-to-file-map path);
|
||||
|
||||
list-nix-files = nix-files;
|
||||
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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue