# 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 ]; }