Changes over time
This commit is contained in:
parent
125d2d3d57
commit
2048377c3f
7
lib.nix
7
lib.nix
|
@ -1,10 +1,11 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
ip = import ./lib/lib/ip.nix { inherit pkgs; };
|
||||
dns = import ./lib/lib/dns.nix { inherit pkgs; };
|
||||
passwd = import ./lib/lib/passwd.nix { inherit pkgs; };
|
||||
fs = import ./lib/lib/filesystem.nix { inherit pkgs; };
|
||||
ip = import ./lib/lib/ip.nix { inherit pkgs; };
|
||||
lisp = import ./lib/lib/lisp.nix { inherit pkgs; };
|
||||
network = import ./lib/lib/network.nix { inherit pkgs; };
|
||||
fs = import ./lib/lib/filesystem.nix { inherit pkgs; };
|
||||
passwd = import ./lib/lib/passwd.nix { inherit pkgs; };
|
||||
text = import ./lib/lib/text.nix { inherit pkgs; };
|
||||
}
|
||||
|
|
|
@ -118,25 +118,48 @@ in {
|
|||
# Sigh. Leave it the same as nginx default, so it works whether or not
|
||||
# nginx feels like helping or not.
|
||||
default = "/var/lib/acme/acme-challenge";
|
||||
# default = "/run/acme-challenge";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
security.acme.certs = mapAttrs (domain: domainOpts: {
|
||||
email = domainOpts.admin-email;
|
||||
webroot = cfg.challenge-path;
|
||||
extraDomainNames = domainOpts.extra-domains;
|
||||
# email = domainOpts.admin-email;
|
||||
# webroot = cfg.challenge-path;
|
||||
# group = "nginx";
|
||||
# extraDomainNames = domainOpts.extra-domains;
|
||||
}) localDomains;
|
||||
|
||||
# Assume that if we're acquiring SSL certs, we have a real IP for the
|
||||
# host. nginx must have an acme dir for security.acme to work.
|
||||
services.nginx = mkIf hasLocalDomains {
|
||||
enable = true;
|
||||
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedTlsSettings = true;
|
||||
recommendedProxySettings = true;
|
||||
virtualHosts = let
|
||||
server-path = "/.well-known/acme-challenge";
|
||||
in (mapAttrs (domain: domainOpts: {
|
||||
# THIS IS A HACK. Getting redundant paths. So if {domain} is configured
|
||||
# somewhere else, assume ACME is already set.
|
||||
# locations.${server-path} = mkIf (! (hasAttr domain config.services.nginx.virtualHosts)) {
|
||||
# root = cfg.challenge-path;
|
||||
# extraConfig = "auth_basic off;";
|
||||
# };
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
serverAliases = domainOpts.extra-domains;
|
||||
}) localDomains) // {
|
||||
"default" = {
|
||||
serverName = "_";
|
||||
default = true;
|
||||
locations = {
|
||||
${server-path} = {
|
||||
root = cfg.challenge-path;
|
||||
extraConfig = "auth_basic off;";
|
||||
};
|
||||
"/".return = "403 Forbidden";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
|
@ -170,7 +193,17 @@ in {
|
|||
if (copyOpts.group != null) then
|
||||
"${copyOpts.user}:${copyOpts.group}"
|
||||
else copyOpts.user;
|
||||
dirs = unique [
|
||||
(dirOf copyOpts.certificate)
|
||||
(dirOf copyOpts.full-certificate)
|
||||
(dirOf copyOpts.chain)
|
||||
(dirOf copyOpts.private-key)
|
||||
];
|
||||
install-certs = pkgs.writeShellScript "fudo-install-${domain}-${copy}-certs.sh" ''
|
||||
${concatStringsSep "\n" (map (dir: ''
|
||||
mkdir -p ${dir}
|
||||
chown ${owners} ${dir}
|
||||
'') dirs)}
|
||||
cp ${source}/cert.pem ${copyOpts.certificate}
|
||||
chmod 0444 ${copyOpts.certificate}
|
||||
chown ${owners} ${copyOpts.certificate}
|
||||
|
|
|
@ -32,7 +32,6 @@ in {
|
|||
domain = mkOption {
|
||||
type = types.str;
|
||||
description = "Domain under which this host is registered.";
|
||||
default = "fudo.link";
|
||||
};
|
||||
|
||||
server = mkOption {
|
||||
|
|
|
@ -1,48 +1,52 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib; {
|
||||
imports = [
|
||||
./acme-certs.nix
|
||||
./authentication.nix
|
||||
./backplane
|
||||
./chat.nix
|
||||
./client/dns.nix
|
||||
./deploy.nix
|
||||
./distributed-builds.nix
|
||||
./dns.nix
|
||||
./domains.nix
|
||||
./garbage-collector.nix
|
||||
./git.nix
|
||||
./global.nix
|
||||
./grafana.nix
|
||||
./hosts.nix
|
||||
./host-filesystems.nix
|
||||
./initrd-network.nix
|
||||
./ipfs.nix
|
||||
./jabber.nix
|
||||
./kdc.nix
|
||||
./ldap.nix
|
||||
./local-network.nix
|
||||
./mail.nix
|
||||
./mail-container.nix
|
||||
./minecraft-server.nix
|
||||
./netinfo-email.nix
|
||||
./node-exporter.nix
|
||||
./nsd.nix
|
||||
./password.nix
|
||||
./postgres.nix
|
||||
./prometheus.nix
|
||||
./secrets.nix
|
||||
./secure-dns-proxy.nix
|
||||
./sites.nix
|
||||
./slynk.nix
|
||||
./ssh.nix
|
||||
./system.nix
|
||||
./system-networking.nix
|
||||
./users.nix
|
||||
./vpn.nix
|
||||
./webmail.nix
|
||||
./wireless-networks.nix
|
||||
./zones.nix
|
||||
];
|
||||
imports = [
|
||||
./backplane-service/dns.nix
|
||||
|
||||
./acme-certs.nix
|
||||
./adguard-dns-proxy.nix
|
||||
./authentication.nix
|
||||
./backplane.nix
|
||||
./chat.nix
|
||||
./client/dns.nix
|
||||
./deploy.nix
|
||||
./distributed-builds.nix
|
||||
./dns.nix
|
||||
./domains.nix
|
||||
./garbage-collector.nix
|
||||
./git.nix
|
||||
./global.nix
|
||||
./grafana.nix
|
||||
./hosts.nix
|
||||
./host-filesystems.nix
|
||||
./initrd-network.nix
|
||||
./ipfs.nix
|
||||
./jabber.nix
|
||||
./kdc.nix
|
||||
./ldap.nix
|
||||
./local-network.nix
|
||||
./mail.nix
|
||||
./mail-container.nix
|
||||
./minecraft-server.nix
|
||||
./netinfo-email.nix
|
||||
./node-exporter.nix
|
||||
./nsd.nix
|
||||
./password.nix
|
||||
./postgres.nix
|
||||
./powerdns.nix
|
||||
./prometheus.nix
|
||||
./secrets.nix
|
||||
./secure-dns-proxy.nix
|
||||
./sites.nix
|
||||
./slynk.nix
|
||||
./ssh.nix
|
||||
./system.nix
|
||||
./system-networking.nix
|
||||
./users.nix
|
||||
./vpn.nix
|
||||
./webmail.nix
|
||||
./wireless-networks.nix
|
||||
./zones.nix
|
||||
];
|
||||
}
|
||||
|
|
|
@ -48,12 +48,54 @@ let
|
|||
default = "admin@${domain}";
|
||||
};
|
||||
|
||||
gssapi-realm = mkOption {
|
||||
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 = "GSSAPI (i.e. Kerberos) realm of this domain.";
|
||||
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;
|
||||
};
|
||||
|
||||
gssapi-realm = mkOption {
|
||||
type = str;
|
||||
description = "GSSAPI (i.e. Kerberos) realm of this domain.";
|
||||
};
|
||||
|
||||
kerberos-master = mkOption {
|
||||
type = nullOr str;
|
||||
description = "Hostname of the Kerberos master server for the domain, if applicable.";
|
||||
|
@ -72,6 +114,12 @@ let
|
|||
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.";
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.grafana;
|
||||
cfg = config.fudo.metrics.grafana;
|
||||
|
||||
hostname = config.instance.hostname;
|
||||
domain-name = config.fudo.hosts.${hostname}.domain;
|
||||
|
||||
in {
|
||||
|
||||
options.fudo.grafana = with types; {
|
||||
options.fudo.metrics.grafana = with types; {
|
||||
enable = mkEnableOption "Fudo Metrics Display Service";
|
||||
|
||||
hostname = mkOption {
|
||||
|
@ -88,72 +88,75 @@ in {
|
|||
description = "Directory at which to store Grafana state data.";
|
||||
default = "/var/lib/grafana";
|
||||
};
|
||||
|
||||
private-network = mkEnableOption "Network is private, no SSL.";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
systemd.tmpfiles.rules = let
|
||||
grafana-user = config.systemd.services.grafana.serviceConfig.User;
|
||||
in [
|
||||
"d ${cfg.state-directory} 0700 ${grafana-user} - - -"
|
||||
];
|
||||
|
||||
virtualHosts = {
|
||||
"${cfg.hostname}" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
services = {
|
||||
nginx = {
|
||||
enable = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = 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;
|
||||
'';
|
||||
virtualHosts = {
|
||||
"${cfg.hostname}" = {
|
||||
enableACME = ! cfg.private-network;
|
||||
forceSSL = ! cfg.private-network;
|
||||
locations."/".proxyPass = "http://127.0.0.1:3000";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
|
||||
addr = "127.0.0.1";
|
||||
protocol = "http";
|
||||
port = 3000;
|
||||
domain = cfg.hostname;
|
||||
rootUrl = "https://${cfg.hostname}/";
|
||||
dataDir = cfg.state-directory;
|
||||
|
||||
security = {
|
||||
adminPasswordFile = cfg.admin-password-file;
|
||||
secretKeyFile = cfg.secret-key-file;
|
||||
};
|
||||
|
||||
smtp = {
|
||||
grafana = {
|
||||
enable = true;
|
||||
fromAddress = "metrics@fudo.org";
|
||||
host = "mail.fudo.org:25";
|
||||
user = cfg.smtp-username;
|
||||
passwordFile = cfg.smtp-password-file;
|
||||
};
|
||||
|
||||
database = {
|
||||
host = cfg.database.hostname;
|
||||
name = cfg.database.name;
|
||||
user = cfg.database.user;
|
||||
passwordFile = cfg.database.password-file;
|
||||
type = "postgres";
|
||||
};
|
||||
addr = "127.0.0.1";
|
||||
protocol = "http";
|
||||
port = 3000;
|
||||
domain = cfg.hostname;
|
||||
rootUrl = let
|
||||
scheme = if cfg.private-network then "http" else "https";
|
||||
in "${scheme}://${cfg.hostname}/";
|
||||
dataDir = cfg.state-directory;
|
||||
|
||||
provision.datasources = [
|
||||
{
|
||||
security = {
|
||||
adminPasswordFile = cfg.admin-password-file;
|
||||
secretKeyFile = cfg.secret-key-file;
|
||||
};
|
||||
|
||||
smtp = {
|
||||
enable = true;
|
||||
fromAddress = "metrics@fudo.org";
|
||||
host = "${cfg.smtp.hostname}:25";
|
||||
user = cfg.smtp.username;
|
||||
passwordFile = cfg.smtp.password-file;
|
||||
};
|
||||
|
||||
database = {
|
||||
host = cfg.database.hostname;
|
||||
name = cfg.database.name;
|
||||
user = cfg.database.user;
|
||||
passwordFile = cfg.database.password-file;
|
||||
type = "postgres";
|
||||
};
|
||||
|
||||
provision.datasources = imap0 (i: host: {
|
||||
editable = false;
|
||||
isDefault = true;
|
||||
name = cfg.prometheus-host;
|
||||
isDefault = (i == 0);
|
||||
name = builtins.trace "PROMETHEUS-HOST: ${host}" host;
|
||||
type = "prometheus";
|
||||
url = "https://${cfg.prometheus-host}/";
|
||||
}
|
||||
];
|
||||
url = let
|
||||
scheme = if private-network then "http" else "https";
|
||||
in "${scheme}://${host}/";
|
||||
}) cfg.prometheus-hosts;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ in {
|
|||
packages = map (p: "${p.name}")
|
||||
config.environment.systemPackages;
|
||||
sorted-unique = sort lessThan (unique packages);
|
||||
in concatStringsSep "\n" sorted-unique;
|
||||
in "${concatStringsSep "\n" sorted-unique}\n";
|
||||
};
|
||||
|
||||
systemPackages = with pkgs;
|
||||
|
|
|
@ -32,8 +32,9 @@ in {
|
|||
config = {
|
||||
boot = mkIf (initrd-cfg != null) {
|
||||
kernelParams = let
|
||||
site = config.fudo.sites.${config.instance.local-site};
|
||||
site-gateway = site.gateway-v4;
|
||||
site-name = config.instance.local-site;
|
||||
site = config.fudo.sites.${site-name};
|
||||
site-gateway = pkgs.lib.network.site-gateway config site-name;
|
||||
netmask =
|
||||
pkgs.lib.ip.maskFromV32Network site.network;
|
||||
in [
|
||||
|
|
|
@ -337,11 +337,15 @@ in {
|
|||
users = {
|
||||
users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
uid = 88;
|
||||
home = state-directory;
|
||||
group = cfg.group;
|
||||
};
|
||||
|
||||
groups.${cfg.group} = { members = [ cfg.user ]; };
|
||||
groups.${cfg.group} = {
|
||||
gid = 88;
|
||||
members = [ cfg.user ];
|
||||
};
|
||||
};
|
||||
|
||||
krb5 = {
|
||||
|
|
|
@ -218,6 +218,15 @@ in {
|
|||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users = {
|
||||
users.openldap = {
|
||||
uid = 389;
|
||||
};
|
||||
groups.openldap = {
|
||||
gid = 389;
|
||||
};
|
||||
};
|
||||
|
||||
environment = {
|
||||
etc = {
|
||||
"openldap/sasl2/slapd.conf" = mkIf (cfg.kerberos-keytab != null) {
|
||||
|
|
|
@ -31,7 +31,13 @@ in {
|
|||
|
||||
dns-listen-ips = mkOption {
|
||||
type = listOf str;
|
||||
description = "A list of IPs on which to server DNS queries.";
|
||||
description = "A list of IPv4 addresses on which to server DNS queries.";
|
||||
};
|
||||
|
||||
dns-listen-ipv6s = mkOption {
|
||||
type = listOf str;
|
||||
description = "A list of IPv6 addresses on which to server DNS queries.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
gateway = mkOption {
|
||||
|
@ -61,10 +67,22 @@ in {
|
|||
default = false;
|
||||
};
|
||||
|
||||
recursive-resolver = mkOption {
|
||||
type = str;
|
||||
description = "DNS nameserver to use for recursive resolution.";
|
||||
default = "1.1.1.1 port 53";
|
||||
# recursive-resolver = mkOption {
|
||||
# type = str;
|
||||
# description = "DNS nameserver to use for recursive resolution.";
|
||||
# default = "1.1.1.1 port 53";
|
||||
# };
|
||||
|
||||
recursive-resolver = {
|
||||
host = mkOption {
|
||||
type = str;
|
||||
description = "DNS server host or (preferably) IP.";
|
||||
};
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = "Remote host port for DNS queries.";
|
||||
default = 53;
|
||||
};
|
||||
};
|
||||
|
||||
search-domains = mkOption {
|
||||
|
@ -161,18 +179,22 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
ipToBlock = ip:
|
||||
filterRedundantIps = official-hosts: hosts: let
|
||||
host-by-ip = groupBy (hostOpts: hostOpts.ipv4-address) hosts;
|
||||
in filter (hostOpts:
|
||||
if (length (getAttr hostOpts.ipv4-address host-by-ip) == 1) then
|
||||
true
|
||||
else elem hostOpts.hostname official-hosts) hosts;
|
||||
ipTo24Block = ip:
|
||||
concatStringsSep "." (reverseList (take 3 (splitString "." ip)));
|
||||
compactHosts =
|
||||
mapAttrsToList (host: data: data // { host = host; }) zone.hosts;
|
||||
hostsByBlock =
|
||||
groupBy (host-data: ipToBlock host-data.ipv4-address) compactHosts;
|
||||
hostsByBlock = official-hosts:
|
||||
groupBy (host-data: ipTo24Block host-data.ipv4-address)
|
||||
(filterRedundantIps official-hosts (attrValues zone.hosts));
|
||||
hostPtrRecord = host-data:
|
||||
"${
|
||||
last (splitString "." host-data.ipv4-address)
|
||||
} IN PTR ${host-data.host}.${cfg.domain}.";
|
||||
"${last (splitString "." host-data.ipv4-address)} IN PTR ${host-data.hostname}.${cfg.domain}.";
|
||||
|
||||
blockZones = mapAttrsToList blockHostsToZone hostsByBlock;
|
||||
blockZones = official-hosts:
|
||||
mapAttrsToList blockHostsToZone (hostsByBlock official-hosts);
|
||||
|
||||
hostARecord = host: data: "${host} IN A ${data.ipv4-address}";
|
||||
hostSshFpRecords = host: data:
|
||||
|
@ -189,13 +211,21 @@ in {
|
|||
|
||||
known-hosts = config.fudo.hosts;
|
||||
|
||||
domain-name = config.instance.local-domain;
|
||||
|
||||
domain-hosts =
|
||||
attrNames
|
||||
(filterAttrs (_: hostOpts:
|
||||
hostOpts.domain == domain-name)
|
||||
config.fudo.hosts);
|
||||
|
||||
in {
|
||||
enable = true;
|
||||
cacheNetworks = [ cfg.network "localhost" "localnets" ];
|
||||
forwarders = [ cfg.recursive-resolver ];
|
||||
forwarders = [ "${cfg.recursive-resolver.host} port ${toString cfg.recursive-resolver.port}" ];
|
||||
listenOn = cfg.dns-listen-ips;
|
||||
listenOnIpv6 = cfg.dns-listen-ipv6s;
|
||||
extraOptions = concatStringsSep "\n" [
|
||||
"dnssec-enable yes;"
|
||||
"dnssec-validation yes;"
|
||||
"auth-nxdomain no;"
|
||||
"recursion yes;"
|
||||
|
@ -204,36 +234,44 @@ in {
|
|||
zones = [{
|
||||
master = true;
|
||||
name = cfg.domain;
|
||||
file = pkgs.writeText "${cfg.domain}-zone" ''
|
||||
@ IN SOA ns1.${cfg.domain}. hostmaster.${cfg.domain}. (
|
||||
${toString config.instance.build-timestamp}
|
||||
5m
|
||||
2m
|
||||
6w
|
||||
5m)
|
||||
file = let
|
||||
zone-data = pkgs.lib.dns.zoneToZonefile
|
||||
config.instance.build-timestamp
|
||||
cfg.domain
|
||||
zone;
|
||||
in pkgs.writeText "zone-${cfg.domain}" zone-data;
|
||||
# file = pkgs.writeText "${cfg.domain}-zone" ''
|
||||
# @ IN SOA ns1.${cfg.domain}. hostmaster.${cfg.domain}. (
|
||||
# ${toString config.instance.build-timestamp}
|
||||
# 5m
|
||||
# 2m
|
||||
# 6w
|
||||
# 5m)
|
||||
|
||||
$TTL 1h
|
||||
# $TTL 1h
|
||||
|
||||
@ IN NS ns1.${cfg.domain}.
|
||||
# @ IN NS ns1.${cfg.domain}.
|
||||
|
||||
$ORIGIN ${cfg.domain}.
|
||||
# $ORIGIN ${cfg.domain}.
|
||||
|
||||
$TTL 30m
|
||||
# $TTL 30m
|
||||
|
||||
${optionalString (zone.gssapi-realm != null)
|
||||
''_kerberos IN TXT "${zone.gssapi-realm}"''}
|
||||
# ${optionalString (zone.gssapi-realm != null)
|
||||
# ''_kerberos IN TXT "${zone.gssapi-realm}"''}
|
||||
|
||||
${join-lines
|
||||
(imap1 (i: server-ip: "ns${toString i} IN A ${server-ip}")
|
||||
cfg.dns-servers)}
|
||||
${join-lines (mapAttrsToList hostARecord zone.hosts)}
|
||||
${join-lines (mapAttrsToList hostSshFpRecords zone.hosts)}
|
||||
${join-lines (mapAttrsToList cnameRecord zone.aliases)}
|
||||
${join-lines zone.verbatim-dns-records}
|
||||
${pkgs.lib.dns.srvRecordsToBindZone zone.srv-records}
|
||||
${join-lines cfg.extra-records}
|
||||
'';
|
||||
}] ++ blockZones;
|
||||
# ${join-lines
|
||||
# (imap1 (i: server-ip: "ns${toString i} IN A ${server-ip}")
|
||||
# cfg.dns-servers)}
|
||||
# ${join-lines (mapAttrsToList hostARecord zone.hosts)}
|
||||
# ${join-lines (mapAttrsToList hostSshFpRecords zone.hosts)}
|
||||
# ${join-lines (mapAttrsToList cnameRecord zone.aliases)}
|
||||
# ${join-lines zone.verbatim-dns-records}
|
||||
# ${pkgs.lib.dns.srvRecordsToBindZone zone.srv-records}
|
||||
# ${join-lines cfg.extra-records}
|
||||
# '';
|
||||
}] ++ (optionals
|
||||
cfg.enable-reverse-mappings
|
||||
(blockZones domain-hosts));
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,14 +4,14 @@ with lib;
|
|||
let
|
||||
inherit (lib.strings) concatStringsSep;
|
||||
|
||||
cfg = config.fudo.node-exporter;
|
||||
cfg = config.fudo.metrics.node-exporter;
|
||||
|
||||
allow-network = network: "allow ${network};";
|
||||
|
||||
local-networks = config.instance.local-networks;
|
||||
|
||||
in {
|
||||
options.fudo.node-exporter = with types; {
|
||||
options.fudo.metrics.node-exporter = with types; {
|
||||
enable = mkEnableOption "Enable a Prometheus node exporter with some reasonable settings.";
|
||||
|
||||
hostname = mkOption {
|
||||
|
@ -24,6 +24,8 @@ in {
|
|||
description = "User as which to run the node exporter job.";
|
||||
default = "node-exporter";
|
||||
};
|
||||
|
||||
private-network = mkEnableOption "Network is private.";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
@ -45,10 +47,12 @@ in {
|
|||
# list of trusted networks, with SSL protection.
|
||||
nginx = {
|
||||
enable = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
|
||||
virtualHosts."${cfg.hostname}" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
enableACME = ! cfg.private-network;
|
||||
forceSSL = ! cfg.private-network;
|
||||
|
||||
locations."/metrics/node" = {
|
||||
extraConfig = ''
|
||||
|
@ -57,7 +61,6 @@ in {
|
|||
deny all;
|
||||
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
'';
|
||||
|
||||
proxyPass = "http://127.0.0.1:9100/metrics";
|
||||
|
|
|
@ -105,17 +105,22 @@ let
|
|||
nameValuePair "DATABASE ${database}" databaseOpts.access) databases;
|
||||
|
||||
makeEntry = nw:
|
||||
"host all all ${nw} gss include_realm=0 krb_realm=${gssapi-realm}";
|
||||
"hostssl all all ${nw} gss include_realm=0 krb_realm=${gssapi-realm}";
|
||||
|
||||
makeNetworksEntry = networks: join-lines (map makeEntry networks);
|
||||
|
||||
makeLocalUserPasswordEntries = users:
|
||||
join-lines (mapAttrsToList (user: opts:
|
||||
join-lines (map (db: ''
|
||||
local ${db} ${user} md5
|
||||
host ${db} ${user} 127.0.0.1/16 md5
|
||||
host ${db} ${user} ::1/128 md5
|
||||
'') (attrNames opts.databases))) (filterPasswordedUsers users));
|
||||
makeLocalUserPasswordEntries = users: networks: let
|
||||
network-entries = user: db:
|
||||
join-lines
|
||||
(map (network: "hostssl ${db} ${user} ${network} md5")
|
||||
networks);
|
||||
in join-lines (mapAttrsToList (user: opts:
|
||||
join-lines (map (db: ''
|
||||
local ${db} ${user} md5
|
||||
host ${db} ${user} 127.0.0.1/16 md5
|
||||
host ${db} ${user} ::1/128 md5
|
||||
${network-entries user db}
|
||||
'') (attrNames opts.databases))) (filterPasswordedUsers users));
|
||||
|
||||
userTableAccessSql = user: entity: access:
|
||||
"GRANT ${access} ON ${entity} TO ${user};";
|
||||
|
@ -134,18 +139,21 @@ in {
|
|||
enable = mkEnableOption "Fudo PostgreSQL Server";
|
||||
|
||||
ssl-private-key = mkOption {
|
||||
type = str;
|
||||
type = nullOr str;
|
||||
description = "Location of the server SSL private key.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
ssl-certificate = mkOption {
|
||||
type = str;
|
||||
type = nullOr str;
|
||||
description = "Location of the server SSL certificate.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
keytab = mkOption {
|
||||
type = str;
|
||||
type = nullOr str;
|
||||
description = "Location of the server Kerberos keytab.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
local-networks = mkOption {
|
||||
|
@ -224,32 +232,9 @@ in {
|
|||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment = {
|
||||
systemPackages = with pkgs; [ postgresql_11_gssapi ];
|
||||
networking.firewall.allowedTCPPorts = [ 5432 ];
|
||||
|
||||
# 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;
|
||||
# };
|
||||
# };
|
||||
};
|
||||
environment.systemPackages = with pkgs; [ postgresql_11_gssapi ];
|
||||
|
||||
users.groups = {
|
||||
${cfg.socket-group} = { members = [ "postgres" ] ++ cfg.local-users; };
|
||||
|
@ -269,12 +254,14 @@ in {
|
|||
ensurePermissions = { "DATABASE ${database}" = "ALL PRIVILEGES"; };
|
||||
}) opts.users)) cfg.databases)));
|
||||
|
||||
settings = {
|
||||
krb_server_keyfile = cfg.keytab;
|
||||
settings = let
|
||||
ssl-enabled = cfg.ssl-certificate != null;
|
||||
in {
|
||||
krb_server_keyfile = mkIf (cfg.keytab != null) cfg.keytab;
|
||||
|
||||
ssl = true;
|
||||
ssl_cert_file = cfg.ssl-certificate;
|
||||
ssl_key_file = cfg.ssl-private-key;
|
||||
ssl = ssl-enabled;
|
||||
ssl_cert_file = mkIf ssl-enabled cfg.ssl-certificate;
|
||||
ssl_key_file = mkIf ssl-enabled cfg.ssl-private-key;
|
||||
|
||||
unix_socket_directories = cfg.socket-directory;
|
||||
unix_socket_group = cfg.socket-group;
|
||||
|
@ -282,12 +269,12 @@ in {
|
|||
};
|
||||
|
||||
authentication = lib.mkForce ''
|
||||
${makeLocalUserPasswordEntries cfg.users}
|
||||
${makeLocalUserPasswordEntries cfg.users cfg.local-networks}
|
||||
|
||||
local all all ident
|
||||
|
||||
# host-local
|
||||
host all all 127.0.0.1/32 gss include_realm=0 krb_realm=${gssapi-realm}
|
||||
host all all 127.0.0.1/16 gss include_realm=0 krb_realm=${gssapi-realm}
|
||||
host all all ::1/128 gss include_realm=0 krb_realm=${gssapi-realm}
|
||||
|
||||
# local networks
|
||||
|
@ -308,6 +295,22 @@ in {
|
|||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
paths = let
|
||||
user-password-files = mapAttrsToList
|
||||
(user: userOpts: userOpts.password-file)
|
||||
cfg.users;
|
||||
in {
|
||||
postgresql-password-watcher = mkIf (length user-password-files > 0) {
|
||||
wantedBy = [ "default.target" ];
|
||||
description =
|
||||
"Reset all user passwords if any changes occur.";
|
||||
pathConfig = {
|
||||
PathChanged = user-password-files;
|
||||
Unit = "postgresql-password-setter.service";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services = {
|
||||
postgresql-password-setter = let
|
||||
passwords-script = passwords-setter-script cfg.users;
|
||||
|
@ -350,22 +353,42 @@ in {
|
|||
partOf = [ cfg.systemd-target ];
|
||||
wants = [ "postgresql-password-setter.service" ];
|
||||
|
||||
postStart = let
|
||||
# postStart = let
|
||||
# allow-user-login = user: "ALTER ROLE ${user} WITH LOGIN;";
|
||||
|
||||
# extra-settings-sql = pkgs.writeText "settings.sql" ''
|
||||
# ${concatStringsSep "\n"
|
||||
# (map allow-user-login (mapAttrsToList (key: val: key) cfg.users))}
|
||||
# ${usersAccessSql cfg.users}
|
||||
# '';
|
||||
# in ''
|
||||
# ${pkgs.postgresql}/bin/psql --port ${
|
||||
# toString config.services.postgresql.port
|
||||
# } -d postgres -f ${extra-settings-sql}
|
||||
# ${pkgs.coreutils}/bin/chgrp ${cfg.socket-group} ${cfg.socket-directory}/.s.PGSQL*
|
||||
# '';
|
||||
|
||||
postStop = concatStringsSep "\n" cfg.cleanup-tasks;
|
||||
};
|
||||
|
||||
postgresql-finalizer = {
|
||||
requires = [ "postgresql.target" ];
|
||||
after = [ "postgresql.target" "postgresql-password-setter.target" ];
|
||||
partOf = [ cfg.systemd-target ];
|
||||
script = let
|
||||
allow-user-login = user: "ALTER ROLE ${user} WITH LOGIN;";
|
||||
|
||||
extra-settings-sql = pkgs.writeText "settings.sql" ''
|
||||
${concatStringsSep "\n"
|
||||
(map allow-user-login (mapAttrsToList (key: val: key) cfg.users))}
|
||||
${usersAccessSql cfg.users}
|
||||
'';
|
||||
in ''
|
||||
${concatStringsSep "\n"
|
||||
(map allow-user-login (mapAttrsToList (key: val: key) cfg.users))}
|
||||
${usersAccessSql cfg.users}
|
||||
'';
|
||||
in ''
|
||||
${pkgs.postgresql}/bin/psql --port ${
|
||||
toString config.services.postgresql.port
|
||||
} -d postgres -f ${extra-settings-sql}
|
||||
${pkgs.coreutils}/bin/chgrp ${cfg.socket-group} ${cfg.socket-directory}/.s.PGSQL*
|
||||
'';
|
||||
|
||||
postStop = concatStringsSep "\n" cfg.cleanup-tasks;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
|
||||
with lib;
|
||||
let
|
||||
inherit (lib.strings) concatStringsSep;
|
||||
cfg = config.fudo.prometheus;
|
||||
cfg = config.fudo.metrics.prometheus;
|
||||
|
||||
in {
|
||||
|
||||
options.fudo.prometheus = with types; {
|
||||
options.fudo.metrics.prometheus = with types; {
|
||||
enable = mkEnableOption "Fudo Prometheus Data-Gathering Server";
|
||||
|
||||
service-discovery-dns = mkOption {
|
||||
|
@ -78,32 +77,33 @@ in {
|
|||
description = "Directory at which to store Prometheus state.";
|
||||
default = "/var/lib/prometheus";
|
||||
};
|
||||
|
||||
private-network = mkEnableOption "Network is private.";
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.state-directory} 0700 ${config.systemd.services.prometheus.serviceConfig.User} - - -"
|
||||
];
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
|
||||
virtualHosts = {
|
||||
"${cfg.hostname}" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
enableACME = ! cfg.private-network;
|
||||
forceSSL = ! cfg.private-network;
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:9090";
|
||||
|
||||
extraConfig = let
|
||||
local-networks = config.instance.local-networks;
|
||||
in ''
|
||||
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 local-networks) > 0)
|
||||
(concatStringsSep "\n" (map (network: "allow ${network};") local-networks)) + "\ndeny all;"}
|
||||
'';
|
||||
in "${optionalString ((length local-networks) > 0)
|
||||
(concatStringsSep "\n"
|
||||
(map (network: "allow ${network};") local-networks)) + "\ndeny all;"}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -118,85 +118,18 @@ in {
|
|||
listenAddress = "127.0.0.1";
|
||||
port = 9090;
|
||||
|
||||
scrapeConfigs = [
|
||||
{
|
||||
job_name = "docker";
|
||||
scrapeConfigs = let
|
||||
make-job = type: {
|
||||
job_name = type;
|
||||
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;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
scheme = if cfg.private-network then "http" else "https";
|
||||
metrics_path = "/metrics/${type}";
|
||||
dns_sd_configs = if (hasAttr type cfg.service-discovery-dns) then
|
||||
[ { names = cfg.service-discovery-dns.${type}; } ] else [];
|
||||
static_configs = if (hasAttr type cfg.static-targets) then
|
||||
[ { targets = cfg.static-targets.${type}; } ] else [];
|
||||
};
|
||||
in map make-job ["docker" "node" "dovecot" "postfix" "rspamd"];
|
||||
|
||||
pushgateway = {
|
||||
enable = if (cfg.push-url != null) then true else false;
|
||||
|
|
|
@ -66,6 +66,7 @@ let
|
|||
user = mkOption {
|
||||
type = str;
|
||||
description = "User (on target host) to which the file will belong.";
|
||||
default = "root";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
|
|
|
@ -133,6 +133,12 @@ let
|
|||
type = str;
|
||||
description = "Hostname of the mail server to use for this site.";
|
||||
};
|
||||
|
||||
local-gateway = mkOption {
|
||||
type = nullOr str;
|
||||
description = "If this is a NAT site, this should point to the host acting as network gateway.";
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -166,7 +166,7 @@ let
|
|||
"Level of protection to apply to the system for this service.";
|
||||
};
|
||||
addressFamilies = mkOption {
|
||||
type = listOf (enum address-families);
|
||||
type = nullOr (listOf (enum address-families));
|
||||
default = [ ];
|
||||
description = "List of address families which the service can use.";
|
||||
};
|
||||
|
@ -435,7 +435,8 @@ in {
|
|||
WorkingDirectory =
|
||||
mkIf (opts.workingDirectory != null) opts.workingDirectory;
|
||||
RestrictAddressFamilies =
|
||||
restrict-address-families opts.addressFamilies;
|
||||
optionals (opts.addressFamilies != null)
|
||||
(restrict-address-families opts.addressFamilies);
|
||||
RestrictNamespaces = opts.restrictNamespaces;
|
||||
User = mkIf (opts.user != null) opts.user;
|
||||
Group = mkIf (opts.group != null) opts.group;
|
||||
|
|
|
@ -161,7 +161,7 @@ in {
|
|||
GEMINI_TEXTFILES_ROOT = cfg.textfiles-archive;
|
||||
GEMINI_FEEDS = "${generate-feeds cfg.feeds}";
|
||||
|
||||
CL_SOURCE_REGISTRY = "${pkgs.lib.lisp.lisp-source-registry pkgs.cl-gemini}";
|
||||
CL_SOURCE_REGISTRY = pkgs.lib.lisp.lisp-source-registry pkgs.cl-gemini;
|
||||
};
|
||||
|
||||
path = with pkgs; [
|
||||
|
|
|
@ -62,9 +62,21 @@ in {
|
|||
description = "Networks which are considered local to this host, site, or domain.";
|
||||
};
|
||||
|
||||
service-home = mkOption {
|
||||
type = str;
|
||||
description = "Path to runtime home directories for services.";
|
||||
default = "/run/service";
|
||||
};
|
||||
|
||||
build-seed = mkOption {
|
||||
type = str;
|
||||
description = "Seed used to generate configuration.";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${config.instance.service-home} 755 root root - -"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ let
|
|||
toString record.priority
|
||||
} ${
|
||||
toString record.weight
|
||||
} ${record.host}.") records);
|
||||
} ${toString record.port} ${record.host}.") records);
|
||||
|
||||
srvRecordOpts = with types; {
|
||||
options = {
|
||||
|
|
|
@ -44,8 +44,14 @@ let
|
|||
not-null = o: o != null;
|
||||
in filter not-null [ ipv4 ipv6 ];
|
||||
|
||||
site-gateway = config: site-name: let
|
||||
site = config.fudo.sites.${site-name};
|
||||
in if (site.local-gateway != null)
|
||||
then host-ipv4 config site.local-gateway
|
||||
else site.gateway-v4;
|
||||
|
||||
in {
|
||||
inherit host-ipv4 host-ipv6 host-ips;
|
||||
inherit host-ipv4 host-ipv6 host-ips site-gateway;
|
||||
|
||||
generate-mac-address = hostname: interface: let
|
||||
pkg = generate-mac-address hostname interface;
|
||||
|
|
|
@ -12,12 +12,12 @@ let
|
|||
installPhase = let
|
||||
passwd = removeSuffix "\n" (readFile passwd-file);
|
||||
in ''
|
||||
slappasswd -s ${passwd} > $out
|
||||
slappasswd -s ${passwd} | tr -d '\n' > $out
|
||||
'';
|
||||
};
|
||||
|
||||
hash-ldap-passwd = name: passwd-file:
|
||||
builtins.readFile "${hash-ldap-passwd-pkg name passwd-file}";
|
||||
readFile "${hash-ldap-passwd-pkg name passwd-file}";
|
||||
|
||||
generate-random-passwd = name: length: pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-random-passwd";
|
||||
|
@ -27,10 +27,28 @@ let
|
|||
buildInputs = with pkgs; [ pwgen ];
|
||||
|
||||
installPhase = ''
|
||||
pwgen --secure --num-passwords=1 ${toString length} > $out
|
||||
pwgen --secure --num-passwords=1 ${toString length} | tr -d '\n' > $out
|
||||
'';
|
||||
};
|
||||
|
||||
bcrypt-passwd-pkg = name: passwd-file: pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-bcrypt";
|
||||
|
||||
phases = [ "installPhase" ];
|
||||
|
||||
buildInputs = with pkgs; [ apacheHttpd ];
|
||||
|
||||
installPhase = let
|
||||
passwd = removeSuffix "\n" (readFile passwd-file);
|
||||
in ''
|
||||
htpasswd -bnBC 10 "" ${passwd} | tr -d ':\n' | sed 's/$2y/$2a/' > $out
|
||||
'';
|
||||
};
|
||||
|
||||
bcrypt-passwd = name: passwd-file:
|
||||
readFile "${bcrypt-passwd-pkg name passwd-file}";
|
||||
|
||||
|
||||
generate-stablerandom-passwd = name: { seed, length ? 20, ... }:
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-stablerandom-passwd";
|
||||
|
@ -41,13 +59,15 @@ let
|
|||
|
||||
installPhase = ''
|
||||
echo "${name}-${seed}" > seedfile
|
||||
pwgen --secure --num-passwords=1 -H seedfile ${toString length} > $out
|
||||
pwgen --secure --num-passwords=1 -H seedfile ${toString length} | tr -d '\n' > $out
|
||||
'';
|
||||
};
|
||||
|
||||
in {
|
||||
hash-ldap-passwd = hash-ldap-passwd;
|
||||
|
||||
bcrypt-passwd = bcrypt-passwd;
|
||||
|
||||
random-passwd-file = name: length:
|
||||
builtins.toPath "${generate-random-passwd name length}";
|
||||
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
{ lib, ... }:
|
||||
|
||||
with lib;
|
||||
{
|
||||
{ name, ... }: {
|
||||
options = with types; {
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description = "Hostname.";
|
||||
default = name;
|
||||
};
|
||||
ipv4-address = mkOption {
|
||||
type = nullOr str;
|
||||
description = "The V4 IP of a given host, if any.";
|
||||
|
|
Loading…
Reference in New Issue