Currently broken config...
This commit is contained in:
61
config/fudo/acme-for-hostname.nix
Normal file
61
config/fudo/acme-for-hostname.nix
Normal file
@@ -0,0 +1,61 @@
|
||||
# Starts an Nginx server on $HOSTNAME just to get a cert for this host
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.acme;
|
||||
|
||||
wwwRoot = hostname:
|
||||
pkgs.writeTextFile {
|
||||
name = "index.html";
|
||||
|
||||
text = ''
|
||||
<html>
|
||||
<head>
|
||||
<title>${hostname}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${hostname}</title>
|
||||
</body>
|
||||
</html>
|
||||
'';
|
||||
destination = "/www";
|
||||
};
|
||||
|
||||
in {
|
||||
|
||||
options.fudo.acme = {
|
||||
hostnames = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = "A list of hostnames mapping to this host, for which to acquire SSL certificates.";
|
||||
default = [];
|
||||
example = [
|
||||
"my.hostname.com"
|
||||
"alt.hostname.com"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
virtualHosts = listToAttrs
|
||||
(map
|
||||
(hostname:
|
||||
nameValuePair hostname
|
||||
{
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
root = (wwwRoot hostname) + ("/" + "www");
|
||||
})
|
||||
cfg.hostnames);
|
||||
};
|
||||
|
||||
security.acme.certs = listToAttrs
|
||||
(map (hostname: nameValuePair hostname { email = "admin@fudo.org"; })
|
||||
cfg.hostnames);
|
||||
};
|
||||
}
|
||||
67
config/fudo/authentication.nix
Normal file
67
config/fudo/authentication.nix
Normal file
@@ -0,0 +1,67 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.authentication;
|
||||
in {
|
||||
options.fudo.authentication = {
|
||||
enable = mkEnableOption "Use Fudo users & groups from LDAP.";
|
||||
|
||||
ssl-ca-certificate = mkOption {
|
||||
type = types.str;
|
||||
description = "Path to the CA certificate to use to bind to the server.";
|
||||
};
|
||||
|
||||
bind-passwd-file = mkOption {
|
||||
type = types.str;
|
||||
description = "Path to a file containing the password used to bind to the server.";
|
||||
};
|
||||
|
||||
ldap-url = mkOption {
|
||||
type = types.str;
|
||||
description = "URL of the LDAP server.";
|
||||
example = "ldaps://auth.fudo.org";
|
||||
};
|
||||
|
||||
base = mkOption {
|
||||
type = types.str;
|
||||
description = "The LDAP base in which to look for users.";
|
||||
default = "dc=fudo,dc=org";
|
||||
};
|
||||
|
||||
bind-dn = mkOption {
|
||||
type = types.str;
|
||||
description = "The DN with which to bind the LDAP server.";
|
||||
default = "cn=auth_reader,dc=fudo,dc=org";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users.ldap = {
|
||||
enable = true;
|
||||
base = cfg.base;
|
||||
bind = {
|
||||
distinguishedName = cfg.bind-dn;
|
||||
passwordFile = cfg.bind-passwd-file;
|
||||
timeLimit = 5;
|
||||
};
|
||||
loginPam = true;
|
||||
nsswitch = true;
|
||||
server = cfg.ldap-url;
|
||||
timeLimit = 5;
|
||||
useTLS = true;
|
||||
extraConfig = ''
|
||||
TLS_CACERT ${cfg.ssl-ca-certificate}
|
||||
TSL_REQCERT allow
|
||||
'';
|
||||
|
||||
daemon = {
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
tls_cacertfile ${cfg.ssl-ca-certificate}
|
||||
tls_reqcert allow
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
16
config/fudo/common.nix
Normal file
16
config/fudo/common.nix
Normal file
@@ -0,0 +1,16 @@
|
||||
# General Fudo config, shared across packages
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
{
|
||||
options.fudo.common = {
|
||||
local-networks = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = ''
|
||||
A list of networks to consider 'local'. Used by various services to
|
||||
limit access to the external world.
|
||||
'';
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
}
|
||||
127
config/fudo/grafana.nix
Normal file
127
config/fudo/grafana.nix
Normal file
@@ -0,0 +1,127 @@
|
||||
# NOTE: this assumes that postgres is running locally.
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.grafana;
|
||||
|
||||
database-name = "grafana";
|
||||
database-user = "grafana";
|
||||
|
||||
in {
|
||||
|
||||
options.fudo.grafana = {
|
||||
enable = mkEnableOption "Fudo Metrics Display Service";
|
||||
|
||||
hostname = mkOption {
|
||||
type = types.str;
|
||||
description = "Grafana site hostname.";
|
||||
example = "fancy-graphs.fudo.org";
|
||||
};
|
||||
|
||||
smtp-username = mkOption {
|
||||
type = types.str;
|
||||
description = "Username with which to send email.";
|
||||
};
|
||||
|
||||
smtp-password-file = mkOption {
|
||||
type = types.path;
|
||||
description = "Path to a file containing the email user's password.";
|
||||
};
|
||||
|
||||
database-password-file = mkOption {
|
||||
type = types.path;
|
||||
description = "Path to a file containing the database user's password.";
|
||||
};
|
||||
|
||||
admin-password-file = mkOption {
|
||||
type = types.path;
|
||||
description = "Path to a file containing the admin user's password.";
|
||||
};
|
||||
|
||||
secret-key-file = mkOption {
|
||||
type = types.path;
|
||||
description = "Path to a file containing the server's secret key, used for signatures.";
|
||||
};
|
||||
|
||||
prometheus-host = mkOption {
|
||||
type = types.str;
|
||||
description = "The URL of the prometheus data source.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
fudo.postgresql = {
|
||||
databases.${database-name} = {};
|
||||
|
||||
local-users.${database-user} = {
|
||||
password = (fileContents cfg.database-password-file);
|
||||
databases = ["${database-name}"];
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
virtualHosts = {
|
||||
"${cfg.hostname}" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:3000";
|
||||
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-By $server_addr:$server_port;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
|
||||
addr = "127.0.0.1";
|
||||
protocol = "http";
|
||||
domain = "${cfg.hostname}";
|
||||
rootUrl = "https://${cfg.hostname}/";
|
||||
|
||||
security = {
|
||||
adminPasswordFile = cfg.admin-password-file;
|
||||
secretKeyFile = cfg.secret-key-file;
|
||||
};
|
||||
|
||||
smtp = {
|
||||
enable = true;
|
||||
fromAddress = "metrics@fudo.org";
|
||||
host = "mail.fudo.org:25";
|
||||
user = cfg.smtp-username;
|
||||
passwordFile = cfg.smtp-password-file;
|
||||
};
|
||||
|
||||
database = {
|
||||
host = "localhost";
|
||||
name = database-name;
|
||||
user = database-user;
|
||||
passwordFile = cfg.database-password-file;
|
||||
type = "postgres";
|
||||
};
|
||||
|
||||
provision.datasources = [
|
||||
{
|
||||
editable = false;
|
||||
isDefault = true;
|
||||
name = cfg.prometheus-host;
|
||||
type = "prometheus";
|
||||
url = "https://${cfg.prometheus-host}/";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
99
config/fudo/kdc.nix
Normal file
99
config/fudo/kdc.nix
Normal file
@@ -0,0 +1,99 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
|
||||
cfg = config.fudo.auth.kdc;
|
||||
|
||||
stringJoin = joiner: attrList:
|
||||
if (length attrList) == 0 then
|
||||
""
|
||||
else
|
||||
foldr(lAttr: rAttr: "${lAttr}${joiner}${rAttr}") (last attrList) (init attrList);
|
||||
|
||||
in {
|
||||
|
||||
options.fudo.auth.kdc = {
|
||||
enable = mkEnableOption "Fudo KDC";
|
||||
|
||||
database-path = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The path at which to store the database files.
|
||||
'';
|
||||
default = "/var/heimdal/heimdal";
|
||||
};
|
||||
|
||||
realm = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The realm for which we are the acting KDC.
|
||||
'';
|
||||
};
|
||||
|
||||
mkey-file = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The path to the master key file.
|
||||
'';
|
||||
};
|
||||
|
||||
acl-file = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The path to the Access Control file.
|
||||
'';
|
||||
};
|
||||
|
||||
bind-addresses = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = ''
|
||||
A list of IP addresses on which to bind.
|
||||
'';
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment = {
|
||||
systemPackages = [
|
||||
pkgs.heimdalFull
|
||||
];
|
||||
|
||||
etc."krb5.conf" = {
|
||||
text = mkAfter ''
|
||||
[kdc]
|
||||
database = {
|
||||
realm = ${cfg.realm}
|
||||
mkey_file = ${cfg.mkey-file}
|
||||
acl_file = ${cfg.acl-file}
|
||||
}
|
||||
addresses = ${stringJoin " " cfg.bind-addresses}
|
||||
enable-http = true
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services = {
|
||||
heimdal-kdc = {
|
||||
enable = true;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
description = "Heimdal Kerberos Key Distribution Center (ticket server)";
|
||||
serviceConfig = {
|
||||
ExecStart = ''${pkgs.heimdalFull}/libexec/heimdal/kdc'';
|
||||
};
|
||||
};
|
||||
|
||||
heimdal-admin-server = {
|
||||
enable = true;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
description = "Heimdal Kerberos Remote Administration Server";
|
||||
serviceConfig = {
|
||||
ExecStart = ''${pkgs.heimdalFull}/libexec/heimdal/kadmind'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -206,10 +206,11 @@ in {
|
||||
};
|
||||
|
||||
sslCACert = mkOption {
|
||||
type = types.str;
|
||||
type = with types; nullOr str;
|
||||
description = ''
|
||||
The path to the SSL CA cert used to sign the certificate.
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
|
||||
organization = mkOption {
|
||||
@@ -329,7 +330,7 @@ in {
|
||||
|
||||
TLSCertificateFile ${cfg.sslCert}
|
||||
TLSCertificateKeyFile ${cfg.sslKey}
|
||||
TLSCACertificateFile ${cfg.sslCACert}
|
||||
${optionalString (cfg.sslCACert != null) "TLSCACertificateFile ${cfg.sslCACert}"}
|
||||
|
||||
authz-regexp "^uid=auth/([^.]+)\.fudo\.org,cn=fudo\.org,cn=gssapi,cn=auth$" "cn=$1,ou=hosts,dc=fudo,dc=org"
|
||||
authz-regexp "^uid=[^,/]+/root,cn=fudo\.org,cn=gssapi,cn=auth$" "cn=admin,dc=fudo,dc=org"
|
||||
@@ -363,8 +364,9 @@ access to dn.subtree="ou=groups,${cfg.base}" attrs=memberUid
|
||||
by users read
|
||||
by * none
|
||||
|
||||
access to dn.subtree="ou=members,${cfg.base}" attrs=cn,sn,homeDirectory,loginShell,gecos,description
|
||||
access to dn.subtree="ou=members,${cfg.base}" attrs=cn,sn,homeDirectory,loginShell,gecos,description,homeDirectory,uidNumber,gidNumber
|
||||
by group.exact="cn=admin,ou=groups,${cfg.base}" write
|
||||
by dn.exact="cn=user_db_reader,${cfg.base}" read
|
||||
by users read
|
||||
by * none
|
||||
|
||||
@@ -384,6 +386,9 @@ access to *
|
||||
by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
|
||||
by users read
|
||||
by * none
|
||||
|
||||
|
||||
index objectClass,uid eq
|
||||
'';
|
||||
|
||||
declarativeContents = ''
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# UNFINISHED!
|
||||
#
|
||||
# The plan is to bootstrap a local network config: DNS, DHCP, etc.
|
||||
|
||||
{ lib, config, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
@@ -33,14 +37,14 @@ let
|
||||
ipv6Address = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The V6 IP of a given host, if any.
|
||||
The V6 IP of this nameserver, if any.
|
||||
'';
|
||||
};
|
||||
|
||||
ipv4Address = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The V4 IP of a given host, if any.
|
||||
The V4 IP of this nameserver, if any.
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -56,9 +60,6 @@ let
|
||||
};
|
||||
|
||||
in {
|
||||
imports = [
|
||||
./fudo/ldap.nix
|
||||
];
|
||||
|
||||
options = {
|
||||
|
||||
256
config/fudo/mail-container.nix
Normal file
256
config/fudo/mail-container.nix
Normal file
@@ -0,0 +1,256 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.mail-server;
|
||||
container-maildir = "/var/lib/mail";
|
||||
container-statedir = "/var/lib/mail-state";
|
||||
container-shared = "container/mail-server";
|
||||
container-postfix-cert = "${container-shared}/postfix/cert.pem";
|
||||
container-postfix-key = "${container-shared}/postfix/key.pem";
|
||||
container-dovecot-cert = "${container-shared}/dovecot/cert.pem";
|
||||
container-dovecot-key = "${container-shared}/dovecot/key.pem";
|
||||
container-fudo-ca-cert = "${container-shared}/fudo-ca.pem";
|
||||
|
||||
# Don't bother with group-id, nixos doesn't seem to use it anyway
|
||||
container-mail-user = "mailer";
|
||||
container-mail-user-id = 542;
|
||||
container-mail-group = "mailer";
|
||||
trusted-networks = config.fudo.prometheus.trusted-networks;
|
||||
|
||||
in rec {
|
||||
options.fudo.mail-server.container = {
|
||||
ldap-url = mkOption {
|
||||
type = types.str;
|
||||
description = "URL of the LDAP server to use for authentication.";
|
||||
example = "ldaps://auth.fudo.org/";
|
||||
};
|
||||
|
||||
# host-ip = mkOption {
|
||||
# type = types.str;
|
||||
# description = "The IP to assign to this server, for communication with the mail server container.";
|
||||
# default = "10.110.0.1";
|
||||
# };
|
||||
|
||||
# container-ip = mkOption {
|
||||
# type = types.str;
|
||||
# description = "The IP to assign to the mail server container.";
|
||||
# default = "10.110.0.2";
|
||||
# };
|
||||
};
|
||||
|
||||
config = mkIf (cfg.enableContainer && !cfg.enable) {
|
||||
|
||||
# Disable postfix on thi host--it'll be run in the container instead
|
||||
services.postfix.enable = false;
|
||||
|
||||
# Copy data intended for the container to a path in /etc which can be
|
||||
# bind-mounted.
|
||||
environment.etc = {
|
||||
"${container-postfix-cert}" = {
|
||||
mode = "0444";
|
||||
source = cfg.postfix.ssl-certificate;
|
||||
};
|
||||
|
||||
"${container-postfix-key}" = {
|
||||
mode = "0400";
|
||||
source = cfg.postfix.ssl-private-key;
|
||||
};
|
||||
|
||||
"${container-dovecot-cert}" = {
|
||||
mode = "0444";
|
||||
source = cfg.dovecot.ssl-certificate;
|
||||
};
|
||||
|
||||
"${container-dovecot-key}" = {
|
||||
mode = "0400";
|
||||
source = cfg.dovecot.ssl-private-key;
|
||||
};
|
||||
|
||||
"${container-fudo-ca-cert}" = {
|
||||
mode = "0444";
|
||||
source = "/etc/nixos/static/fudo_ca.pem";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx = mkIf cfg.monitoring {
|
||||
enable = true;
|
||||
|
||||
virtualHosts = let
|
||||
proxy-headers = ''
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
''
|
||||
;
|
||||
trusted-network-string = optionalString ((length trusted-networks) > 0)
|
||||
(concatStringsSep "\n"
|
||||
(map (network: "allow ${network};") trusted-networks)) + "\ndeny all;";
|
||||
in {
|
||||
"${cfg.hostname}" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
|
||||
locations."/metrics/postfix" = {
|
||||
proxyPass = "http://127.0.0.1:9154/metrics";
|
||||
|
||||
extraConfig = ''
|
||||
${proxy-headers}
|
||||
|
||||
${trusted-network-string}
|
||||
'';
|
||||
};
|
||||
|
||||
locations."/metrics/dovecot" = {
|
||||
proxyPass = "http://127.0.0.1:9166/metrics";
|
||||
|
||||
extraConfig = ''
|
||||
${proxy-headers}
|
||||
|
||||
${trusted-network-string}
|
||||
'';
|
||||
};
|
||||
|
||||
locations."/metrics/rspamd" = {
|
||||
proxyPass = "http://127.0.0.1:7980/metrics";
|
||||
|
||||
extraConfig = ''
|
||||
${proxy-headers}
|
||||
|
||||
${trusted-network-string}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# services.xinetd = let
|
||||
# xinetd-entry = name: port: {
|
||||
# name = name;
|
||||
# port = port;
|
||||
# protocol = "tcp";
|
||||
# server = "";
|
||||
# extraConfig = ''
|
||||
# socket_type = stream
|
||||
# wait = no
|
||||
# redirect = ${cfg.container.container-ip} ${toString port}
|
||||
# '';
|
||||
# };
|
||||
# in {
|
||||
# enable = true;
|
||||
# services = [
|
||||
# (xinetd-entry "smtp" 25)
|
||||
# (xinetd-entry "pop3" 110)
|
||||
# (xinetd-entry "pop3s" 995)
|
||||
# (xinetd-entry "imap" 143)
|
||||
# (xinetd-entry "imaps" 993)
|
||||
# (xinetd-entry "submission" 587)
|
||||
# ];
|
||||
# };
|
||||
|
||||
containers.mail-server = {
|
||||
|
||||
autoStart = true;
|
||||
|
||||
bindMounts = {
|
||||
"${container-maildir}" = {
|
||||
hostPath = cfg.mail-directory;
|
||||
isReadOnly = false;
|
||||
};
|
||||
|
||||
"${container-statedir}" = {
|
||||
hostPath = cfg.state-directory;
|
||||
isReadOnly = false;
|
||||
};
|
||||
|
||||
"/etc/${container-shared}" = {
|
||||
hostPath = "/etc/${container-shared}";
|
||||
isReadOnly = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = { config, pkgs, ... }: {
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
nmap
|
||||
];
|
||||
|
||||
imports = [
|
||||
../local.nix
|
||||
];
|
||||
|
||||
environment = {
|
||||
etc = {
|
||||
"postfix-certs/key.pem" = {
|
||||
source = "/etc/${container-postfix-key}";
|
||||
user = config.services.postfix.user;
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
"dovecot-certs/key.pem" = {
|
||||
source = "/etc/${container-dovecot-key}";
|
||||
user = config.services.dovecot2.user;
|
||||
mode = "0400";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
users = {
|
||||
users = {
|
||||
${container-mail-user} = {
|
||||
isSystemUser = true;
|
||||
uid = container-mail-user-id;
|
||||
group = "mailer";
|
||||
};
|
||||
};
|
||||
|
||||
groups = {
|
||||
${container-mail-group} = {
|
||||
members = ["mailer"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fudo.mail-server =
|
||||
{
|
||||
enable = true;
|
||||
hostname = cfg.hostname;
|
||||
domain = cfg.domain;
|
||||
|
||||
debug = cfg.debug;
|
||||
monitoring = cfg.monitoring;
|
||||
|
||||
state-directory = container-statedir;
|
||||
mail-directory = container-maildir;
|
||||
|
||||
postfix.ssl-certificate = "/etc/${container-postfix-cert}";
|
||||
postfix.ssl-private-key = "/etc/postfix-certs/key.pem";
|
||||
|
||||
dovecot = {
|
||||
ssl-certificate = "/etc/${container-dovecot-cert}";
|
||||
ssl-private-key = "/etc/dovecot-certs/key.pem";
|
||||
ldap-ca = "/etc/${container-fudo-ca-cert}";
|
||||
ldap-urls = cfg.dovecot.ldap-urls;
|
||||
ldap-reader-dn = cfg.dovecot.ldap-reader-dn;
|
||||
ldap-reader-passwd = cfg.dovecot.ldap-reader-passwd;
|
||||
};
|
||||
|
||||
local-domains = cfg.local-domains;
|
||||
|
||||
alias-users = cfg.alias-users;
|
||||
user-aliases = cfg.user-aliases;
|
||||
sender-blacklist = cfg.sender-blacklist;
|
||||
recipient-blacklist = cfg.recipient-blacklist;
|
||||
trusted-networks = cfg.trusted-networks;
|
||||
|
||||
mail-user = container-mail-user;
|
||||
mail-user-id = container-mail-user-id;
|
||||
mail-group = container-mail-group;
|
||||
|
||||
clamav.enable = cfg.clamav.enable;
|
||||
|
||||
dkim.signing = cfg.dkim.signing;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
170
config/fudo/mail.nix
Normal file
170
config/fudo/mail.nix
Normal file
@@ -0,0 +1,170 @@
|
||||
{ config, lib, pkgs, environment, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
inherit (lib.strings) concatStringsSep;
|
||||
cfg = config.fudo.mail-server;
|
||||
|
||||
in {
|
||||
|
||||
options.fudo.mail-server = {
|
||||
enable = mkEnableOption "Fudo Email Server";
|
||||
|
||||
enableContainer = mkEnableOption ''
|
||||
Run the mail server in a container.
|
||||
|
||||
Mutually exclusive with mail-server.enable.
|
||||
'';
|
||||
|
||||
domain = mkOption {
|
||||
type = types.str;
|
||||
description = "The main and default domain name for this email server.";
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
type = types.str;
|
||||
description = "The domain name to use for the mail server.";
|
||||
};
|
||||
|
||||
monitoring = mkEnableOption "Enable monitoring for the mail server.";
|
||||
|
||||
mail-user = mkOption {
|
||||
type = types.str;
|
||||
description = "User to use for mail delivery.";
|
||||
};
|
||||
|
||||
# No group id, because NixOS doesn't seem to use it
|
||||
mail-group = mkOption {
|
||||
type = types.str;
|
||||
description = "Group to use for mail delivery.";
|
||||
};
|
||||
|
||||
mail-user-id = mkOption {
|
||||
type = types.int;
|
||||
description = "UID of mail-user.";
|
||||
};
|
||||
|
||||
local-domains = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = "A list of domains for which we accept mail.";
|
||||
default = ["localhost" "localhost.localdomain"];
|
||||
example = [
|
||||
"localhost"
|
||||
"localhost.localdomain"
|
||||
"somedomain.com"
|
||||
"otherdomain.org"
|
||||
];
|
||||
};
|
||||
|
||||
mail-directory = mkOption {
|
||||
type = types.str;
|
||||
description = "Path to use for mail storage.";
|
||||
};
|
||||
|
||||
state-directory = mkOption {
|
||||
type = types.str;
|
||||
description = "Path to use for state data.";
|
||||
};
|
||||
|
||||
trusted-networks = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = "A list of trusted networks, for which we will happily relay without auth.";
|
||||
example = [
|
||||
"10.0.0.0/16"
|
||||
"192.168.0.0/24"
|
||||
];
|
||||
};
|
||||
|
||||
sender-blacklist = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = "A list of email addresses for whom we will not send email.";
|
||||
default = [];
|
||||
example = [
|
||||
"baduser@test.com"
|
||||
"change-pw@test.com"
|
||||
];
|
||||
};
|
||||
|
||||
recipient-blacklist = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = "A list of email addresses for whom we will not accept email.";
|
||||
default = [];
|
||||
example = [
|
||||
"baduser@test.com"
|
||||
"change-pw@test.com"
|
||||
];
|
||||
};
|
||||
|
||||
message-size-limit = mkOption {
|
||||
type = types.int;
|
||||
description = "Size of max email in megabytes.";
|
||||
default = 30;
|
||||
};
|
||||
|
||||
user-aliases = mkOption {
|
||||
type = with types; loaOf(listOf str);
|
||||
description = "A map of real user to list of aliases.";
|
||||
example = {
|
||||
someuser = ["alias0" "alias1"];
|
||||
};
|
||||
};
|
||||
|
||||
alias-users = mkOption {
|
||||
type = with types; loaOf(listOf str);
|
||||
description = "A map of email alias to a list of users.";
|
||||
example = {
|
||||
alias = ["realuser0" "realuser1"];
|
||||
};
|
||||
};
|
||||
|
||||
mailboxes = mkOption {
|
||||
description = ''
|
||||
The mailboxes for dovecot.
|
||||
|
||||
Depending on the mail client used it might be necessary to change some mailbox's name.
|
||||
'';
|
||||
default = [
|
||||
{
|
||||
name = "Trash";
|
||||
auto = "no";
|
||||
specialUse = "Trash";
|
||||
}
|
||||
{
|
||||
name = "Junk";
|
||||
auto = "subscribe";
|
||||
specialUse = "Junk";
|
||||
}
|
||||
{
|
||||
name = "Drafts";
|
||||
auto = "subscribe";
|
||||
specialUse = "Drafts";
|
||||
}
|
||||
{
|
||||
name = "Sent";
|
||||
auto = "subscribe";
|
||||
specialUse = "Sent";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
debug = mkOption {
|
||||
description = "Enable debugging on mailservers.";
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
max-user-connections = mkOption {
|
||||
description = "Max simultaneous connections per user.";
|
||||
type = types.int;
|
||||
default = 20;
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
./mail/dkim.nix
|
||||
./mail/dovecot.nix
|
||||
./mail/postfix.nix
|
||||
./mail/rspamd.nix
|
||||
./mail/clamav.nix
|
||||
];
|
||||
}
|
||||
28
config/fudo/mail/clamav.nix
Normal file
28
config/fudo/mail/clamav.nix
Normal file
@@ -0,0 +1,28 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.mail-server;
|
||||
|
||||
in {
|
||||
options.fudo.mail-server.clamav = {
|
||||
enable = mkOption {
|
||||
description = "Enable virus scanning with ClamAV.";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.enable && cfg.clamav.enable) {
|
||||
|
||||
services.clamav = {
|
||||
daemon = {
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
PhishingScanURLs no
|
||||
'';
|
||||
};
|
||||
updater.enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
114
config/fudo/mail/dkim.nix
Normal file
114
config/fudo/mail/dkim.nix
Normal file
@@ -0,0 +1,114 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.fudo.mail-server;
|
||||
|
||||
createDomainDkimCert = dom:
|
||||
let
|
||||
dkim_key = "${cfg.dkim.key-directory}/${dom}.${cfg.dkim.selector}.key";
|
||||
dkim_txt = "${cfg.dkim.key-directory}/${dom}.${cfg.dkim.selector}.txt";
|
||||
in
|
||||
''
|
||||
if [ ! -f "${dkim_key}" ] || [ ! -f "${dkim_txt}" ]
|
||||
then
|
||||
${cfg.dkim.package}/bin/opendkim-genkey -s "${cfg.dkim.selector}" \
|
||||
-d "${dom}" \
|
||||
--bits="${toString cfg.dkim.key-bits}" \
|
||||
--directory="${cfg.dkim.key-directory}"
|
||||
mv "${cfg.dkim.key-directory}/${cfg.dkim.selector}.private" "${dkim_key}"
|
||||
mv "${cfg.dkim.key-directory}/${cfg.dkim.selector}.txt" "${dkim_txt}"
|
||||
echo "Generated key for domain ${dom} selector ${cfg.dkim.selector}"
|
||||
fi
|
||||
'';
|
||||
|
||||
createAllCerts = lib.concatStringsSep "\n" (map createDomainDkimCert cfg.local-domains);
|
||||
|
||||
keyTable = pkgs.writeText "opendkim-KeyTable"
|
||||
(lib.concatStringsSep "\n" (lib.flip map cfg.local-domains
|
||||
(dom: "${dom} ${dom}:${cfg.dkim.selector}:${cfg.dkim.key-directory}/${dom}.${cfg.dkim.selector}.key")));
|
||||
signingTable = pkgs.writeText "opendkim-SigningTable"
|
||||
(lib.concatStringsSep "\n" (lib.flip map cfg.local-domains (dom: "${dom} ${dom}")));
|
||||
|
||||
dkim = config.services.opendkim;
|
||||
args = [ "-f" "-l" ] ++ lib.optionals (dkim.configFile != null) [ "-x" dkim.configFile ];
|
||||
in
|
||||
{
|
||||
|
||||
options.fudo.mail-server.dkim = {
|
||||
signing = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable dkim signatures for mail.";
|
||||
};
|
||||
|
||||
key-directory = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/dkim";
|
||||
description = "Path to use to store DKIM keys.";
|
||||
};
|
||||
|
||||
selector = mkOption {
|
||||
type = types.str;
|
||||
default = "mail";
|
||||
description = "Name to use for mail-signing keys.";
|
||||
};
|
||||
|
||||
key-bits = mkOption {
|
||||
type = types.int;
|
||||
default = 2048;
|
||||
description = ''
|
||||
How many bits in generated DKIM keys. RFC6376 advises minimum 1024-bit keys.
|
||||
|
||||
If you have already deployed a key with a different number of bits than specified
|
||||
here, then you should use a different selector (dkimSelector). In order to get
|
||||
this package to generate a key with the new number of bits, you will either have to
|
||||
change the selector or delete the old key file.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.opendkim;
|
||||
description = "OpenDKIM package to use.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.dkim.signing && cfg.enable) {
|
||||
services.opendkim = {
|
||||
enable = true;
|
||||
selector = cfg.dkim.selector;
|
||||
domains = "csl:${builtins.concatStringsSep "," cfg.local-domains}";
|
||||
configFile = pkgs.writeText "opendkim.conf" (''
|
||||
Canonicalization relaxed/simple
|
||||
UMask 0002
|
||||
Socket ${dkim.socket}
|
||||
KeyTable file:${keyTable}
|
||||
SigningTable file:${signingTable}
|
||||
'' + (lib.optionalString cfg.debug ''
|
||||
Syslog yes
|
||||
SyslogSuccess yes
|
||||
LogWhy yes
|
||||
''));
|
||||
};
|
||||
|
||||
users.users = {
|
||||
"${config.services.postfix.user}" = {
|
||||
extraGroups = [ "${config.services.opendkim.group}" ];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.opendkim = {
|
||||
preStart = lib.mkForce createAllCerts;
|
||||
serviceConfig = {
|
||||
ExecStart = lib.mkForce "${cfg.dkim.package}/bin/opendkim ${escapeShellArgs args}";
|
||||
PermissionsStartOnly = lib.mkForce false;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dkim.key-directory}' - ${config.services.opendkim.user} ${config.services.opendkim.group} - -"
|
||||
];
|
||||
};
|
||||
}
|
||||
242
config/fudo/mail/dovecot.nix
Normal file
242
config/fudo/mail/dovecot.nix
Normal file
@@ -0,0 +1,242 @@
|
||||
{ config, lib, pkgs, environment, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.mail-server;
|
||||
|
||||
state-directory = "${cfg.state-directory}/dovecot";
|
||||
|
||||
pipe-bin = pkgs.stdenv.mkDerivation {
|
||||
name = "pipe_bin";
|
||||
src = ./dovecot/pipe_bin;
|
||||
buildInputs = with pkgs; [ makeWrapper coreutils bash rspamd ];
|
||||
buildCommand = ''
|
||||
mkdir -p $out/pipe/bin
|
||||
cp $src/* $out/pipe/bin/
|
||||
chmod a+x $out/pipe/bin/*
|
||||
patchShebangs $out/pipe/bin
|
||||
|
||||
for file in $out/pipe/bin/*; do
|
||||
wrapProgram $file \
|
||||
--set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin"
|
||||
done
|
||||
'';
|
||||
};
|
||||
|
||||
ldap-conf = filename: uris:
|
||||
pkgs.writeText filename ''
|
||||
uris = ${concatStringsSep " " uris}
|
||||
ldap_version = 3
|
||||
dn = ${cfg.dovecot.ldap-reader-dn}
|
||||
dnpass = ${cfg.dovecot.ldap-reader-passwd}
|
||||
auth_bind = yes
|
||||
auth_bind_userdn = uid=%u,ou=members,dc=fudo,dc=org
|
||||
base = dc=fudo,dc=org
|
||||
# tls_ca_cert_file = ${cfg.dovecot.ldap-ca}
|
||||
# FIXME: turn back on when certs work
|
||||
tls = no
|
||||
tls_require_cert = try
|
||||
'';
|
||||
|
||||
dovecot-user = config.services.dovecot2.user;
|
||||
|
||||
in {
|
||||
options.fudo.mail-server.dovecot = {
|
||||
ssl-private-key = mkOption {
|
||||
type = types.str;
|
||||
description = "Location of the server SSL private key.";
|
||||
};
|
||||
|
||||
ssl-certificate = mkOption {
|
||||
type = types.str;
|
||||
description = "Location of the server SSL certificate.";
|
||||
};
|
||||
|
||||
ldap-ca = mkOption {
|
||||
type = types.str;
|
||||
description = "The path to the CA cert used to sign the LDAP server certificate.";
|
||||
};
|
||||
|
||||
ldap-urls = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = "The urls of LDAP servers.";
|
||||
};
|
||||
|
||||
ldap-reader-dn = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
DN to use for reading user information. Needs access to homeDirectory,
|
||||
uidNumber, gidNumber, and uid, but not password attributes.
|
||||
'';
|
||||
};
|
||||
|
||||
ldap-reader-passwd = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Password for the user specified in ldap-reader-dn.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.prometheus.exporters.dovecot = mkIf cfg.monitoring {
|
||||
enable = true;
|
||||
scopes = ["user" "global"];
|
||||
listenAddress = "127.0.0.1";
|
||||
port = 9166;
|
||||
socketPath = "/var/run/dovecot2/old-stats";
|
||||
};
|
||||
|
||||
services.dovecot2 = {
|
||||
enable = true;
|
||||
enableImap = true;
|
||||
enableLmtp = true;
|
||||
enablePop3 = true;
|
||||
enablePAM = false;
|
||||
|
||||
|
||||
createMailUser = true;
|
||||
|
||||
mailUser = cfg.mail-user;
|
||||
mailGroup = cfg.mail-group;
|
||||
mailLocation = "maildir:${cfg.mail-directory}/%u/";
|
||||
|
||||
sslServerCert = cfg.dovecot.ssl-certificate;
|
||||
sslServerKey = cfg.dovecot.ssl-private-key;
|
||||
|
||||
modules = [ pkgs.dovecot_pigeonhole ];
|
||||
protocols = [ "sieve" ];
|
||||
|
||||
sieveScripts = {
|
||||
after = builtins.toFile "spam.sieve" ''
|
||||
require "fileinto";
|
||||
|
||||
if header :is "X-Spam" "Yes" {
|
||||
fileinto "Junk";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
mailboxes = cfg.mailboxes;
|
||||
|
||||
extraConfig = ''
|
||||
#Extra Config
|
||||
|
||||
# The prometheus exporter still expects an older style of metrics
|
||||
mail_plugins = $mail_plugins old_stats
|
||||
service old-stats {
|
||||
unix_listener old-stats {
|
||||
user = dovecot-exporter
|
||||
group = dovecot-exporter
|
||||
}
|
||||
}
|
||||
|
||||
${lib.optionalString cfg.debug ''
|
||||
mail_debug = yes
|
||||
auth_debug = yes
|
||||
verbose_ssl = yes
|
||||
''}
|
||||
|
||||
protocol imap {
|
||||
mail_max_userip_connections = ${toString cfg.max-user-connections}
|
||||
mail_plugins = $mail_plugins imap_sieve
|
||||
}
|
||||
|
||||
protocol pop3 {
|
||||
mail_max_userip_connections = ${toString cfg.max-user-connections}
|
||||
}
|
||||
|
||||
protocol lmtp {
|
||||
mail_plugins = $mail_plugins sieve
|
||||
}
|
||||
|
||||
mail_access_groups = ${cfg.mail-group}
|
||||
ssl = required
|
||||
|
||||
# When looking up usernames, just use the name, not the full address
|
||||
auth_username_format = %n
|
||||
|
||||
service lmtp {
|
||||
# Enable logging in debug mode
|
||||
${optionalString cfg.debug "executable = lmtp -L"}
|
||||
|
||||
# Unix socket for postfix to deliver messages via lmtp
|
||||
unix_listener dovecot-lmtp {
|
||||
user = "postfix"
|
||||
group = ${cfg.mail-group}
|
||||
mode = 0600
|
||||
}
|
||||
|
||||
# Drop privs, since all mail is owned by one user
|
||||
user = ${cfg.mail-user}
|
||||
group = ${cfg.mail-group}
|
||||
}
|
||||
|
||||
auth_mechanisms = login plain
|
||||
passdb {
|
||||
driver = ldap
|
||||
args = ${ldap-conf "ldap-passdb.conf" cfg.dovecot.ldap-urls}
|
||||
}
|
||||
userdb {
|
||||
driver = static
|
||||
args = uid=${toString cfg.mail-user-id} home=${cfg.mail-directory}/%u
|
||||
}
|
||||
|
||||
# Used by postfix to authorize users
|
||||
service auth {
|
||||
unix_listener auth {
|
||||
mode = 0660
|
||||
user = "${config.services.postfix.user}"
|
||||
group = ${config.services.postfix.group}
|
||||
}
|
||||
}
|
||||
|
||||
namespace inbox {
|
||||
separator = "/"
|
||||
inbox = yes
|
||||
}
|
||||
|
||||
plugin {
|
||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||
sieve = file:/var/sieve/%u/scripts;active=/var/sieve/%u/active.sieve
|
||||
sieve_default = file:/var/sieve/%u/default.sieve
|
||||
sieve_default_name = default
|
||||
# From elsewhere to Spam folder
|
||||
imapsieve_mailbox1_name = Junk
|
||||
imapsieve_mailbox1_causes = COPY
|
||||
imapsieve_mailbox1_before = file:${state-directory}/imap_sieve/report-spam.sieve
|
||||
# From Spam folder to elsewhere
|
||||
imapsieve_mailbox2_name = *
|
||||
imapsieve_mailbox2_from = Junk
|
||||
imapsieve_mailbox2_causes = COPY
|
||||
imapsieve_mailbox2_before = file:${state-directory}/imap_sieve/report-ham.sieve
|
||||
sieve_pipe_bin_dir = ${pipe-bin}/pipe/bin
|
||||
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
|
||||
}
|
||||
|
||||
recipient_delimiter = +
|
||||
|
||||
lmtp_save_to_detail_mailbox = yes
|
||||
|
||||
lda_mailbox_autosubscribe = yes
|
||||
lda_mailbox_autocreate = yes
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.dovecot2.preStart = ''
|
||||
mkdir -p '${state-directory}'
|
||||
chown ${dovecot-user}:${cfg.mail-group} '${state-directory}'
|
||||
rm -rf '${state-directory}/imap_sieve'
|
||||
mkdir '${state-directory}/imap_sieve'
|
||||
cp -p "${./dovecot/imap_sieve}"/*.sieve '${state-directory}/imap_sieve/'
|
||||
for k in "${state-directory}/imap_sieve"/*.sieve ; do
|
||||
${pkgs.dovecot_pigeonhole}/bin/sievec "$k"
|
||||
done
|
||||
chown -R '${dovecot-user}:${cfg.mail-group}' '${state-directory}/imap_sieve'
|
||||
|
||||
chown ${cfg.mail-user}:${cfg.mail-group} ${cfg.mail-directory}
|
||||
'';
|
||||
};
|
||||
}
|
||||
15
config/fudo/mail/dovecot/imap_sieve/report-ham.sieve
Normal file
15
config/fudo/mail/dovecot/imap_sieve/report-ham.sieve
Normal file
@@ -0,0 +1,15 @@
|
||||
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||
|
||||
if environment :matches "imap.mailbox" "*" {
|
||||
set "mailbox" "${1}";
|
||||
}
|
||||
|
||||
if string "${mailbox}" "Trash" {
|
||||
stop;
|
||||
}
|
||||
|
||||
if environment :matches "imap.user" "*" {
|
||||
set "username" "${1}";
|
||||
}
|
||||
|
||||
pipe :copy "sa-learn-ham.sh" [ "${username}" ];
|
||||
7
config/fudo/mail/dovecot/imap_sieve/report-spam.sieve
Normal file
7
config/fudo/mail/dovecot/imap_sieve/report-spam.sieve
Normal file
@@ -0,0 +1,7 @@
|
||||
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||
|
||||
if environment :matches "imap.user" "*" {
|
||||
set "username" "${1}";
|
||||
}
|
||||
|
||||
pipe :copy "sa-learn-spam.sh" [ "${username}" ];
|
||||
3
config/fudo/mail/dovecot/pipe_bin/sa-learn-ham.sh
Executable file
3
config/fudo/mail/dovecot/pipe_bin/sa-learn-ham.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -o errexit
|
||||
exec rspamc -h /run/rspamd/worker-controller.sock learn_ham
|
||||
3
config/fudo/mail/dovecot/pipe_bin/sa-learn-spam.sh
Executable file
3
config/fudo/mail/dovecot/pipe_bin/sa-learn-spam.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -o errexit
|
||||
exec rspamc -h /run/rspamd/worker-controller.sock learn_spam
|
||||
297
config/fudo/mail/postfix.nix
Normal file
297
config/fudo/mail/postfix.nix
Normal file
@@ -0,0 +1,297 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
inherit (lib.strings) concatStringsSep;
|
||||
|
||||
cfg = config.fudo.mail-server;
|
||||
|
||||
make-user-aliases = entries:
|
||||
concatStringsSep "\n"
|
||||
(mapAttrsToList (user: aliases:
|
||||
concatStringsSep "\n"
|
||||
(map (alias: "${alias} ${user}") aliases))
|
||||
entries);
|
||||
|
||||
make-alias-users = domains: entries:
|
||||
concatStringsSep "\n"
|
||||
(flatten
|
||||
(mapAttrsToList (alias: users:
|
||||
(map (domain:
|
||||
"${alias}@${domain} ${concatStringsSep "," users}")
|
||||
domains))
|
||||
entries));
|
||||
|
||||
policyd-spf = pkgs.writeText "policyd-spf.conf" (
|
||||
cfg.postfix.policy-spf-extra-config
|
||||
+ (lib.optionalString cfg.debug ''
|
||||
debugLevel = 4
|
||||
''));
|
||||
|
||||
submission-header-cleanup-rules = pkgs.writeText "submission_header_cleanup_rules" (''
|
||||
# Removes sensitive headers from mails handed in via the submission port.
|
||||
# See https://thomas-leister.de/mailserver-debian-stretch/
|
||||
# Uses "pcre" style regex.
|
||||
|
||||
/^Received:/ IGNORE
|
||||
/^X-Originating-IP:/ IGNORE
|
||||
/^X-Mailer:/ IGNORE
|
||||
/^User-Agent:/ IGNORE
|
||||
/^X-Enigmail:/ IGNORE
|
||||
'');
|
||||
|
||||
blacklist-postfix-entry = sender: "${sender} REJECT";
|
||||
blacklist-postfix-file = entries:
|
||||
concatStringsSep "\n" (map blacklist-postfix-entry entries);
|
||||
sender-blacklist-file = builtins.toFile "reject_senders"
|
||||
(blacklist-postfix-file cfg.sender-blacklist);
|
||||
recipient-blacklist-file = builtins.toFile "reject_recipients"
|
||||
(blacklist-postfix-file cfg.recipient-blacklist);
|
||||
|
||||
# A list of domains for which we accept mail
|
||||
virtual-mailbox-map-file = builtins.toFile "virtual_mailbox_map"
|
||||
(concatStringsSep "\n"
|
||||
(map (domain: "@${domain} OK") cfg.local-domains));
|
||||
|
||||
sender-login-map-file = let
|
||||
escapeDot = (str: replaceStrings ["."] ["\\."] str);
|
||||
in builtins.toFile "sender_login_maps"
|
||||
(concatStringsSep "\n"
|
||||
(map (domain: "/^(.*)@${escapeDot domain}$/ \${1}") cfg.local-domains));
|
||||
|
||||
mapped-file = name: "hash:/var/lib/postfix/conf/${name}";
|
||||
|
||||
pcre-file = name: "pcre:/var/lib/postfix/conf/${name}";
|
||||
|
||||
in {
|
||||
|
||||
options.fudo.mail-server.postfix = {
|
||||
|
||||
ssl-private-key = mkOption {
|
||||
type = types.str;
|
||||
description = "Location of the server SSL private key.";
|
||||
};
|
||||
|
||||
ssl-certificate = mkOption {
|
||||
type = types.str;
|
||||
description = "Location of the server SSL certificate.";
|
||||
};
|
||||
|
||||
policy-spf-extra-config = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
|
||||
'';
|
||||
description = ''
|
||||
Extra configuration options for policyd-spf. This can be use to among
|
||||
other things skip spf checking for some IP addresses.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.prometheus.exporters.postfix = mkIf cfg.monitoring {
|
||||
enable = true;
|
||||
systemd.enable = true;
|
||||
};
|
||||
|
||||
services.postfix = {
|
||||
enable = true;
|
||||
domain = cfg.domain;
|
||||
origin = cfg.domain;
|
||||
hostname = cfg.hostname;
|
||||
destination = ["localhost" "localhost.localdomain"] ++
|
||||
(map (domain: "localhost.${domain}") cfg.local-domains);
|
||||
|
||||
enableHeaderChecks = true;
|
||||
enableSmtp = true;
|
||||
enableSubmission = true;
|
||||
|
||||
mapFiles."reject_senders" = sender-blacklist-file;
|
||||
mapFiles."reject_recipients" = recipient-blacklist-file;
|
||||
mapFiles."virtual_mailbox_map" = virtual-mailbox-map-file;
|
||||
mapFiles."sender_login_map" = sender-login-map-file;
|
||||
|
||||
# TODO: enable!
|
||||
# headerChecks = [ { action = "REDIRECT spam@example.com"; pattern = "/^X-Spam-Flag:/"; } ];
|
||||
networks = cfg.trusted-networks;
|
||||
|
||||
virtual = ''
|
||||
${make-user-aliases cfg.user-aliases}
|
||||
|
||||
${make-alias-users cfg.local-domains cfg.alias-users}
|
||||
'';
|
||||
|
||||
sslCert = cfg.postfix.ssl-certificate;
|
||||
sslKey = cfg.postfix.ssl-private-key;
|
||||
|
||||
config = {
|
||||
virtual_mailbox_domains = builtins.toFile "domain-list" (concatStringsSep "\n" cfg.local-domains);
|
||||
# virtual_mailbox_base = "${cfg.mail-directory}/";
|
||||
virtual_mailbox_maps = mapped-file "virtual_mailbox_map";
|
||||
|
||||
virtual_uid_maps = "static:${toString cfg.mail-user-id}";
|
||||
virtual_gid_maps = "static:${toString config.users.groups."${cfg.mail-group}".gid}";
|
||||
|
||||
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
||||
|
||||
# NOTE: it's important that this ends with /, to indicate Maildir format!
|
||||
# mail_spool_directory = "${cfg.mail-directory}/";
|
||||
message_size_limit = toString(cfg.message-size-limit * 1024 * 1024);
|
||||
|
||||
smtpd_banner = "${cfg.hostname} ESMTP NO UCE";
|
||||
|
||||
tls_eecdh_strong_curve = "prime256v1";
|
||||
tls_eecdh_ultra_curve = "secp384r1";
|
||||
|
||||
policy-spf_time_limit = "3600s";
|
||||
|
||||
smtp_host_lookup = "dns, native";
|
||||
|
||||
smtpd_sasl_type = "dovecot";
|
||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
||||
smtpd_sasl_auth_enable = "yes";
|
||||
|
||||
smtpd_sender_login_maps = (pcre-file "sender_login_map");
|
||||
|
||||
disable_vrfy_command = "yes";
|
||||
|
||||
recipient_delimiter = "+";
|
||||
|
||||
milter_protocol = "6";
|
||||
milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
|
||||
|
||||
smtpd_milters = [
|
||||
"unix:/run/rspamd/rspamd-milter.sock"
|
||||
"unix:/var/run/opendkim/opendkim.sock"
|
||||
];
|
||||
|
||||
non_smtpd_milters = [
|
||||
"unix:/run/rspamd/rspamd-milter.sock"
|
||||
"unix:/var/run/opendkim/opendkim.sock"
|
||||
];
|
||||
|
||||
smtpd_relay_restrictions = [
|
||||
"permit_mynetworks"
|
||||
"permit_sasl_authenticated"
|
||||
"reject_unauth_destination"
|
||||
"reject_unauth_pipelining"
|
||||
"reject_unauth_destination"
|
||||
"reject_unknown_sender_domain"
|
||||
];
|
||||
|
||||
smtpd_sender_restrictions = [
|
||||
"check_sender_access ${mapped-file "reject_senders"}"
|
||||
];
|
||||
|
||||
smtpd_recipient_restrictions = [
|
||||
"check_sender_access ${mapped-file "reject_recipients"}"
|
||||
"permit_mynetworks"
|
||||
"permit_sasl_authenticated"
|
||||
"check_policy_service unix:private/policy-spf"
|
||||
"reject_unknown_recipient_domain"
|
||||
"reject_unauth_pipelining"
|
||||
"reject_unauth_destination"
|
||||
"reject_invalid_hostname"
|
||||
"reject_non_fqdn_hostname"
|
||||
"reject_non_fqdn_sender"
|
||||
"reject_non_fqdn_recipient"
|
||||
];
|
||||
|
||||
# Handled by submission
|
||||
smtpd_tls_security_level = "may";
|
||||
|
||||
smtpd_tls_eecdh_grade = "ultra";
|
||||
|
||||
# Disable obselete protocols
|
||||
smtpd_tls_protocols = [
|
||||
"TLSv1.2"
|
||||
"TLSv1.1"
|
||||
"!TLSv1"
|
||||
"!SSLv2"
|
||||
"!SSLv3"
|
||||
];
|
||||
smtp_tls_protocols = [
|
||||
"TLSv1.2"
|
||||
"TLSv1.1"
|
||||
"!TLSv1"
|
||||
"!SSLv2"
|
||||
"!SSLv3"
|
||||
];
|
||||
smtpd_tls_mandatory_protocols = [
|
||||
"TLSv1.2"
|
||||
"TLSv1.1"
|
||||
"!TLSv1"
|
||||
"!SSLv2"
|
||||
"!SSLv3"
|
||||
];
|
||||
smtp_tls_mandatory_protocols = [
|
||||
"TLSv1.2"
|
||||
"TLSv1.1"
|
||||
"!TLSv1"
|
||||
"!SSLv2"
|
||||
"!SSLv3"
|
||||
];
|
||||
|
||||
smtp_tls_ciphers = "high";
|
||||
smtpd_tls_ciphers = "high";
|
||||
smtp_tls_mandatory_ciphers = "high";
|
||||
smtpd_tls_mandatory_ciphers = "high";
|
||||
|
||||
smtpd_tls_mandatory_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
|
||||
smtpd_tls_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
|
||||
smtp_tls_mandatory_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
|
||||
smtp_tls_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
|
||||
|
||||
tls_preempt_cipherlist = "yes";
|
||||
|
||||
smtpd_tls_auth_only = "yes";
|
||||
|
||||
smtpd_tls_loglevel = "1";
|
||||
|
||||
tls_random_source = "dev:/dev/urandom";
|
||||
};
|
||||
|
||||
submissionOptions = {
|
||||
smtpd_tls_security_level = "encrypt";
|
||||
smtpd_sasl_auth_enable = "yes";
|
||||
smtpd_sasl_type = "dovecot";
|
||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
||||
smtpd_sasl_security_options = "noanonymous";
|
||||
smtpd_sasl_local_domain = cfg.domain;
|
||||
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
||||
smtpd_sender_restrictions = "reject_sender_login_mismatch,reject_unknown_sender_domain";
|
||||
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
||||
cleanup_service_name = "submission-header-cleanup";
|
||||
};
|
||||
|
||||
masterConfig = {
|
||||
"policy-spf" = {
|
||||
type = "unix";
|
||||
privileged = true;
|
||||
chroot = false;
|
||||
command = "spawn";
|
||||
args = [ "user=nobody" "argv=${pkgs.pypolicyd-spf}/bin/policyd-spf" "${policyd-spf}"];
|
||||
};
|
||||
"submission-header-cleanup" = {
|
||||
type = "unix";
|
||||
private = false;
|
||||
chroot = false;
|
||||
maxproc = 0;
|
||||
command = "cleanup";
|
||||
args = ["-o" "header_checks=pcre:${submission-header-cleanup-rules}"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work
|
||||
systemd.services.postfix = {
|
||||
after = [ "dovecot2.service" ]
|
||||
++ (lib.optional cfg.dkim.signing "opendkim.service");
|
||||
requires = [ "dovecot2.service" ]
|
||||
++ (lib.optional cfg.dkim.signing "opendkim.service");
|
||||
};
|
||||
};
|
||||
}
|
||||
88
config/fudo/mail/rspamd.nix
Normal file
88
config/fudo/mail/rspamd.nix
Normal file
@@ -0,0 +1,88 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.mail-server;
|
||||
|
||||
in {
|
||||
config = mkIf cfg.enable {
|
||||
services.prometheus.exporters.rspamd.enable = true;
|
||||
|
||||
services.rspamd = {
|
||||
|
||||
enable = true;
|
||||
|
||||
locals = {
|
||||
"milter_headers.conf" = {
|
||||
text = ''
|
||||
extended_spam_headers = yes;
|
||||
'';
|
||||
};
|
||||
|
||||
"antivirus.conf" = {
|
||||
text = ''
|
||||
clamav {
|
||||
action = "reject";
|
||||
symbol = "CLAM_VIRUS";
|
||||
type = "clamav";
|
||||
log_clean = true;
|
||||
servers = "/run/clamav/clamd.ctl";
|
||||
scan_mime_parts = false; # scan mail as a whole unit, not parts. seems to be needed to work at all
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
overrides = {
|
||||
"milter_headers.conf" = {
|
||||
text = ''
|
||||
extended_spam_headers = true;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
workers.rspamd_proxy = {
|
||||
type = "rspamd_proxy";
|
||||
bindSockets = [{
|
||||
socket = "/run/rspamd/rspamd-milter.sock";
|
||||
mode = "0664";
|
||||
}];
|
||||
count = 1; # Do not spawn too many processes of this type
|
||||
extraConfig = ''
|
||||
milter = yes; # Enable milter mode
|
||||
timeout = 120s; # Needed for Milter usually
|
||||
|
||||
upstream "local" {
|
||||
default = yes; # Self-scan upstreams are always default
|
||||
self_scan = yes; # Enable self-scan
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
workers.controller = {
|
||||
type = "controller";
|
||||
count = 1;
|
||||
bindSockets = [
|
||||
"localhost:11334"
|
||||
{
|
||||
socket = "/run/rspamd/worker-controller.sock";
|
||||
mode = "0666";
|
||||
}
|
||||
];
|
||||
includes = [];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.rspamd = {
|
||||
requires = (optional cfg.clamav.enable "clamav-daemon.service");
|
||||
after = (optional cfg.clamav.enable "clamav-daemon.service");
|
||||
};
|
||||
|
||||
systemd.services.postfix = {
|
||||
after = [ "rspamd.service" ];
|
||||
requires = [ "rspamd.service" ];
|
||||
};
|
||||
|
||||
users.extraUsers.${config.services.postfix.user}.extraGroups = [ config.services.rspamd.group ];
|
||||
};
|
||||
}
|
||||
60
config/fudo/minecraft-server.nix
Normal file
60
config/fudo/minecraft-server.nix
Normal file
@@ -0,0 +1,60 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.minecraft-server;
|
||||
|
||||
in {
|
||||
options.fudo.minecraft-server = {
|
||||
enable = mkEnableOption "Start a minecraft server.";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
description = "Minecraft package to use.";
|
||||
default = pkgs.minecraft-server_1_15_1;
|
||||
};
|
||||
|
||||
data-dir = mkOption {
|
||||
type = types.path;
|
||||
description = "Path at which to store minecraft data.";
|
||||
};
|
||||
|
||||
world-name = mkOption {
|
||||
type = types.str;
|
||||
description = "Name of the server world (used in saves etc).";
|
||||
};
|
||||
|
||||
motd = mkOption {
|
||||
type = types.str;
|
||||
description = "Welcome message for newcomers.";
|
||||
};
|
||||
|
||||
game-mode = mkOption {
|
||||
type = types.enum ["survival" "creative" "adventure" "spectator"];
|
||||
description = "Game mode of the server.";
|
||||
default = "survival";
|
||||
};
|
||||
|
||||
difficulty = mkOption {
|
||||
type = types.int;
|
||||
description = "Difficulty level, where 0 is peaceful and 3 is hard.";
|
||||
default = 2;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.minecraft-server = {
|
||||
enable = true;
|
||||
package = cfg.package;
|
||||
dataDir = cfg.data-dir;
|
||||
eula = true;
|
||||
declarative = true;
|
||||
serverProperties = {
|
||||
level-name = cfg.world-name;
|
||||
motd = cfg.motd;
|
||||
difficulty = cfg.difficulty;
|
||||
gamemode = cfg.game-mode;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
58
config/fudo/node-exporter.nix
Normal file
58
config/fudo/node-exporter.nix
Normal file
@@ -0,0 +1,58 @@
|
||||
{ lib, config, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
inherit (lib.strings) concatStringsSep;
|
||||
|
||||
cfg = config.fudo.node-exporter;
|
||||
fudo-cfg = config.fudo.common;
|
||||
|
||||
allow-network = network: "allow ${network};";
|
||||
|
||||
in {
|
||||
options.fudo.node-exporter = {
|
||||
enable = mkEnableOption "Enable a Prometheus node exporter with some reasonable settings.";
|
||||
|
||||
hostname = mkOption {
|
||||
type = types.str;
|
||||
description = "Hostname from which to export statistics.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services = {
|
||||
# This'll run an exporter at localhost:9100
|
||||
prometheus.exporters.node = {
|
||||
enable = true;
|
||||
enabledCollectors = [ "systemd" ];
|
||||
listenAddress = "127.0.0.1";
|
||||
port = 9100;
|
||||
user = "node";
|
||||
};
|
||||
|
||||
# ...And this'll expose the above to the outside world, or at least the
|
||||
# list of trusted networks, with SSL protection.
|
||||
nginx = {
|
||||
enable = true;
|
||||
|
||||
virtualHosts."${cfg.hostname}" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
|
||||
location."/metrics/node" = {
|
||||
extraConfig = ''
|
||||
${concatStringsSep "\n" (map allow-network fudo-cfg.local-networks)}
|
||||
allow 127.0.0.0/16;
|
||||
deny all;
|
||||
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
'';
|
||||
|
||||
proxyPass = "http://127.0.0.1:9100/metrics";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
197
config/fudo/postgres.nix
Normal file
197
config/fudo/postgres.nix
Normal file
@@ -0,0 +1,197 @@
|
||||
{ config, lib, pkgs, environment, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
|
||||
cfg = config.fudo.postgresql;
|
||||
|
||||
userOpts = { username, ... }: {
|
||||
options = {
|
||||
password = mkOption {
|
||||
type = types.str;
|
||||
description = "The user's (plaintext) password.";
|
||||
};
|
||||
|
||||
databases = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = "Databases to which this user has access.";
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
databaseOpts = { dbname, ... }: {
|
||||
options = {
|
||||
users = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = "A list of users who should have access to this database.";
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
userDatabaseAccess = user: databases:
|
||||
listToAttrs (map (database-name:
|
||||
{
|
||||
name = "DATABASE ${database-name}";
|
||||
value = "ALL PRIVILEGES";
|
||||
})
|
||||
databases);
|
||||
|
||||
stringJoin = joiner: els:
|
||||
if (length els) == 0 then
|
||||
""
|
||||
else
|
||||
foldr(lel: rel: "${lel}${joiner}${rel}") (last els) (init els);
|
||||
|
||||
makeEntry = nw:
|
||||
"host all all ${nw} gss include_realm=0 krb_realm=FUDO.ORG";
|
||||
|
||||
makeNetworksEntry = networks:
|
||||
stringJoin "\n" (map makeEntry networks);
|
||||
|
||||
setPasswordSql = username: attrs:
|
||||
"ALTER USER ${username} ENCRYPTED PASSWORD '${attrs.password}';";
|
||||
|
||||
setPasswordsSql = users:
|
||||
stringJoin "\n"
|
||||
(mapAttrsToList (username: attrs: setPasswordSql username attrs)
|
||||
users);
|
||||
|
||||
makeLocalUserPasswordEntries = users:
|
||||
stringJoin "\n"
|
||||
(mapAttrsToList
|
||||
(username: attrs:
|
||||
stringJoin "\n"
|
||||
(map (db: ''
|
||||
host ${username} ${db} 127.0.0.1/16 md5
|
||||
host ${username} ${db} ::1/128 md5
|
||||
'') attrs.databases))
|
||||
users);
|
||||
|
||||
|
||||
in {
|
||||
|
||||
options.fudo.postgresql = {
|
||||
enable = mkEnableOption "Fudo PostgreSQL Server";
|
||||
|
||||
ssl-private-key = mkOption {
|
||||
type = types.str;
|
||||
description = "Location of the server SSL private key.";
|
||||
};
|
||||
|
||||
ssl-certificate = mkOption {
|
||||
type = types.str;
|
||||
description = "Location of the server SSL certificate.";
|
||||
};
|
||||
|
||||
keytab = mkOption {
|
||||
type = types.str;
|
||||
description = "Location of the server Kerberos keytab.";
|
||||
};
|
||||
|
||||
local-networks = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = "A list of networks from which to accept connections.";
|
||||
example = [
|
||||
"10.0.0.1/16"
|
||||
];
|
||||
default = [];
|
||||
};
|
||||
|
||||
local-users = mkOption {
|
||||
type = with types; loaOf (submodule userOpts);
|
||||
description = "A map of users to user attributes.";
|
||||
example = {
|
||||
sampleUser = {
|
||||
password = "some-password";
|
||||
databases = [ "sample_user_db" ];
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
};
|
||||
|
||||
databases = mkOption {
|
||||
type = with types; loaOf (submodule databaseOpts);
|
||||
description = "A map of databases to database options.";
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment = {
|
||||
systemPackages = with pkgs; [
|
||||
postgresql_11_gssapi
|
||||
];
|
||||
|
||||
etc = {
|
||||
"postgresql/private/privkey.pem" = {
|
||||
mode = "0400";
|
||||
user = "postgres";
|
||||
group = "postgres";
|
||||
source = cfg.ssl-private-key;
|
||||
};
|
||||
|
||||
"postgresql/cert.pem" = {
|
||||
mode = "0444";
|
||||
user = "postgres";
|
||||
group = "postgres";
|
||||
source = cfg.ssl-certificate;
|
||||
};
|
||||
|
||||
"postgresql/private/postgres.keytab" = {
|
||||
mode = "0400";
|
||||
user = "postgres";
|
||||
group = "postgres";
|
||||
source = cfg.keytab;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
package = pkgs.postgresql_11_gssapi;
|
||||
enableTCPIP = true;
|
||||
ensureDatabases = mapAttrsToList (name: value: name) cfg.databases;
|
||||
ensureUsers = mapAttrsToList
|
||||
(username: attrs:
|
||||
{
|
||||
name = username;
|
||||
ensurePermissions =
|
||||
#{ "DATABASE ${username}" = "ALL PRIVILEGES"; };
|
||||
(userDatabaseAccess username attrs.databases);
|
||||
})
|
||||
cfg.local-users;
|
||||
|
||||
extraConfig =
|
||||
''
|
||||
krb_server_keyfile = '/etc/postgresql/private/postgres.keytab'
|
||||
|
||||
ssl = true
|
||||
ssl_cert_file = '/etc/postgresql/cert.pem'
|
||||
ssl_key_file = '/etc/postgresql/private/privkey.pem'
|
||||
|
||||
unix_socket_directories = '/var/run/postgresql'
|
||||
'';
|
||||
|
||||
authentication =
|
||||
''
|
||||
local all all ident
|
||||
|
||||
${makeLocalUserPasswordEntries cfg.local-users}
|
||||
|
||||
# host-local
|
||||
host all all 127.0.0.1/32 gss include_realm=0 krb_realm=FUDO.ORG
|
||||
host all all ::1/128 gss include_realm=0 krb_realm=FUDO.ORG
|
||||
|
||||
# local networks
|
||||
${makeNetworksEntry cfg.local-networks}
|
||||
'';
|
||||
|
||||
initialScript = pkgs.writeText "database-init.sql" ''
|
||||
${setPasswordsSql cfg.local-users}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
32
config/fudo/profiles.nix
Normal file
32
config/fudo/profiles.nix
Normal file
@@ -0,0 +1,32 @@
|
||||
# Switch between different basic profiles
|
||||
{ lib, config, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
profile = config.fudo.profile;
|
||||
|
||||
profiles = {
|
||||
desktop = ./profiles/desktop.nix {
|
||||
pkgs = pkgs;
|
||||
config = config;
|
||||
};
|
||||
server = ./profiles/server.nix {
|
||||
pkgs = pkgs;
|
||||
config = config;
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
options.fudo.profile = {
|
||||
type = types.enum (attrNames profiles);
|
||||
example = "desktop";
|
||||
description = ''
|
||||
The profile to use for this host. This will do some profile-dependent
|
||||
configuration, for example removing X-libs from servers and adding UI
|
||||
packages to desktops.
|
||||
'';
|
||||
default = "server";
|
||||
};
|
||||
|
||||
config = optionalAttrs (profiles ? profile) profiles.${profile};
|
||||
}
|
||||
151
config/fudo/profiles/desktop.nix
Normal file
151
config/fudo/profiles/desktop.nix
Normal file
@@ -0,0 +1,151 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
cool-retro-term
|
||||
chrome-gnome-shell
|
||||
chromium
|
||||
ffmpeg-full
|
||||
firefox
|
||||
gimp
|
||||
glxinfo
|
||||
gnome3.gnome-shell
|
||||
gnome3.gnome-session
|
||||
google-chrome
|
||||
gtk2
|
||||
gtk2-x11
|
||||
gtk3
|
||||
gtkimageview
|
||||
i3lock
|
||||
libfixposix
|
||||
minecraft
|
||||
mplayer
|
||||
nomacs
|
||||
openssl_1_1
|
||||
redshift
|
||||
rhythmbox
|
||||
shotwell
|
||||
spotify
|
||||
sqlite
|
||||
steam
|
||||
system-config-printer
|
||||
virtmanager
|
||||
xorg.xev
|
||||
xzgv
|
||||
virtmanager-qt
|
||||
];
|
||||
|
||||
# Splash screen
|
||||
boot.plymouth.enable = true;
|
||||
|
||||
services.avahi = {
|
||||
enable = true;
|
||||
browseDomains = [config.fudo.domain;];
|
||||
domainName = config.fudo.domain;
|
||||
};
|
||||
|
||||
boot.tmpOnTmpfs = true;
|
||||
|
||||
services.xserver = {
|
||||
enable = true;
|
||||
|
||||
layout = "us";
|
||||
xkbVariant = "dvp";
|
||||
xkbOptions = "ctrl:nocaps";
|
||||
|
||||
desktopManager.gnome3.enable = true;
|
||||
desktopManager.default = "gnome3";
|
||||
|
||||
displayManager.gdm.enable = true;
|
||||
|
||||
windowManager.session = pkgs.lib.singleton {
|
||||
name = "stumpwm";
|
||||
start = ''
|
||||
${pkgs.lispPackages.stumpwm}/bin/stumpwm &
|
||||
waidPID=$!
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
services.printing = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
services.gnome3 = {
|
||||
evolution-data-server.enable = pkgs.lib.mkForce false;
|
||||
gnome-user-share.enable = pkgs.lib.mkForce false;
|
||||
};
|
||||
|
||||
services.dbus.socketActivated = true;
|
||||
|
||||
services.openssh.forwardX11 = true;
|
||||
|
||||
programs.ssh.forwardX11 = true;
|
||||
|
||||
sound.enable = true;
|
||||
|
||||
hardware.pulseaudio.enable = true;
|
||||
|
||||
fonts = {
|
||||
enableCoreFonts = true;
|
||||
enableFontDir = true;
|
||||
enableGhostscriptFonts = false;
|
||||
fontconfig.ultimate.enable = true;
|
||||
|
||||
fonts = with pkgs; [
|
||||
cantarell_fonts
|
||||
dejavu_fonts
|
||||
dina-font
|
||||
dosemu_fonts
|
||||
fira-code
|
||||
fira-code-symbols
|
||||
freefont_ttf
|
||||
liberation_ttf
|
||||
mplus-outline-fonts
|
||||
nerdfonts
|
||||
noto-fonts
|
||||
noto-fonts-cjk
|
||||
noto-fonts-emoji
|
||||
proggyfonts
|
||||
terminus_font
|
||||
ubuntu_font_family
|
||||
ucsFonts
|
||||
unifont
|
||||
vistafonts
|
||||
xlibs.fontadobe100dpi
|
||||
xlibs.fontadobe75dpi
|
||||
xlibs.fontadobeutopia100dpi
|
||||
xlibs.fontadobeutopia75dpi
|
||||
xlibs.fontadobeutopiatype1
|
||||
xlibs.fontarabicmisc
|
||||
xlibs.fontbh100dpi
|
||||
xlibs.fontbh75dpi
|
||||
xlibs.fontbhlucidatypewriter100dpi
|
||||
xlibs.fontbhlucidatypewriter75dpi
|
||||
xlibs.fontbhttf
|
||||
xlibs.fontbhtype1
|
||||
xlibs.fontbitstream100dpi
|
||||
xlibs.fontbitstream75dpi
|
||||
xlibs.fontbitstreamtype1
|
||||
xlibs.fontcronyxcyrillic
|
||||
xlibs.fontcursormisc
|
||||
xlibs.fontdaewoomisc
|
||||
xlibs.fontdecmisc
|
||||
xlibs.fontibmtype1
|
||||
xlibs.fontisasmisc
|
||||
xlibs.fontjismisc
|
||||
xlibs.fontmicromisc
|
||||
xlibs.fontmisccyrillic
|
||||
xlibs.fontmiscethiopic
|
||||
xlibs.fontmiscmeltho
|
||||
xlibs.fontmiscmisc
|
||||
xlibs.fontmuttmisc
|
||||
xlibs.fontschumachermisc
|
||||
xlibs.fontscreencyrillic
|
||||
xlibs.fontsonymisc
|
||||
xlibs.fontsunmisc
|
||||
xlibs.fontwinitzkicyrillic
|
||||
xlibs.fontxfree86type1
|
||||
];
|
||||
};
|
||||
}
|
||||
27
config/fudo/profiles/server.nix
Normal file
27
config/fudo/profiles/server.nix
Normal file
@@ -0,0 +1,27 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
environment = {
|
||||
systemPackages = with pkgs; [
|
||||
];
|
||||
|
||||
noXlibs = true;
|
||||
};
|
||||
|
||||
security = {
|
||||
hideProcessInformation = true;
|
||||
};
|
||||
|
||||
boot.tmpOnTmpfs = true;
|
||||
|
||||
services.xserver.enable = false;
|
||||
|
||||
programs = {
|
||||
gnupg.agent = {
|
||||
enable = true;
|
||||
enableSSHSupport = true;
|
||||
};
|
||||
|
||||
ssh.startAgent = true;
|
||||
};
|
||||
}
|
||||
204
config/fudo/prometheus.nix
Normal file
204
config/fudo/prometheus.nix
Normal file
@@ -0,0 +1,204 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
inherit (lib.strings) concatStringsSep;
|
||||
cfg = config.fudo.prometheus;
|
||||
fudo-cfg = config.fudo.common;
|
||||
|
||||
in {
|
||||
|
||||
options.fudo.prometheus = {
|
||||
enable = mkEnableOption "Fudo Prometheus Data-Gathering Server";
|
||||
|
||||
service-discovery-dns = mkOption {
|
||||
type = with types; loaOf (listOf str);
|
||||
description = ''
|
||||
A map of exporter type to a list of domains to use for service discovery.
|
||||
'';
|
||||
example = {
|
||||
node = [ "node._metrics._tcp.my-domain.com" ];
|
||||
postfix = [ "postfix._metrics._tcp.my-domain.com" ];
|
||||
};
|
||||
default = {
|
||||
dovecot = [];
|
||||
node = [];
|
||||
postfix = [];
|
||||
rspamd = [];
|
||||
};
|
||||
};
|
||||
|
||||
static-targets = mkOption {
|
||||
type = with types; loaOf (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" ];
|
||||
};
|
||||
default = {
|
||||
dovecot = [];
|
||||
node = [];
|
||||
postfix = [];
|
||||
rspamd = [];
|
||||
};
|
||||
};
|
||||
|
||||
docker-hosts = mkOption {
|
||||
type = with types; listOf str;
|
||||
description = ''
|
||||
A list of explicit <host:port> docker targets from which to gather node data.
|
||||
'';
|
||||
default = [];
|
||||
};
|
||||
|
||||
push-url = mkOption {
|
||||
type = with types; nullOr str;
|
||||
description = ''
|
||||
The <host:port> that services can use to manually push data.
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
|
||||
push-address = mkOption {
|
||||
type = with types; nullOr str;
|
||||
description = ''
|
||||
The <host:port> address on which to listen for incoming data.
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
type = with types; str;
|
||||
description = "The hostname upon which Prometheus will serve.";
|
||||
example = "my-metrics-server.fudo.org";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
virtualHosts = {
|
||||
"${cfg.hostname}" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:9090";
|
||||
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-By $server_addr:$server_port;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
${optionalString ((length fudo-cfg.local-networks) > 0)
|
||||
(concatStringsSep "\n" (map (network: "allow ${network};") fudo-cfg.local-networks)) + "\ndeny all;"}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.prometheus = {
|
||||
|
||||
enable = true;
|
||||
|
||||
webExternalUrl = "https://${cfg.hostname}";
|
||||
|
||||
scrapeConfigs = [
|
||||
{
|
||||
job_name = "docker";
|
||||
honor_labels = false;
|
||||
static_configs = [
|
||||
{
|
||||
targets = cfg.docker-hosts;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
{
|
||||
job_name = "node";
|
||||
scheme = "https";
|
||||
metrics_path = "/metrics/node";
|
||||
honor_labels = false;
|
||||
dns_sd_configs = [
|
||||
{
|
||||
names = cfg.service-discovery-dns.node;
|
||||
}
|
||||
];
|
||||
static_configs = [
|
||||
{
|
||||
targets = cfg.static-targets.node;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
{
|
||||
job_name = "dovecot";
|
||||
scheme = "https";
|
||||
metrics_path = "/metrics/dovecot";
|
||||
honor_labels = false;
|
||||
dns_sd_configs = [
|
||||
{
|
||||
names = cfg.service-discovery-dns.dovecot;
|
||||
}
|
||||
];
|
||||
static_configs = [
|
||||
{
|
||||
targets = cfg.static-targets.dovecot;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
{
|
||||
job_name = "postfix";
|
||||
scheme = "https";
|
||||
metrics_path = "/metrics/postfix";
|
||||
honor_labels = false;
|
||||
dns_sd_configs = [
|
||||
{
|
||||
names = cfg.service-discovery-dns.postfix;
|
||||
}
|
||||
];
|
||||
static_configs = [
|
||||
{
|
||||
targets = cfg.static-targets.postfix;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
{
|
||||
job_name = "rspamd";
|
||||
scheme = "https";
|
||||
metrics_path = "/metrics/rspamd";
|
||||
honor_labels = false;
|
||||
dns_sd_configs = [
|
||||
{
|
||||
names = cfg.service-discovery-dns.rspamd;
|
||||
}
|
||||
];
|
||||
static_configs = [
|
||||
{
|
||||
targets = cfg.static-targets.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;
|
||||
listen-address = cfg.push-address;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
79
config/fudo/sites.nix
Normal file
79
config/fudo/sites.nix
Normal file
@@ -0,0 +1,79 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
site = config.fudo.site;
|
||||
|
||||
hostname = config.networking.hostName;
|
||||
|
||||
winnipeg-networks = [
|
||||
"208.81.1.128/28"
|
||||
"208.81.3.112/28"
|
||||
"192.168.11.1/24"
|
||||
];
|
||||
|
||||
site-configs = {
|
||||
global-config = {
|
||||
};
|
||||
|
||||
winnipeg = global-config // {
|
||||
time.timeZone = "America/Winnipeg";
|
||||
|
||||
fudo.common.local-networks = winnipeg-networks;
|
||||
|
||||
services.cron = {
|
||||
mailto = "admin@fudo.org";
|
||||
};
|
||||
|
||||
networking = {
|
||||
domain = "fudo.org";
|
||||
search = ["fudo.org"];
|
||||
firewall.enable = false;
|
||||
networkmanager.enable = pkgs.lib.mkForce false;
|
||||
nameservers = [ "1.1.1.1" "208.81.7.14" "2606:4700:4700::1111" ];
|
||||
};
|
||||
|
||||
security.acme.certs."${hostname}" = {
|
||||
email = "admin@fudo.org";
|
||||
|
||||
plugins = [
|
||||
"fullchain.pem"
|
||||
"full.pem"
|
||||
"key.pem"
|
||||
"chain.pem"
|
||||
"cert.pem"
|
||||
];
|
||||
};
|
||||
|
||||
fudo.node-exporter = {
|
||||
enable = true;
|
||||
hostname = hostname;
|
||||
};
|
||||
|
||||
nginx = {
|
||||
enable = true;
|
||||
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedTlsSettings = true;
|
||||
};
|
||||
};
|
||||
|
||||
nutty-club = winnipeg // {
|
||||
defaultGateway = "208.81.3.113";
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
options.fudo.site = mkOption {
|
||||
type = types.enum (attrNames site-configs);
|
||||
example = "nutty-club";
|
||||
description = ''
|
||||
The site at which this host is located. This will do some site-dependent
|
||||
configuration.
|
||||
'';
|
||||
default = "";
|
||||
};
|
||||
|
||||
config = optionalAttrs (site-configs ? site) site-configs.${site};
|
||||
}
|
||||
21
config/local.nix
Normal file
21
config/local.nix
Normal file
@@ -0,0 +1,21 @@
|
||||
{ lib, config, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
{
|
||||
imports = [
|
||||
./fudo/acme-for-hostname.nix
|
||||
./fudo/authentication.nix
|
||||
./fudo/common.nix
|
||||
./fudo/grafana.nix
|
||||
./fudo/kdc.nix
|
||||
./fudo/ldap.nix
|
||||
./fudo/mail.nix
|
||||
./fudo/mail-container.nix
|
||||
./fudo/minecraft-server.nix
|
||||
./fudo/node-exporter.nix
|
||||
./fudo/postgres.nix
|
||||
./fudo/profiles.nix
|
||||
./fudo/prometheus.nix
|
||||
./fudo/sites.nix
|
||||
];
|
||||
}
|
||||
@@ -72,67 +72,4 @@ in {
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# config = mkIf config.fudo.postgresql.enable
|
||||
|
||||
# environment = {
|
||||
|
||||
# systemPackages = with pkgs; [
|
||||
# postgresql_11_gssapi
|
||||
# ];
|
||||
|
||||
# etc = {
|
||||
# "postgresql/private/privkey.pem" = {
|
||||
# mode = "0400";
|
||||
# user = "postgres";
|
||||
# group = "postgres";
|
||||
# source = dataPath + "/certs/private/privkey.pem";
|
||||
# };
|
||||
|
||||
# "postgresql/cert.pem" = {
|
||||
# mode = "0444";
|
||||
# user = "postgres";
|
||||
# group = "postgres";
|
||||
# source = dataPath + "/certs/cert.pem";
|
||||
# };
|
||||
|
||||
# "postgresql/private/postgres.keytab" = {
|
||||
# mode = "0400";
|
||||
# user = "postgres";
|
||||
# group = "postgres";
|
||||
# source = dataPath + "/keytabs/postgres.keytab";
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
|
||||
# services.postgresql = {
|
||||
# enable = true;
|
||||
# package = pkgs.postgresql_11_gssapi;
|
||||
# enableTCPIP = true;
|
||||
|
||||
# extraConfig = ''
|
||||
# krb_server_keyfile = '/etc/postgresql/private/postgres.keytab'
|
||||
|
||||
# ssl = true
|
||||
# ssl_cert_file = '/etc/postgresql/cert.pem'
|
||||
# ssl_key_file = '/etc/postgresql/private/privkey.pem'
|
||||
# '';
|
||||
|
||||
# authentication = ''
|
||||
# local all all ident
|
||||
|
||||
# # host-local
|
||||
# host all all 127.0.0.1/32 gss include_realm=0 krb_realm=FUDO.ORG
|
||||
# host all all ::1/128 gss include_realm=0 krb_realm=FUDO.ORG
|
||||
|
||||
# # local network
|
||||
# host all all 10.0.0.1/24 gss include_realm=0 krb_realm=FUDO.ORG
|
||||
# host all all 2601:600:997f:fc00::/60 gss include_realm=0 krb_realm=FUDO.ORG
|
||||
# '';
|
||||
|
||||
# initialScript = pkgs.writeText "backend-initscript" ''
|
||||
# ${catLines (map createUserSql fudo.postgresql.users)}
|
||||
# ${catLines (map createDatabaseSql fudo.postgresql.databases)}
|
||||
# '';
|
||||
# };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user