Added live disk flake file
This commit is contained in:
parent
9c9df8c3c2
commit
2954dfc1b2
|
@ -0,0 +1,7 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./domains/sea.fudo.org.nix
|
||||
];
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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";
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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}";
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
|
@ -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 = {
|
||||
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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 = {};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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
|
||||
];
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./zones/selby.ca.nix
|
||||
];
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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="
|
||||
];
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue