Added live disk flake file

This commit is contained in:
niten 2022-03-16 09:49:54 -07:00
parent 9c9df8c3c2
commit 2954dfc1b2
21 changed files with 2323 additions and 0 deletions

7
config/domains.nix Normal file
View File

@ -0,0 +1,7 @@
{ config, lib, pkgs, ... }:
{
imports = [
./domains/sea.fudo.org.nix
];
}

View File

@ -0,0 +1,19 @@
{ config, lib, pkgs, ... }:
let
fudo = config.fudo.domains."fudo.org";
in {
config.fudo.domains."sea.fudo.org" = {
local-networks = fudo.local-networks;
gssapi-realm = fudo.gssapi-realm;
kerberos-master = fudo.kerberos-master;
kerberos-slaves = fudo.kerberos-slaves;
primary-mailserver = fudo.primary-mailserver;
ldap-servers = fudo.ldap-servers;
xmpp-servers = fudo.xmpp-servers;
};
}

View File

@ -0,0 +1,74 @@
{ config, lib, pkgs, modulesPath, ... }:
with lib; {
system.stateVersion = "22.05";
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
boot = {
initrd = {
availableKernelModules = [ "xhci_pci" "usbhid" "usb_storage" ];
kernelModules = [ ];
};
loader = {
grub.enable = false;
# generic-extlinux-compatible.enable = true;
raspberryPi = {
enable = true;
version = 4;
};
};
tmpOnTmpfs = true;
kernelModules = [ ];
kernelPackages = pkgs.linuxPackages_rpi4;
kernelParams = [
"8250.nr_uarts=1"
"console=ttyAMA0,115200"
"console=tty1"
];
extraModulePackages = [ ];
};
hardware = {
enableRedistributableFirmware = true;
# raspberry-pi."4".fkms-3d.enable = true;
};
fileSystems = {
"/" = {
device = "/dev/disk/by-label/NIXOS_SD";
fsType = "ext4";
options = [ "noatime" ];
};
"/boot" = {
device = "/dev/disk/by-label/FIRMWARE";
fsType = "vfat";
options = [ "noatime" ];
};
};
swapDevices = [ ];
networking = {
useDHCP = mkDefault false;
macvlans = {
intif0 = {
interface = "eth0";
mode = "bridge";
};
};
interfaces = {
eth0.useDHCP = false;
intif0.macAddress = "02:fa:d4:07:cf:f4";
};
};
powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand";
}

View File

@ -0,0 +1,216 @@
{ config, lib, pkgs, ... }:
with lib;
let
parent-config = config;
host-ipv4 = "199.87.154.175";
local-packages = with pkgs; [
bind
emacs-nox
mtr
vim
];
fudo-zone = pkgs.lib.dns.zoneToZonefile
config.instance.build-timestamp "fudo.org"
config.fudo.zones."fudo.org";
selby-zone = pkgs.lib.dns.zoneToZonefile
config.instance.build-timestamp "selby.ca"
config.fudo.zones."selby.ca";
in {
environment.etc = {
"generated-zones/fudo.org".text = fudo-zone;
"generated-zones/selby.ca".text = selby-zone;
};
fudo = {
services.dns.zones = let
in {
"fudo.org" = {
enable = true;
external-nameservers = [
{
ipv4-address = "209.177.102.102";
ipv6-address = "2001:470:1f16:40::2";
description = "Nameserver 2, Musashi.100percenthost.net, in Winnipeg, MB, CA";
}
{
ipv4-address = "104.131.53.95";
ipv6-address = "2604:a880:800:10::8:7001";
description = "Nameserver 3, ns2.henchmman21.net, in New York City, NY, US";
}
{
ipv4-address = "204.42.254.5";
ipv6-address = "2001:418:3f4::5";
description = "Nameserver 4, puck.nether.net, in Chicago, IL, US";
}
];
};
"selby.ca" = {
enable = true;
external-nameservers = map (n: let
i = toString n;
in {
authoritative-hostname = "ns${i}.fudo.org";
description = "Nameserver ${i}, ns${i}.fudo.org.";
}) [2 3 4];
};
};
domains."selby.ca" = {
local-networks = config.fudo.domains."fudo.org".local-networks;
};
zones = {
"fudo.org" = {
default-host = host-ipv4;
verbatim-dns-records = [
# TODO: create these automatically
"node._metrics._tcp IN SRV 0 0 443 france.fudo.org."
"node._metrics._tcp IN SRV 0 0 9900 hanover.fudo.org."
"node._metrics._tcp IN SRV 0 0 443 paris.fudo.org."
"node._metrics._tcp IN SRV 0 0 443 legatus.fudo.org."
"node._metrics._tcp IN SRV 0 0 443 nutboy3.fudo.org."
"dovecot._metrics._tcp IN SRV 0 0 443 mail.fudo.org."
"postfix._metrics._tcp IN SRV 0 0 443 mail.fudo.org."
"rspamd._metrics._tcp IN SRV 0 0 443 mail.fudo.org."
];
};
"selby.ca" = {
default-host = host-ipv4;
};
};
};
containers.cashew = {
autoStart = true;
bindMounts = {
"/state" = {
hostPath = "/state/cashew";
isReadOnly = false;
};
"/etc/bind" = {
hostPath = "/state/cashew/bind";
isReadOnly = false;
};
"/var/log" = {
hostPath = "/state/cashew/logs";
isReadOnly = false;
};
"/home" = {
hostPath = "/state/cashew/home";
isReadOnly = false;
};
"/etc/dns-root-data" = {
hostPath = "${pkgs.dns-root-data}/";
isReadOnly = true;
};
};
interfaces = [ "eno2" ];
config = { config, ... }: {
nixpkgs.pkgs = pkgs;
environment = {
systemPackages = local-packages;
etc = {
"generated-zones/fudo.org" = {
text = fudo-zone;
};
"generated-zones/selby.ca" = {
text = selby-zone;
};
};
};
users = {
users = {
niten = parent-config.users.users.niten;
reaper = parent-config.users.users.reaper // {
openssh.authorizedKeys.keys = [
"ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADtR1gMK7JnIOht8yZNPROr+0VHgt5eWrGFPscVPk1crVuEvIv1MF544Qk1IHi+2OA2xUvI1BTgmXp3TLvCjEn4lQF4Uc5hcUGENS6TNMPByHx69rAeXVMtmjW0sL4Tbhqd0iNh85STdtzXNZUY31+A6ugrJSnvnSt5wv9ZpMz0SFIE1Q=="
];
};
root.openssh.authorizedKeys.keys = [
"ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADtR1gMK7JnIOht8yZNPROr+0VHgt5eWrGFPscVPk1crVuEvIv1MF544Qk1IHi+2OA2xUvI1BTgmXp3TLvCjEn4lQF4Uc5hcUGENS6TNMPByHx69rAeXVMtmjW0sL4Tbhqd0iNh85STdtzXNZUY31+A6ugrJSnvnSt5wv9ZpMz0SFIE1Q=="
];
};
groups = {
wheel.members = [
"niten"
"reaper"
];
dns = {
members = [
"niten"
"reaper"
"named"
];
};
};
};
networking = {
defaultGateway = {
address = "208.81.4.81";
interface = "eno2";
};
interfaces.eno2 = {
ipv4.addresses = [
{
address = "208.81.4.82";
prefixLength = 29;
}
{
address = "208.81.1.141";
prefixLength = 32;
}
];
};
firewall.enable = false;
};
# /etc/bind ended up not belonging to the correct user/group
systemd.services.bind-perms = {
requiredBy = [ "bind.service" ];
before = [ "bind.service" ];
script = "chown -R named:named /etc/bind";
};
services = {
bind = {
enable = true;
configFile = "/etc/bind/named.conf";
};
openssh = {
enable = true;
startWhenNeeded = true;
useDns = true;
permitRootLogin = "prohibit-password";
hostKeys = [
{
path = "/state/ssh/ssh_host_ed25519_key";
type = "ed25519";
}
{
path = "/state/ssh/ssh_host_rsa_key";
type = "rsa";
bits = 4096;
}
];
};
};
};
};
}

View File

@ -0,0 +1,42 @@
{ config, lib, pkgs, ... }:
with lib;
let
primary-ip = "10.0.0.3";
in {
networking = {
hostName = "wormhole0";
firewall.enable = false;
defaultGateway = {
address = "10.0.0.1";
interface = "intif0";
};
nameservers = [ "10.0.0.1" ];
interfaces = {
intif0 = {
ipv4.addresses = [{
address = primary-ip;
prefixLength = 24;
}];
};
wlan0.useDHCP = true;
};
};
nix = {
# settings = {
# auto-optimise-store = true;
# };
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};
};
}

View File

@ -0,0 +1,188 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
domain-name = config.fudo.hosts.${hostname}.domain;
domain = config.fudo.domains.${domain-name};
zone-name = config.fudo.domains.${domain-name}.zone;
host-fqdn = hostname: "${hostname}.${config.fudo.hosts.${hostname}.domain}";
postgresql-server = domain.postgresql-server;
isDatabase = hostname == postgresql-server;
isJabber = elem hostname domain.xmpp-servers;
isDNSBackplane = hostname == domain.backplane.dns-service;
backplaneEnabled = domain.backplane != null;
isNameserver = hostname == domain.backplane.nameserver;
database-name = "backplane_dns";
make-passwd-file = hostname: let
name = "backplane-host-${hostname}-client-passwd";
seed = "${name}-${config.instance.build-seed}";
in pkgs.lib.passwd.stablerandom-passwd-file name seed;
host-secrets = config.fudo.secrets.host-secrets.${hostname};
host-password-files = mapAttrs (hostname: hostOpts:
make-passwd-file hostname) config.fudo.hosts;
backplane-user = "backplane_dns";
database-backplane-user = "backplane_dns";
database-powerdns-user = "backplane_powerdns_dns";
backplane-host-domain = config.fudo.hosts.${domain.backplane.dns-service}.domain;
backplane-server = head config.fudo.domains.${backplane-host-domain}.xmpp-servers;
backplane-host-fqdn = "${backplane-server}.${backplane-host-domain}";
backplane-fqdn = "backplane.${backplane-host-domain}";
in {
config = mkIf backplaneEnabled {
fudo = let
powerdns-password = pkgs.lib.passwd.stablerandom-passwd-file
"backplane-powerdns-passwd-${postgresql-server}"
"backplane-powerdns-passwd-${postgresql-server}-${config.instance.build-seed}";
backplane-database-password = pkgs.lib.passwd.stablerandom-passwd-file
"backplane-passwd-${postgresql-server}"
"backplane-passwd-${postgresql-server}-${config.instance.build-seed}";
xmpp-password = pkgs.lib.passwd.stablerandom-passwd-file
"backplane-xmpp-passwd-${postgresql-server}"
"backplane-xmpp-passwd-${postgresql-server}-${config.instance.build-seed}";
in {
secrets.host-secrets.${hostname} = {
powerdns-database-passwd = mkIf isNameserver {
source-file = powerdns-password;
target-file = "/run/backplane-powerdns/powerdns.passwd";
user = config.fudo.powerdns.user;
};
backplane-database-passwd = mkIf isDNSBackplane {
source-file = backplane-database-password;
target-file = "/run/backplane-dns/database.passwd";
user = config.fudo.backplane.dns.user;
};
backplane-xmpp-passwd = mkIf isDNSBackplane {
source-file = xmpp-password;
target-file = "/run/backplane-dns/xmpp.passwd";
user = config.fudo.backplane.dns.user;
};
database-powerdns-passwd = mkIf isDatabase {
source-file = powerdns-password;
target-file = "/run/postgres/powerdns.passwd";
user = config.services.postgresql.superUser;
};
database-backplane-passwd = mkIf isDatabase {
source-file = backplane-database-password;
target-file = "/run/postgres/backplane-database.passwd";
user = config.services.postgresql.superUser;
};
ejabberd-backplane-passwd = mkIf isJabber {
source-file = xmpp-password;
target-file = "/run/backplane-jabber/service-dns.passwd";
user = config.services.ejabberd.user;
};
backplane-client-passwd = {
source-file = host-password-files.${hostname};
target-file = "/run/backplane-client/client.passwd";
user = config.fudo.client.dns.user;
};
};
client.dns = {
password-file = host-secrets.backplane-client-passwd.target-file;
domain = domain.backplane.domain;
};
zones.${zone-name} = {
aliases = {
backplane = "${backplane-host-fqdn}.";
};
};
postgresql = mkIf isDatabase {
required-services = [ "fudo-passwords.target" ];
users = {
${database-powerdns-user} = {
password-file = host-secrets.database-powerdns-passwd.target-file;
databases.${database-name} = {
access = "CONNECT";
entity-access = {
"ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE";
"ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE";
};
};
};
${database-backplane-user} = {
password-file = host-secrets.database-backplane-passwd.target-file;
databases.${database-name} = {
access = "CONNECT";
entity-access = {
"ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE";
"ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE";
};
};
};
};
databases.${database-name}.users = config.instance.local-admins;
};
backplane = {
enable = isJabber;
client-hosts = mapAttrs (hostname: hostOpts: {
password-file = host-password-files.${hostname};
}) config.fudo.hosts;
services = {
dns.password-file = host-secrets.ejabberd-backplane-passwd.source-file;
};
backplane-hostname = backplane-fqdn;
dns = mkIf isDNSBackplane {
enable = true;
database = {
host = pkgs.lib.network.host-ipv4 config postgresql-server;
database = database-name;
username = database-backplane-user;
password-file = host-secrets.backplane-database-passwd.target-file;
};
backplane-role = {
role = "service-dns";
password-file = host-secrets.backplane-xmpp-passwd.target-file;
};
};
};
powerdns = mkIf (isNameserver) {
enable = true;
domains = let
served-domain = domain.backplane.domain;
in {
${served-domain}.admin = domain.admin-email;
};
listen-v4-addresses = let
ipv4-addr = pkgs.lib.network.host-ipv4 config hostname;
in [ ipv4-addr ];
listen-v6-addresses = let
ipv6-addr = pkgs.lib.network.host-ipv6 config hostname;
in optional (ipv6-addr != null) ipv6-addr;
database = {
host = pkgs.lib.network.host-ipv4 config postgresql-server;
database = database-name;
user = database-powerdns-user;
password-file = host-secrets.powerdns-database-passwd.target-file;
};
};
};
};
}

100
config/service/chute.nix Normal file
View File

