Compare commits

...

67 Commits

Author SHA1 Message Date
b6e1c626c0 Correct name of ssh-options 2025-05-07 12:43:58 -07:00
4ec8579399 Add deployment vars to hosts. 2025-05-07 12:29:04 -07:00
be05273ab4 Move records up to default TTL 2025-04-23 12:17:31 -07:00
988e3a5f06 Add function to write env file 2025-04-23 11:37:19 -07:00
90806f2f24 Add ID to subnet in Kea 2025-01-29 16:39:44 -08:00
1d8be242a1 Remove IPv6 addrs from defaults
It's trying them, they're timing out
2025-01-29 12:08:10 -08:00
bcb2bdf9eb Remove ref to socket-group, which no longer exists 2025-01-28 22:18:06 -08:00
caf1a162db Forget the socket 2025-01-28 22:15:31 -08:00
437fc6402f Provide debug output 2025-01-28 20:52:08 -08:00
feb97b1e54 Didn't work, remove ExecStartPre 2025-01-28 20:47:25 -08:00
83a217777a Full path to mkdir & chown 2025-01-28 20:31:05 -08:00
52e5c05ebd Fuckin create the directory manually 2025-01-28 20:26:11 -08:00
007a88cdfa Add runtime dir to readwrite paths 2025-01-28 20:19:19 -08:00
66bbe7b760 Create runtime directory for postgresql 2025-01-28 20:04:22 -08:00
c9f66e2ed0 Switch to 24.11 2025-01-26 10:54:09 -08:00
358859f0b7 Fucking hell 2024-10-31 17:20:50 -07:00
82cd4d1db4 Don't set a default for domain 2024-10-31 16:02:36 -07:00
0eeb0eea47 Belongs in 'services' 2024-10-17 06:11:54 -07:00
217444aaf3 Add udev rules for android 2024-10-17 06:10:46 -07:00
cc8debb111 Open DNS port for UDP.
It seems like disabling the firewall only really disables it for TCP,
not UDP as well.
2024-10-06 22:35:32 -07:00
c49b3e8259 Remove initrd-network, never finished that 2024-09-21 16:49:23 -07:00
39c9dc8376 Remove old cruft, add initrd address 2024-09-21 16:31:37 -07:00
f1f6f5e6a1 Disable resolution from /etc/hosts
Screws with resolution of the host it's running on (returns 127.0.0.1).
2024-08-23 11:19:24 -07:00
3d82323e22 Create state directory 2024-08-22 20:25:50 -07:00
d4980a2d5e Wrap scripts in scripts 2024-08-22 20:19:07 -07:00
4aa532bf4d Why syslog.target anyway?
It doesn't exist
2024-08-22 17:57:26 -07:00
763670e46a Get rid of some cruft, and un-nest systemd 2024-08-22 17:52:20 -07:00
2c20446df3 Switch to regular systemd service 2024-08-22 17:49:36 -07:00
233c1fb29c Set a default for domain-upstreams 2024-08-18 17:06:23 -07:00
9eaea4c641 Allow specifying specific upstreams per-domain 2024-08-18 12:00:07 -07:00
ef116b2b14 Nah...stick to a simpler service 2024-08-10 12:10:41 -07:00
3c0347e734 Add initial kubernetes configuration 2024-08-10 11:52:35 -07:00
f8a5c9d3ec Was double-defining it... 2024-07-29 22:33:20 -07:00
340e0fcb15 Set a default null for local-gateway 2024-07-29 22:32:24 -07:00
46e5d85c30 mkif -> mkIf 2024-07-28 13:14:02 -07:00
101b3994a7 Don't add empty clauses 2024-07-28 13:13:04 -07:00
964974bca1 secured should be a bool 2024-07-28 13:08:29 -07:00
37ea31b085 Better default for scraper port 2024-07-27 18:29:00 -07:00
730c93fc4c Big changes to prometheus scrapers 2024-07-27 18:16:07 -07:00
b0b3f03231 Shit...nevermind 2024-07-21 16:26:41 -07:00
804eb74969 Stick with just /metrics, it's cleaner 2024-07-21 16:21:00 -07:00
c2406ccc1e minecraft requires network 2024-07-20 13:16:06 -07:00
afaf723dee Make webmail init depend on secret services 2024-07-20 09:16:13 -07:00
05fd51218c dataDir must be a path? 2024-07-19 18:31:33 -07:00
d2e648160f Don't set a state dir for prometheus either 2024-07-19 18:21:32 -07:00
16c9d4b7c4 Don't set a default state dir for grafana 2024-07-19 18:21:01 -07:00
89c241cb88 Import getHostFqdn too 2024-07-18 13:16:49 -07:00
e9520e66cd Wrap filter in () 2024-07-18 13:15:23 -07:00
ff7101ce62 Filter for those hosts with valid IP addresses 2024-07-18 13:01:13 -07:00
f45d9fa935 Need to import getHostIpv4 2024-07-18 12:38:27 -07:00
29405fae48 Include only real hosts from local site in hosts 2024-07-18 12:12:45 -07:00
8279a28d15 Oops, messed with some concatStringsSep 2024-07-18 11:12:20 -07:00
9a55c90d7f Don't use hostssl if we're local and ssl-free 2024-07-18 11:10:36 -07:00
8f38f6a1c1 Don't nead load-paths anymore 2024-07-12 23:54:44 -07:00
1977bafbb5 apparently uiop doesn't exist 2024-07-12 23:51:02 -07:00
27a6a04f67 quicklisp is apparently obsolete? I hope? 2024-07-12 23:47:19 -07:00
c10160bf26 Use new lisp-modules method 2024-07-12 23:41:44 -07:00
2d94cf0b1b Merge branch '24.05' of github.com:fudoniten/fudo-nix-lib into 24.05 2024-07-12 13:17:06 -07:00
a9f1822e6d Add 'config-user' to users 2024-07-12 13:16:43 -07:00
31bd4bd157 Wrap funcall in brackets 2024-07-11 09:16:57 -07:00
1a3b73440a Typo: reqires -> requires 2024-07-11 09:12:20 -07:00
a1f2e7f28b ensurePermissions no longer exists 2024-07-11 09:08:08 -07:00
4e071df85f krb5 in security 2024-07-04 23:14:51 -07:00
1e95fbc8f1 kerberos renamed to package 2024-07-04 22:23:43 -07:00
c1eb271eda Nest kerberos settings in settings 2024-07-04 22:22:10 -07:00
dfad95b705 krb5 -> security.krb5 2024-07-04 21:30:30 -07:00
113e7f3b52 Change format of nexus doamins 2024-07-01 05:41:48 -07:00
18 changed files with 336 additions and 400 deletions

