Lots of changes.
This commit is contained in:
parent
c40aba6133
commit
9edae4a39c
@ -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;
|
||||||
gitea-admin = writeShellScriptBin "gitea-admin" ''
|
let
|
||||||
TMP=$(mktemp -d /tmp/gitea-XXXXXXXX)
|
gitea-admin = writeShellScriptBin "gitea-admin" ''
|
||||||
${gitea}/bin/gitea --custom-path ${cfg.state-dir}/custom --config ${cfg.state-dir}/custom/conf/app.ini --work-path $TMP $@
|
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
|
in [ gitea-admin ];
|
||||||
];
|
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
gitea = {
|
gitea = {
|
||||||
|
@ -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,25 +108,25 @@ let
|
|||||||
|
|
||||||
makeNetworksEntry = networks: join-lines (map makeEntry networks);
|
makeNetworksEntry = networks: join-lines (map makeEntry networks);
|
||||||
|
|
||||||
makeLocalUserPasswordEntries = users: networks: let
|
makeLocalUserPasswordEntries = users: networks:
|
||||||
network-entries = user: db:
|
let
|
||||||
join-lines
|
network-entries = user: db:
|
||||||
(map (network: "hostssl ${db} ${user} ${network} md5")
|
join-lines
|
||||||
networks);
|
(map (network: "hostssl ${db} ${user} ${network} md5") 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
|
||||||
host ${db} ${user} 127.0.0.1/16 md5
|
host ${db} ${user} 127.0.0.1/16 md5
|
||||||
host ${db} ${user} ::1/128 md5
|
host ${db} ${user} ::1/128 md5
|
||||||
${network-entries user db}
|
${network-entries user db}
|
||||||
'') (attrNames opts.databases))) (filterPasswordedUsers users));
|
'') (attrNames opts.databases))) (filterPasswordedUsers users));
|
||||||
|
|
||||||
userTableAccessSql = user: entity: access:
|
userTableAccessSql = user: entity: access:
|
||||||
"GRANT ${access} ON ${entity} TO ${user};";
|
"GRANT ${access} ON ${entity} TO ${user};";
|
||||||
userDatabaseAccessSql = user: database: dbOpts: ''
|
userDatabaseAccessSql = user: database: dbOpts: ''
|
||||||
\c ${database}
|
\c ${database}
|
||||||
${join-lines
|
${join-lines
|
||||||
(mapAttrsToList (userTableAccessSql user) dbOpts.entity-access)}
|
(mapAttrsToList (userTableAccessSql user) dbOpts.entity-access)}
|
||||||
'';
|
'';
|
||||||
userAccessSql = user: userOpts:
|
userAccessSql = user: userOpts:
|
||||||
join-lines (mapAttrsToList (userDatabaseAccessSql user) userOpts.databases);
|
join-lines (mapAttrsToList (userDatabaseAccessSql user) userOpts.databases);
|
||||||
@ -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,9 +284,9 @@ 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} = {
|
||||||
description = "Postgresql and associated systemd services.";
|
description = "Postgresql and associated systemd services.";
|
||||||
@ -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" ''
|
||||||
|
@ -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,61 +38,67 @@ 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:
|
||||||
domain-name = domain.domain;
|
let
|
||||||
host-ip = pkgs.lib.network.host-ipv4 config hostname;
|
domain-name = domain.domain;
|
||||||
ipv6-net = net: (builtins.match ":" net) != null;
|
host-ip = pkgs.lib.network.host-ipv4 config hostname;
|
||||||
ipv4-net = net: !(ipv6-net net);
|
ipv6-net = net: (builtins.match ":" net) != null;
|
||||||
domain-records = [
|
ipv4-net = net: !(ipv6-net net);
|
||||||
(mkRecord domain-name "SOA" "ns1.${domain-name} hostmaster.${domain-name} ${toString config.instance.build-timestamp} 10800 3600 1209600 3600")
|
domain-records = [
|
||||||
(mkRecord "_dmark.${domain-name}" "TXT" ''"v=DMARC1; p=reject; rua=mailto:${domain.admin}; ruf=mailto:${domain.admin}; fo=1;"'')
|
(mkRecord domain-name "SOA"
|
||||||
(mkRecord domain-name "NS" "ns1.${domain-name}")
|
"ns1.${domain-name} hostmaster.${domain-name} ${
|
||||||
(mkRecord domain-name "TXT" (let
|
toString config.instance.build-timestamp
|
||||||
networks = config.instance.local-networks;
|
} 10800 3600 1209600 3600")
|
||||||
v4-nets = map (net: "ip4:${net}") (filter ipv4-net networks);
|
(mkRecord "_dmark.${domain-name}" "TXT" ''
|
||||||
v6-nets = map (net: "ip6:${net}") (filter ipv6-net networks);
|
"v=DMARC1; p=reject; rua=mailto:${domain.admin}; ruf=mailto:${domain.admin}; fo=1;"'')
|
||||||
networks-string = concatStringsSep " " (v4-nets ++ v6-nets);
|
(mkRecord domain-name "NS" "ns1.${domain-name}")
|
||||||
in ''"v=spf1 mx ${networks-string} -all"''))
|
(mkRecord domain-name "TXT" (let
|
||||||
(mkRecord "ns1.${domain-name}" "A" host-ip)
|
networks = config.instance.local-networks;
|
||||||
(mkRecord domain-name "A" host-ip)
|
v4-nets = map (net: "ip4:${net}") (filter ipv4-net networks);
|
||||||
] ++
|
v6-nets = map (net: "ip6:${net}") (filter ipv6-net networks);
|
||||||
(optional (domain.gssapi-realm != null)
|
networks-string = concatStringsSep " " (v4-nets ++ v6-nets);
|
||||||
(mkRecord "_kerberos.${domain-name}" "TXT" ''"domain.gssapi-realm"'')) ++
|
in ''"v=spf1 mx ${networks-string} -all"''))
|
||||||
(mapAttrsToList (alias: target: mkRecord alias "CNAME" target)
|
(mkRecord "ns1.${domain-name}" "A" host-ip)
|
||||||
domain.aliases);
|
(mkRecord domain-name "A" host-ip)
|
||||||
records-strings = map (record:
|
] ++ (optional (domain.gssapi-realm != null)
|
||||||
"INSERT INTO records (domain_id, name, type, content) SELECT id, '${record.name}', '${record.type}', '${record.content}' FROM domains WHERE name='${domain-name}';")
|
(mkRecord "_kerberos.${domain-name}" "TXT" ''"domain.gssapi-realm"''))
|
||||||
domain-records;
|
++ (mapAttrsToList (alias: target: mkRecord alias "CNAME" target)
|
||||||
in pkgs.writeText "initialize-${domain-name}.sql" ''
|
domain.aliases);
|
||||||
INSERT INTO domains (name, master, type, notified_serial) VALUES ('${domain-name}', '${host-ip}', 'MASTER', '${toString config.instance.build-timestamp}');
|
records-strings = map (record:
|
||||||
${concatStringsSep "\n" records-strings}
|
"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
|
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"
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
logger "initializing ${domain-name} in powerdns database"
|
logger "initializing ${domain-name} in powerdns database"
|
||||||
psql -f ${initialize-domain-sql domain}
|
psql -f ${initialize-domain-sql domain}
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
domainOpts = { name, ... }: {
|
domainOpts = { name, ... }: {
|
||||||
options = with types; {
|
options = with types; {
|
||||||
@ -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,32 +244,34 @@ 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"
|
||||||
requiredBy = [ "powerdns.service" ];
|
"powerdns-generate-pgpass.service"
|
||||||
wantedBy = [ "powerdns.service" ];
|
];
|
||||||
before = [ "powerdns.service" ];
|
after = [
|
||||||
environment = {
|
"powerdns-initialize-db.service"
|
||||||
PGHOST = cfg.database.host;
|
"powerdns-generate-pgpass.service"
|
||||||
PGUSER = cfg.database.user;
|
];
|
||||||
PGDATABASE = cfg.database.database;
|
requiredBy = [ "powerdns.service" ];
|
||||||
PGPORT = toString cfg.database.port;
|
wantedBy = [ "powerdns.service" ];
|
||||||
PGSSLMODE= "require";
|
before = [ "powerdns.service" ];
|
||||||
PGPASSFILE = pgpass-file;
|
environment = {
|
||||||
};
|
PGHOST = cfg.database.host;
|
||||||
path = with pkgs; [ postgresql util-linux ];
|
PGUSER = cfg.database.user;
|
||||||
serviceConfig = {
|
PGDATABASE = cfg.database.database;
|
||||||
ExecStart = initialize-domain-script domain;
|
PGPORT = toString cfg.database.port;
|
||||||
};
|
PGSSLMODE = "require";
|
||||||
}) cfg.domains;
|
PGPASSFILE = pgpass-file;
|
||||||
|
};
|
||||||
|
path = with pkgs; [ postgresql util-linux ];
|
||||||
|
serviceConfig = { ExecStart = initialize-domain-script domain; };
|
||||||
|
}) 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,35 +330,33 @@ 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"
|
||||||
"${pkgs.powerdns}/bin/pdns_server"
|
(concatStringsSep " " [
|
||||||
"--setuid=${cfg.user}"
|
"${pkgs.powerdns}/bin/pdns_server"
|
||||||
"--setgid=${cfg.group}"
|
"--setuid=${cfg.user}"
|
||||||
"--chroot=${runtime-dir}"
|
"--setgid=${cfg.group}"
|
||||||
"--daemon=no"
|
"--chroot=${runtime-dir}"
|
||||||
"--guardian=no"
|
"--daemon=no"
|
||||||
"--write-pid=no"
|
"--guardian=no"
|
||||||
"--config-dir=${pdns-config-dir}"
|
"--write-pid=no"
|
||||||
]);
|
"--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
|
DNSINFO=$(${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} show-zone ${domain.domain})
|
||||||
(_: domain: ''
|
if [[ "x$DNSINFO" =~ "xNo such zone in the database" ]]; then
|
||||||
DNSINFO=$(${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} show-zone ${domain.domain})
|
logger "zone ${domain.domain} does not exist in powerdns database"
|
||||||
if [[ "x$DNSINFO" =~ "xNo such zone in the database" ]]; then
|
elif [[ "x$DNSINFO" =~ "xZone is not actively secured" ]]; then
|
||||||
logger "zone ${domain.domain} does not exist in powerdns database"
|
logger "securing zone ${domain.domain} in powerdns database"
|
||||||
elif [[ "x$DNSINFO" =~ "xZone is not actively secured" ]]; then
|
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
|
||||||
logger "securing zone ${domain.domain} in powerdns database"
|
elif [[ "x$DNSINFO" =~ "xNo keys for zone" ]]; then
|
||||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
|
logger "securing zone ${domain.domain} in powerdns database"
|
||||||
elif [[ "x$DNSINFO" =~ "xNo keys for zone" ]]; then
|
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
|
||||||
logger "securing zone ${domain.domain} in powerdns database"
|
else
|
||||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
|
logger "not securing zone ${domain.domain} in powerdns database"
|
||||||
else
|
fi
|
||||||
logger "not securing zone ${domain.domain} in powerdns database"
|
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} rectify-zone ${domain.domain}
|
||||||
fi
|
'') cfg.domains));
|
||||||
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} rectify-zone ${domain.domain}
|
|
||||||
'')
|
|
||||||
cfg.domains));
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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,26 +18,24 @@ 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 = [ ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
static-targets = mkOption {
|
static-targets = mkOption {
|
||||||
type = attrsOf (listOf str);
|
type = attrsOf (listOf str);
|
||||||
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;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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 =
|
||||||
in decrypt-script {
|
let host-master-key = config.fudo.hosts.${target-host}.master-key;
|
||||||
inherit secret-name source-file target-host target-file host-master-key
|
in decrypt-script {
|
||||||
user group permissions;
|
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 ];
|
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 {
|
||||||
@ -97,32 +106,34 @@ let
|
|||||||
|
|
||||||
nix-build-users = let usernames = attrNames config.users.users;
|
nix-build-users = let usernames = attrNames config.users.users;
|
||||||
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:
|
||||||
name = "${name}-generated-passwd";
|
pkgs.stdenv.mkDerivation {
|
||||||
|
name = "${name}-generated-passwd";
|
||||||
|
|
||||||
phases = [ "installPhase" ];
|
phases = [ "installPhase" ];
|
||||||
|
|
||||||
buildInputs = with pkgs; [ pwgen ];
|
buildInputs = with pkgs; [ pwgen ];
|
||||||
|
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
echo "${name}-${config.instance.build-timestamp}" >> file.txt
|
echo "${name}-${config.instance.build-timestamp}" >> file.txt
|
||||||
pwgen --secure --symbols --num-passwords=1 --sha1=file.txt 40 > passwd
|
pwgen --secure --symbols --num-passwords=1 --sha1=file.txt 40 > passwd
|
||||||
rm -f file.txt
|
rm -f file.txt
|
||||||
'';
|
'';
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir $out
|
mkdir $out
|
||||||
mv passwd $out/passwd
|
mv passwd $out/passwd
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
in {
|
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,25 +198,24 @@ 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 ${
|
||||||
in "d ${dirOf secretOpts.target-file} ${perms} ${secretOpts.user} ${secretOpts.group} - -")
|
dirOf secretOpts.target-file
|
||||||
host-secrets;
|
} ${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} - -")
|
||||||
cfg.secret-paths;
|
cfg.secret-paths;
|
||||||
|
|
||||||
in {
|
in {
|
||||||
tmpfiles.rules = host-secret-paths ++ build-secret-paths;
|
tmpfiles.rules = host-secret-paths ++ build-secret-paths;
|
||||||
@ -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" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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)
|
||||||
in listToAttrs
|
(filterAttrs is-regular-file (builtins.readDir path)));
|
||||||
(map (file:
|
|
||||||
nameValuePair (strip-ext "nix" file)
|
nix-files = ext-files "nix";
|
||||||
(path + "/${file}"))
|
|
||||||
|
basename-to-file-map = path:
|
||||||
|
let files = nix-files path;
|
||||||
|
in listToAttrs
|
||||||
|
(map (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;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user