@ -0,0 +1,100 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
host-secrets = config.fudo.secrets.host-secrets.${hostname};
cfg = config.informis.services.chute;
make-env-file = { hostname, secret, passphrase, key, jabber-password ? null }:
pkgs.writeText "chute-environment" ''
COINBASE_API_HOSTNAME=${hostname}
COINBASE_API_SECRET=${secret}
COINBASE_API_PASSPHRASE=${passphrase}
COINBASE_API_KEY=${key}
${optionalString (jabber-password != null)
"JABBER_PASSWORD=${jabber-password}"}
'';
read-and-trim = filename:
removeSuffix "\n" (readFile filename);
in {
options.informis.services.chute = with types; {
enable = mkEnableOption "Enable the Chute cryptocurrency stopgap.";
jabber-user = mkOption {
type = nullOr str;
description = "User to which messages will be sent.";
default = null;
};
staging = {
secret-file = mkOption {
type = path;
description = "Path to file containing Coinbase API secret.";
};
key-file = mkOption {
type = path;
description = "Path to file containing Coinbase API key.";
};
passphrase-file = mkOption {
type = path;
description = "Path to file containing Coinbase API passphrase.";
};
};
};
config = let
chute-jabber-passwd =
pkgs.lib.passwd.stablerandom-passwd-file
"chute-jabber-passwd"
"chute-jabber-passwd-${config.instance.build-seed}";
in {
fudo = {
users.chute = {
uid = 11007;
primary-group = "informis";
common-name = "Chute Cryptocurrency Trader";
ldap-hashed-passwd =
pkgs.lib.passwd.hash-ldap-passwd "chute-jabber-passwd-ldaphash"
chute-jabber-passwd;
};
secrets.host-secrets.${hostname}.chute-staging-environment =
mkIf cfg.enable {
source-file = make-env-file {
hostname = "api-public.sandbox.exchange.coinbase.com";
secret = read-and-trim cfg.staging.secret-file;
passphrase = read-and-trim cfg.staging.passphrase-file;
key = read-and-trim cfg.staging.key-file;
jabber-password = read-and-trim chute-jabber-passwd;
};
target-file = "/run/chute/staging.env";
user = "root";
};
};
informis.chute = {
enable = cfg.enable;
stages = {
staging = mkIf cfg.enable {
package = pkgs.chuteUnstable;
environment-file =
host-secrets.chute-staging-environment.target-file;
jabber = {
jid = "chute@jabber.fudo.org";
target-jid = cfg.jabber-user;
resource = "procul-staging";
};
currencies = {
btc.stop-percentile = 98;
};
};
};
};
};
}

View File

@ -0,0 +1,207 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
domain-name = config.fudo.hosts.${hostname}.domain;
site-name = config.fudo.hosts.${hostname}.site;
zone-name = config.fudo.domains.${domain-name}.zone;
site = config.fudo.sites.${site-name};
cfg = config.fudo.services.local-network;
resolverOpts = {
options = {
ip = mkOption {
type = str;
description = "IP address of the upstream recursive resolver.";
default = "1.1.1.1";
};
port = mkOption {
type = port;
description = "Port of DNS server on the recursive resolver.";
default = 53;
};
};
};
in {
options.fudo.services.local-network = with types; {
enable = mkEnableOption "Enable local network server.";
internal-interfaces = mkOption {
type = listOf str;
description = ''
Interfaces on which to:
* Accept NAT traffic
* Serve DNS
* Serve DHCP
'';
};
external-interface = mkOption {
type = str;
description = "Interface facing the larger internet.";
example = "extif0";
};
resolvers = mkOption {
type = listOf (submodule resolverOpts);
description = "List of upstream DNS servers.";
default = [
{ ip = "1.1.1.1"; }
{ ip = "1.0.0.1"; }
{ ip = "9.9.9.9"; }
{ ip = "149.112.112.112"; }
];
};
dns-filter-proxy = {
enable = mkEnableOption "Enable DNS filter.";
http-listen-port = mkOption {
type = port;
description = "Port on localhost on which to listen for HTTP requests.";
default = 4080;
};
http-host-alias = mkOption {
type = str;
description = "Host alias for the DNS filter server.";
default = "dns-filter";
};
dns-listen-port = mkOption {
type = port;
description = "Port on localhost on which to listen for DNS requests.";
default = 4053;
};
upstream-dns = mkOption {
type = listOf str;
description = "List of upstream DNS-over-HTTPS endpoints.";
default = [
"https://1.1.1.1/dns-query"
"https://1.0.0.1/dns-query"
# These 11 addrs send the network, so the response can prefer closer answers
"https://9.9.9.11/dns-query"
"https://149.112.112.11/dns-query"
"https://2620:fe::11/dns-query"
"https://2620:fe::fe:11/dns-query"
];
};
};
};
config = mkIf (site.local-gateway != null) (let
host-ipv4 = pkgs.lib.network.host-ipv4 config;
gateway-host = site.local-gateway;
nameserver-host = gateway-host;
gateway-ip = host-ipv4 gateway-host;
nameserver-ip = host-ipv4 gateway-host;
is-gateway = hostname == gateway-host;
agp = cfg.dns-filter-proxy;
fqdn = hostname: "${hostname}.${domain-name}.";
in {
networking = {
nat = mkIf is-gateway {
enable = true;
externalInterface = cfg.external-interface;
internalInterfaces = cfg.internal-interfaces;
};
nameservers = [ nameserver-ip ];
firewall = if is-gateway then {
enable = true;
trustedInterfaces = cfg.internal-interfaces;
} else {
enable = false;
};
};
fudo = {
adguard-dns-proxy = mkIf agp.enable {
enable = true;
http = {
listen-ip = "127.0.0.1";
listen-port = agp.http-listen-port;
};
dns = {
listen-ips = [ "127.0.0.1" ];
listen-port = agp.dns-listen-port;
};
local-domain-name = domain-name;
};
zones.${zone-name} = {
aliases = {
${agp.http-host-alias} = optionalAttrs (agp.enable)
(fqdn gateway-host);
ns = (fqdn nameserver-host);
gw = (fqdn gateway-host);
};
hosts = {
gateway.ipv4-address = gateway-ip;
nameserver.ipv4-address = nameserver-ip;
};
nameservers = [
"nameserver"
];
srv-records = {
tcp.domain = [{
host = "nameserver.${domain-name}";
port = 53;
}];
udp.domain = [{
host = "nameserver.${domain-name}";
port = 53;
}];
};
};
local-network = {
enable = is-gateway;
domain = domain-name;
dns-servers = [ nameserver-ip ];
gateway = gateway-ip;
dhcp-interfaces = cfg.internal-interfaces;
dns-listen-ips = optionals is-gateway [ nameserver-ip "127.0.0.1" "127.0.1.1" ];
dns-listen-ipv6s = optionals (is-gateway && config.networking.enableIPv6) [ "::1" ];
recursive-resolver = if agp.enable then {
host = "127.0.0.1";
port = agp.dns-listen-port;
} else {
host = cfg.resolver.ip;
port = cfg.resolver.port;
};
network = site.network;
dhcp-dynamic-network = site.dynamic-network;
search-domains = [ domain-name "fudo.org" ];
enable-reverse-mappings = true;
zone-definition = config.fudo.zones.${zone-name};
};
};
services.nginx = mkIf agp.enable {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
virtualHosts = {
"${agp.http-host-alias}.${domain-name}" = {
locations."/".proxyPass =
"http://127.0.0.1:${toString agp.http-listen-port}";
};
};
};
});
}

231
config/service/logging.nix Normal file
View File