View File

@ -4,7 +4,7 @@ with lib;
let let
cfg = config.fudo.adguard-dns-proxy; cfg = config.fudo.adguard-dns-proxy;
hostname = config.instance.hostname; inherit (config.instance) hostname;
get-basename = filename: get-basename = filename:
head (builtins.match "^[a-zA-Z0-9]+-(.+)$" (baseNameOf filename)); head (builtins.match "^[a-zA-Z0-9]+-(.+)$" (baseNameOf filename));
@ -41,7 +41,12 @@ let
}; };
generate-config = { dns, http, filters, verbose, upstream-dns, bootstrap-dns generate-config = { dns, http, filters, verbose, upstream-dns, bootstrap-dns
, blocked-hosts, enable-dnssec, local-domain-name, ... }: { , blocked-hosts, enable-dnssec, domain-upstreams, local-domain-name, ... }:
let
upstreamDnsEntries = mapAttrsToList (_: opts:
let domainClause = concatStringsSep "/" opts.domains;
in "[/${domainClause}/]${opts.upstream}") domain-upstreams;
in {
bind_host = http.listen-ip; bind_host = http.listen-ip;
bind_port = http.listen-port; bind_port = http.listen-port;
users = [{ users = [{
@ -55,7 +60,7 @@ let
dns = { dns = {
bind_hosts = dns.listen-ips; bind_hosts = dns.listen-ips;
port = dns.listen-port; port = dns.listen-port;
upstream_dns = upstream-dns; upstream_dns = upstream-dns ++ upstreamDnsEntries;
bootstrap_dns = bootstrap-dns; bootstrap_dns = bootstrap-dns;
enable_dnssec = enable-dnssec; enable_dnssec = enable-dnssec;
local_domain_name = local-domain-name; local_domain_name = local-domain-name;
@ -67,16 +72,17 @@ let
safesearch_enabled = false; safesearch_enabled = false;
use_private_ptr_resolvers = cfg.dns.reverse-dns != [ ]; use_private_ptr_resolvers = cfg.dns.reverse-dns != [ ];
local_ptr_upstreams = cfg.dns.reverse-dns; local_ptr_upstreams = cfg.dns.reverse-dns;
hostsfile_enabled = false;
}; };
tls.enabled = false; tls.enabled = false;
filters = imap1 (i: filter: { filters = imap1 (i:
enabled = true; { name, url, ... }: {
name = filter.name; enabled = true;
url = filter.url; inherit name url;
}) filters; }) filters;
dhcp.enabled = false; dhcp.enabled = false;
clients = [ ]; clients = [ ];
verbose = verbose; inherit verbose;
schema_version = 10; schema_version = 10;
}; };
@ -122,6 +128,25 @@ in {
}; };
}; };
domain-upstreams = mkOption {
type = attrsOf (submodule ({ name, ... }: {
options = {
domains = mkOption {
type = listOf str;
description =
"List of domains to route to a specific upstream DNS target.";
default = [ name ];
};
upstream = mkOption {
type = str;
description = "Upstream DNS target, in {ip}:{port} format.";
};
};
}));
default = { };
};
filters = mkOption { filters = mkOption {
type = listOf (submodule filterOpts); type = listOf (submodule filterOpts);
description = "List of filters to apply to DNS traffic."; description = "List of filters to apply to DNS traffic.";
@ -183,8 +208,8 @@ in {
# These 11 addrs send the network, so the response can prefer closer answers # These 11 addrs send the network, so the response can prefer closer answers
"https://9.9.9.11/dns-query" "https://9.9.9.11/dns-query"
"https://149.112.112.11/dns-query" "https://149.112.112.11/dns-query"
"https://2620:fe::11/dns-query" # "https://2620:fe::11/dns-query"
"https://2620:fe::fe:11/dns-query" # "https://2620:fe::fe:11/dns-query"
]; ];
}; };
@ -222,18 +247,7 @@ in {
verbose = mkEnableOption "Keep verbose logs."; verbose = mkEnableOption "Keep verbose logs.";
}; };
config = mkIf cfg.enable (let config = mkIf cfg.enable {
upgrade-perms = cfg.dns.listen-port <= 1024 || cfg.http.listen-port <= 1024;
in {
users = mkIf upgrade-perms {
users.${cfg.user} = {
isSystemUser = true;
group = cfg.user;
};
groups.${cfg.user} = { members = [ cfg.user ]; };
};
fudo = { fudo = {
secrets.host-secrets.${hostname} = { secrets.host-secrets.${hostname} = {
adguard-dns-proxy-admin-password = { adguard-dns-proxy-admin-password = {
@ -242,41 +256,75 @@ in {
user = "root"; user = "root";
}; };
}; };
};
system.services.adguard-dns-proxy = networking.firewall = {
let cfg-path = "/run/adguard-dns-proxy/config.yaml"; allowedTCPPorts = [ cfg.dns.listen-port ];
in { allowedUDPPorts = [ cfg.dns.listen-port ];
description = };
"DNS Proxy for ad filtering and DNS-over-HTTPS lookups.";
wantedBy = [ "default.target" ];
after = [ "syslog.target" ];
requires = [ "network.target" ];
privateNetwork = false;
requiredCapabilities = optional upgrade-perms "CAP_NET_BIND_SERVICE";
restartWhen = "always";
addressFamilies = null;
networkWhitelist = cfg.allowed-networks;
user = mkIf upgrade-perms cfg.user;
runtimeDirectory = "adguard-dns-proxy";
stateDirectory = "adguard-dns-proxy";
preStart = ''
cp ${generate-config-file cfg} ${cfg-path};
chown $USER ${cfg-path};
chmod u+w ${cfg-path};
'';
execStart = let systemd.services.adguard-dns-proxy =
args = [ let configFile = "/run/adguard-dns-proxy/config.yaml";
in {
description = "DNS proxy for ad filtering and DNS-over-HTTPS lookups.";
wantedBy = [ "default.target" ];
after = [ "network.target" ];
requires = [ "network.target" ];
serviceConfig = {
ExecStartPre = pkgs.writeShellScript "adguardsProxyPrestart.sh"
"cp ${generate-config-file cfg} $RUNTIME_DIRECTORY/config.yaml";
ExecStart = pkgs.writeShellScript "adguardProxyStart.sh"
(concatStringsSep " " [
"${pkgs.adguardhome}/bin/adguardhome"
"--no-check-update" "--no-check-update"
"--work-dir /var/lib/adguard-dns-proxy" "--work-dir /var/lib/adguard-dns-proxy"
"--pidfile /run/adguard-dns-proxy/adguard-dns-proxy.pid" "--pidfile /run/adguard-dns-proxy.pid"
"--host ${cfg.http.listen-ip}" "--host ${cfg.http.listen-ip}"
"--port ${toString cfg.http.listen-port}" "--port ${toString cfg.http.listen-port}"
"--config ${cfg-path}" "--config $RUNTIME_DIRECTORY/config.yaml"
]; ]);
arg-string = concatStringsSep " " args; AmbientCapabilities = optional
in "${pkgs.adguardhome}/bin/adguardhome ${arg-string}"; (cfg.dns.listen-port <= 1024 || cfg.http.listen-port <= 1024)
[ "CAP_NET_BIND_SERVICE" ];
DynamicUser = true;
RuntimeDirectory = "adguard-dns-proxy";
StateDirectory = "adguard-dns-proxy";
}; };
}; };
});
# system.services.adguard-dns-proxy =
# let cfg-path = "/run/adguard-dns-proxy/config.yaml";
# in {
# description =
# "DNS Proxy for ad filtering and DNS-over-HTTPS lookups.";
# wantedBy = [ "default.target" ];
# after = [ "syslog.target" ];
# requires = [ "network.target" ];
# privateNetwork = false;
# requiredCapabilities = optional upgrade-perms "CAP_NET_BIND_SERVICE";
# restartWhen = "always";
# addressFamilies = null;
# networkWhitelist = cfg.allowed-networks;
# user = mkIf upgrade-perms cfg.user;
# runtimeDirectory = "adguard-dns-proxy";
# stateDirectory = "adguard-dns-proxy";
# preStart = ''
# cp ${generate-config-file cfg} ${cfg-path};
# chown $USER ${cfg-path};
# chmod u+w ${cfg-path};
# '';
# execStart = let
# args = [
# "--no-check-update"
# "--work-dir /var/lib/adguard-dns-proxy"
# "--pidfile /run/adguard-dns-proxy/adguard-dns-proxy.pid"
# "--host ${cfg.http.listen-ip}"
# "--port ${toString cfg.http.listen-port}"
# "--config ${cfg-path}"
# ];
# arg-string = concatStringsSep " " args;
# in "${pkgs.adguardhome}/bin/adguardhome ${arg-string}";
# };
};
} }

