{ 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"; tvbSync = mkFlag "Configure system syncthing for tvb"; hosts = mkFlag "Default 10.22.20.* DNS host entries"; }; }; }; config = mkMerge [ { # Options to always set networking.hostName = cfg.hostName; nix.extraOptions = "experimental-features = nix-command flakes"; environment.etc.nixos.source = ./..; environment.shellAliases.nr = "sudo nixos-rebuild --flake $HOME/nixos-configs"; security.sudo.extraRules = [{ users = [ "tvb" ]; commands = [ { command = "/run/current-system/sw/bin/nixos-rebuild"; options = [ "NOPASSWD" ]; } ]; }]; } (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; [ bc # Terminal calculator curl duf file killall nebula python3 tree vim wget ]; programs = { git.enable = true; htop.enable = true; }; # 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.imperium.pub ../keys/tvb.palamas.pub ../keys/tvb.stagirite.pub ../keys/tvb.unfolder.pub ../keys/tvb.vagrant.pub ]; }; }) (mkIf cfg.defaults.tvbSync { # I haven't gotten user services to work correctly yet, # so for now, tvb monopolizes the system syncthing instance. # Adding users in the future should probably involve multiple # system services so as not to require login to sync. services.syncthing = { enable = true; dataDir = "/home/tvb"; openDefaultPorts = true; user = "tvb"; group = "tvb"; }; }) (mkIf cfg.defaults.hosts { # Create *.home host entries for all the beatific members networking.hosts = { "10.22.20.1" = [ "empyrean.home" ]; "10.22.20.2" = [ "catacomb.home" ]; "10.22.20.3" = [ "palamas.home" ]; "10.22.20.4" = [ "stagirite.home" ]; "10.22.20.5" = [ "vagrant.home" ]; "10.22.20.6" = [ "unfolder.home" ]; "10.22.20.7" = [ "centroid.home" ]; "10.22.20.8" = [ "backyard.home" "jellyfin.backyard.home" ]; "10.22.20.9" = [ "imperium.home" ]; }; }) ]; }