@ -0,0 +1,231 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
domain-name = config.fudo.hosts.${hostname}.domain;
domain = config.fudo.domains.${domain-name};
host-fqdn = hostname: let
host-domain = config.fudo.hosts.${hostname}.domain;
in "${hostname}.${host-domain}";
logAggregationEnabled = domain.log-aggregator != null;
isLogAggregator = hostname == domain.log-aggregator;
is-private-network = let
site-name = config.fudo.hosts.${hostname}.site;
in config.fudo.sites.${site-name}.local-gateway != null;
cfg = config.fudo.services.logging;
in {
options.fudo.services.logging = with types; {
loki = {
port = mkOption {
type = port;
description = "Port on which to listen on localhost.";
default = 3021;
};
state-directory = mkOption {
type = str;
description = "Path at which to store state for Loki.";
};
};
promtail = {
http-listen-port = mkOption {
type = port;
default = 6041;
};
grpc-listen-port = mkOption {
type = port;
default = 6042;
};
user = mkOption {
type = str;
description = "User as which to run promtail job.";
default = "promtail";
};
};
};
config = mkIf logAggregationEnabled {
users = {
users.${cfg.promtail.user} = {
isSystemUser = true;
uid = 441;
group = cfg.promtail.user;
};
groups = {
${cfg.promtail.user}.members = [ cfg.promtail.user ];
systemd-journal = {
members = [ cfg.promtail.user ];
};
};
};
fudo = let
aggregator-domain = config.fudo.hosts.${domain.log-aggregator}.domain;
log-aggregator-fqdn = "${domain.log-aggregator}.${aggregator-domain}";
in {
zones.${domain.zone} = {
aliases.log-aggregator = "${log-aggregator-fqdn}.";
};
metrics.grafana.datasources = let
scheme = if is-private-network then "http" else "https";
in {
"loki-${domain.log-aggregator}" = {
url = "${scheme}://log-aggregator.${aggregator-domain}";
type = "loki";
};
};
};
services = {
nginx = mkIf isLogAggregator {
enable = true;
virtualHosts."log-aggregator.${domain-name}" = {
enableACME = ! is-private-network;
forceSSL = ! is-private-network;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString cfg.loki.port}";
extraConfig = let
local-networks = config.instance.local-networks;
in "${optionalString ((length local-networks) > 0)
(concatStringsSep "\n"
(map (network: "allow ${network};") local-networks)) + "\ndeny all;"}";
};
};
};
loki = mkIf isLogAggregator {
enable = true;
dataDir = cfg.loki.state-directory;
# cargo-culted from https://gist.github.com/Xe/c3c786b41ec2820725ee77a7af551225
configuration = {
auth_enabled = false;
server.http_listen_port = cfg.loki.port;
ingester = {
lifecycler = {
address = "127.0.0.1";
ring = {
kvstore.store = "inmemory";
replication_factor = 1;
};
final_sleep = "0s";
};
chunk_idle_period = "1h";
max_chunk_age = "1h";
chunk_target_size = 10485576;
chunk_retain_period = "30s";
max_transfer_retries = 0;
};
compactor = {
working_directory = "${cfg.loki.state-directory}/compactor";
};
schema_config.configs = [
{
from = "2022-01-01";
store = "boltdb-shipper";
object_store = "filesystem";
schema = "v11";
index = {
prefix = "index_";
period = "24h";
};
}
];
storage_config = {
boltdb_shipper = {
active_index_directory = "${cfg.loki.state-directory}/boltdb-shipper/active";
cache_location = "${cfg.loki.state-directory}/boltdb-shipper/cache";
cache_ttl = "24h";
shared_store = "filesystem";
};
filesystem.directory = "${cfg.loki.state-directory}/chunks";
};
limits_config = {
reject_old_samples = true;
reject_old_samples_max_age = "168h";
};
chunk_store_config.max_look_back_period = "0s";
table_manager = {
retention_deletes_enabled = false;
retention_period = "0s";
};
};
};
};
systemd = {
tmpfiles.rules = mkIf isLogAggregator (let
user = config.services.loki.user;
statedir = cfg.loki.state-directory;
in [
"d ${statedir} 0700 ${user} - - -"
"d ${statedir}/boltdb-shipper 0700 ${user} - - -"
"d ${statedir}/boltdb-shipper/active 0700 ${user} - - -"
"d ${statedir}/boltdb-shipper/cache 0700 ${user} - - -"
"d ${statedir}/chunks 0700 ${user} - - -"
"d ${statedir}/compactor 0700 ${user} - - -"
]);
services.promtail = let
scheme = if is-private-network then "http" else "https";
loki-host = host-fqdn domain.log-aggregator;
config = builtins.toJSON {
server = {
http_listen_port = cfg.promtail.http-listen-port;
grpc_listen_port = cfg.promtail.grpc-listen-port;
};
positions.filename = "/tmp/positions.yml";
clients = [
{ url = "${scheme}://log-aggregator.${domain-name}/loki/api/v1/push"; }
];
scrape_configs = [
{
job_name = "journal";
journal = {
max_age = "12h";
labels = {
job = "systemd-journal";
host = hostname;
};
};
relabel_configs = [{
source_labels = [ "__journal__systemd_unit" ];
target_label = "unit";
}];
}
];
};
config-file = pkgs.writeText "promtail-config.json" config;
in {
description = "PromTail log aggregator client for Loki.";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = ''
${pkgs.grafana-loki}/bin/promtail --config.file ${config-file}
'';
User = cfg.promtail.user;
PrivateTmp = true;
};
};
};
};
}

View File

@ -0,0 +1,180 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.fudo.services.mail-server;
hostname = config.instance.hostname;
domain-name = config.instance.local-domain;
domain = config.fudo.domains.${domain-name};
mailserver-host = domain.primary-mailserver;
mailserver-domain-name = config.fudo.hosts.${mailserver-host}.domain;
mailserver-domain = config.fudo.domains.${mailserver-domain-name};
mailserver-host-fqdn = "${mailserver-host}.${mailserver-domain-name}";
isMailServer = hostname == mailserver-host;
isLocalMailserver = domain-name == mailserver-domain-name;
metricsEnabled = mailserver-domain.prometheus-hosts != [];
host-certs = config.fudo.acme.host-domains.${hostname};
in {
options.fudo.services.mail-server = with types; {
debug = mkEnableOption "Enable debug options for mailserver.";
state-directory = mkOption {
type = str;
description = "Directory at which to store mailserver state.";
};
};
config = {
services.nginx = mkIf (isMailServer && metricsEnabled) {
enable = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
virtualHosts."mail-stats.${mailserver-domain-name}" = let
trusted-networks = config.instance.local-networks;
trustedNetworkString = optionalString (length trusted-networks > 0)
(concatStringsSep "\n"
(map (network: "allow ${network};")
trusted-networks)) + "\n\ndeny all;";
in {
enableACME = true;
forceSSL = true;
locations = let
monitor-cfg = config.fudo.mail-server.monitoring;
in {
"/metrics/dovecot" = {
proxyPass = "http://127.0.0.1:${toString monitor-cfg.dovecot-listen-port}/metrics";
extraConfig = trustedNetworkString;
};
"/metrics/postfix" = {
proxyPass = "http://127.0.0.1:${toString monitor-cfg.postfix-listen-port}/metrics";
extraConfig = trustedNetworkString;
};
"/metrics/rspamd" = {
proxyPass = "http://127.0.0.1:${toString monitor-cfg.rspamd-listen-port}/metrics";
extraConfig = trustedNetworkString;
};
};
};
};
fudo = {
acme.host-domains = mkIf isMailServer {
${hostname} = {
"imap.${mailserver-domain-name}" = {
admin-email = "admin@${mailserver-domain-name}";
local-copies.dovecot = {
user = config.services.dovecot2.user;
dependent-services = [ "dovecot2.services" ];
};
};
"smtp.${mailserver-domain-name}" = {
admin-email = "admin@${mailserver-domain-name}";
local-copies.postfix = {
user = config.services.postfix.user;
dependent-services = [ "postfix.services" ];
};
};
};
};
zones = mkIf isLocalMailserver {
${mailserver-domain.zone} = let
server-ipv4 = pkgs.lib.network.host-ipv4 config mailserver-host;
server-ipv6 = pkgs.lib.network.host-ipv6 config mailserver-host;
srv-record = host: port: [{
host = "${host}.${mailserver-domain-name}";
port = port;
}];
in {
hosts = genAttrs [ "imap" "smtp" ] (alias: {
ipv4-address = server-ipv4;
ipv6-address = server-ipv6;
description = "Primary ${toUpper alias} server for ${mailserver-domain-name}.";
});
mx = [ "smtp.${mailserver-domain-name}" ];
aliases = mkIf metricsEnabled {
mail-stats = "${mailserver-host-fqdn}.";
};
srv-records.tcp = {
pop3 = srv-record "imap" 110;
pop3s = srv-record "imap" 995;
imap = srv-record "imap" 143;
imaps = srv-record "imap" 993;
smtp = srv-record "smtp" 25;
submission = srv-record "smtp" 587;
};
metric-records = mkIf metricsEnabled
(genAttrs [ "dovecot" "postfix" "rspamd" ]
(_: srv-record "mail-stats" 443));
};
};
metrics.prometheus.service-discovery-dns = mkIf metricsEnabled
(genAttrs [ "dovecot" "postfix" "rspamd" ]
(mtype: [ "${mtype}._metrics._tcp.${mailserver-domain-name}" ]));
mail-server = mkIf isLocalMailserver {
enable = isMailServer;
domain = mailserver-domain-name;
mail-hostname = "smtp.${mailserver-domain-name}";
monitoring.enable = metricsEnabled;
debug = cfg.debug;
clamav.enable = true;
dkim.signing = true;
dovecot = let
cert-copy = host-certs."imap.${mailserver-domain-name}".local-copies.dovecot;
in {
ssl-certificate = cert-copy.full-certificate;
ssl-private-key = cert-copy.private-key;
};
postfix = let
cert-copy = host-certs."smtp.${mailserver-domain-name}".local-copies.postfix;
in {
ssl-certificate = cert-copy.full-certificate;
ssl-private-key = cert-copy.private-key;
};
local-domains = [ mailserver-host-fqdn "smtp.${mailserver-domain-name}" ];
mail-directory = "${cfg.state-directory}/mail";
state-directory = "${cfg.state-directory}/state";
trusted-networks = config.instance.local-networks;
alias-users = genAttrs [
"root"
"postmaster"
"hostmaster"
"webmaster"
"system"
"admin"
"dmarc-report"
] (alias: config.instance.local-admins);
};
};
};
}

