From d4e1577ee1cddb9043c80e1d059cf2f7d6f15b79 Mon Sep 17 00:00:00 2001 From: SebastianStork Date: Wed, 25 Feb 2026 22:23:25 +0100 Subject: [PATCH 1/4] Add custom installer iso --- flake-parts/iso.nix | 61 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 flake-parts/iso.nix diff --git a/flake-parts/iso.nix b/flake-parts/iso.nix new file mode 100644 index 0000000..92cf348 --- /dev/null +++ b/flake-parts/iso.nix @@ -0,0 +1,61 @@ +{ inputs, self, ... }: +{ + perSystem = + { system, lib, ... }: + { + packages.iso = + (inputs.nixpkgs.lib.nixosSystem { + specialArgs = { + inherit inputs; + inherit (self) allHosts; + }; + + modules = lib.singleton ( + { + config, + inputs, + pkgs, + allHosts, + ... + }: + { + nixpkgs.hostPlatform = system; + + nix.settings.experimental-features = [ "pipe-operators" ]; + + networking = { + hostName = "installer"; + wireless.enable = false; + networkmanager.enable = true; + }; + + console.keyMap = "de-latin1-nodeadkeys"; + + boot.supportedFilesystems = { + zfs = false; + bcachefs = true; + }; + + environment.systemPackages = [ inputs.disko.packages.${pkgs.stdenv.hostPlatform.system}.default ]; + + services.openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + }; + }; + + users.users.root.openssh.authorizedKeys.keyFiles = + allHosts + |> lib.attrValues + |> lib.filter (host: host.config.networking.hostName != config.networking.hostName) + |> lib.filter (host: host.config |> lib.hasAttr "home-manager") + |> lib.map (host: host.config.home-manager.users.seb.custom.programs.ssh) + |> lib.filter (ssh: ssh.enable) + |> lib.map (ssh: ssh.publicKeyFile); + } + ); + }).config.system.build.images.iso-installer; + }; +} From 30a23218058b02316d790430bf4542c23f805e87 Mon Sep 17 00:00:00 2001 From: SebastianStork Date: Wed, 25 Feb 2026 23:18:48 +0100 Subject: [PATCH 2/4] Add new host `nas` --- hosts/nas/default.nix | 25 +++++++++ hosts/nas/disko.nix | 111 ++++++++++++++++++++++++++++++++++++++ hosts/nas/hardware.nix | 33 ++++++++++++ hosts/nas/keys/age.pub | 1 + hosts/nas/keys/nebula.crt | 6 +++ hosts/nas/keys/nebula.pub | 3 ++ hosts/nas/secrets.json | 26 +++++++++ 7 files changed, 205 insertions(+) create mode 100644 hosts/nas/default.nix create mode 100644 hosts/nas/disko.nix create mode 100644 hosts/nas/hardware.nix create mode 100644 hosts/nas/keys/age.pub create mode 100644 hosts/nas/keys/nebula.crt create mode 100644 hosts/nas/keys/nebula.pub create mode 100644 hosts/nas/secrets.json diff --git a/hosts/nas/default.nix b/hosts/nas/default.nix new file mode 100644 index 0000000..1649383 --- /dev/null +++ b/hosts/nas/default.nix @@ -0,0 +1,25 @@ +{ self, ... }: +{ + imports = [ self.nixosModules.server-profile ]; + + system.stateVersion = "25.11"; + + custom = { + boot.loader.grub.enable = true; + + networking = { + overlay = { + address = "10.254.250.6"; + isLighthouse = true; + }; + underlay = { + interface = "enp2s0"; + cidr = "192.168.0.64/24"; + isPublic = true; + gateway = "192.168.0.1"; + }; + }; + + services.dns.enable = true; + }; +} diff --git a/hosts/nas/disko.nix b/hosts/nas/disko.nix new file mode 100644 index 0000000..b1c4d38 --- /dev/null +++ b/hosts/nas/disko.nix @@ -0,0 +1,111 @@ +{ + disko.devices = { + nodev."/" = { + fsType = "tmpfs"; + mountOptions = [ + "defaults" + "mode=755" + ]; + }; + disk = { + nvme0n1 = { + type = "disk"; + device = "/dev/disk/by-id/nvme-eui.002538b581b34925"; + content = { + type = "gpt"; + partitions = { + swap = { + size = "8G"; + content.type = "swap"; + }; + root = { + size = "100%"; + content = { + type = "bcachefs"; + filesystem = "rootfs"; + label = "nvme.nvme0n1"; + extraFormatArgs = [ + "--discard" + "--durability=0" + ]; + }; + }; + }; + }; + }; + sda = { + type = "disk"; + device = "/dev/disk/by-id/ata-CT1000BX500SSD1_2527E9C5CD54"; + content = { + type = "gpt"; + partitions = { + boot = { + size = "1G"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot1"; + mountOptions = [ "umask=0077" ]; + }; + }; + root = { + size = "100%"; + content = { + type = "bcachefs"; + filesystem = "rootfs"; + label = "sata.sda"; + extraFormatArgs = [ + "--discard" + "--durability=1" + ]; + }; + }; + }; + }; + }; + sdb = { + type = "disk"; + device = "/dev/disk/by-id/ata-Samsung_SSD_860_QVO_1TB_S4CZNF1N102994T"; + content = { + type = "gpt"; + partitions = { + boot = { + size = "1G"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot2"; + mountOptions = [ "umask=0077" ]; + }; + }; + root = { + size = "100%"; + content = { + type = "bcachefs"; + filesystem = "rootfs"; + label = "sata.sdb"; + extraFormatArgs = [ + "--discard" + "--durability=1" + ]; + }; + }; + }; + }; + }; + }; + bcachefs_filesystems.rootfs = { + type = "bcachefs_filesystem"; + extraFormatArgs = [ + "--replicas=2" + "--compression=lz4" + ]; + subvolumes = { + nix.mountpoint = "/nix"; + persist.mountpoint = "/persist"; + }; + }; + }; +} diff --git a/hosts/nas/hardware.nix b/hosts/nas/hardware.nix new file mode 100644 index 0000000..ac0e37c --- /dev/null +++ b/hosts/nas/hardware.nix @@ -0,0 +1,33 @@ +_: { + nixpkgs.hostPlatform = "x86_64-linux"; + + boot = { + kernelModules = [ "kvm-intel" ]; + initrd.availableKernelModules = [ + "xhci_pci" + "ahci" + "nvme" + "sd_mod" + "sdhci_pci" + ]; + + supportedFilesystems = [ "bcachefs" ]; + + loader = { + efi.canTouchEfiVariables = true; + grub = { + efiSupport = true; + mirroredBoots = [ + { + devices = [ "nodev" ]; + path = "/boot1"; + } + { + devices = [ "nodev" ]; + path = "/boot2"; + } + ]; + }; + }; + }; +} diff --git a/hosts/nas/keys/age.pub b/hosts/nas/keys/age.pub new file mode 100644 index 0000000..aafc50b --- /dev/null +++ b/hosts/nas/keys/age.pub @@ -0,0 +1 @@ +age1p582v7x0k36csmtp66a0j28j5u5slruqqkfh6kkqutkmsquwdups3xd2lq diff --git a/hosts/nas/keys/nebula.crt b/hosts/nas/keys/nebula.crt new file mode 100644 index 0000000..19c8c8c --- /dev/null +++ b/hosts/nas/keys/nebula.crt @@ -0,0 +1,6 @@ +-----BEGIN NEBULA CERTIFICATE V2----- +MIGsoEaAA25hc6EHBAUK/voGGKMIDAZzZXJ2ZXKFBGmfZhaGBGsoffSHIBVD/hlb +qt7XLMVqDE4DhIQzJRBaXtQIwm5gRTI7c0VogiAZe96epRDtw/rMTdFK2zGNir1I +wMaj+yBQZk7+5zkMdYNAq9DkNJ5a+W5M27gkxC4iNpi5+HhQksJpuQyRJthGmoUK ++cBkIymP7vlwF1rWRIUAwFiuhSlKvKg9H6RrM5mGBw== +-----END NEBULA CERTIFICATE V2----- diff --git a/hosts/nas/keys/nebula.pub b/hosts/nas/keys/nebula.pub new file mode 100644 index 0000000..606460d --- /dev/null +++ b/hosts/nas/keys/nebula.pub @@ -0,0 +1,3 @@ +-----BEGIN NEBULA X25519 PUBLIC KEY----- +GXvenqUQ7cP6zE3RStsxjYq9SMDGo/sgUGZO/uc5DHU= +-----END NEBULA X25519 PUBLIC KEY----- diff --git a/hosts/nas/secrets.json b/hosts/nas/secrets.json new file mode 100644 index 0000000..966595f --- /dev/null +++ b/hosts/nas/secrets.json @@ -0,0 +1,26 @@ +{ + "seb-password": "ENC[AES256_GCM,data:sCMOhgNrWyGVRUlL0bFTjaXXd8/tQJI43yPfAHzzWu1M5KYzPu0G7GhzjsGUNIwYeP8CO01Zh6zqkBy1h4dNbuX8NvuVJDWZjA==,iv:ClzISC4OJ/EFHQI420D+JkdC18ZdB9I7bwnZDWa0pHs=,tag:oMfHJgyhb4tPoTg9OsB7BA==,type:str]", + "nebula": { + "host-key": "ENC[AES256_GCM,data:lFOyE+dn5Gg5qfOywA673g6x120unVNMiYG/bmkXAdGMCwUi4vknSIY4vDJsKNR5YAZJ26x6Ezboj9aM2pRzXwZC7duaGpkCNwMQS5+j+T/ClZOptFLaLnxnWNcLfVkupRr4uyAb2DlyTPI2uUGJFVWCKRric04fyOTd8T0TzQ==,iv:2se7H9YWtPIScMq5hCZyirM6KS9cVnlv/HPnlh2swfE=,tag:/N+Ewdl1tH9o6LVuoTSTcA==,type:str]" + }, + "porkbun": { + "api-key": "ENC[AES256_GCM,data:eWZXxOfCQ7fXqwUAtsD968EjOeibkFbBeClNmazPk7uEtSR+WnpteB2pY0VFSEQhTKN7zCunKcfkKiSiG0C9r0TXxYM=,iv:FHmy/gR1Zzpro2Vm2e13nfTkHGEGwyw+81CDgkVlbYM=,tag:e4nBvSgw3wJU8viSp23Fjw==,type:str]", + "secret-api-key": "ENC[AES256_GCM,data:6Ss1wkeNMlnkwFtoytwtSHsIbpZN+CmPVshGu7GZfAH8FsHpc/Xyj8D1TRaUGpOI4gz/II4P/LZnmPR5bZ521MxbL+o=,iv:ycpfMGtis8l8TYj2sMO1plSNPKnFzBDF6i6xhxDabx4=,tag:WZCj0CDaPJ1vL6cap2rGvA==,type:str]" + }, + "sops": { + "age": [ + { + "recipient": "age1mpq8m4p7dnxh5ze3fh7etd2k6sp85zdnmp9te3e9chcw4pw07pcq960zh5", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwQ0w4S1lHL21ld0Z1bDho\nMUV6UnpUYTdZZGZmSHRaK3MycVUvcitXU0M4CjdESGdwb2pnaXRkdnhIVGlvNW51\nRG4yVnFsUGIzSU16aUtuaFkxQlhGZjAKLS0tIGlFTUs4blhvYjFnS2lKcHp1MElu\neW5OV2FOYXEyUHhrQ3JlRnQ5MlRCNDgKo1abZY7O16Tqd+qMeeQtS+3aLB3bsi3g\nlSvatQ9R8D9Ogk8J7D1crrD8KMEX6Ob3Wov9OhY4tPSGfkRq61TLkw==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1p582v7x0k36csmtp66a0j28j5u5slruqqkfh6kkqutkmsquwdups3xd2lq", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXOVRQU1R4VlBhdEFUdnR6\nZFNuNldaSkxJL3ptOVVscjRBNkQ4dFBmQUVBCmZrQmFMV0hWbTBQcm1FS3JrR0ZC\nbktvT04xczd6VkdCUWk2NnVVZHNFWkUKLS0tIGUwOHJSMHVsNTEyZEU2VWJFNGVy\nMVFDVThrRGQwZEtPeFYzZUVQYi80ZjAKUd/XzyzqMkMowvyeCnQDbOGJDKbuAUQb\nFClQuiH5iSQQrVPw7SHBNgdqbcdtC+hZ4tpPaV/wWtlpcqpr5mBJSA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-02-25T19:43:39Z", + "mac": "ENC[AES256_GCM,data:2pIwmbnsgL5DmZqeZQrnHHNXU1tNdGayytKFD0/g8GM1RQGDL2vGf8J/LX2JkpOeqeG/7q0t0Aa9ABeIGMNjAFSm0RIM6CIHVugPUx+mD7eziof6MRZ2LIzhlI49htxngToHBgOLnmWQt+7AueoLIowqkrP5d2ocbwmb8ObXaoo=,iv:IoLdmrRzmSN+3rr1ogeAOz8fVBoyH+ttZnco6rtmvR4=,tag:Bjco8HoAaplUuyxNMxjEIg==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.11.0" + } +} From bcf3650d2e722cb3952d88db8b920fdbc7883551 Mon Sep 17 00:00:00 2001 From: SebastianStork Date: Wed, 25 Feb 2026 23:19:28 +0100 Subject: [PATCH 3/4] nebula: Fix start up sequence --- modules/system/services/nebula/default.nix | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/system/services/nebula/default.nix b/modules/system/services/nebula/default.nix index bce4a75..edc86ee 100644 --- a/modules/system/services/nebula/default.nix +++ b/modules/system/services/nebula/default.nix @@ -122,11 +122,18 @@ in networking.firewall.trustedInterfaces = [ netCfg.overlay.interface ]; - systemd.network.networks."40-nebula" = { - matchConfig.Name = netCfg.overlay.interface; - address = [ netCfg.overlay.cidr ]; - dns = netCfg.overlay.dnsServers; - domains = [ netCfg.overlay.domain ]; + systemd = { + services."nebula@mesh" = { + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + }; + + network.networks."40-nebula" = { + matchConfig.Name = netCfg.overlay.interface; + address = [ netCfg.overlay.cidr ]; + dns = netCfg.overlay.dnsServers; + domains = [ netCfg.overlay.domain ]; + }; }; }; } From d83843e5e16c5b5b1c5100d6bc20af3edffb36d3 Mon Sep 17 00:00:00 2001 From: SebastianStork Date: Thu, 26 Feb 2026 00:32:25 +0100 Subject: [PATCH 4/4] lib: Add `existingPath` type to validate path existence at eval time --- flake-parts/lib.nix | 4 ++++ modules/home/programs/ssh.nix | 2 +- modules/home/sops.nix | 2 +- modules/system/services/nebula/default.nix | 6 +++--- modules/system/sops.nix | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/flake-parts/lib.nix b/flake-parts/lib.nix index 5922d99..c330513 100644 --- a/flake-parts/lib.nix +++ b/flake-parts/lib.nix @@ -15,5 +15,9 @@ name: "${name} should only be exposed on private networks; access control isn't yet configured"; relativePath = path: path |> toString |> lib.removePrefix "${self}/"; + + types.existingPath = (lib.types.addCheck lib.types.path lib.pathExists) // { + description = "path that exists"; + }; }; } diff --git a/modules/home/programs/ssh.nix b/modules/home/programs/ssh.nix index afc18a0..8222747 100644 --- a/modules/home/programs/ssh.nix +++ b/modules/home/programs/ssh.nix @@ -9,7 +9,7 @@ options.custom.programs.ssh = { enable = lib.mkEnableOption ""; publicKeyFile = lib.mkOption { - type = lib.types.path; + type = self.lib.types.existingPath; default = "${self}/users/${config.home.username}/@${osConfig.networking.hostName}/keys/ssh.pub"; }; }; diff --git a/modules/home/sops.nix b/modules/home/sops.nix index 37d2c8f..9ff4bea 100644 --- a/modules/home/sops.nix +++ b/modules/home/sops.nix @@ -22,7 +22,7 @@ in |> lib.trim; }; secretsFile = lib.mkOption { - type = lib.types.path; + type = self.lib.types.existingPath; default = "${self}/users/${config.home.username}/@${osConfig.networking.hostName}/secrets.json"; }; secrets = lib.mkOption { diff --git a/modules/system/services/nebula/default.nix b/modules/system/services/nebula/default.nix index edc86ee..2fa908f 100644 --- a/modules/system/services/nebula/default.nix +++ b/modules/system/services/nebula/default.nix @@ -28,15 +28,15 @@ in }; caCertificateFile = lib.mkOption { - type = lib.types.path; + type = self.lib.types.existingPath; default = ./ca.crt; }; publicKeyFile = lib.mkOption { - type = lib.types.path; + type = self.lib.types.existingPath; default = "${self}/hosts/${netCfg.hostName}/keys/nebula.pub"; }; certificateFile = lib.mkOption { - type = lib.types.path; + type = self.lib.types.existingPath; default = "${self}/hosts/${netCfg.hostName}/keys/nebula.crt"; }; privateKeyFile = lib.mkOption { diff --git a/modules/system/sops.nix b/modules/system/sops.nix index a8f25be..4add3a9 100644 --- a/modules/system/sops.nix +++ b/modules/system/sops.nix @@ -18,7 +18,7 @@ in default = "${self}/hosts/${config.networking.hostName}/keys/age.pub" |> lib.readFile |> lib.trim; }; secretsFile = lib.mkOption { - type = lib.types.path; + type = self.lib.types.existingPath; default = "${self}/hosts/${config.networking.hostName}/secrets.json"; }; secrets = lib.mkOption {