{ config, lib, pkgs, ... }: let inherit (lib) mkDefault mkIf mkMerge mkOption mkOverride types; cfg = config.beatific; mkFlag = description: mkOption { type = types.bool; inherit description; default = true; }; in { options = { beatific = { # The host name is reused for beatific-specific configuration. # The bulk of common config is handled in beatific.defaults below, but # having one option without a default ensures that the module cannot be # imported accidentally. hostName = mkOption { type = types.str; description = "Hostname"; }; isLighthouse = mkOption { type = types.bool; description = "Whether this host is a Nebula lighthouse"; default = false; }; # Groups of related defaults can be disabled by flipping off the switches here: # beatific.defaults.${category} = false; # They default to true because the point is to do these things by default. defaults = { time = mkFlag "Default time zone and NTP"; i18n = mkFlag "Default locale settings"; programs = mkFlag "Default installed programs"; ssh = mkFlag "Default sshd settings"; nebula = mkFlag "Default beatific nebula settings"; tvb = mkFlag "Default tvb account"; }; }; }; config = mkMerge [ { # Options to always set networking.hostName = cfg.hostName; nix.extraOptions = "experimental-features = nix-command flakes"; } (mkIf cfg.defaults.time { # mkDefault time zone to make it easy to configure it to non-UTC time.timeZone = mkDefault "UTC"; services.ntp.enable = true; services.ntp.servers = [ "time.nist.gov" ]; }) (mkIf cfg.defaults.i18n { # en_US.UTF-8 i18n.defaultLocale = "en_US.UTF-8"; i18n.extraLocaleSettings = { LC_ADDRESS = "en_US.UTF-8"; LC_IDENTIFICATION = "en_US.UTF-8"; LC_MEASUREMENT = "en_US.UTF-8"; LC_MONETARY = "en_US.UTF-8"; LC_NAME = "en_US.UTF-8"; LC_NUMERIC = "en_US.UTF-8"; LC_PAPER = "en_US.UTF-8"; LC_TELEPHONE = "en_US.UTF-8"; LC_TIME = "en_US.UTF-8"; }; }) (mkIf cfg.defaults.programs { environment.systemPackages = with pkgs; [ curl duf file git htop nebula python3 tree vim wget ]; # The nixpkgs default is "nano", so we go one priority higher environment.variables.EDITOR = mkOverride 999 "vim"; }) (mkIf cfg.defaults.ssh { services.openssh.enable = true; services.openssh.banner = let ascii = import ./ascii.nix; in ascii.${cfg.hostName} or ascii.beatific; networking.firewall.allowPing = true; networking.firewall.allowedTCPPorts = [ 22 ]; }) (mkIf cfg.defaults.nebula { services.nebula.networks.beatific = let empyreanExternalDns = "vpn.alogoulogoi.com"; empyreanInternalIp = "10.22.20.1"; nebulaPort = 4242; in { enable = true; # The lighthouse only listens on the designated subdomain listen.host = if cfg.isLighthouse then empyreanExternalDns else "0.0.0.0"; listen.port = nebulaPort; # Standard certificate paths ca = "/etc/nebula/beatific/beatific.crt"; cert = "/etc/nebula/beatific/${cfg.hostName}.crt"; key = "/etc/nebula/beatific/${cfg.hostName}.key"; isLighthouse = cfg.isLighthouse; # Non-lighthouses connect to the lighthouse at empyrean # This should be a VPN address in the static host map lighthouses = mkIf (! cfg.isLighthouse) [ empyreanInternalIp ]; # Currently there is no VPN-level traffic filtering firewall.outbound = [ { port = "any"; proto = "any"; host = "any"; } ]; firewall.inbound = [ { port = "any"; proto = "any"; host = "any"; } ]; # Map the lighthouse address to its public address staticHostMap = { ${empyreanInternalIp} = [ "${empyreanExternalDns}:${toString nebulaPort}" ]; }; settings = { # Enable UDP holepunching both ways, which allows nodes to establish more direct connections with each other punchy = { punch = true; response = true; }; }; }; }) (mkIf cfg.defaults.tvb { users.groups.tvb = {}; users.users.tvb = { isNormalUser = true; group = "tvb"; extraGroups = [ "wheel" ]; initialPassword = "password"; openssh.authorizedKeys.keyFiles = [ ../keys/tvb.backyard.pub ../keys/tvb.catacomb.pub ../keys/tvb.empyrean.pub ../keys/tvb.palamas.pub ../keys/tvb.stagirite.pub ../keys/tvb.unfolder.pub ../keys/tvb.vagrant.pub ]; }; }) ]; }