311
config/service/metrics.nix Normal file
View File

@ -0,0 +1,311 @@
{ config, lib, pkgs, ... }@toplevel:
with lib;
let
hostname = config.instance.hostname;
domain-name = config.fudo.hosts.${hostname}.domain;
domain = config.fudo.domains.${domain-name};
pthru = obj: builtins.trace "TRACE(${hostname}): ${toString obj}" obj;
host-secrets = config.fudo.secrets.host-secrets.${hostname};
notEmpty = lst: (length lst) > 0;
metricsEnabled = notEmpty domain.prometheus-hosts;
metricsScraper = elem hostname domain.prometheus-hosts;
metricsMonitor = elem hostname domain.grafana-hosts;
prometheus-cfg = config.fudo.services.metrics.prometheus;
grafana-cfg = config.fudo.services.metrics.grafana;
host-fqdn = hostname:
let host-domain = config.fudo.hosts.${hostname}.domain;
in "${hostname}.${host-domain}";
host-auth-fqdn = hostname: "${host-fqdn hostname}.";
make-alias-map = type: hosts:
listToAttrs
(imap0 (i: hostname: nameValuePair hostname "${type}-${toString i}") hosts);
headOrNull = lst: if notEmpty lst then head lst else null;
metrics-master = headOrNull domain.prometheus-hosts;
monitor-master = headOrNull domain.grafana-hosts;
metrics-alias-map = make-alias-map "metrics" domain.prometheus-hosts;
monitor-alias-map = make-alias-map "monitor" domain.grafana-hosts;
alias-map-to-cnames =
mapAttrs' (hostname: alias: nameValuePair alias (host-auth-fqdn hostname));
alias-map-to-hostnames =
mapAttrsToList (hostname: alias: "${alias}.${domain-name}");
grafana-smtp-password-file =
pkgs.lib.passwd.stablerandom-passwd-file "grafana-smtp-passwd"
"grafana-smtp-passwd-${config.instance.build-seed}";
grafana-auth-password-file =
pkgs.lib.passwd.stablerandom-passwd-file "grafana-auth-passwd"
"grafana-auth-passwd-${config.instance.build-seed}";
grafana-admin-password-file =
pkgs.lib.passwd.stablerandom-passwd-file "grafana-admin-passwd"
"grafana-admin-passwd-${config.instance.build-seed}";
grafana-secret-key-file =
pkgs.lib.passwd.stablerandom-passwd-file "grafana-secret-key"
"grafana-secret-key-${config.instance.build-seed}";
is-private-network = let site-name = config.fudo.hosts.${hostname}.site;
in config.fudo.sites.${site-name}.local-gateway != null;
domainToBaseDn = domain:
concatStringsSep "," (map (el: "dc=${el}") (splitString "." domain));
ldapEnabled = domain.ldap-servers != [ ];
in {
options.fudo.services.metrics = with types; {
prometheus = {
static-targets = mkOption {
type = attrsOf (listOf str);
description =
"A map of exporter type to a list of host:ports from which to collect metrics.";
example = { dovecot = [ "my.host.name:1111" ]; };
default = { };
};
state-directory = mkOption {
type = str;
description = "Path at which to store Prometheus state.";
default = "/var/lib/prometheus";
};
};
grafana = {
smtp = {
username = mkOption {
type = str;
description = "Username from which to send Grafana alerts.";
default = "monitor";
};
hostname = mkOption {
type = str;
description = "Hostname of the SMTP host.";
default = "mail.${toplevel.config.instance.local-domain}";
};
};
ldap = let base-dn = domainToBaseDn config.instance.local-domain;
in {
base-dn = mkOption {
type = str;
description = "DN under which to search for users.";
default = base-dn;
};
bind-user = mkOption {
type = str;
description = "DN as which to bind to the LDAP server.";
default = "grafana_reader";
};
bind-passwd = mkOption {
type = nullOr str;
description = "Path to file with bind password. Generated if null.";
default = null;
};
};
database = {
hostname = mkOption {
type = str;
description = "Hostname of the postgresql database.";
default = "localhost";
};
user = mkOption {
type = str;
description =
"Username as which to authenticate to the postgresql database.";
};
password-file = mkOption {
type = str;
description =
"Password file (on the target host) which to authenticate to the postgresql database.";
};
name = mkOption {
type = str;
description = "Database name.";
default = "grafana";
};
};
state-directory = mkOption {
type = str;
description = "Path at which to store Grafana state.";
default = "/var/lib/grafana";
};
};
};
config = mkIf metricsEnabled {
fudo = {
system-users = {
${grafana-cfg.smtp.username} = {
description = "Grafana Alerts";
ldap-hashed-password =
pkgs.lib.passwd.hash-ldap-passwd "grafana-smtp-passwd"
grafana-smtp-password-file;
};
${grafana-cfg.ldap.bind-user} = mkIf ((domain.ldap-servers != [ ])
&& (grafana-cfg.ldap.bind-passwd == null)) {
description = "Grafana Authentication Reader";
ldap-hashed-password =
pkgs.lib.passwd.hash-ldap-passwd "grafana-auth-passwd"
grafana-auth-password-file;
};
};
secrets.host-secrets = mkIf metricsMonitor
(let grafana-user = config.systemd.services.grafana.serviceConfig.User;
in {
${hostname} = {
grafana-smtp-password = {
source-file = grafana-smtp-password-file;
target-file = "/run/metrics/grafana/smtp.passwd";
user = grafana-user;
};
grafana-admin-password = {
source-file = grafana-admin-password-file;
target-file = "/run/metrics/grafana/admin.passwd";
user = grafana-user;
};
grafana-secret-key = {
source-file = grafana-secret-key-file;
target-file = "/run/metrics/grafana/secret.key";
user = grafana-user;
};
};
});
zones.${domain.zone} = {
aliases = let
metrics-aliases = alias-map-to-cnames metrics-alias-map;
monitor-aliases = alias-map-to-cnames monitor-alias-map;
metrics-master-cname = optionalAttrs (metrics-master != null) {
metrics = "${metrics-master}.${domain-name}.";
};
monitor-master-cname = optionalAttrs (monitor-master != null) {
monitor = "${monitor-master}.${domain-name}.";
};
in metrics-aliases // monitor-aliases // metrics-master-cname
// monitor-master-cname;
metric-records = let
domain-hosts = filterAttrs (hostname: hostOpts:
hostOpts.domain == domain-name && hostOpts.nixos-system)
config.fudo.hosts;
in {
node = map (hostname: {
host = "${hostname}.${domain-name}";
port = if is-private-network then 80 else 443;
}) (attrNames domain-hosts);
};
};
metrics = {
node-exporter = {
enable = true;
hostname = host-fqdn hostname;
private-network = is-private-network;
};
prometheus = mkIf metricsScraper {
enable = true;
service-discovery-dns = {
node = [ "node._metrics._tcp.${domain-name}" ];
};
static-targets = prometheus-cfg.static-targets;
hostname = let alias = metrics-alias-map.${hostname};
in "${alias}.${domain-name}";
state-directory = prometheus-cfg.state-directory;
private-network = is-private-network;
};
grafana = mkIf metricsMonitor {
enable = true;
hostname = let alias = monitor-alias-map.${hostname};
in "${alias}.${domain-name}";
smtp = let cfg = grafana-cfg.smtp;
in {
username = cfg.username;
password-file = host-secrets.grafana-smtp-password.target-file;
hostname = cfg.hostname;
email = "${cfg.username}@${domain-name}";
};
database = let cfg = grafana-cfg.database;
in {
name = cfg.name;
user = cfg.user;
password-file = cfg.password-file;
hostname = cfg.hostname;
};
ldap = mkIf (domain.ldap-servers != [ ]) {
hosts = map host-fqdn domain.ldap-servers;
base-dn = grafana-cfg.ldap.base-dn;
bind-dn =
"cn=${grafana-cfg.ldap.bind-user},${grafana-cfg.ldap.base-dn}";
bind-passwd = if (grafana-cfg.ldap.bind-passwd != null) then
grafana-cfg.ldap.bind-passwd
else
(readFile grafana-auth-password-file);
};
admin-password-file = host-secrets.grafana-admin-password.target-file;
secret-key-file = host-secrets.grafana-secret-key.target-file;
datasources = let
scheme = if is-private-network then "http" else "https";
host-config = hostname: {
url = "${scheme}://${hostname}.${domain-name}";
type = "prometheus";
default = hostname == "metrics-0";
};
in listToAttrs
(map (host: nameValuePair "prometheus-${host}" (host-config host))
(attrValues metrics-alias-map));
state-directory = grafana-cfg.state-directory;
private-network = is-private-network;
};
};
};
services.nginx =
mkIf (hostname == metrics-master || hostname == monitor-master) {
enable = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
virtualHosts =
let scheme = if is-private-network then "http" else "https";
in {
"metrics.${domain-name}" = mkIf (hostname == metrics-master) {
enableACME = !is-private-network;
forceSSL = !is-private-network;
locations."/".return = let alias = metrics-alias-map.${hostname};
in "301 ${scheme}://${alias}.${domain-name}$request_uri";
};
"monitor.${domain-name}" = mkIf (hostname == monitor-master) {
enableACME = !is-private-network;
forceSSL = !is-private-network;
locations."/".return = let alias = monitor-alias-map.${hostname};
in "301 ${scheme}://${alias}.${domain-name}$request_uri";
};
};
};
};
}

