diff --git a/hosts/orm/configuration.nix b/hosts/orm/configuration.nix index a1063ad..cd96237 100644 --- a/hosts/orm/configuration.nix +++ b/hosts/orm/configuration.nix @@ -4,11 +4,17 @@ imports = [ ./hardware-configuration.nix ./home.nix - ./wireguard.nix ]; boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; + age.secrets.wireguard-peer-orm.file = ../../secrets/wireguard-peer-orm.age; + + birdsong.peer = { + enable = true; + privateKeyFile = config.age.secrets.wireguard-peer-orm.path; + }; + system.stateVersion = "23.11"; } diff --git a/hosts/orm/wireguard.nix b/hosts/orm/wireguard.nix deleted file mode 100644 index be20446..0000000 --- a/hosts/orm/wireguard.nix +++ /dev/null @@ -1,33 +0,0 @@ -{ config, lib, pkgs, ... }: - -{ - age.secrets.wireguard-hub.file = ../../secrets/wireguard-hub.age; - - networking = { - nat = { - enable = true; - externalInterface = "ens3"; - internalInterfaces = [ "wg0" ]; - }; - - firewall.allowedUDPPorts = [ config.networking.wireguard.interfaces.wg0.listenPort ]; - - wireguard.interfaces.wg0 = { - ips = [ "10.127.1.1/24" "fd70:81ca:0f8f:1::1/64" ]; - listenPort = 51820; - privateKeyFile = config.age.secrets.wireguard-hub.path; - peers = [ - { - name = "shaw"; - publicKey = "eD79pROC2zjhKz4tGRS43O95gcFRqO+SFb2XDnTr0zc="; - allowedIPs = [ "10.127.1.2" "fd70:81ca:0f8f:1::2" ]; - } - { - name = "tohru"; - publicKey = "lk3PCQM1jmZoI8sM/rWSyKNuZOUnjox3n9L9geJD+18="; - allowedIPs = [ "10.127.1.3" "fd70:81ca:0f8f:1::3" ]; - } - ]; - }; - }; -} diff --git a/hosts/tohru/configuration.nix b/hosts/tohru/configuration.nix index 64d293a..4218f5d 100644 --- a/hosts/tohru/configuration.nix +++ b/hosts/tohru/configuration.nix @@ -8,13 +8,20 @@ ../../services/fonts.nix ../../services/steam.nix ./syncthing.nix - ./wireguard.nix ]; boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; boot.loader.systemd-boot.editor = false; + age.secrets.wireguard-peer-tohru.file = ../../secrets/wireguard-peer-tohru.age; + + birdsong.peer = { + enable = true; + privateKeyFile = config.age.secrets.wireguard-peer-tohru.path; + persistentKeepalive = 23; + }; + programs.evolution.enable = true; qenya.services.fonts.enable = true; qenya.services.steam.enable = true; diff --git a/hosts/tohru/wireguard.nix b/hosts/tohru/wireguard.nix deleted file mode 100644 index dc52429..0000000 --- a/hosts/tohru/wireguard.nix +++ /dev/null @@ -1,23 +0,0 @@ -{ config, lib, pkgs, ... }: - -{ - age.secrets.wireguard-peer-tohru.file = ../../secrets/wireguard-peer-tohru.age; - - networking = { - firewall.allowedUDPPorts = [ config.networking.wireguard.interfaces.wg0.listenPort ]; - - wireguard.interfaces.wg0 = { - ips = [ "10.127.1.3/24" "fd70:81ca:0f8f:1::3/64" ]; - listenPort = 51821; - privateKeyFile = config.age.secrets.wireguard-peer-tohru.path; - peers = [ - { - publicKey = "birdLVh8roeZpcVo308Ums4l/aibhAxbi7MBsglkJyA="; - allowedIPs = [ "10.127.1.0/24" "fd70:81ca:0f8f:1::/64" ]; - endpoint = "birdsong.network:51820"; - persistentKeepalive = 23; - } - ]; - }; - }; -} diff --git a/hosts/yevaud/configuration.nix b/hosts/yevaud/configuration.nix index 7abce5a..4fc10f9 100644 --- a/hosts/yevaud/configuration.nix +++ b/hosts/yevaud/configuration.nix @@ -9,6 +9,13 @@ boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; + age.secrets.wireguard-peer-yevaud.file = ../../secrets/wireguard-peer-yevaud.age; + + birdsong.peer = { + enable = true; + privateKeyFile = config.age.secrets.wireguard-peer-yevaud.path; + }; + qenya.services.forgejo = { enable = true; domain = "git.qenya.tel"; diff --git a/secrets.nix b/secrets.nix index a5423aa..0e3f84f 100644 --- a/secrets.nix +++ b/secrets.nix @@ -5,8 +5,8 @@ let secrets = with keys; { wireguard-hub = [ machines.orm ]; - wireguard-peer-orm = [ machines.orm ]; wireguard-peer-tohru = [ machines.tohru ]; + wireguard-peer-yevaud = [ machines.yevaud ]; }; in builtins.listToAttrs ( diff --git a/secrets/wireguard-hub.age b/secrets/wireguard-peer-orm.age similarity index 100% rename from secrets/wireguard-hub.age rename to secrets/wireguard-peer-orm.age diff --git a/secrets/wireguard-hub.pub b/secrets/wireguard-peer-orm.pub similarity index 100% rename from secrets/wireguard-hub.pub rename to secrets/wireguard-peer-orm.pub diff --git a/secrets/wireguard-peer-yevaud.age b/secrets/wireguard-peer-yevaud.age new file mode 100644 index 0000000..d331bda Binary files /dev/null and b/secrets/wireguard-peer-yevaud.age differ diff --git a/secrets/wireguard-peer-yevaud.pub b/secrets/wireguard-peer-yevaud.pub new file mode 100644 index 0000000..871b993 --- /dev/null +++ b/secrets/wireguard-peer-yevaud.pub @@ -0,0 +1 @@ +YPJsIs9x4wuWdFi/QRWSJbWvKE0GQAfVL4MNMqHygDw= diff --git a/services/birdsong/default.nix b/services/birdsong/default.nix new file mode 100644 index 0000000..3a42299 --- /dev/null +++ b/services/birdsong/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./peer.nix + ./hosts.nix + ]; +} \ No newline at end of file diff --git a/services/birdsong/hosts.nix b/services/birdsong/hosts.nix new file mode 100644 index 0000000..47b45cf --- /dev/null +++ b/services/birdsong/hosts.nix @@ -0,0 +1,134 @@ +{ config, lib, pkgs, ... }: + +with lib; +{ + options.birdsong.hosts = mkOption { + description = "List of hosts in the birdsong network"; + type = types.attrsOf + (types.submodule { + options = { + hostKey = mkOption { + default = null; + description = "SSH public key of the host, for use in known_hosts files"; + type = with types; nullOr str; + }; + subnet = mkOption { + default = "internet"; + example = "roaming"; + description = '' + Identifier representing a LAN the host belongs to. Hosts in the + same LAN will peer with each other. + + The special value `internet` (the default) will accept peering + from all other hosts. This is to be used for servers that are + accessible from the public internet. + + The special value `roaming` will not peer with other `roaming` + hosts, but will still peer with `internet` hosts. This is to be + used for portable devices like laptops that regularly move between + networks. + ''; + type = types.str; + }; + endpoint = mkOption { + default = null; + example = "example.com"; + description = '' + Address (e.g. IP or domain name) by which the host is reachable + within its LAN. + + If {option}`birdsong.hosts..subnet` is set to `internet`, + the host must be reachable at this address from the public + internet. + + If {option}`birdsong.hosts..subnet` is set to `roaming`, + this option is not used. + ''; + type = with types; nullOr str; + }; + ipv4 = mkOption { + example = "10.127.1.1"; + description = "IPv4 address of this peer within the network"; + type = types.str; + }; + ipv6 = mkOption { + example = "fd70:81ca:0f8f:1::1"; + description = "IPv6 address of this peer within the network"; + type = types.str; + }; + port = mkOption { + default = 51820; + example = 51821; + description = '' + Which port to expose WireGuard on. Change this for peers behind + NAT, to a port not used by another peer in the same LAN. + ''; + type = types.port; + }; + wireguardKey = mkOption { + example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="; + description = "WireGuard public key for this peer, as generated by `wg pubkey`"; + type = types.str; + }; + isRouter = mkOption { + default = false; + description = '' + The host with this flag set is the subnet router. It forwards + packets between WireGuard peers that can't connect directly to + each other. WireGuard's scope doesn't (yet) include full mesh + networking with load-balancing between routers, so only one peer + can hold this status. It should be peered with all other hosts + (i.e., {option}`birdsong.hosts..subnet` set to `internet`). + ''; + type = types.bool; + }; + }; + }); + }; + + config.birdsong.hosts = { + yevaud = { + hostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICHUAgyQhl390yUObLUI+jEbuNrZ2U6+8px628DolD+T root@yevaud"; + endpoint = "yevaud.birdsong.network"; + ipv4 = "10.127.1.1"; + ipv6 = "fd70:81ca:0f8f:1::1"; + wireguardKey = "YPJsIs9x4wuWdFi/QRWSJbWvKE0GQAfVL4MNMqHygDw="; + isRouter = true; + }; + + orm = { + hostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGc9rkcdOVWozBFj3kLVnSyUQQbyyH+UG+bLawanQkRQ root@orm"; + endpoint = "orm.birdsong.network"; + ipv4 = "10.127.1.2"; + ipv6 = "fd70:81ca:0f8f:1::2"; + wireguardKey = "birdLVh8roeZpcVo308Ums4l/aibhAxbi7MBsglkJyA="; + }; + + tohru = { + hostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOk8wuGzF0Y7SaH9aimo3SmCz99MTQwL+rEVhx0jsueU root@tohru"; + subnet = "roaming"; + ipv4 = "10.127.2.1"; + ipv6 = "fd70:81ca:0f8f:2::1"; + port = 51821; + wireguardKey = "lk3PCQM1jmZoI8sM/rWSyKNuZOUnjox3n9L9geJD+18="; + }; + + # kilgharrah = { + # # hostKey = ""; + # subnet = "weyrhold"; + # endpoint = "192.168.2.1"; + # ipv4 = "10.127.3.1"; + # ipv6 = "fd70:81ca:0f8f:3::1"; + # # wireguardKey = ""; + # }; + + shaw = { + hostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMC0AomCZZiUV/BCpImiV4p/vGvFaz5QNc+fJLXmS5p root@shaw"; + subnet = "library"; + # endpoint = ""; + ipv4 = "10.127.4.1"; + ipv6 = "fd70:81ca:0f8f:4::1"; + wireguardKey = "eD79pROC2zjhKz4tGRS43O95gcFRqO+SFb2XDnTr0zc="; + }; + }; +} diff --git a/services/birdsong/peer.nix b/services/birdsong/peer.nix new file mode 100644 index 0000000..d1b659b --- /dev/null +++ b/services/birdsong/peer.nix @@ -0,0 +1,91 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.birdsong.peer; + hostName = if null != cfg.hostName then cfg.hostName else config.networking.hostName; + hosts = config.birdsong.hosts; + host = hosts.${hostName}; +in +{ + options.birdsong.peer = { + enable = mkEnableOption "WireGuard peering with the birdsong network"; + hostName = mkOption { + default = null; + description = '' + The hostname of this peer within the network. Must be listed in + {option}`birdsong.hosts`. If not set, defaults to + {option}`networking.hostName`. + ''; + type = with types; nullOr str; + }; + interface = mkOption { + default = "birdsong"; + example = "wg0"; + description = "The name of the network interface to use for WireGuard."; + type = types.str; + }; + openPorts = mkOption { + default = true; + description = "Whether to automatically open firewall ports."; + type = types.bool; + }; + privateKeyFile = mkOption { + description = "Path to the private key for this peer, as generated by `wg genkey`."; + type = types.path; + }; + persistentKeepalive = mkOption { + default = null; + example = 23; + description = '' + Constantly ping each peer outside the LAN this often, in seconds, in + order to keep the WireGuard tunnel open. Set this if you are behind NAT + to keep the NAT session active, or if you have a dynamic IP to keep the + other peers aware when your IP changes. To avoid syncing, this should + ideally be a prime number that is not shared by another peer in the same + LAN. + ''; + type = with types; nullOr int; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg ? privateKeyFile; + message = "birdsong.peer.privateKeyFile must be set"; + } + { + assertion = hostName != null; + message = "birdsong.peer.hostName or networking.hostName must be set"; + } + ]; + + networking = { + firewall.allowedUDPPorts = mkIf cfg.openPorts [ host.port ]; + + wireguard.interfaces.${cfg.interface} = { + ips = [ "${host.ipv4}/16" "${host.ipv6}/48" ] + ++ optionals host.isRouter [ "10.127.0.0/16" "fd70:81ca:0f8f::/48" ]; + privateKeyFile = cfg.privateKeyFile; + listenPort = host.port; + + peers = + let + canDirectPeer = host: peer: peer.subnet == "internet" || (host.subnet != "roaming" && peer.subnet == host.subnet); + in + mapAttrsToList + (name: peer: { + name = name; + publicKey = peer.wireguardKey; + allowedIPs = [ peer.ipv4 peer.ipv6 ] + ++ optionals peer.isRouter [ "10.127.0.0/16" "fd70:81ca:0f8f::/48" ]; + endpoint = mkIf (canDirectPeer host peer) "${peer.endpoint}:${toString peer.port}"; + dynamicEndpointRefreshSeconds = mkIf (canDirectPeer host peer) 5; + persistentKeepalive = mkIf (peer.subnet != host.subnet) cfg.persistentKeepalive; + }) + (filterAttrs (name: peer: peer != host && (host.subnet == "internet" || canDirectPeer host peer)) hosts); + }; + }; + }; +} diff --git a/services/default.nix b/services/default.nix index 7c73723..304281d 100644 --- a/services/default.nix +++ b/services/default.nix @@ -1,5 +1,6 @@ { imports = [ + ./birdsong ./fonts.nix ./forgejo.nix ./steam.nix diff --git a/services/fonts.nix b/services/fonts.nix index 2845030..dcd9d1b 100644 --- a/services/fonts.nix +++ b/services/fonts.nix @@ -1,8 +1,8 @@ { config, lib, pkgs, ... }: +with lib; let cfg = config.qenya.services.fonts; - inherit (lib) mkIf mkEnableOption; in { options.qenya.services.fonts = { diff --git a/services/forgejo.nix b/services/forgejo.nix index 8cca791..9f3f6f1 100644 --- a/services/forgejo.nix +++ b/services/forgejo.nix @@ -1,8 +1,8 @@ { config, lib, pkgs, ... }: +with lib; let cfg = config.qenya.services.forgejo; - inherit (lib) mkIf mkEnableOption mkOption types; in { options.qenya.services.forgejo = { diff --git a/services/steam.nix b/services/steam.nix index 0a3c3d4..d7ef010 100644 --- a/services/steam.nix +++ b/services/steam.nix @@ -1,8 +1,8 @@ { config, lib, pkgs, ... }: +with lib; let cfg = config.qenya.services.steam; - inherit (lib) mkIf mkEnableOption; in { options.qenya.services.steam = {