nix-config/lib/default.nix

313 lines
8.2 KiB
Nix

# This module is for use by all C3D2 machines.
# That includes physical servers, VMs, containers, and personal machines.
#
{ config, lib, pkgs, ... }:
let
hqPrefix64 = "fd23:42:c3d2:523";
# TODO: Is this stable? Is there a better place to specifiy this?
# 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;
# toHqPublicAddress = toIpv6Address publicPrefix64;
cfg = config.c3d2;
in {
imports = [
./users
./stats.nix
./openwebrx.nix
./audio-server
./pi-sensors.nix
];
options.c3d2 = with lib;
with lib.types; {
isInHq = mkEnableOption "HQ presence (TODO: what is this? association to VLAN 5?)";
enableMotd = mkOption {
type = bool;
default = cfg.isInHq;
defaultText = literalExample "config.c3d2.isInHq";
};
mapPublicHosts = mkOption {
type = bool;
default = false;
description = ''
Whether to add all external HQ host mappings to /etc/hosts.
'';
};
mapHqHosts = mkOption {
type = bool;
default = cfg.isInHq;
description = ''
Whether to add all internal HQ host mappings to /etc/hosts.
'';
};
acmeEmail = mkOption {
type = str;
default = "mail@c3d2.de";
description = ''
Admin email address to use for Letsencrypt
'';
};
hq = {
/* externalInterface = mkOption {
type = nullOr str;
default = null;
example = "eth0";
description = ''
Configure the given interface name with an external IP address.
'';
};
*/
interface = mkOption {
type = nullOr str;
default = null;
example = "eth0";
description = ''
Configure the given interface name with an internal IP address.
'';
};
enableBinaryCache = mkOption {
type = bool;
default = cfg.isInHq;
defaultText = literalExample "config.c3d2.isInHq";
description = "Whether to enable the local Nix binary cache";
};
enableMpdProxy = mkOption {
type = bool;
default = false;
description = "Whether to proxy the local MPD database";
};
yggdrasil.enableGateway = mkEnableOption
"Whether to join the host to the Yggdrasil network via a gateway";
};
};
config = let
cfg = config.c3d2;
hostRegistry = import ../host-registry.nix;
mkIfIsInHq = x: lib.mkIf cfg.isInHq (lib.mkDefault x);
in {
# Configuration specific to this machine
assertions = [
{
assertion = cfg.isInHq -> (config.users.users.root.password == null);
message = "Root passwords not allowed in HQ";
}
{
assertion = let
check = hostName: hostName == config.networking.hostName;
checkRegistry = list: builtins.any check list;
in cfg.isInHq -> checkRegistry hostRegistry.hqLocal;
message = "${config.networking.hostName} is not registered in ${
toString ../host-registry.nix
}";
}
{
assertion = cfg.hq.enableBinaryCache -> cfg.mapHqHosts;
message = "mapHqHosts must be enabled for enableBinaryCache";
}
{
assertion = cfg.hq.enableMpdProxy -> cfg.mapHqHosts;
message = "mapHqHosts must be enabled for enableMpdProxy";
}
];
networking.defaultGateway = lib.mkIf (!config.networking.useNetworkd) (
mkIfIsInHq "172.22.99.4"
);
networking.domain = mkIfIsInHq "hq.c3d2.de";
users.motd = lib.mkIf cfg.enableMotd (builtins.readFile ./motd);
networking.hosts = let
getHost = hostName: builtins.getAttr hostName hostRegistry.hosts;
mapHostsNamesToAttrs = f: list: builtins.listToAttrs (map f list);
/* hqPublicHosts = mapHostsNamesToAttrs (hostName: {
name = toHqPublicAddress hostName;
value = [ "${hostName}.hq.c3d2.de" hostName ];
}) hostRegistry.hqPublic;
*/
hqLocalHosts = with builtins;
let
f = hostName:
let
host = getHost hostName;
ip6 = if hasAttr "ip6" host then
host.ip6
else
toHqPrivateAddress hostName;
in [{
name = ip6;
value = [ "${hostName}.hq" hostName ];
}] ++ lib.optional (hasAttr "ip4" host) {
name = host.ip4;
value = [ "${hostName}.hq" hostName ];
};
in listToAttrs (concatLists (map f (attrNames hostRegistry.hosts)));
in if cfg.mapHqHosts then hqLocalHosts else { };
systemd.network.networks =
if cfg.hq.interface != null && config.networking.useNetworkd
then {
"40-eth0".routes = [ {
routeConfig = {
Gateway = "172.22.99.4";
GatewayOnLink = true;
};
} ];
} else {};
networking.interfaces =
/* (if cfg.hq.externalInterface == null then
{ }
else {
"${cfg.hq.externalInterface}" = {
ipv6.addresses = [{
address = toHqPublicAddress config.networking.hostName;
prefixLength = 64;
}];
};
}) //
*/
(if cfg.hq.interface == null then
{ }
else {
"${cfg.hq.interface}" = {
ipv6.addresses = [{
address = toHqPrivateAddress config.networking.hostName;
prefixLength = 64;
}];
};
});
nix = {
autoOptimiseStore = true;
extraOptions = "experimental-features = nix-command flakes";
gc = {
automatic = true;
dates = "weekly";
};
package = pkgs.nixUnstable;
registry.c3d2 = {
from = {
id = "c3d2";
type = "indirect";
};
to = {
type = "git";
url = "git+ssh://gitea@gitea.c3d2.de:C3D2/nix-config.git";
};
};
};
# Required for deployment
services.openssh = {
enable = true;
permitRootLogin = "prohibit-password";
};
environment = {
systemPackages = with pkgs; [
curl
git
htop
tmux
vim
wget
];
variables = {
TERM = "xterm-256color";
};
};
programs = {
ssh.knownHosts = with builtins;
let
hostNames = hostRegistry.hqLocal;
intersectKeys = intersectAttrs {
publicKey = null;
publicKeyFile = null;
};
list = map (name:
let
host = getAttr name hostRegistry.hosts;
sshAttrs = intersectKeys host;
in if sshAttrs == { } then
null
else {
inherit name;
value = let
ip6 = if hasAttr "ip6" host then
host.ip6
else
toHqPrivateAddress name;
in {
publicKey = null;
publicKeyFile = null;
hostNames = [ ip6 "${name}.hq.c3d2.de" "${name}.hq" name ];
} // sshAttrs;
}) hostNames;
keyedHosts = filter (x: x != null) list;
in listToAttrs keyedHosts;
vim.defaultEditor = true;
};
services.mpd.extraConfig = lib.mkIf cfg.hq.enableMpdProxy ''
database {
plugin "proxy"
host "mpd-index.hq"
}
'';
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 = {
acceptTerms = true;
email = cfg.acmeEmail;
};
};
meta.maintainers = with lib.maintainers; [ ehmry ];
}