View File

@ -0,0 +1,78 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
domain-name = config.fudo.hosts.${hostname}.domain;
domain = config.fudo.domains.${domain-name};
cfg = config.fudo.services.postgresql;
zone-name = domain.zone;
host-secrets = config.fudo.secrets.host-secrets.${hostname};
postgresEnabled = domain.postgresql-server == hostname;
publicNetwork = let
site-name = config.fudo.hosts.${hostname}.site;
in config.fudo.sites.${site-name}.local-gateway == null;
isPostgresHost = hostname == domain.postgresql-server;
postgresql-hostname = "postgresql.${domain-name}";
acme-copies = config.fudo.acme.host-domains.${hostname};
postgresUser = config.systemd.services.postgresql.serviceConfig.User;
in {
options.fudo.services.postgresql = with types; {
state-directory = mkOption {
type = str;
description = "Path at which to store PostgreSQL state.";
};
keytab = mkOption {
type = str;
description = "Keytab for PostgreSQL.";
};
};
config = mkIf postgresEnabled {
fudo = {
acme.host-domains.${hostname} = mkIf (publicNetwork && isPostgresHost) {
${postgresql-hostname}.local-copies = {
postgresql = {
user = postgresUser;
dependent-services = [ "postgresql.service" ];
part-of = [ config.fudo.postgresql.systemd-target ];
};
};
};
secrets.host-secrets.${hostname}.postgres-keytab = mkIf (cfg.keytab != null) {
source-file = cfg.keytab;
target-file = "/run/postgresql/postgres.keytab";
user = postgresUser;
};
zones.${zone-name}.aliases.postgresql =
"${domain.postgresql-server}.${domain-name}.";
postgresql = mkIf isPostgresHost (let
ssl-config = optionalAttrs publicNetwork (let
cert-copy = acme-copies.${postgresql-hostname}.local-copies.postgresql;
in {
ssl-certificate = mkIf publicNetwork cert-copy.full-certificate;
ssl-private-key = mkIf publicNetwork cert-copy.private-key;
required-services = [ cert-copy.service ];
});
in {
enable = true;
keytab = mkIf (cfg.keytab != null) host-secrets.postgres-keytab.target-file;
local-networks = config.instance.local-networks;
state-directory = cfg.state-directory;
required-services = [ config.fudo.secrets.secret-target ];
} // ssl-config);
};
};
}

View File

@ -0,0 +1,277 @@
{ config, lib, pkgs, ... }:
with lib;
let
relative-hostname = "forum.test";
strip-hash = filename: head (builtins.match "^[a-zA-Z0-9]+-(.+)$" filename);
clean-utf8-file = filename:
pkgs.stdenv.mkDerivation {
name = "${strip-hash (baseNameOf filename)}.utf8";
phases = [ "installPhase" ];
installPhase = "iconv -c -f utf-8 -t utf-8 -o $out ${filename}";
};
in {
options.fudo.services.selby-forum = with types; {
enable = mkEnableOption "Enable Selby Forum on this host.";
state-directory = mkOption {
type = str;
description = "Directory at which to store Selby Forum state.";
};
legacy-forum-data = mkOption {
type = path;
description = "Path to legacy Vanilla forum data.";
};
external-interface = mkOption {
type = str;
description = "Public-facing network interface on this host.";
};
mail = {
host = mkOption {
type = str;
description = "Mail server hostname.";
};
};
};
config = (let
hostname = config.instance.hostname;
cfg = config.fudo.services.selby-forum;
host-fqdn = let host-domain = config.fudo.hosts.${hostname}.domain;
in "${hostname}.${host-domain}";
host-secrets = config.fudo.secrets.host-secrets.${hostname};
parent-ip = "192.168.92.1";
child-ip = "192.168.92.2";
mkPasswd = name:
pkgs.lib.passwd.stablerandom-passwd-file "selby-forum-${name}"
"selby-forum-${name}-${config.instance.build-seed}";
mail-user = "selby-forum";
mail-password = mkPasswd "mail-password";
database-name = "forum_selby_ca";
database-user = "forum_selby_ca";
runtime-path = "/run/selby/forum";
domain-name = "selby.ca";
zone-name = config.fudo.domains.${domain-name}.zone;
forum-hostname = "${relative-hostname}.${domain-name}";
in {
networking = mkIf cfg.enable {
nat = {
enable = true;
internalInterfaces = [ "ve-selby-forum" ];
externalInterface = cfg.external-interface;
};
};
services.nginx = mkIf cfg.enable {
enable = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
virtualHosts.${forum-hostname} = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://${child-ip}:80";
};
};
fudo = {
# Email user
system-users.${mail-user} = {
description = "Selby Forum";
ldap-hashed-password =
pkgs.lib.passwd.hash-ldap-passwd "selby-forum-mail-passwd"
mail-password;
};
zones.${zone-name}.aliases = { ${relative-hostname} = "${host-fqdn}."; };
secrets.host-secrets.${hostname} = mkIf cfg.enable
(let db-passwd = mkPasswd "database-password";
in {
selby-forum-database-password = {
source-file = db-passwd;
target-file = "${runtime-path}/db.passwd";
};
postgres-selby-forum-password = {
source-file = db-passwd;
target-file = "/run/postgres-users/selby-forum.passwd";
user = config.services.postgresql.superUser;
};
selby-forum-admin-passwd = {
source-file = mkPasswd "admin-password";
target-file = "${runtime-path}/admin.passwd";
};
selby-forum-mail-passwd = {
source-file = mail-password;
target-file = "${runtime-path}/mail.passwd";
};
legacy-selby-forum-data = {
source-file = "${clean-utf8-file cfg.legacy-forum-data}";
target-file = "${runtime-path}/selby-forum-data.sql";
};
});
postgresql = mkIf cfg.enable {
local-networks = [ "192.168.92.0/30" ];
databases.${database-name}.users = config.instance.local-admins;
users.${database-user} = {
password-file =
host-secrets.postgres-selby-forum-password.target-file;
databases.${database-name} = {
access = "CONNECT,CREATE";
entity-access = {
"ALL TABLES IN SCHEMA public" = "SELECT,INSERT,UPDATE,DELETE";
"ALL SEQUENCES IN SCHEMA public" = "SELECT,UPDATE";
};
};
};
};
};
systemd = mkIf cfg.enable {
tmpfiles.rules = [ "d ${cfg.state-directory} 750 - - - -" ];
services.discourse-prepare-selby-forum = {
description =
"Perform superuser tasks for Discourse, which doesn't have SU perms.";
wantedBy = [ "container@selby-forum.service" ];
before = [ "container@selby-forum.service" ];
requires = [ config.fudo.postgresql.systemd-target ];
after = [ config.fudo.postgresql.systemd-target ];
path = with pkgs; [ postgresql ];
serviceConfig = {
User = config.services.postgresql.superUser;
ExecStart =
pkgs.writeShellScript "discourse-prepare-selby-forum.sh" ''
psql -d ${database-name} -c "CREATE EXTENSION IF NOT EXISTS hstore;"
psql -d ${database-name} -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
'';
};
};
};
containers.selby-forum = mkIf cfg.enable {
ephemeral = true;
privateNetwork = true;
hostAddress = parent-ip;
localAddress = child-ip;
autoStart = true;
bindMounts = {
${runtime-path} = {
hostPath = runtime-path;
isReadOnly = true;
};
"/var/lib/discourse" = {
hostPath = cfg.state-directory;
isReadOnly = false;
};
};
config = { config, lib, ... }:
let
discourse-user = config.systemd.services.discourse.serviceConfig.User;
in {
networking = {
defaultGateway = parent-ip;
firewall.enable = false;
};
services.discourse = {
enable = true;
hostname = forum-hostname;
enableACME = false;
admin = {
username = "admin";
fullName = "Admin";
email = "admin@selby.ca";
passwordFile = "/etc/selby-forum/admin.passwd";
};
database = {
name = database-name;
host = parent-ip;
username = database-user;
passwordFile = "/etc/selby-forum/db.passwd";
};
mail.outgoing = {
username = mail-user;
passwordFile = "/etc/selby-forum/mail.passwd";
domain = domain-name;
forceTLS = true;
serverAddress = cfg.mail.host;
};
};
environment.etc = {
"selby-forum/admin.passwd" = {
source = "/run/selby/forum/admin.passwd";
user = discourse-user;
mode = "0400";
};
"selby-forum/db.passwd" = {
source = "/run/selby/forum/db.passwd";
user = discourse-user;
mode = "0400";
};
"selby-forum/selby-forum-data.sql" = {
source = "/run/selby/forum/selby-forum-data.sql";
user = discourse-user;
mode = "0400";
};
"selby-forum/mail.passwd" = {
source = "/run/selby/forum/mail.passwd";
user = discourse-user;
mode = "0400";
};
};
systemd = {
tmpfiles.rules =
[ "d /var/lib/discourse 700 ${discourse-user} - - -" ];
services = {
discourse = { after = [ "multi-user.target" ]; };
discourse-import-selby-forum = let
env-without-path = filterAttrs (attr: _: attr != "PATH")
config.systemd.services.discourse.environment;
in {
description = "One-off job to import Vanilla Selby Forum data.";
path = config.systemd.services.discourse.path;
environment = env-without-path;
serviceConfig = {
User = discourse-user;
type = "oneshot";
WorkingDirectory =
config.systemd.services.discourse.serviceConfig.WorkingDirectory;
ExecStart = pkgs.writeShellScript
"import-vanilla-selby-forum-data.sh" ''
ruby script/import_scripts/vanilla.rb /etc/selby-forum/selby-forum-data.sql
'';
};
};
};
};
};
};
});
}

