forked from c3d2/nix-config
528 lines
15 KiB
Nix
528 lines
15 KiB
Nix
{ zentralwerk, hostRegistry, config, options, lib, pkgs, ... }:
|
|
|
|
let
|
|
cfg = config.c3d2;
|
|
|
|
hqPrefix64 = lib.removeSuffix "::" (builtins.head (
|
|
builtins.split "/" zentralwerk.lib.config.site.net.c3d2.subnets6.dn42
|
|
));
|
|
|
|
neighMod = with lib; types.submodule {
|
|
options = {
|
|
addrs = mkOption {
|
|
type = with types; attrsOf str;
|
|
default = { };
|
|
};
|
|
via = mkOption
|
|
{
|
|
type = with types; listOf str;
|
|
default = [ ];
|
|
};
|
|
} // (with builtins; let value = mkOption { type = types.str; }; in
|
|
listToAttrs (map (name: { inherit name value; }) [ "exchpub" "id" "noisepub" "signpub" ]));
|
|
};
|
|
|
|
# Generate a deterministic IPv6 address for a 64 bit prefix
|
|
# and seed string. Prefix must not contain trailing ':'.
|
|
toIpv6Address = prefix64: seed:
|
|
with builtins;
|
|
let
|
|
digest = builtins.hashString "sha256" seed;
|
|
hextets = map (i: substring (4 * i) 4 digest) [ 0 1 2 3 ];
|
|
in
|
|
concatStringsSep ":" ([ prefix64 ] ++ hextets);
|
|
|
|
# Generate a deterministic public IPv6 addresses
|
|
# for the HQ networking using a seed string.
|
|
toHqPrivateAddress = toIpv6Address hqPrefix64;
|
|
in
|
|
{
|
|
imports = [
|
|
./stats.nix
|
|
./audio-server.nix
|
|
./logging.nix
|
|
];
|
|
|
|
options.c3d2 = with lib; {
|
|
acmeEmail = mkOption {
|
|
type = types.str;
|
|
default = "mail@c3d2.de";
|
|
description = ''
|
|
Admin email address to use for Letsencrypt
|
|
'';
|
|
};
|
|
|
|
allUsersCanSshRoot = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Let all people in <literal>c3d2.users</literal>
|
|
login as root for deployment via SSH.
|
|
'';
|
|
};
|
|
|
|
isInHq = mkEnableOption "HQ presence (TODO: what is this? association to VLAN 5?)";
|
|
|
|
enableMotd = mkOption {
|
|
type = types.bool;
|
|
default = cfg.isInHq;
|
|
defaultText = literalExample "config.c3d2.isInHq";
|
|
};
|
|
|
|
mergeHostsFile = mkOption {
|
|
type = types.bool;
|
|
default = cfg.isInHq;
|
|
description = ''
|
|
Whether to add <literal>c3d2.hosts</literal> to /etc/hosts.
|
|
'';
|
|
};
|
|
|
|
mergeNncpSettings = mkEnableOption ''
|
|
Whether to merge <literal>c3d2.nncp.<…>.nncp</literal>
|
|
into <literal>programs.nncp.settings</literal>.
|
|
'';
|
|
|
|
k-ot.enable = mkEnableOption ''
|
|
Add k-ot user to this machine. Anyone with an SSH key listed in
|
|
<literal>c3d2.users</literal> can log in as this user.
|
|
'';
|
|
|
|
hq = {
|
|
interface = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
example = "eth0";
|
|
description = ''
|
|
Configure the given interface name with an internal IP address.
|
|
'';
|
|
};
|
|
|
|
enableBinaryCache = mkOption {
|
|
type = types.bool;
|
|
default = cfg.isInHq;
|
|
defaultText = literalExample "config.c3d2.isInHq";
|
|
description = "Whether to enable the local Nix binary cache";
|
|
};
|
|
|
|
enableMpdProxy = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Whether to proxy the local MPD database";
|
|
};
|
|
|
|
journalToMqtt = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
};
|
|
};
|
|
|
|
hosts =
|
|
mkOption {
|
|
type = types.attrsOf (types.submodule {
|
|
options = {
|
|
ether = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
};
|
|
ip4 = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
};
|
|
ip6 = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
};
|
|
publicKey = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
};
|
|
wol = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
};
|
|
serial = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
description = ''
|
|
Hardware serial number to help identification when netbooting.
|
|
'';
|
|
};
|
|
};
|
|
});
|
|
};
|
|
|
|
nncp = {
|
|
neigh = mkOption {
|
|
type = with types; attrsOf neighMod;
|
|
default = { };
|
|
description = ''
|
|
Attrset of NNCP neighbours for relaying packets.
|
|
User endpoints go in <literal>c3d2.users</literal>.
|
|
'';
|
|
};
|
|
};
|
|
|
|
users = mkOption {
|
|
type = types.attrsOf (types.submodule {
|
|
options.sshKeys = mkOption {
|
|
type = with types; listOf str;
|
|
default = [ ];
|
|
};
|
|
});
|
|
};
|
|
};
|
|
|
|
config =
|
|
let
|
|
adminKeys = (with builtins; lib.lists.flatten (
|
|
map
|
|
(getAttr "sshKeys")
|
|
(attrValues cfg.users)
|
|
));
|
|
mkIfIsInHq = x: lib.mkIf cfg.isInHq (lib.mkDefault x);
|
|
in
|
|
{
|
|
networking.hosts = lib.mkIf cfg.mergeHostsFile
|
|
((
|
|
lib.attrsets.mapAttrs' (n: v: { name = v.ip4; value = [ "${n}.c3d2" ]; })
|
|
(lib.attrsets.filterAttrs (n: v: v.ip4 != null) cfg.hosts)
|
|
) // (
|
|
lib.attrsets.mapAttrs' (n: v: { name = v.ip6; value = [ "${n}.c3d2" ]; })
|
|
(lib.attrsets.filterAttrs (n: v: v.ip6 != null) cfg.hosts)
|
|
));
|
|
|
|
programs.nncp.settings = lib.optionalAttrs cfg.mergeNncpSettings cfg.nncp;
|
|
|
|
users.motd = lib.mkIf cfg.enableMotd (builtins.readFile ./motd);
|
|
|
|
users = {
|
|
users = {
|
|
k-ot = lib.mkIf cfg.k-ot.enable {
|
|
createHome = true;
|
|
isNormalUser = true;
|
|
uid = 1000;
|
|
extraGroups = [
|
|
"audio"
|
|
"video"
|
|
"wheel"
|
|
];
|
|
password = "k-otk-ot";
|
|
openssh.authorizedKeys.keys = adminKeys;
|
|
};
|
|
|
|
root.openssh.authorizedKeys.keys = lib.mkIf cfg.allUsersCanSshRoot adminKeys;
|
|
};
|
|
};
|
|
|
|
services.vector = lib.mkIf config.c3d2.hq.journalToMqtt {
|
|
enable = true;
|
|
journaldAccess = true;
|
|
settings = {
|
|
sources.journal = {
|
|
type = "journald";
|
|
current_boot_only = true;
|
|
};
|
|
sinks.mqtt = {
|
|
inputs = [ "journal" ];
|
|
type = "mqtt";
|
|
host = "broker.serv.zentralwerk.org";
|
|
# port = 8883;
|
|
user = "SECRET[mqtt.user]";
|
|
password = "SECRET[mqtt.password]";
|
|
client_id = "vector-${config.networking.hostName}";
|
|
encoding.codec = "json";
|
|
topic = "journal/{{ host }}/{{ _SYSTEMD_UNIT }}/{{ PRIORITY }}";
|
|
# tls.enabled = true;
|
|
# tls.ca_file = "/etc/ssl/certs/ca-certificates.crt";
|
|
};
|
|
secret.mqtt =
|
|
let
|
|
catSecrets = with pkgs; writeScript "cat-vector-secrets" ''
|
|
#!${runtimeShell} -e
|
|
echo '{'
|
|
COMMA=n
|
|
for F in $@; do
|
|
if [ $COMMA = y ]; then
|
|
echo ' ,'
|
|
else
|
|
COMMA=y
|
|
fi
|
|
|
|
echo ' "'$(basename $F)'": {"value": "'$(cat $F)'", "error": null }'
|
|
done
|
|
echo '}'
|
|
'';
|
|
in
|
|
{
|
|
type = "exec";
|
|
command = [
|
|
catSecrets
|
|
config.sops.secrets."mqtt/user".path
|
|
config.sops.secrets."mqtt/password".path
|
|
];
|
|
};
|
|
};
|
|
};
|
|
sops.secrets = lib.mkIf config.c3d2.hq.journalToMqtt {
|
|
"mqtt/user" = {
|
|
sopsFile = ../modules/mqtt.yaml;
|
|
owner = config.systemd.services.vector.serviceConfig.User;
|
|
};
|
|
"mqtt/password" = {
|
|
sopsFile = ../modules/mqtt.yaml;
|
|
owner = config.systemd.services.vector.serviceConfig.User;
|
|
};
|
|
};
|
|
|
|
assertions = [
|
|
{
|
|
assertion = cfg.isInHq -> (config.users.users.root.password == null);
|
|
message = "Root passwords not allowed in HQ";
|
|
}
|
|
{
|
|
assertion = cfg.hq.enableBinaryCache -> cfg.mergeHostsFile;
|
|
message = "mergeHostsFile must be enabled for enableBinaryCache";
|
|
}
|
|
{
|
|
assertion = cfg.hq.enableMpdProxy -> cfg.mergeHostsFile;
|
|
message = "mergeHostsFile must be enabled for enableMpdProxy";
|
|
}
|
|
{
|
|
assertion = cfg.isInHq -> builtins.hasAttr config.networking.hostName cfg.hosts;
|
|
message = "${config.networking.hostName} is not registered in ${toString ../host-registry.nix}";
|
|
}
|
|
(
|
|
# Check for host registry address collisions
|
|
let
|
|
getAddrHosts = key:
|
|
builtins.foldl'
|
|
(result: host:
|
|
if cfg.hosts.${host}.${key} != null
|
|
then
|
|
let
|
|
addr = cfg.hosts."${host}"."${key}";
|
|
in
|
|
if result ? "${addr}"
|
|
then result // {
|
|
"${addr}" = result."${addr}" ++ [ host ];
|
|
}
|
|
else result // {
|
|
"${addr}" = [ host ];
|
|
}
|
|
else result
|
|
)
|
|
{ }
|
|
(builtins.attrNames cfg.hosts);
|
|
dupHosts =
|
|
builtins.concatMap
|
|
(hosts:
|
|
if builtins.length hosts == 1
|
|
then [ ]
|
|
else hosts
|
|
)
|
|
(
|
|
builtins.attrValues (
|
|
getAddrHosts "ip4" // getAddrHosts "ip6"
|
|
)
|
|
);
|
|
in
|
|
{
|
|
assertion = dupHosts == [ ];
|
|
message = "Hosts have duplicate addresses: ${lib.concatStringsSep " " dupHosts}";
|
|
}
|
|
)
|
|
];
|
|
|
|
boot.cleanTmpDir = true;
|
|
|
|
documentation.nixos.enable = false;
|
|
|
|
c3d2.allUsersCanSshRoot = lib.mkDefault true;
|
|
|
|
i18n = {
|
|
defaultLocale = "en_US.UTF-8";
|
|
supportedLocales = [
|
|
"en_US.UTF-8/UTF-8"
|
|
"de_DE.UTF-8/UTF-8"
|
|
];
|
|
};
|
|
|
|
systemd.network.networks = lib.mkIf (cfg.hq.interface != null && config.networking.useNetworkd) {
|
|
"40-eth0".routes = [{
|
|
routeConfig = {
|
|
Gateway = "172.22.99.4";
|
|
GatewayOnLink = true;
|
|
};
|
|
}];
|
|
};
|
|
|
|
networking = {
|
|
defaultGateway = lib.mkIf (!config.networking.useNetworkd) (
|
|
mkIfIsInHq "172.22.99.4"
|
|
);
|
|
|
|
domain = mkIfIsInHq "hq.c3d2.de";
|
|
|
|
interfaces = lib.mkIf (cfg.hq.interface != null) {
|
|
"${cfg.hq.interface}".ipv6.addresses = [{
|
|
address = toHqPrivateAddress config.networking.hostName;
|
|
prefixLength = 64;
|
|
}];
|
|
};
|
|
|
|
nameservers = with hostRegistry.hosts.dnscache; [
|
|
ip4
|
|
ip6
|
|
"9.9.9.9"
|
|
];
|
|
useHostResolvConf = lib.mkIf (!config.services.resolved.enable) true;
|
|
};
|
|
|
|
environment.etc."resolv.conf" = lib.mkIf (!config.services.resolved.enable) {
|
|
text = lib.concatMapStrings
|
|
(ns: ''
|
|
nameserver ${ns}
|
|
'')
|
|
config.networking.nameservers;
|
|
};
|
|
|
|
nix = {
|
|
settings = {
|
|
auto-optimise-store = true;
|
|
trusted-public-keys = lib.mkIf (config.networking.hostName != "hydra") [
|
|
(builtins.readFile ../hosts/hydra/cache-pub.key)
|
|
];
|
|
substituters = lib.mkIf (config.networking.hostName != "hydra") (
|
|
lib.mkBefore [ "https://nix-serve.hq.c3d2.de" ]
|
|
);
|
|
};
|
|
gc = {
|
|
automatic = true;
|
|
dates = "06:00";
|
|
options = "--delete-older-than 21d";
|
|
randomizedDelaySec = "6h";
|
|
};
|
|
registry.c3d2 = {
|
|
from = {
|
|
id = "c3d2";
|
|
type = "indirect";
|
|
};
|
|
to = {
|
|
type = "git";
|
|
url = "https://gitea.c3d2.de/C3D2/nix-config.git";
|
|
};
|
|
};
|
|
extraOptions = ''
|
|
experimental-features = nix-command flakes
|
|
builders-use-substitutes = true
|
|
'';
|
|
};
|
|
|
|
services.openssh = {
|
|
# Required for deployment
|
|
enable = true;
|
|
permitRootLogin = "prohibit-password";
|
|
};
|
|
|
|
sops.age.sshKeyPaths = lib.mkDefault [ "/etc/ssh/ssh_host_ed25519_key" ];
|
|
|
|
environment = {
|
|
systemPackages = with pkgs; [
|
|
# Network fetchers
|
|
curl
|
|
wget
|
|
git
|
|
# System monitors
|
|
htop
|
|
iotop
|
|
bmon
|
|
ripgrep
|
|
# Terminal managers
|
|
tmux
|
|
screen
|
|
# Editors
|
|
vim
|
|
# Pipeview
|
|
pv
|
|
# Network debugging
|
|
tcpdump
|
|
ethtool
|
|
mtr
|
|
];
|
|
variables = {
|
|
# TERM = "xterm-256color";
|
|
};
|
|
# breaks various package builds
|
|
noXlibs = lib.mkForce false;
|
|
};
|
|
|
|
programs = {
|
|
ssh.knownHosts = with builtins;
|
|
let
|
|
intersectKeys = intersectAttrs {
|
|
publicKey = null;
|
|
publicKeyFile = null;
|
|
};
|
|
list = map
|
|
(name:
|
|
let
|
|
host = getAttr name cfg.hosts;
|
|
sshAttrs = intersectKeys host;
|
|
in
|
|
if sshAttrs == { } then
|
|
null
|
|
else {
|
|
inherit name;
|
|
value =
|
|
let ip6 = if host.ip6 != null then host.ip6 else toHqPrivateAddress name;
|
|
in
|
|
{
|
|
publicKey = null;
|
|
publicKeyFile = null;
|
|
hostNames = [ ip6 "${name}.hq.c3d2.de" "${name}.hq" name ];
|
|
} // sshAttrs;
|
|
})
|
|
(builtins.attrNames cfg.hosts);
|
|
keyedHosts = filter (x: x.value.publicKey != null || x.value.publicKeyFile != null) list;
|
|
in
|
|
listToAttrs keyedHosts;
|
|
|
|
vim.defaultEditor = true;
|
|
};
|
|
|
|
services.nginx = lib.mkIf config.services.nginx.enable {
|
|
recommendedGzipSettings = true;
|
|
recommendedOptimisation = true;
|
|
recommendedProxySettings = true;
|
|
recommendedTlsSettings = true;
|
|
};
|
|
|
|
time.timeZone = lib.mkDefault "Europe/Berlin";
|
|
|
|
# Reboot on hang
|
|
systemd.watchdog = lib.mkIf (!config.boot.isContainer) {
|
|
runtimeTime = "15s";
|
|
rebootTime = "15s";
|
|
};
|
|
|
|
# Defaults for LetsEncrypt
|
|
security.acme =
|
|
if options.security.acme ? defaults
|
|
then {
|
|
acceptTerms = true;
|
|
# NixOS>=22.05
|
|
defaults = {
|
|
email = cfg.acmeEmail;
|
|
# letsencrypt staging server with way higher rate limits
|
|
# server = "https://acme-staging-v02.api.letsencrypt.org/directory";
|
|
};
|
|
}
|
|
else {
|
|
acceptTerms = true;
|
|
# TODO: NixOS<=21.05
|
|
email = cfg.acmeEmail;
|
|
};
|
|
zramSwap.enable = true;
|
|
};
|
|
}
|