View File

@ -6,22 +6,24 @@ in {
imports = [ ./kdc.nix ]; imports = [ ./kdc.nix ];
config = { config = {
krb5 = { security.krb5 = {
enable = true; enable = true;
kerberos = pkgs.heimdal; package = pkgs.heimdal;
libdefaults = { settings = {
default_realm = realm; libdefaults = {
allow_weak_crypto = false; default_realm = realm;
dns_lookup_kdc = true; allow_weak_crypto = false;
dns_lookup_realm = true; dns_lookup_kdc = true;
forwardable = true; dns_lookup_realm = true;
proxiable = true; forwardable = true;
}; proxiable = true;
appdefaults = { };
forwardable = true; appdefaults = {
proxiable = true; forwardable = true;
encrypt = true; proxiable = true;
forward = true; encrypt = true;
forward = true;
};
}; };
}; };

View File

@ -22,7 +22,6 @@ with lib; {
./grafana.nix ./grafana.nix
./hosts.nix ./hosts.nix
./host-filesystems.nix ./host-filesystems.nix
./initrd-network.nix
./ipfs.nix ./ipfs.nix
./jabber.nix ./jabber.nix
# ./kdc.nix # ./kdc.nix

View File

@ -159,7 +159,6 @@ in {
state-directory = mkOption { state-directory = mkOption {
type = str; type = str;
description = "Directory at which to store Grafana state data."; description = "Directory at which to store Grafana state data.";
default = "/var/lib/grafana";
}; };
private-network = mkEnableOption "Network is private, no SSL."; private-network = mkEnableOption "Network is private, no SSL.";
@ -199,7 +198,7 @@ in {
grafana = { grafana = {
enable = true; enable = true;
dataDir = cfg.state-directory; dataDir = toPath cfg.state-directory;
settings = { settings = {

View File

@ -93,11 +93,12 @@ in {
time.timeZone = site.timezone; time.timeZone = site.timezone;
krb5.libdefaults.default_realm = domain.gssapi-realm; security.krb5.settings.libdefaults.default_realm = domain.gssapi-realm;
services = { services = {
cron.mailto = domain.admin-email; cron.mailto = domain.admin-email;
fail2ban.ignoreIP = config.instance.local-networks; fail2ban.ignoreIP = config.instance.local-networks;
udev.packages = optional host-cfg.android-dev pkgs.android-udev-rules;
}; };
virtualisation.docker = mkIf (host-cfg.docker-server) { virtualisation.docker = mkIf (host-cfg.docker-server) {

View File

@ -1,86 +0,0 @@
{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
initrd-cfg = config.fudo.hosts.${hostname}.initrd-network;
read-lines = filename: splitString "\n" (fileContents filename);
concatLists = lsts: concatMap (i: i) lsts;
gen-sshfp-records-pkg = hostname: pubkey: let
pubkey-file = builtins.toFile "${hostname}-initrd-ssh-pubkey" pubkey;
in pkgs.stdenv.mkDerivation {
name = "${hostname}-initrd-ssh-firngerprint";
phases = [ "installPhase" ];
buildInputs = with pkgs; [ openssh ];
installPhase = ''
mkdir $out
ssh-keygen -r REMOVEME -f "${pubkey-file}" | sed 's/^REMOVEME IN SSHFP //' >> $out/initrd-ssh-pubkey.sshfp
'';
};
gen-sshfp-records = hostname: pubkey: let
sshfp-record-pkg = gen-sshfp-records-pkg hostname pubkey;
in read-lines "${sshfp-record-pkg}/initrd-ssh-pubkey.sshfp";
in {
config = {
boot = mkIf (initrd-cfg != null) {
kernelParams = let
site-name = config.instance.local-site;
site = config.fudo.sites.${site-name};
site-gateway = pkgs.lib.network.site-gateway config site-name;
netmask =
pkgs.lib.ip.maskFromV32Network site.network;
in [
"ip=${initrd-cfg.ip}:${site-gateway}:${netmask}:${hostname}:${initrd-cfg.interface}"
];
initrd = {
network = {
enable = true;
ssh = let
admin-ssh-keys =
concatMap (admin: config.fudo.users.${admin}.ssh-authorized-keys)
config.instance.local-admins;
in {
enable = true;
port = 22;
authorizedKeys = admin-ssh-keys;
hostKeys = [
initrd-cfg.keypair.private-key-file
];
};
};
};
};
fudo = {
local-network = let
initrd-network-hosts =
filterAttrs
(hostname: hostOpts: hostOpts.initrd-network != null)
config.instance.local-hosts;
in {
zone-definition.hosts = mapAttrs'
(hostname: hostOpts: nameValuePair "${hostname}-recovery" {
ipv4-address = hostOpts.initrd-network.ip;
description = "${hostname} initrd host";
}) initrd-network-hosts;
extra-records = let
recs = (mapAttrsToList
(hostname: hostOpts: map
(sshfp: "${hostname} IN SSHFP ${sshfp}")
(gen-sshfp-records hostname hostOpts.initrd-network.keypair.public-key))
initrd-network-hosts);
in concatLists recs;
};
};
};
}

View File

@ -4,6 +4,8 @@ with lib;
let let
cfg = config.fudo.local-network; cfg = config.fudo.local-network;
inherit (pkgs.lib) getHostIpv4 getHostFqdn;
join-lines = concatStringsSep "\n"; join-lines = concatStringsSep "\n";
inherit (pkgs.lib.ip) inherit (pkgs.lib.ip)
@ -117,14 +119,12 @@ in {
config = mkIf cfg.enable { config = mkIf cfg.enable {
fudo.system.hostfile-entries = let fudo.system.hostfile-entries = let
other-hosts = siteHosts =
filterAttrs (hostname: hostOpts: hostname != config.instance.hostname) (filterAttrs (_: hostOpts: hostOpts.site == config.instance.local-site)
cfg.zone-definition.hosts; config.fudo.hosts);
in mapAttrs' (hostname: hostOpts: in mapAttrs' (hostname: _:
nameValuePair hostOpts.ipv4-address [ nameValuePair (getHostIpv4 hostname) [ (getHostFqdn hostname) hostname ])
"${hostname}.${cfg.domain}" (filterAttrs (hostname: _: !isNull (getHostIpv4 hostname)) siteHosts);
hostname
]) other-hosts;
services.kea.dhcp4 = { services.kea.dhcp4 = {
enable = true; enable = true;
@ -161,6 +161,7 @@ in {
} }
]; ];
subnet4 = [{ subnet4 = [{
id = 1;
pools = [{ pools = [{
pool = let pool = let
minIp = networkMinIp cfg.dhcp-dynamic-network; minIp = networkMinIp cfg.dhcp-dynamic-network;

View File

@ -268,6 +268,7 @@ in {
"${worldOpts.world-name} Minecraft Server with Clojure REPL"; "${worldOpts.world-name} Minecraft Server with Clojure REPL";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ]; after = [ "network-online.target" ];
requires = [ "network-online.target" ];
serviceConfig = { serviceConfig = {
User = cfg.user; User = cfg.user;
Group = cfg.group; Group = cfg.group;

View File

@ -9,14 +9,14 @@ let
default = name; default = name;
}; };
servers = mkOption { server = mkOption {
type = listOf str; type = str;
description = "List of servers for this Nexus domain."; description = "Primary server for this Nexus domain.";
}; };
dns-servers = mkOption { secondary-dns-servers = mkOption {
type = listOf str; type = listOf str;
description = "List of DNS servers for this Nexus domain."; description = "List of secondary DNS servers for this Nexus domain.";
}; };
gssapi-realm = mkOption { gssapi-realm = mkOption {

View File

@ -7,12 +7,14 @@ let
hostname = config.instance.hostname; hostname = config.instance.hostname;
domain-name = config.instance.local-domain; domain-name = config.instance.local-domain;
sslEnabled = cfg.ssl-certificate != null;
gssapi-realm = config.fudo.domains.${domain-name}.gssapi-realm; gssapi-realm = config.fudo.domains.${domain-name}.gssapi-realm;
join-lines = lib.concatStringsSep "\n";
strip-ext = filename: head (builtins.match "^(.+)[.][^.]+$" filename); strip-ext = filename: head (builtins.match "^(.+)[.][^.]+$" filename);
joinLines = concatStringsSep "\n";
userDatabaseOpts = { database, ... }: { userDatabaseOpts = { database, ... }: {
options = { options = {
access = mkOption { access = mkOption {
@ -101,7 +103,7 @@ let
exit 2 exit 2
fi fi
${join-lines (mapAttrsToList (user: opts: ${joinLines (mapAttrsToList (user: opts:
password-setter-script user opts.password-file "$OUTPUT_FILE") password-setter-script user opts.password-file "$OUTPUT_FILE")
(filterPasswordedUsers users))} (filterPasswordedUsers users))}
''; '';
@ -111,17 +113,19 @@ let
nameValuePair "DATABASE ${database}" databaseOpts.access) databases; nameValuePair "DATABASE ${database}" databaseOpts.access) databases;
makeEntry = nw: makeEntry = nw:
"hostssl all all ${nw} gss include_realm=0 krb_realm=${gssapi-realm}"; let hostClause = if sslEnabled then "hostssl" else "host";
in "${hostClause} all all ${nw} gss include_realm=0 krb_realm=${gssapi-realm}";
makeNetworksEntry = networks: join-lines (map makeEntry networks); makeNetworksEntry = networks: joinLines (map makeEntry networks);
makeLocalUserPasswordEntries = users: networks: makeLocalUserPasswordEntries = users: networks:
let let
network-entries = user: db: network-entries = user: db:
join-lines joinLines (map (network:
(map (network: "hostssl ${db} ${user} ${network} md5") networks); let hostClause = if sslEnabled then "hostssl" else "host";
in join-lines (mapAttrsToList (user: opts: in "${hostClause} ${db} ${user} ${network} md5") networks);
join-lines (map (db: '' in joinLines (mapAttrsToList (user: opts:
joinLines (map (db: ''
local ${db} ${user} md5 local ${db} ${user} md5
host ${db} ${user} 127.0.0.1/16 md5 host ${db} ${user} 127.0.0.1/16 md5
host ${db} ${user} ::1/128 md5 host ${db} ${user} ::1/128 md5
@ -132,19 +136,18 @@ let
enableDatabaseExtensionsSql = database: databaseOpts: '' enableDatabaseExtensionsSql = database: databaseOpts: ''
\c ${database} \c ${database}
${join-lines (map enableExtensionSql databaseOpts.extensions)} ${joinLines (map enableExtensionSql databaseOpts.extensions)}
''; '';
userTableAccessSql = user: entity: access: userTableAccessSql = user: entity: access:
"GRANT ${access} ON ${entity} TO ${user};"; "GRANT ${access} ON ${entity} TO ${user};";
userDatabaseAccessSql = user: database: dbOpts: '' userDatabaseAccessSql = user: database: dbOpts: ''
\c ${database} \c ${database}
${join-lines ${joinLines (mapAttrsToList (userTableAccessSql user) dbOpts.entity-access)}
(mapAttrsToList (userTableAccessSql user) dbOpts.entity-access)}
''; '';
userAccessSql = user: userOpts: userAccessSql = user: userOpts:
join-lines (mapAttrsToList (userDatabaseAccessSql user) userOpts.databases); joinLines (mapAttrsToList (userDatabaseAccessSql user) userOpts.databases);
usersAccessSql = users: join-lines (mapAttrsToList userAccessSql users); usersAccessSql = users: joinLines (mapAttrsToList userAccessSql users);
in { in {
@ -205,23 +208,23 @@ in {
default = { }; default = { };
}; };
socket-directory = mkOption { # socket-directory = mkOption {
type = str; # type = str;
description = "Directory in which to place unix sockets."; # description = "Directory in which to place unix sockets.";
default = "/run/postgresql"; # default = "/run/postgresql";
}; # };
socket-group = mkOption { # socket-group = mkOption {
type = str; # type = str;
description = "Group for accessing sockets."; # description = "Group for accessing sockets.";
default = "postgres_local"; # default = "postgres_local";
}; # };
local-users = mkOption { # local-users = mkOption {
type = listOf str; # type = listOf str;
description = "Users able to access the server via local socket."; # description = "Users able to access the server via local socket.";
default = [ ]; # default = [ ];
}; # };
required-services = mkOption { required-services = mkOption {
type = listOf str; type = listOf str;
@ -255,23 +258,27 @@ in {
environment.systemPackages = with pkgs; [ cfg.package ]; environment.systemPackages = with pkgs; [ cfg.package ];
users.groups = { # users.groups = {
${cfg.socket-group} = { members = [ "postgres" ] ++ cfg.local-users; }; # ${cfg.socket-group} = { members = [ "postgres" ] ++ cfg.local-users; };
}; # };
services.postgresql = { services.postgresql = {
enable = true; enable = true;
package = cfg.package; package = cfg.package;
enableTCPIP = true; enableTCPIP = true;
ensureDatabases = mapAttrsToList (name: value: name) cfg.databases; ensureDatabases = mapAttrsToList (name: value: name) cfg.databases;
ensureUsers = ((mapAttrsToList (username: attrs: { ensureUsers = map (user: {
name = username; name = user;
ensurePermissions = userDatabaseAccess username attrs.databases; ensureClauses.login = true;
}) cfg.users) ++ (flatten (mapAttrsToList (database: opts: }) (attrNames cfg.users);
(map (username: { # ensureUsers = ((mapAttrsToList (username: attrs: {
name = username; # name = username;
ensurePermissions = { "DATABASE ${database}" = "ALL PRIVILEGES"; }; # ensurePermissions = userDatabaseAccess username attrs.databases;
}) opts.users)) cfg.databases))); # }) cfg.users) ++ (flatten (mapAttrsToList (database: opts:
# (map (username: {
# name = username;
# ensurePermissions = { "DATABASE ${database}" = "ALL PRIVILEGES"; };
# }) opts.users)) cfg.databases)));
settings = let ssl-enabled = cfg.ssl-certificate != null; settings = let ssl-enabled = cfg.ssl-certificate != null;
in { in {
@ -281,9 +288,11 @@ in {
ssl_cert_file = mkIf ssl-enabled cfg.ssl-certificate; ssl_cert_file = mkIf ssl-enabled cfg.ssl-certificate;
ssl_key_file = mkIf ssl-enabled cfg.ssl-private-key; ssl_key_file = mkIf ssl-enabled cfg.ssl-private-key;
unix_socket_directories = cfg.socket-directory; # unix_socket_directories = cfg.socket-directory;
unix_socket_group = cfg.socket-group; # unix_socket_group = cfg.socket-group;
unix_socket_permissions = "0777"; # unix_socket_permissions = "0777";
log_min_error_statement = "DEBUG3";
}; };
authentication = lib.mkForce '' authentication = lib.mkForce ''
@ -304,9 +313,9 @@ in {
systemd = { systemd = {
tmpfiles.rules = optional (cfg.state-directory != null) tmpfiles.rules = optionals (cfg.state-directory != null)
(let user = config.systemd.services.postgresql.serviceConfig.User; (let user = config.systemd.services.postgresql.serviceConfig.User;
in "d ${cfg.state-directory} 0700 ${user} - - -"); in [ "d ${cfg.state-directory} 0700 ${user} - - -" ]);
targets.${strip-ext cfg.systemd-target} = { targets.${strip-ext cfg.systemd-target} = {
description = "Postgresql and associated systemd services."; description = "Postgresql and associated systemd services.";
@ -353,11 +362,12 @@ in {
description = description =
"A service to set postgresql user passwords after the server has started."; "A service to set postgresql user passwords after the server has started.";
after = [ "postgresql.service" ] ++ cfg.required-services; after = [ "postgresql.service" ] ++ cfg.required-services;
requires = [ "postgresql.service" ] ++ cfg.required-services;
wantedBy = [ "postgresql.service" ]; wantedBy = [ "postgresql.service" ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
User = config.services.postgresql.superUser; User = config.services.postgresql.superUser;
ExecStart = "${password-wrapper-script}"; ExecStart = password-wrapper-script;
}; };
partOf = [ cfg.systemd-target ]; partOf = [ cfg.systemd-target ];
}; };
@ -372,7 +382,7 @@ in {
# allow-user-login = user: "ALTER ROLE ${user} WITH LOGIN;"; # allow-user-login = user: "ALTER ROLE ${user} WITH LOGIN;";
# extra-settings-sql = pkgs.writeText "settings.sql" '' # extra-settings-sql = pkgs.writeText "settings.sql" ''
# ${concatStringsSep "\n" # ${joinLines
# (map allow-user-login (mapAttrsToList (key: val: key) cfg.users))} # (map allow-user-login (mapAttrsToList (key: val: key) cfg.users))}
# ${usersAccessSql cfg.users} # ${usersAccessSql cfg.users}
# ''; # '';
@ -384,28 +394,40 @@ in {
# ''; # '';
# Wait a bit before starting dependent services, to let postgres finish initializing # Wait a bit before starting dependent services, to let postgres finish initializing
serviceConfig.ExecStartPost = serviceConfig = {
mkAfter [ "${pkgs.coreutils}/bin/sleep 10" ]; # ReadWritePaths = [ cfg.socket-directory ];
ExecStartPost = mkAfter [ "${pkgs.coreutils}/bin/sleep 10" ];
};
postStop = concatStringsSep "\n" cfg.cleanup-tasks; postStop = joinLines cfg.cleanup-tasks;
}; };
postgresql-finalizer = { postgresql-finalizer = {
requires = [ "postgresql.service" ]; requires = [ "postgresql.service" ];
after = [ "postgresql.service" "postgresql-password-setter.service" ]; after = [ "postgresql.service" "postgresql-password-setter.service" ];
partOf = [ "postgresql.target" ]; partOf = [ cfg.systemd-target ];
wantedBy = [ "postgresql.service" ]; wantedBy = [ "postgresql.service" ];
serviceConfig = { serviceConfig = {
User = config.services.postgresql.superUser; User = config.services.postgresql.superUser;
ExecStart = let ExecStart = let
allow-user-login = user: "ALTER ROLE ${user} WITH LOGIN;"; enableExtensionsClause = joinLines
(mapAttrsToList enableDatabaseExtensionsSql cfg.databases);
grantMasterAccessSql = db: user: ''
GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user};
\c ${db} postgres
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ${user};
\c postgres postgres
'';
grantMasterAccess = joinLines (mapAttrsToList (database: opts:
joinLines (map (grantMasterAccessSql database) opts.users))
cfg.databases);
extra-settings-sql = pkgs.writeText "settings.sql" '' extra-settings-sql = pkgs.writeText "settings.sql" ''
${join-lines ${enableExtensionsClause}
(mapAttrsToList enableDatabaseExtensionsSql cfg.databases)}
${concatStringsSep "\n" (map allow-user-login ${grantMasterAccess}
(mapAttrsToList (key: val: key) cfg.users))}
${usersAccessSql cfg.users} ${usersAccessSql cfg.users}
''; '';
@ -413,7 +435,6 @@ in {
${pkgs.postgresql}/bin/psql --port ${ ${pkgs.postgresql}/bin/psql --port ${
toString config.services.postgresql.port toString config.services.postgresql.port
} -d postgres -f ${extra-settings-sql} } -d postgres -f ${extra-settings-sql}
chgrp ${cfg.socket-group} ${cfg.socket-directory}/.s.PGSQL*
''; '';
}; };
}; };

View File

@ -1,4 +1,4 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }@toplevel:
with lib; with lib;
let cfg = config.fudo.metrics.prometheus; let cfg = config.fudo.metrics.prometheus;
@ -15,35 +15,50 @@ in {
description = "The prometheus package that should be used."; description = "The prometheus package that should be used.";
}; };
service-discovery-dns = mkOption { scrapers = let
type = attrsOf (listOf str); scraperOpts.options = {
description = '' name = mkOption {
A map of exporter type to a list of domains to use for service discovery. type = str;
''; description = "Name of this exporter.";
example = { };
node = [ "node._metrics._tcp.my-domain.com" ];
postfix = [ "postfix._metrics._tcp.my-domain.com" ];
};
default = {
dovecot = [ ];
node = [ ];
postfix = [ ];
rspamd = [ ];
};
};
static-targets = mkOption { secured = mkOption {
type = attrsOf (listOf str); type = bool;
description = '' description = "Whether to use https instead of http.";
A map of exporter type to a list of host:ports from which to collect metrics. default = !toplevel.config.fudo.metrics.prometheus.private-network;
''; };
example = { node = [ "my-host.my-domain:1111" ]; };
default = { path = mkOption {
dovecot = [ ]; type = str;
node = [ ]; description = "Host path at which to find exported metrics.";
postfix = [ ]; default = "/metrics";
rspamd = [ ]; };
port = mkOption {
type = port;
description = "Port on which to scrape for metrics.";
default =
if toplevel.config.fudo.metrics.prometheus.private-network then
80
else
443;
};
static-targets = mkOption {
type = listOf str;
description = "Explicit list of hosts to scrape for metrics.";
default = [ ];
};
dns-sd-records = mkOption {
type = listOf str;
description = "List of DNS records to query for hosts to scrape.";
default = [ ];
};
}; };
in mkOption {
type = listOf (submodule scraperOpts);
default = [ ];
}; };
docker-hosts = mkOption { docker-hosts = mkOption {
@ -79,7 +94,6 @@ in {
state-directory = mkOption { state-directory = mkOption {
type = str; type = str;
description = "Directory at which to store Prometheus state."; description = "Directory at which to store Prometheus state.";
default = "/var/lib/prometheus";
}; };
private-network = mkEnableOption "Network is private."; private-network = mkEnableOption "Network is private.";
@ -134,21 +148,19 @@ in {
port = 9090; port = 9090;
scrapeConfigs = let scrapeConfigs = let
make-job = type: { mkScraper =
job_name = type; { name, secured, path, port, static-targets, dns-sd-records }: {
honor_labels = false; job_name = name;
scheme = if cfg.private-network then "http" else "https"; honor_labels = false;
metrics_path = "/metrics/${type}"; scheme = if secured then "https" else "http";
static_configs = if (hasAttr type cfg.static-targets) then [{ metrics_path = path;
targets = cfg.static-targets.${type}; static_configs = mkIf (static-targets != [ ])
}] else (let attachPort = target: "${target}:${toString port}";
[ ]; in [{ targets = map attachPort static-targets; }]);
dns_sd_configs = if (hasAttr type cfg.service-discovery-dns) then [{ dns_sd_configs =
names = cfg.service-discovery-dns.${type}; mkIf (dns-sd-records != [ ]) [{ names = dns-sd-records; }];
}] else };
[ ]; in map mkScraper cfg.scrapers;
};
in map make-job [ "docker" "node" "dovecot" "postfix" "rspamd" ];
pushgateway = { pushgateway = {
enable = if (cfg.push-url != null) then true else false; enable = if (cfg.push-url != null) then true else false;

View File

@ -4,34 +4,23 @@ with lib;
let let
cfg = config.fudo.slynk; cfg = config.fudo.slynk;
initScript = port: load-paths: initScript = port:
let pkgs.writeText "slynk.lisp" ''
load-path-string = (asdf:load-system 'slynk)
concatStringsSep " " (map (path: ''"${path}"'') load-paths);
in pkgs.writeText "slynk.lisp" ''
(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))
(ql:quickload :slynk)
(setf asdf:*central-registry*
(append asdf:*central-registry*
(list ${load-path-string})))
(slynk:create-server :port ${toString port} :dont-close t) (slynk:create-server :port ${toString port} :dont-close t)
(dolist (var '("LD_LIBRARY_PATH"))
(format t "~S: ~S~%" var (sb-unix::posix-getenv var)))
(loop (sleep 60)) (loop (sleep 60))
''; '';
lisp-libs = with pkgs.lispPackages; [ sbclWithLibs = pkgs.sbcl.withPackages (ps:
alexandria with ps; [
asdf-package-system alexandria
asdf-system-connections asdf-package-system
cl_plus_ssl asdf-system-connections
cl-ppcre cl_plus_ssl
quicklisp cl-ppcre
quri quri
uiop usocket
usocket ]);
];
in { in {
options.fudo.slynk = { options.fudo.slynk = {
@ -48,20 +37,17 @@ in {
systemd.user.services.slynk = { systemd.user.services.slynk = {
description = "Slynk Common Lisp server."; description = "Slynk Common Lisp server.";
serviceConfig = serviceConfig = {
let load-paths = (map (pkg: "${pkg}/lib/common-lisp/") lisp-libs); ExecStart = "sbcl --load ${initScript cfg.port}";
in { Restart = "on-failure";
ExecStartPre = "${pkgs.lispPackages.quicklisp}/bin/quicklisp init"; PIDFile = "/run/slynk.$USERNAME.pid";
ExecStart = };
"${pkgs.sbcl}/bin/sbcl --load ${initScript cfg.port load-paths}";
Restart = "on-failure";
PIDFile = "/run/slynk.$USERNAME.pid";
};
path = with pkgs; [ path = with pkgs; [
gcc gcc
glibc # for getent glibc # for getent
file file
sbclWithLibs
]; ];
environment = { LD_LIBRARY_PATH = "${pkgs.openssl.out}/lib"; }; environment = { LD_LIBRARY_PATH = "${pkgs.openssl.out}/lib"; };

View File

@ -366,6 +366,9 @@ in {
''); '');
in { in {
requiredBy = [ "nginx.service" ]; requiredBy = [ "nginx.service" ];
before = [ "nginx.service" ];
requires = [ config.fudo.secrets.secret-target ];
after = [ config.fudo.secrets.secret-target ];
description = description =
"Initialize webmail service directories prior to starting nginx."; "Initialize webmail service directories prior to starting nginx.";
script = "${scriptPkg}/bin/webmail-init.sh"; script = "${scriptPkg}/bin/webmail-init.sh";

View File

@ -19,7 +19,8 @@ in {
build-timestamp = mkOption { build-timestamp = mkOption {
type = int; type = int;
description = "Timestamp associated with the build. Used for e.g. DNS serials."; description =
"Timestamp associated with the build. Used for e.g. DNS serials.";
}; };
local-domain = mkOption { local-domain = mkOption {
@ -45,7 +46,8 @@ in {
local-admins = mkOption { local-admins = mkOption {
type = listOf str; type = listOf str;
description = "List of users who should have admin access to the local host."; description =
"List of users who should have admin access to the local host.";
}; };
local-groups = mkOption { local-groups = mkOption {
@ -55,7 +57,8 @@ in {
local-hosts = mkOption { local-hosts = mkOption {
type = attrsOf (submodule host.hostOpts); type = attrsOf (submodule host.hostOpts);
description = "List of hosts that should be considered local to the current host."; description =
"List of hosts that should be considered local to the current host.";
}; };
local-users = mkOption { local-users = mkOption {
@ -65,7 +68,8 @@ in {
local-networks = mkOption { local-networks = mkOption {
type = listOf str; type = listOf str;
description = "Networks which are considered local to this host, site, or domain."; description =
"Networks which are considered local to this host, site, or domain.";
}; };
service-home = mkOption { service-home = mkOption {
@ -81,8 +85,7 @@ in {
}; };
config = { config = {
systemd.tmpfiles.rules = [ systemd.tmpfiles.rules =
"d ${config.instance.service-home} 755 root root - -" [ "d ${config.instance.service-home} 755 root root - -" ];
];
}; };
} }

View File

@ -153,14 +153,14 @@ let
${join-lines (mapAttrsToList makeMetricRecords zone.metric-records)} ${join-lines (mapAttrsToList makeMetricRecords zone.metric-records)}
$TTL ${zone.host-record-ttl}
${join-lines (mapAttrsToList hostRecords zone.hosts)}
${join-lines (mapAttrsToList cnameRecord zone.aliases)} ${join-lines (mapAttrsToList cnameRecord zone.aliases)}
${join-lines zone.verbatim-dns-records} ${join-lines zone.verbatim-dns-records}
$TTL ${zone.host-record-ttl}
${join-lines (mapAttrsToList hostRecords zone.hosts)}
${join-lines (mapAttrsToList ${join-lines (mapAttrsToList
(subdom: subdomCfg: domain-records "${subdom}.${dom}" subdomCfg) (subdom: subdomCfg: domain-records "${subdom}.${dom}" subdomCfg)
zone.subdomains)} zone.subdomains)}

View File

@ -13,4 +13,7 @@ let
installPhase = "python -mjson.tool ${filename} > $out"; installPhase = "python -mjson.tool ${filename} > $out";
}; };
in { inherit format-json-file; } writeEnv = name: vars:
pkgs.writeText name (mapAttrsToList (var: val: "${var}=${val}") vars);
in { inherit format-json-file writeEnv; }

View File

@ -92,7 +92,7 @@ in rec {
}; };
}; };
masterKeyOpts = { ... }: { masterKeyOpts = _: {
options = with types; { options = with types; {
key-path = mkOption { key-path = mkOption {
type = str; type = str;
@ -122,7 +122,7 @@ in rec {
type = str; type = str;
description = description =
"Primary domain to which the host belongs, in the form of a domain name."; "Primary domain to which the host belongs, in the form of a domain name.";
default = "fudo.org"; default = "unassigned.domain";
}; };
extra-domains = mkOption { extra-domains = mkOption {
@ -290,92 +290,28 @@ in rec {
default = false; default = false;
}; };
wireguard = let
clientOpts = {
options = {
ip = mkOption {
type = nullOr str;
description =
"IP address assigned to this host in the WireGuard network.";
};
bound = mkOption {
type = bool;
description = "Whether to route all traffic from this host.";
default = false;
};
};
};
wireguardOpts = {
options = {
private-key-file = mkOption {
type = str;
description = "WireGuard private key file of the host.";
};
public-key = mkOption {
type = str;
description = "WireGuard public key.";
};
client = mkOption {
type = nullOr (submodule clientOpts);
default = null;
};
};
};
in mkOption {
type = nullOr (submodule wireguardOpts);
default = null;
};
initrd-network = let
keypair-type = { ... }: {
options = {
public-key = mkOption {
type = str;
description = "SSH public key.";
};
private-key-file = mkOption {
type = str;
description = "Path to SSH private key (on the local host!).";
};
};
};
initrd-network-config = { ... }: {
options = {
ip = mkOption {
type = str;
description =
"IP to assign to the initrd image, allowing access to host during bootup.";
};
keypair = mkOption {
type = (submodule keypair-type);
description = "SSH host key pair to use for initrd.";
};
interface = mkOption {
type = str;
description =
"Name of interface on which to listen for connections.";
};
};
};
in mkOption {
type = nullOr (submodule initrd-network-config);
description =
"Configuration parameters to set up initrd SSH network.";
default = null;
};
backplane-password-file = mkOption { backplane-password-file = mkOption {
type = path; type = path;
description = description =
"File containing the password used by this host to connect to the backplane."; "File containing the password used by this host to connect to the backplane.";
}; };
initrd-ssh-key = mkOption {
type = nullOr str;
description =
"Path on the local filesystem to a host SSH key for initrd, if any.";
default = null;
};
deploy = {
enable = mkEnableOption "Enable deploy-rs deployments.";
ssh-options = mkOption {
type = listOf str;
description = "List of SSH options to use when deploying.";
default = [ ];
};
};
}; };
}; };
} }

View File

@ -119,6 +119,13 @@ with lib; rec {
description = "User's surname."; description = "User's surname.";
default = null; default = null;
}; };
config-user = mkOption {
type = nullOr str;
description =
"When generating config, consider this to be the 'real' user.";
default = null;
};
}; };
}; };