View File

@ -0,0 +1,27 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
host = config.fudo.hosts.${hostname};
domain-name = config.instance.local-domain;
domain = config.fudo.domains.${domain-name};
gateway = domain.wireguard.gateway;
is-wireguard-client =
gateway != null && gateway != hostname &&
host.wireguard.private-key-file != null;
in {
config = mkIf is-wireguard-client (let
in {
fudo.wireguard-client = {
enable = true;
server = {
};
};
});
}

View File

@ -0,0 +1,90 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
host = config.fudo.hosts.${hostname};
host-secrets = config.fudo.secrets.host-secrets.${hostname};
cfg = config.fudo.services.wireguard-gateway;
peerOpts = { name, ... }: {
options = with types; {
public-key = mkOption {
type = str;
description = "Peer public key.";
};
assigned-ip = mkOption {
type = str;
description = "IP address assigned to this peer.";
};
};
};
in {
options.fudo.services.wireguard-gateway = with types; {
enable = mkEnableOption "Enable WireGuard gateway: let external clients join the local network.";
network = mkOption {
type = str;
description = "IP address range to use for clients.";
default = "172.16.0.0/24";
};
listen-port = mkOption {
type = port;
description = "Port on which to listen for incoming connections.";
default = 51820;
};
peers = mkOption {
type = attrsOf (submodule peerOpts);
description = "Map of peer to peer options.";
default = {};
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = all (peerOpts:
pkgs.lib.ip.ipv4OnNetwork peerOpts.assigned-ip cfg.network)
(attrValues cfg.peers);
message = "Peer IPs must be on the assigned network.";
}
{
assertion = host.wireguard.private-key-file != null;
message = "WireGuard gateway server private key file must be set.";
}
];
fudo.secrets.host-secrets.${hostname} = {
wireguard-gateway-privkey-file = {
source-file = host.wireguard.private-key-file;
target-file = "/run/wireguard-gateway/key";
};
};
networking = {
firewall.allowedUDPPorts = [ cfg.listen-port ];
wireguard.interfaces.wg-gw0 = {
ips = [ cfg.network ];
listenPort = cfg.listen-port;
postSetup =
"${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s ${cfg.network} -o ${cfg.external-interface} -j MASQUERADE";
postShutdown =
"${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s ${cfg.network} -o ${cfg.external-interface} -j MASQUERADE";
privateKeyFile = host-secrets.wireguard-gateway-privkey-file.target-file;
peers = let
peerList = attrValues cfg.peers;
in map (peerOpts: {
publicKey = peerOpts.public-key;
allowedIPs = [ "${peerOpts.ip}/32" ];
}) cfg.peers;
};
};
};
}

