# Edit this configuration file to define what should be installed on # your system. Help is availanodev"; # https://search.nixos.org/options and in the NixOS manual (`nixos-help`). { lib, pkgs, ... }: let services = { ssh = { tcp = [ 22 ]; }; dhcp = { udp = [ 67 68 ]; }; dns = { udp = [ 53 ]; }; web = { tcp = [ 80 443 ]; }; }; vlans = { koti = { id = 10; ipv6 = true; availableServices = with services; [ dhcp dns ssh ]; }; gast = { id = 20; ipv6 = true; staticLeases = [ { macAddress = "dc:a6:32:05:08:5d"; address = "10.20.1.235"; } ]; availableServices = with services; [ dhcp dns ]; }; iot = { id = 30; availableServices = with services; [ dhcp dns ]; }; cfg = { id = 40; staticLeases = [ { macAddress = "8c:3b:ad:c5:b8:ee"; address = "10.40.0.10"; } ]; availableServices = with services; [ dhcp dns ]; }; }; pvid = vlans.cfg.id; vlanIds = lib.map ({ id, ... }: id) (lib.attrValues vlans); vlanRange = { min = lib.foldr lib.min 999 vlanIds; max = lib.foldr lib.max 0 vlanIds; }; buildVlanNetdev = name: { id, ... }: { name = "20-${name}"; value = { netdevConfig = { Name = name; Kind = "vlan"; MACAddress = "00:0d:b9:49:d2:${toString id}"; }; vlanConfig = { Id = id; }; }; }; buildStaticLease = { macAddress, address }: '' [DHCPServerStaticLease] MACAddress=${macAddress} Address=${address} ''; buildBridgeVLANConfig = id: { VLAN = id; }; buildVlanNetwork = name: { id, ipv6 ? false, staticLeases ? [ ], ... }: { name = "30-${name}"; value = { matchConfig = { Name = name; }; networkConfig = { Address = "10.${toString id}.0.1/23"; IPMasquerade = "ipv4"; DHCPServer = true; IPv6AcceptRA = false; IPv6SendRA = ipv6; DHCPPrefixDelegation = ipv6; }; dhcpServerConfig = { PoolOffset = 255; DNS = "10.${toString id}.0.1"; }; extraConfig = lib.concatLines (lib.map buildStaticLease staticLeases); }; }; buildFirewallRules = name: { availableServices ? [ ], ... }: { allowedUDPPorts = lib.flatten (lib.map ({ udp ? [ ], ... }: udp) availableServices); allowedTCPPorts = lib.flatten (lib.map ({ tcp ? [ ], ... }: tcp) availableServices); }; vlanNetdevs = lib.mapAttrs' buildVlanNetdev vlans; vlanNetworks = lib.mapAttrs' buildVlanNetwork vlans; firewallRules = lib.mapAttrs buildFirewallRules ( vlans // { "tailscale*".availableServices = with services; [ ssh web ]; } ); nginxVhost = options: { http2 = true; forceSSL = true; enableACME = true; acmeRoot = null; } // options; nginxProxy = options: { proxyWebsockets = true; } // options; in { boot.loader.grub.enable = true; boot.loader.grub.device = "/dev/sda"; networking.hostName = "apu"; networking.useNetworkd = true; systemd.network = { enable = true; config.networkConfig.IPv6Forwarding = true; links = { "10-extern0" = { matchConfig.Path = "pci-0000:01:00.0"; linkConfig.Name = "extern0"; }; "10-intern0" = { matchConfig.Path = "pci-0000:02:00.0"; linkConfig.Name = "intern0"; }; "10-intern1" = { matchConfig.Path = "pci-0000:03:00.0"; linkConfig.Name = "intern1"; }; }; netdevs = { "20-lan" = { netdevConfig = { Name = "lan"; Kind = "bridge"; }; bridgeConfig = { VLANFiltering = true; DefaultPVID = pvid; }; }; } // vlanNetdevs; networks = { "30-bind-lan" = { matchConfig = { Name = "intern*"; }; networkConfig = { Bridge = "lan"; }; extraConfig = '' [BridgeVLAN] VLAN=${toString vlanRange.min}-${toString vlanRange.max} EgressUntagged=${toString pvid} PVID=${toString pvid} ''; }; "30-lan" = { matchConfig = { Name = "lan"; }; networkConfig = { IPv6AcceptRA = false; ConfigureWithoutCarrier = true; }; vlan = lib.attrNames vlans; bridgeVLANs = lib.map buildBridgeVLANConfig vlanIds; }; "30-wan" = { matchConfig = { Name = "extern0"; }; networkConfig = { DHCP = true; DNS = "127.0.0.1"; IPv6AcceptRA = true; IPv4Forwarding = true; }; dhcpV6Config = { PrefixDelegationHint = "::/56"; }; dhcpV4Config = { Use6RD = true; }; }; } // vlanNetworks; }; time.timeZone = "Europe/Helsinki"; users.users.jokke = { isNormalUser = true; extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user. packages = [ pkgs.nh ]; openssh.authorizedKeys.keys = [ "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLIUkESu5NnBi1M0+ZjYrkp6/rIFuwc3aguspf98jmOydNce6l65cnS3GRzc9oWx4lu11ahi87ZuE+pYV+gaHm4=" ]; initialPassword = "change-me"; }; environment.systemPackages = with pkgs; [ wget curl dig neovim vim htop ]; services.openssh = { enable = true; openFirewall = false; settings.PasswordAuthentication = false; }; services.tailscale = { enable = true; useRoutingFeatures = "both"; }; services.resolved.enable = false; services.nextdns = { enable = true; arguments = [ "-profile" "9c4ac9" "-setup-router" "-mdns" "koti" ]; }; services.home-assistant = { enable = true; extraComponents = [ # Components required to complete the onboarding "esphome" "met" "radio_browser" "yeelight" "xiaomi_aqara" "shelly" ]; extraPackages = python3Packages: with python3Packages; [ gtts numpy ]; config = { homeassistant = { name = "Koti"; unit_system = "metric"; time_zone = "Europe/Helsinki"; }; http = { use_x_forwarded_for = true; trusted_proxies = "127.0.0.1"; }; default_config = { }; }; }; services.nginx.virtualHosts."koti.repomaa.com" = nginxVhost { extraConfig = '' proxy_buffering off; ''; locations."/" = nginxProxy { proxyPass = "http://127.0.0.1:8123"; }; }; services.nginx = { enable = true; recommendedProxySettings = true; recommendedTlsSettings = true; recommendedBrotliSettings = true; recommendedGzipSettings = true; recommendedZstdSettings = true; recommendedOptimisation = true; }; security.acme = { acceptTerms = true; defaults = { dnsProvider = "hetzner"; environmentFile = "/var/secrets/lego"; email = "admin@j.repomaa.com"; }; }; networking.nftables.enable = true; networking.firewall.enable = true; networking.useDHCP = false; networking.firewall.interfaces = firewallRules; system.stateVersion = "24.05"; }