Many changes

This commit is contained in:
niten 2022-06-01 13:57:36 -07:00
parent 9edae4a39c
commit df53c13bb7
4 changed files with 293 additions and 242 deletions

View File

@ -102,7 +102,7 @@ in {
EmailSettings = { EmailSettings = {
RequireEmailVerification = true; RequireEmailVerification = true;
SMTPServer = cfg.smtp.server; SMTPServer = cfg.smtp.server;
SMTPPort = 587; SMTPPort = "587";
EnableSMTPAuth = true; EnableSMTPAuth = true;
SMTPUsername = cfg.smtp.user; SMTPUsername = cfg.smtp.user;
SMTPPassword = "__SMTP_PASSWD__"; SMTPPassword = "__SMTP_PASSWD__";
@ -113,22 +113,20 @@ in {
}; };
EnableEmailInvitations = true; EnableEmailInvitations = true;
SqlSettings.DriverName = "postgres"; SqlSettings.DriverName = "postgres";
SqlSettings.DataSource = "postgres://${ SqlSettings.DataSource =
cfg.database.user "postgres://${cfg.database.user}:__DATABASE_PASSWORD__@${cfg.database.hostname}:5432/${cfg.database.name}";
}:__DATABASE_PASSWORD__@${
cfg.database.hostname
}:5432/${
cfg.database.name
}";
}; };
mattermost-config-file-template = mattermost-config-file-template =
pkgs.writeText "mattermost-config.json.template" (builtins.toJSON modified-config); pkgs.writeText "mattermost-config.json.template"
(builtins.toJSON modified-config);
generate-mattermost-config = target: template: smtp-passwd-file: db-passwd-file: generate-mattermost-config =
target: template: smtp-passwd-file: db-passwd-file:
pkgs.writeScript "mattermost-config-generator.sh" '' pkgs.writeScript "mattermost-config-generator.sh" ''
rm ${target}
SMTP_PASSWD=$( cat ${smtp-passwd-file} ) SMTP_PASSWD=$( cat ${smtp-passwd-file} )
DATABASE_PASSWORD=$( cat ${db-passwd-file} ) DATABASE_PASSWORD=$( cat ${db-passwd-file} )
sed -e 's/__SMTP_PASSWD__/"$SMTP_PASSWD"/' -e 's/__DATABASE_PASSWORD__/"$DATABASE_PASSWORD"/' ${template} > ${target} sed -e "s/__SMTP_PASSWD__/$SMTP_PASSWD/" -e "s/__DATABASE_PASSWORD__/$DATABASE_PASSWORD/" ${template} > ${target}
''; '';
in { in {
@ -136,10 +134,10 @@ in {
users = { users = {
${cfg.user} = { ${cfg.user} = {
isSystemUser = true; isSystemUser = true;
group = mattermost-group; group = cfg.group;
}; };
}; };
groups.${cfg.group}.members = [ cfg.user ]; groups = { ${cfg.group} = { members = [ cfg.user ]; }; };
}; };
fudo.system.services.mattermost = { fudo.system.services.mattermost = {
@ -147,17 +145,25 @@ in {
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
after = [ "network.target" ]; after = [ "network.target" ];
preStart = '' preStart =
${generate-mattermost-config let config-target = "${cfg.state-directory}/config/config.json";
mattermost-config-target in ''
mattermost-config-file-template if [ ! -f ${config-target} ]; then
cfg.smtp.password-file ${
cfg.database.password-file} generate-mattermost-config mattermost-config-target
cp ${cfg.smtp.password-file} ${cfg.state-directory}/config/config.json mattermost-config-file-template cfg.smtp.password-file
cp -uRL ${pkg}/client ${cfg.state-directory} cfg.database.password-file
chown ${cfg.user}:${cfg.group} ${cfg.state-directory}/client }
chmod 0750 ${cfg.state-directory}/client cp ${mattermost-config-target} ${config-target}
''; chown ${cfg.user}:${cfg.group} ${config-target}
chmod 640 ${config-target}
fi
if [ ! -e ${cfg.state-directory} ]; then
cp -uRL ${pkg}/client ${cfg.state-directory}
chown ${cfg.user}:${cfg.group} ${cfg.state-directory}/client
chmod 0750 ${cfg.state-directory}/client
fi
'';
execStart = "${pkg}/bin/mattermost"; execStart = "${pkg}/bin/mattermost";
workingDirectory = cfg.state-directory; workingDirectory = cfg.state-directory;
user = cfg.user; user = cfg.user;
@ -167,8 +173,9 @@ in {
systemd = { systemd = {
tmpfiles.rules = [ tmpfiles.rules = [
"d ${cfg.state-directory} 0750 ${cfg.user} ${cfg.group} - -" "d ${cfg.state-directory} 0750 ${cfg.user} - - -"
"d ${cfg.state-directory}/config 0750 ${cfg.user} ${cfg.group} - -" "d ${cfg.state-directory}/config 0750 ${cfg.user} - - -"
"d ${dirOf mattermost-config-target} 0750 ${cfg.user} - - -"
"L ${cfg.state-directory}/bin - - - - ${pkg}/bin" "L ${cfg.state-directory}/bin - - - - ${pkg}/bin"
"L ${cfg.state-directory}/fonts - - - - ${pkg}/fonts" "L ${cfg.state-directory}/fonts - - - - ${pkg}/fonts"
"L ${cfg.state-directory}/i18n - - - - ${pkg}/i18n" "L ${cfg.state-directory}/i18n - - - - ${pkg}/i18n"
@ -183,6 +190,8 @@ in {
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mattermost_cache:10m max_size=3g inactive=120m use_temp_path=off; proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mattermost_cache:10m max_size=3g inactive=120m use_temp_path=off;
''; '';
recommendedProxySettings = true;
virtualHosts = { virtualHosts = {
"${cfg.hostname}" = { "${cfg.hostname}" = {
enableACME = true; enableACME = true;
@ -190,51 +199,52 @@ in {
locations."/" = { locations."/" = {
proxyPass = "http://127.0.0.1:8065"; proxyPass = "http://127.0.0.1:8065";
proxyWebsockets = true;
extraConfig = '' # extraConfig = ''
client_max_body_size 50M; # client_max_body_size 50M;
proxy_set_header Connection ""; # proxy_set_header Connection "";
proxy_set_header Host $host; # proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; # proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-By $server_addr:$server_port; # proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; # proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Frame-Options SAMEORIGIN; # proxy_set_header X-Frame-Options SAMEORIGIN;
proxy_buffers 256 16k; # proxy_buffers 256 16k;
proxy_buffer_size 16k; # proxy_buffer_size 16k;
proxy_read_timeout 600s; # proxy_read_timeout 600s;
proxy_cache mattermost_cache; # proxy_cache mattermost_cache;
proxy_cache_revalidate on; # proxy_cache_revalidate on;
proxy_cache_min_uses 2; # proxy_cache_min_uses 2;
proxy_cache_use_stale timeout; # proxy_cache_use_stale timeout;
proxy_cache_lock on; # proxy_cache_lock on;
proxy_http_version 1.1; # proxy_http_version 1.1;
''; # '';
}; };
locations."~ /api/v[0-9]+/(users/)?websocket$" = { # locations."~ /api/v[0-9]+/(users/)?websocket$" = {
proxyPass = "http://127.0.0.1:8065"; # proxyPass = "http://127.0.0.1:8065";
extraConfig = '' # extraConfig = ''
proxy_set_header Upgrade $http_upgrade; # proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; # proxy_set_header Connection "upgrade";
client_max_body_size 50M; # client_max_body_size 50M;
proxy_set_header Host $host; # proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; # proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-By $server_addr:$server_port; # proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; # proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Frame-Options SAMEORIGIN; # proxy_set_header X-Frame-Options SAMEORIGIN;
proxy_buffers 256 16k; # proxy_buffers 256 16k;
proxy_buffer_size 16k; # proxy_buffer_size 16k;
client_body_timeout 60; # client_body_timeout 60;
send_timeout 300; # send_timeout 300;
lingering_timeout 5; # lingering_timeout 5;
proxy_connect_timeout 90; # proxy_connect_timeout 90;
proxy_send_timeout 300; # proxy_send_timeout 300;
proxy_read_timeout 90s; # proxy_read_timeout 90s;
''; # '';
}; # };
}; };
}; };
}; };

View File

@ -5,171 +5,185 @@ let
hostname = config.instance.hostname; hostname = config.instance.hostname;
domain = config.instance.local-domain; domain = config.instance.local-domain;
domainOpts = { name, ... }: let domainOpts = { name, ... }:
domain = name; let domain = name;
in { in {
options = with types; { options = with types; {
domain = mkOption { domain = mkOption {
type = str;
description = "Domain name.";
default = domain;
};
local-networks = mkOption {
type = listOf str;
description =
"A list of networks to be considered trusted on this network.";
default = [ ];
};
local-users = mkOption {
type = listOf str;
description =
"A list of users who should have local (i.e. login) access to _all_ hosts in this domain.";
default = [ ];
};
local-admins = mkOption {
type = listOf str;
description =
"A list of users who should have admin access to _all_ hosts in this domain.";
default = [ ];
};
local-groups = mkOption {
type = listOf str;
description = "List of groups which should exist within this domain.";
default = [ ];
};
admin-email = mkOption {
type = str;
description = "Email for the administrator of this domain.";
default = "admin@${domain}";
};
grafana-hosts = mkOption {
type = listOf str;
description = "List of hosts acting as Grafana metric analyzers. Requires prometheus hosts as well.";
default = [];
};
log-aggregator = mkOption {
type = nullOr str;
description = "Host which will accept incoming log pushes.";
default = null;
};
postgresql-server = mkOption {
type = nullOr str;
description = "Hostname acting as the local PostgreSQL server.";
default = null;
};
backplane = mkOption {
type = nullOr (submodule {
options = {
nameserver = mkOption {
type = nullOr str;
description = "Host acting as backplane dynamic DNS server.";
default = null;
};
dns-service = mkOption {
type = nullOr str;
description = "DNS backplane service host.";
default = null;
};
domain = mkOption {
type = str;
description = "Domain name of the dynamic zone served by this server.";
};
};
});
description = "Backplane configuration.";
default = null;
};
wireguard = {
gateway = mkOption {
type = str; type = str;
description = "Host serving as WireGuard gateway for this domain."; description = "Domain name.";
default = domain;
}; };
network = mkOption { local-networks = mkOption {
type = str; type = listOf str;
description = "IP subnet used for WireGuard clients."; description =
default = "172.16.0.0/16"; "A list of networks to be considered trusted on this network.";
default = [ ];
}; };
routed-network = mkOption { local-users = mkOption {
type = listOf str;
description =
"A list of users who should have local (i.e. login) access to _all_ hosts in this domain.";
default = [ ];
};
local-admins = mkOption {
type = listOf str;
description =
"A list of users who should have admin access to _all_ hosts in this domain.";
default = [ ];
};
local-groups = mkOption {
type = listOf str;
description = "List of groups which should exist within this domain.";
default = [ ];
};
admin-email = mkOption {
type = str;
description = "Email for the administrator of this domain.";
default = "admin@${domain}";
};
grafana-hosts = mkOption {
type = listOf str;
description =
"List of hosts acting as Grafana metric analyzers. Requires prometheus hosts as well.";
default = [ ];
};
log-aggregator = mkOption {
type = nullOr str; type = nullOr str;
description = "Subnet of larger network for which we NAT traffic."; description = "Host which will accept incoming log pushes.";
default = "172.16.16.0/20"; default = null;
}; };
};
gssapi-realm = mkOption { postgresql-server = mkOption {
type = str; type = nullOr str;
description = "GSSAPI (i.e. Kerberos) realm of this domain."; description = "Hostname acting as the local PostgreSQL server.";
}; default = null;
};
kerberos-master = mkOption { chat-server = mkOption {
type = nullOr str; type = nullOr str;
description = "Hostname of the Kerberos master server for the domain, if applicable."; description =
default = null; "Hostname acting as the domain chat server (using Mattermost).";
}; default = null;
};
kerberos-slaves = mkOption { backplane = mkOption {
type = listOf str; type = nullOr (submodule {
description = "List of hosts acting as Kerberos slaves for the domain."; options = {
default = []; nameserver = mkOption {
}; type = nullOr str;
description = "Host acting as backplane dynamic DNS server.";
default = null;
};
ldap-servers = mkOption { dns-service = mkOption {
type = listOf str; type = nullOr str;
description = "List of hosts acting as LDAP authentication servers for the domain."; description = "DNS backplane service host.";
default = []; default = null;
}; };
prometheus-hosts = mkOption { domain = mkOption {
type = listOf str; type = str;
description = "List of hosts acting aas prometheus metric scrapers for hosts in this network."; description =
default = []; "Domain name of the dynamic zone served by this server.";
}; };
};
});
description = "Backplane configuration.";
default = null;
};
primary-nameserver = mkOption { wireguard = {
type = nullOr str; gateway = mkOption {
description = "Hostname of the primary nameserver for this domain."; type = str;
default = null; description = "Host serving as WireGuard gateway for this domain.";
}; };
secondary-nameservers = mkOption { network = mkOption {
type = listOf str; type = str;
description = "List of hostnames of slave nameservers for this domain."; description = "IP subnet used for WireGuard clients.";
default = []; default = "172.16.0.0/16";
}; };
primary-mailserver = mkOption { routed-network = mkOption {
type = nullOr str; type = nullOr str;
description = "Hostname of the primary mail server for this domain."; description = "Subnet of larger network for which we NAT traffic.";
default = null; default = "172.16.16.0/20";
}; };
};
xmpp-servers = mkOption { gssapi-realm = mkOption {
type = listOf str; type = str;
description = "Hostnames of the domain XMPP servers."; description = "GSSAPI (i.e. Kerberos) realm of this domain.";
default = []; };
};
zone = mkOption { kerberos-master = mkOption {
type = nullOr str; type = nullOr str;
description = "Name of the DNS zone associated with domain."; description =
default = null; "Hostname of the Kerberos master server for the domain, if applicable.";
default = null;
};
kerberos-slaves = mkOption {
type = listOf str;
description =
"List of hosts acting as Kerberos slaves for the domain.";
default = [ ];
};
ldap-servers = mkOption {
type = listOf str;
description =
"List of hosts acting as LDAP authentication servers for the domain.";
default = [ ];
};
prometheus-hosts = mkOption {
type = listOf str;
description =
"List of hosts acting aas prometheus metric scrapers for hosts in this network.";
default = [ ];
};
primary-nameserver = mkOption {
type = nullOr str;
description = "Hostname of the primary nameserver for this domain.";
default = null;
};
secondary-nameservers = mkOption {
type = listOf str;
description =
"List of hostnames of slave nameservers for this domain.";
default = [ ];
};
primary-mailserver = mkOption {
type = nullOr str;
description = "Hostname of the primary mail server for this domain.";
default = null;
};
xmpp-servers = mkOption {
type = listOf str;
description = "Hostnames of the domain XMPP servers.";
default = [ ];
};
zone = mkOption {
type = nullOr str;
description = "Name of the DNS zone associated with domain.";
default = null;
};
}; };
}; };
};
in { in {
options.fudo.domains = mkOption { options.fudo.domains = mkOption {

View File

@ -64,6 +64,13 @@ let
"A list of users who should have full access to this database."; "A list of users who should have full access to this database.";
default = [ ]; default = [ ];
}; };
extensions = mkOption {
type = listOf str;
description =
"A list of extensions which should be created for this database.";
default = [ ];
};
}; };
}; };
@ -121,6 +128,13 @@ let
${network-entries user db} ${network-entries user db}
'') (attrNames opts.databases))) (filterPasswordedUsers users)); '') (attrNames opts.databases))) (filterPasswordedUsers users));
enableExtensionSql = ext: ''CREATE EXTENSION IF NOT EXISTS "${ext}";'';
enableDatabaseExtensionsSql = database: databaseOpts: ''
\c ${database}
${join-lines (map enableExtensionSql databaseOpts.extensions)}
'';
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: ''
@ -382,8 +396,12 @@ 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" ''
${join-lines
(mapAttrsToList enableDatabaseExtensionsSql cfg.databases)}
${concatStringsSep "\n" (map allow-user-login ${concatStringsSep "\n" (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

@ -2,13 +2,14 @@
with pkgs.lib; with pkgs.lib;
let let
generate-mac-address = hostname: interface: pkgs.stdenv.mkDerivation { generate-mac-address = hostname: interface:
name = "mk-mac-${hostname}-${interface}"; pkgs.stdenv.mkDerivation {
phases = [ "installPhase" ]; name = "mk-mac-${hostname}-${interface}";
installPhase = '' phases = [ "installPhase" ];
echo ${hostname}-${interface} | sha1sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/' > $out installPhase = ''
''; echo ${hostname}-${interface} | sha1sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/' > $out
}; '';
};
# dropUntil = pred: lst: let # dropUntil = pred: lst: let
# drop-until-helper = pred: lst: # drop-until-helper = pred: lst:
@ -28,32 +29,40 @@ let
# (lib.reverseList lines-front-stripped)); # (lib.reverseList lines-front-stripped));
# in concatStringsSep "\n" lines-rear-stripped; # in concatStringsSep "\n" lines-rear-stripped;
host-ipv4 = config: hostname: let host-ipv4 = config: hostname:
domain = config.fudo.hosts.${hostname}.domain; let
host-network = config.fudo.zones.${domain}; domain = config.fudo.hosts.${hostname}.domain;
in host-network.hosts.${hostname}.ipv4-address; host-network = config.fudo.zones.${domain};
in host-network.hosts.${hostname}.ipv4-address;
host-ipv6 = config: hostname: let host-ipv6 = config: hostname:
domain = config.fudo.hosts.${hostname}.domain; let
host-network = config.fudo.zones.${domain}; domain = config.fudo.hosts.${hostname}.domain;
in host-network.hosts.${hostname}.ipv6-address; host-network = config.fudo.zones.${domain};
in host-network.hosts.${hostname}.ipv6-address;
host-ips = config: hostname: let host-ips = config: hostname:
ipv4 = host-ipv4 config hostname; let
ipv6 = host-ipv6 config hostname; ipv4 = host-ipv4 config hostname;
not-null = o: o != null; ipv6 = host-ipv6 config hostname;
in filter not-null [ ipv4 ipv6 ]; not-null = o: o != null;
in filter not-null [ ipv4 ipv6 ];
site-gateway = config: site-name: let site-gateway = config: site-name:
site = config.fudo.sites.${site-name}; let site = config.fudo.sites.${site-name};
in if (site.local-gateway != null) in if (site.local-gateway != null) then
then host-ipv4 config site.local-gateway host-ipv4 config site.local-gateway
else site.gateway-v4; else
site.gateway-v4;
host-fqdn = config: hostname:
let domain-name = config.fudo.hosts.${hostname}.domain;
in "${hostname}.${domain-name}";
in { in {
inherit host-ipv4 host-ipv6 host-ips site-gateway; inherit host-ipv4 host-ipv6 host-ips site-gateway host-fqdn;
generate-mac-address = hostname: interface: let generate-mac-address = hostname: interface:
pkg = generate-mac-address hostname interface; let pkg = generate-mac-address hostname interface;
in removeSuffix "\n" (builtins.readFile "${pkg}"); in removeSuffix "\n" (builtins.readFile "${pkg}");
} }