View File

@ -0,0 +1,28 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostOpts = { name, ... }: {
options = with types; {
ip = mkOption {
type = str;
description = "IP of this host on the WireGuard network.";
};
public-key = mkOption {
type = str;
description = "WireGuard public key of this host.";
};
};
};
wg-keys = config.fudo.secrets.files.wireguard.keys;
in {
options.fudo.services.wireguard = with types; {
hosts = mkOption {
type = attrsOf (submodule hostOpts);
default = {};
};
};
}

18
config/services.nix Normal file
View File

@ -0,0 +1,18 @@
{ config, lib, pkgs, ... }:
{
imports = [
./service/backplane.nix
./service/chute.nix
./service/dns.nix
./service/fudo-auth.nix
./service/jabber.nix
./service/local-network.nix
./service/logging.nix
./service/mail-server.nix
./service/metrics.nix
./service/postgresql.nix
./service/selby-forum.nix
# ./service/wireguard-gateway.nix
];
}

75
config/wireguard.nix Normal file
View File

@ -0,0 +1,75 @@
{ config, lib, pkgs, ... }:
with lib;
let
wg-keys = config.fudo.secrets.files.wireguard.keys;
has-key = hostname: _: hasAttr hostname wg-keys;
keyed-hosts = filterAttrs has-key config.fudo.hosts;
sites = config.fudo.sites;
generatePublicKeyPkg = hostname: privkey-file: pkgs.stdenv.mkDerivation {
name = "wireguard-${hostname}-key.pub";
phases = "installPhase";
buildInputs = [ pkgs.wireguard ];
installPhase = ''
wg pubkey < ${privkey-file} > $out
'';
};
generatePublicKey = hostname: privkey-file:
readFile "${generatePublicKeyPkg hostname privkey-file}";
in {
config = {
fudo.services.wireguard.networks = {
fudo-local = {
network = "10.0.0.0/8";
captured-network = "10.192.0.0/10";
external-peers = {
niten-phone = {
public-key = "";
assigned-ip = "10.192.0.100";
};
};
hosts = mapAttrs (hostname: hostOpts: let
private-key-file = wg-keys.${hostname};
in {
inherit private-key-file;
public-key = generatePublicKey hostname private-key-file;
}) keyed-hosts;
sites = {
seattle = {
network = sites.seattle.private-network;
gateway = sites.seattle.local-gateway;
};
nuttyclub = {
network = sites.nuttyclub.private-network;
gateway = "nutboy3";
};
portage = {
network = sites.portage.private-network;
gateway = "france";
};
worldstream = {
network = sites.worldstream.private-network;
gateway = "legatus";
};
russell = {
network = sites.russell.private-network;
gateway = sites.russell.local-gateway;
};
};
};
};
};
}

7
config/zones.nix Normal file
View File

@ -0,0 +1,7 @@
{ config, lib, pkgs, ... }:
{
imports = [
./zones/selby.ca.nix
];
}

38
config/zones/selby.ca.nix Normal file
View File

@ -0,0 +1,38 @@
{ config, lib, pkgs, ... }:
with lib;
let
fudo = config.fudo.zones."fudo.org";
getIfAttrs = attrs: attrmap: let
has-attr = attr: hasAttr attr attrmap;
in getAttrs (filter has-attr attrs) attrmap;
in {
config = {
fudo.zones."selby.ca" = {
srv-records = let
# Mail records will be created, no need to copy
shared-tcp-attrs =
["domain"
"kerberos"
"kerberos-adm"
"ldap"
"ldaps"
"minecraft"
"xmpp-client"
"xmpp-server"];
shared-udp-attrs =
[
"kerberos"
"kerberos-master"
"kpasswd"
];
in {
tcp = getIfAttrs shared-tcp-attrs fudo.srv-records.tcp;
udp = getIfAttrs shared-udp-attrs fudo.srv-records.udp;
};
};
};
}

110
live-disk/flake.nix Normal file
View File

@ -0,0 +1,110 @@
{
description = "Live Disk Flake";
inputs = {
nixpkgs.url = "nixpkgs/nixos-21.05";
fudo-home = {
url = "git+https://git.fudo.org/fudo-nix/home.git";
inputs.nixpkgs.follows = "nixpkgs";
};
# This MUST be a clean git repo, because we use the timestamp.
fudo-entities = {
url = "git+https://git.fudo.org/fudo-nix/entities.git";
inputs.nixpkgs.follows = "nixpkgs";
};
fudo-lib = {
url = "git+https://git.fudo.org/fudo-nix/lib.git";
inputs.nixpkgs.follows = "nixpkgs";
};
fudo-pkgs.url = "git+https://git.fudo.org/fudo-nix/pkgs.git";
};
outputs = { self, nixpkgs, fudo-home, fudo-entities, fudo-lib, fudo-pkgs, ...
}@inputs: {
nixosConfigurations.live-cd-x86_64-linux = let
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
overlays = [ fudo-pkgs.overlay ];
};
in {
inherit system;
modules = [
({ config, ... }: {
imports = [
fudo-home.nixosModule
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
"${nixpkgs}/nixos/modules/installer/cd-dvd/channel.nix"
];
config = with pkgs.lib; {
environment.etc.nixos-live.source = ./.;
hardware.enableAllFirmware = true;
environment.systemPackages = with pkgs; [
btrfs-progs
emacs
git
parted
gparted
nix-prefetch-scripts
wget
];
services.openssh = {
enable = true;
startWhenNeeded = true;
permitRootLogin = mkDefault "prohibit-password";
};
i18n.defaultLocale = "en_US.UTF-8";
console.useXkbConfig = true;
services.xserver = {
layout = "us";
xkbVariant = "dvp";
xkbOptions = "ctrl:nocaps";
};
nix = {
package = pkgs.nixFlakes;
extraOptions = "experimental-features = nix-command flakes";
};
programs = {
ssh = {
startAgent = true;
package = pkgs.openssh_gssapi;
extraConfig = ''
GSSAPIAuthentication yes
GSSAPIDelegateCredentials yes
'';
};
};
krb5.libdefaults.default_realm = "FUDO.ORG";
users.users = {
niten = {
isNormalUser = true;
createHome = true;
hashedPassword =
"$6$a1q2Duoe35hd5$IaZGXPfqyGv9uq5DQm7DZq0vIHsUs39sLktBiBBqMiwl/f/Z4jSvNZLJp9DZJYe5u2qGBYh1ca.jsXvQA8FPZ/";
extraGroups = [ "wheel" ];
};
root.openssh.authorizedKeys.keys = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDGVez4of30f+j0cWKj5kYCKeFjyNsYvG9UbOMxF5hImD2lP5MSbFBv31gFgHjx3yCG4zQRZlpuyU5uWo0qIwe9N84/LcZcB9WrWKZXDmuof7zPFy0J+Hj+LVLDQI/mVXHNwkMhBMHpPrdwA05EYDAYCYklWT4cSByu10pHtST+olF8i+A+UQgUzgNZzdJVeiYZv6MBDTYsJWptGeDUkl2B0Es3gtbGYcCCfnyS3RC7DIXlDo3NBbAr7WaHY2MBbT+R/+jicn9E3IY3NCM5jENxqmvHy9MDsxEEYgFNm7IDwq4V1VRUWy277YsvRbmEaHb+osOA5u1VNN4z3UftOZcSZgR5C/vR71cENXoPt1YQpCzu7i38ojtvL+tDVEKT7sIovrQw8q1sszNlW2nXh8RSPiIq5TMnrV73MP0egKcr9n3tfxwi1BIkLjvfom/02BkTK9R9v+VMNhYU1YwROhORCiMIgoxUGiUvtH8u38JGr7E0hhMoAjCE5k80WPUivl0="
];
};
};
})
];
};
};
}