From e75c7b831beba064d399074347c13f24b1c48893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tristan=20Dani=C3=ABl=20Maat?= Date: Fri, 20 Feb 2026 05:33:26 +0800 Subject: [PATCH 1/2] refactor(flake.nix): Use flake-parts to simplify flake.nix --- checks/default.nix | 114 +++++++++----- checks/lib.nix | 35 ----- .../hardware-specific/hetzner/default.nix | 1 + .../hardware-specific/hetzner/vm.nix | 70 +++++++++ configuration/hardware-specific/vm.nix | 69 --------- dev-utils.nix | 43 ++++++ flake.lock | 32 ++-- flake.nix | 139 +++--------------- flakeModules/deploy-rs.nix | 136 +++++++++++++++++ pkgs/default.nix | 13 +- 10 files changed, 381 insertions(+), 271 deletions(-) delete mode 100644 checks/lib.nix create mode 100644 configuration/hardware-specific/hetzner/vm.nix delete mode 100644 configuration/hardware-specific/vm.nix create mode 100644 dev-utils.nix create mode 100644 flakeModules/deploy-rs.nix diff --git a/checks/default.nix b/checks/default.nix index 737313d..4083181 100644 --- a/checks/default.nix +++ b/checks/default.nix @@ -1,47 +1,83 @@ -{ flake-inputs }: -let - inherit (flake-inputs.nixpkgs) lib; - pkgs = flake-inputs.nixpkgs.legacyPackages.x86_64-linux; - checkLib = pkgs.callPackage ./lib.nix { }; -in +{ self, ... }: { - x86_64-linux = lib.mergeAttrsList [ - flake-inputs.self.nixosConfigurations.hetzner-1.config.serviceTests - + perSystem = { - nix = checkLib.mkLint { - name = "nix-lints"; - fileset = lib.fileset.fileFilter (file: file.hasExt "nix") ../.; + inputs', + lib, + pkgs, + ... + }: + { + checks = + let + mkLint = + { + name, + fileset, + checkInputs ? [ ], + script, + }: + pkgs.stdenvNoCC.mkDerivation { + inherit name; - checkInputs = lib.attrValues { - inherit (pkgs) deadnix nixfmt-rfc-style; + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.difference fileset ( + lib.fileset.fileFilter ( + file: file.type != "regular" || file.name == "hardware-configuration.nix" + ) ../. + ); + }; - statix = pkgs.statix.overrideAttrs (old: { - patches = old.patches ++ [ - (pkgs.fetchpatch { - url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch"; - hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs="; - }) - ]; - }); - }; + checkInputs = [ pkgs.nushell ] ++ checkInputs; - script = '' - statix check **/*.nix - deadnix --fail **/*.nix - nixfmt --check --strict **/*.nix - ''; - }; + checkPhase = '' + nu -c '${script}' | tee $out + ''; - lockfile = checkLib.mkLint { - name = "nix-lockfile"; - fileset = ../flake.lock; - checkInputs = lib.attrValues { inherit (flake-inputs.flint.packages.x86_64-linux) flint; }; + dontPatch = true; + dontConfigure = true; + dontBuild = true; + dontInstall = true; + dontFixup = true; + doCheck = true; + }; + in + { + nix = mkLint { + name = "nix-lints"; + fileset = lib.fileset.fileFilter (file: file.hasExt "nix") ../.; - script = '' - flint --fail-if-multiple-versions - ''; - }; - } - ]; + checkInputs = lib.attrValues { + inherit (pkgs) deadnix nixfmt-rfc-style; + + statix = pkgs.statix.overrideAttrs (old: { + patches = old.patches ++ [ + (pkgs.fetchpatch { + url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch"; + hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs="; + }) + ]; + }); + }; + + script = /* bash */ '' + statix check **/*.nix + deadnix --fail **/*.nix + nixfmt --check --strict **/*.nix + ''; + }; + + lockfile = mkLint { + name = "nix-lockfile"; + fileset = ../flake.lock; + checkInputs = lib.attrValues { inherit (inputs'.flint.packages) flint; }; + + script = /* bash */ '' + flint --fail-if-multiple-versions + ''; + }; + } + // self.nixosConfigurations.hetzner-1.config.serviceTests; + }; } diff --git a/checks/lib.nix b/checks/lib.nix deleted file mode 100644 index d38cd21..0000000 --- a/checks/lib.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ pkgs, lib, ... }: -{ - mkLint = - { - name, - fileset, - checkInputs ? [ ], - script, - }: - pkgs.stdenvNoCC.mkDerivation { - inherit name; - - src = lib.fileset.toSource { - root = ../.; - fileset = lib.fileset.difference fileset ( - lib.fileset.fileFilter ( - file: file.type != "regular" || file.name == "hardware-configuration.nix" - ) ../. - ); - }; - - checkInputs = [ pkgs.nushell ] ++ checkInputs; - - checkPhase = '' - nu -c '${script}' | tee $out - ''; - - dontPatch = true; - dontConfigure = true; - dontBuild = true; - dontInstall = true; - dontFixup = true; - doCheck = true; - }; -} diff --git a/configuration/hardware-specific/hetzner/default.nix b/configuration/hardware-specific/hetzner/default.nix index 4d0408c..3b60011 100644 --- a/configuration/hardware-specific/hetzner/default.nix +++ b/configuration/hardware-specific/hetzner/default.nix @@ -2,6 +2,7 @@ imports = [ ./hardware-configuration.nix ./disko.nix + ./vm.nix ]; # Intel's special encrypted memory<->CPU feature. Hetzner's BIOS diff --git a/configuration/hardware-specific/hetzner/vm.nix b/configuration/hardware-specific/hetzner/vm.nix new file mode 100644 index 0000000..7ecf8f8 --- /dev/null +++ b/configuration/hardware-specific/hetzner/vm.nix @@ -0,0 +1,70 @@ +{ lib, ... }: +{ + virtualisation.vmVariant = { + users.users.tlater.password = "insecure"; + + # Disable graphical tty so -curses works + boot.kernelParams = [ "nomodeset" ]; + + networking.hostName = lib.mkForce "testvm"; + + services = { + # Sets the base domain for nginx to a local domain so that we can + # easily test locally with the VM. + nginx.domain = lib.mkForce "dev.local"; + + # Don't run this + batteryManager.enable = lib.mkForce false; + btrfs.autoScrub.enable = lib.mkForce false; + + openssh.hostKeys = lib.mkForce [ + { + type = "rsa"; + bits = 4096; + path = "/etc/staging.key"; + } + ]; + }; + + # Use the staging secrets + sops.defaultSopsFile = lib.mkOverride 99 ../../../keys/staging.yaml; + + systemd.network.networks."10-eth0" = { + matchConfig.Name = "eth0"; + gateway = [ "192.168.9.1" ]; + networkConfig = { + Address = "192.168.9.2/24"; + }; + }; + + # Both so we have a predictable key for the staging env, as well as + # to have a static key for decrypting the sops secrets for the + # staging env. + environment.etc."staging.key" = { + mode = "0400"; + source = ../../../keys/hosts/staging.key; + }; + + # Pretend the acme renew succeeds. + # + # TODO(tlater): Set up pebble to retrieve certs "properly" + # instead + systemd.services."acme-order-renew-tlater.net".script = '' + touch out/acme-success + ''; + + virtualisation = { + memorySize = 3941; + cores = 2; + graphics = false; + diskSize = 1024 * 20; + + qemu = { + networkingOptions = lib.mkForce [ + "-device virtio-net,netdev=n1" + "-netdev bridge,id=n1,br=br0,helper=$(which qemu-bridge-helper)" + ]; + }; + }; + }; +} diff --git a/configuration/hardware-specific/vm.nix b/configuration/hardware-specific/vm.nix deleted file mode 100644 index 70c1b58..0000000 --- a/configuration/hardware-specific/vm.nix +++ /dev/null @@ -1,69 +0,0 @@ -{ lib, ... }: -{ - users.users.tlater.password = "insecure"; - - # Disable graphical tty so -curses works - boot.kernelParams = [ "nomodeset" ]; - - networking.hostName = "testvm"; - - services = { - # Sets the base domain for nginx to a local domain so that we can - # easily test locally with the VM. - nginx.domain = "dev.local"; - - # Don't run this - batteryManager.enable = lib.mkForce false; - - openssh.hostKeys = lib.mkForce [ - { - type = "rsa"; - bits = 4096; - path = "/etc/staging.key"; - } - ]; - }; - - # Use the staging secrets - sops.defaultSopsFile = lib.mkOverride 99 ../../keys/staging.yaml; - - systemd.network.networks."10-eth0" = { - matchConfig.Name = "eth0"; - gateway = [ "192.168.9.1" ]; - networkConfig = { - Address = "192.168.9.2/24"; - }; - }; - - # Both so we have a predictable key for the staging env, as well as - # to have a static key for decrypting the sops secrets for the - # staging env. - environment.etc."staging.key" = { - mode = "0400"; - source = ../../keys/hosts/staging.key; - }; - - # Pretend the acme renew succeeds. - # - # TODO(tlater): Set up pebble to retrieve certs "properly" - # instead - systemd.services."acme-order-renew-tlater.net".script = '' - touch out/acme-success - ''; - - virtualisation.vmVariant = { - virtualisation = { - memorySize = 3941; - cores = 2; - graphics = false; - diskSize = 1024 * 20; - }; - - virtualisation.qemu = { - networkingOptions = lib.mkForce [ - "-device virtio-net,netdev=n1" - "-netdev bridge,id=n1,br=br0,helper=$(which qemu-bridge-helper)" - ]; - }; - }; -} diff --git a/dev-utils.nix b/dev-utils.nix new file mode 100644 index 0000000..bfa0d17 --- /dev/null +++ b/dev-utils.nix @@ -0,0 +1,43 @@ +{ self, ... }: +{ + # Systems on which to make dev utilities runnable; anything + # NixOS-related encodes its own system. + systems = [ "x86_64-linux" ]; + + perSystem = + { + inputs', + self', + pkgs, + lib, + ... + }: + { + apps = { + default = self'.apps.runVm; + + runVm = { + type = "app"; + program = lib.getExe self.nixosConfigurations.hetzner-1.config.system.build.vm; + meta.description = "Run the test VM"; + }; + }; + + devShells = { + default = pkgs.mkShell { + sopsPGPKeyDirs = [ + "./keys/hosts/" + "./keys/users/" + ]; + + packages = lib.attrValues { + inherit (inputs'.sops-nix.packages) sops-import-keys-hook sops-init-gpg-key; + inherit (pkgs) deploy-rs; + }; + }; + + minecraft = pkgs.mkShell { packages = lib.attrValues { inherit (pkgs) packwiz; }; }; + webserver = self'.packages.webserver.devShell; + }; + }; +} diff --git a/flake.lock b/flake.lock index b7ae789..3094d19 100644 --- a/flake.lock +++ b/flake.lock @@ -46,7 +46,9 @@ "deploy-rs", "flake-compat" ], - "flake-parts": "flake-parts", + "flake-parts": [ + "flake-parts" + ], "nix-test-runner": "nix-test-runner", "nixpkgs": [ "sonnenshift", @@ -154,18 +156,14 @@ }, "flake-parts": { "inputs": { - "nixpkgs-lib": [ - "sonnenshift", - "crate2nix", - "nixpkgs" - ] + "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1712014858, - "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "lastModified": 1769996383, + "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", + "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", "type": "github" }, "original": { @@ -266,6 +264,21 @@ "url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz" } }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1769909678, + "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "72716169fe93074c333e8d0173151350670b824c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, "pre-commit-hooks": { "inputs": { "flake-compat": [ @@ -309,6 +322,7 @@ "inputs": { "deploy-rs": "deploy-rs", "disko": "disko", + "flake-parts": "flake-parts", "flint": "flint", "foundryvtt": "foundryvtt", "nixpkgs": "nixpkgs", diff --git a/flake.nix b/flake.nix index 10b52ec..8680b59 100644 --- a/flake.nix +++ b/flake.nix @@ -1,8 +1,7 @@ { - description = "tlater.net host configuration"; - inputs = { nixpkgs.url = "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz"; + flake-parts.url = "github:hercules-ci/flake-parts"; ## Nix/OS utilities @@ -42,139 +41,49 @@ crate2nix.inputs = { flake-compat.follows = "deploy-rs/flake-compat"; devshell.inputs.flake-utils.follows = "deploy-rs/utils"; + flake-parts.follows = "flake-parts"; }; }; }; }; outputs = - { - self, - nixpkgs, - sops-nix, - ... - }@inputs: - let - system = "x86_64-linux"; - - vm = nixpkgs.lib.nixosSystem { - inherit system; - specialArgs.flake-inputs = inputs; - - modules = [ - ./configuration - ./configuration/hardware-specific/vm.nix + { flake-parts, ... }@inputs: + flake-parts.lib.mkFlake { inherit inputs; } ( + { self, ... }@args: + { + imports = [ + (flake-parts.lib.importApply ./flakeModules/deploy-rs.nix args) + ./checks + ./dev-utils.nix + ./pkgs ]; - }; - # deploy-rs unfortunately uses an `import nixpkgs`, and its - # library functions depend on an instantiated nixpkgs, so we - # can't get around multi-nixpkgs-eval. - inherit - (import nixpkgs { - inherit system; - overlays = [ - inputs.deploy-rs.overlays.default - (_: prev: { - deploy-rs = { - inherit (nixpkgs.legacyPackages.${system}) deploy-rs; - inherit (prev.deploy-rs) lib; - }; - }) - ]; - }) - deploy-rs - ; - in - { - ################## - # Configurations # - ################## - nixosConfigurations = { - # The actual system definition - hetzner-1 = nixpkgs.lib.nixosSystem { - inherit system; + flake.nixosConfigurations.hetzner-1 = inputs.nixpkgs.lib.nixosSystem { specialArgs.flake-inputs = inputs; - modules = [ ./configuration ./configuration/hardware-specific/hetzner ]; }; - }; - ############################ - # Deployment configuration # - ############################ - deploy.nodes = { - hetzner-1 = { + deploy.nodes.hetzner-1 = { hostname = "116.202.158.55"; profiles.system = { user = "root"; - path = deploy-rs.lib.activate.nixos self.nixosConfigurations.hetzner-1; + activation = "nixos"; + closure = self.nixosConfigurations.hetzner-1; + + sshUser = "tlater"; + sshOpts = [ + "-p" + "2222" + "-o" + "ForwardAgent=yes" + ]; }; - - sshUser = "tlater"; - sshOpts = [ - "-p" - "2222" - "-o" - "ForwardAgent=yes" - ]; }; - }; - - ######### - # Tests # - ######### - checks = import ./checks { flake-inputs = inputs; }; - - ########################### - # Garbage collection root # - ########################### - - packages.${system} = { - default = vm.config.system.build.vm; } - // import ./pkgs { pkgs = nixpkgs.legacyPackages.${system}; }; - - ################### - # Utility scripts # - ################### - apps.${system} = { - default = self.apps.${system}.run-vm; - - run-vm = { - type = "app"; - program = - (nixpkgs.legacyPackages.${system}.writeShellScript "" '' - ${vm.config.system.build.vm.outPath}/bin/run-testvm-vm - '').outPath; - }; - }; - - ########################### - # Development environment # - ########################### - devShells.${system} = { - default = nixpkgs.legacyPackages.${system}.mkShell { - sopsPGPKeyDirs = [ - "./keys/hosts/" - "./keys/users/" - ]; - - packages = nixpkgs.lib.attrValues { - inherit (sops-nix.packages.${system}) sops-import-keys-hook sops-init-gpg-key; - inherit (deploy-rs) deploy-rs; - }; - }; - - minecraft = nixpkgs.legacyPackages.${system}.mkShell { - packages = nixpkgs.lib.attrValues { inherit (nixpkgs.legacyPackages.${system}) packwiz; }; - }; - - webserver = self.packages.${system}.webserver.devShell; - }; - }; + ); } diff --git a/flakeModules/deploy-rs.nix b/flakeModules/deploy-rs.nix new file mode 100644 index 0000000..abee5a8 --- /dev/null +++ b/flakeModules/deploy-rs.nix @@ -0,0 +1,136 @@ +{ lib, ... }@exportingFlake: +let + inherit (lib) mkOption types; + + deploy-rs-for-system = + system: + (import exportingFlake.inputs.nixpkgs { + inherit system; + overlays = [ + exportingFlake.inputs.deploy-rs.overlays.default + (_final: prev: { + deploy-rs = { + inherit (exportingFlake.inputs.nixpkgs.legacyPackages.${system}) deploy-rs; + inherit (prev.deploy-rs) lib; + }; + }) + ]; + }).deploy-rs; +in +{ config, ... }: +let + cfg = config.deploy; +in +{ + options.deploy = + let + genericOptions = + let + mkGenericOption = + type: + mkOption { + type = types.nullOr type; + default = null; + }; + in + { + options = { + sshUser = mkGenericOption types.str; + user = mkGenericOption types.str; + sshOpts = mkGenericOption (types.listOf types.str); + fastConnection = mkGenericOption types.bool; + autoRollback = mkGenericOption types.bool; + magicRollback = mkGenericOption types.bool; + confirmTimeout = mkGenericOption types.int; + activationTimeout = mkGenericOption types.int; + tempPath = mkGenericOption types.str; + interactiveSudo = mkGenericOption types.bool; + }; + }; + + profileModule = + { config, ... }: + { + imports = [ genericOptions ]; + + options = { + activation = mkOption { + type = types.oneOf [ + (types.enum [ + "nixos" + "home-manager" + "darwin" + "noop" + ]) + ]; + }; + + closure = mkOption { type = types.raw; }; + + profilePath = mkOption { + type = types.nullOr types.str; + default = null; + }; + + path = mkOption { + type = types.raw; + internal = true; + }; + }; + + config = + let + inherit (config.closure.config.nixpkgs.hostPlatform) system; + deploy-rs = deploy-rs-for-system system; + in + lib.mkMerge [ + (lib.mkIf (lib.elem config.activation [ + "nixos" + "home-manager" + "darwin" + "noop" + ]) { path = deploy-rs.lib.activate.${config.activation} config.closure; }) + ]; + }; + + nodeModule = { + imports = [ genericOptions ]; + + options = { + hostname = mkOption { type = types.str; }; + + profilesOrder = mkOption { + type = types.listOf types.str; + default = [ ]; + }; + + profiles = mkOption { + type = types.attrsOf (types.submoduleWith { modules = [ profileModule ]; }); + + apply = lib.mapAttrs ( + _: profile: + lib.filterAttrs ( + name: val: + !(lib.elem name [ + "activation" + "closure" + ]) + && val != null + ) profile + ); + + default = { }; + }; + }; + }; + in + { + nodes = mkOption { + default = { }; + type = types.attrsOf (types.submoduleWith { modules = [ nodeModule ]; }); + apply = lib.mapAttrs (_: node: lib.filterAttrs (_: val: val != null) node); + }; + }; + + config = lib.mkIf (cfg.nodes != { }) { flake.deploy.nodes = cfg.nodes; }; +} diff --git a/pkgs/default.nix b/pkgs/default.nix index 31335a6..46a9b61 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -1,5 +1,10 @@ -{ pkgs }: -pkgs.lib.packagesFromDirectoryRecursive { - inherit (pkgs) callPackage; - directory = ./packages; +{ + perSystem = + { pkgs, ... }: + { + packages = pkgs.lib.packagesFromDirectoryRecursive { + inherit (pkgs) callPackage; + directory = ./packages; + }; + }; } From 10e72d3c19cb99731f678758510bbf377eabf65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tristan=20Dani=C3=ABl=20Maat?= Date: Fri, 20 Feb 2026 05:33:26 +0800 Subject: [PATCH 2/2] refactor(flake.nix): Use flake-parts to simplify flake.nix --- checks/default.nix | 113 +++++++++----- checks/lib.nix | 35 ----- .../hardware-specific/hetzner/default.nix | 1 + .../hardware-specific/hetzner/vm.nix | 70 +++++++++ configuration/hardware-specific/vm.nix | 69 --------- dev-utils.nix | 43 ++++++ flake.lock | 32 ++-- flake.nix | 139 +++--------------- flakeModules/deploy-rs.nix | 136 +++++++++++++++++ pkgs/default.nix | 13 +- 10 files changed, 380 insertions(+), 271 deletions(-) delete mode 100644 checks/lib.nix create mode 100644 configuration/hardware-specific/hetzner/vm.nix delete mode 100644 configuration/hardware-specific/vm.nix create mode 100644 dev-utils.nix create mode 100644 flakeModules/deploy-rs.nix diff --git a/checks/default.nix b/checks/default.nix index 737313d..03e5b6b 100644 --- a/checks/default.nix +++ b/checks/default.nix @@ -1,47 +1,82 @@ -{ flake-inputs }: -let - inherit (flake-inputs.nixpkgs) lib; - pkgs = flake-inputs.nixpkgs.legacyPackages.x86_64-linux; - checkLib = pkgs.callPackage ./lib.nix { }; -in +{ self, ... }: { - x86_64-linux = lib.mergeAttrsList [ - flake-inputs.self.nixosConfigurations.hetzner-1.config.serviceTests - + perSystem = { - nix = checkLib.mkLint { - name = "nix-lints"; - fileset = lib.fileset.fileFilter (file: file.hasExt "nix") ../.; + inputs', + lib, + pkgs, + ... + }: + let + mkLint = + { + name, + fileset, + checkInputs ? [ ], + script, + }: + pkgs.stdenvNoCC.mkDerivation { + inherit name; - checkInputs = lib.attrValues { - inherit (pkgs) deadnix nixfmt-rfc-style; + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.difference fileset ( + lib.fileset.fileFilter ( + file: file.type != "regular" || file.name == "hardware-configuration.nix" + ) ../. + ); + }; - statix = pkgs.statix.overrideAttrs (old: { - patches = old.patches ++ [ - (pkgs.fetchpatch { - url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch"; - hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs="; - }) - ]; - }); + checkInputs = [ pkgs.nushell ] ++ checkInputs; + + checkPhase = '' + nu -c '${script}' | tee $out + ''; + + dontPatch = true; + dontConfigure = true; + dontBuild = true; + dontInstall = true; + dontFixup = true; + doCheck = true; + }; + in + { + checks = { + nix = mkLint { + name = "nix-lints"; + fileset = lib.fileset.fileFilter (file: file.hasExt "nix") ../.; + + checkInputs = lib.attrValues { + inherit (pkgs) deadnix nixfmt-rfc-style; + + statix = pkgs.statix.overrideAttrs (old: { + patches = old.patches ++ [ + (pkgs.fetchpatch { + url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch"; + hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs="; + }) + ]; + }); + }; + + script = /* bash */ '' + statix check **/*.nix + deadnix --fail **/*.nix + nixfmt --check --strict **/*.nix + ''; }; - script = '' - statix check **/*.nix - deadnix --fail **/*.nix - nixfmt --check --strict **/*.nix - ''; - }; + lockfile = mkLint { + name = "nix-lockfile"; + fileset = ../flake.lock; + checkInputs = lib.attrValues { inherit (inputs'.flint.packages) flint; }; - lockfile = checkLib.mkLint { - name = "nix-lockfile"; - fileset = ../flake.lock; - checkInputs = lib.attrValues { inherit (flake-inputs.flint.packages.x86_64-linux) flint; }; - - script = '' - flint --fail-if-multiple-versions - ''; - }; - } - ]; + script = /* bash */ '' + flint --fail-if-multiple-versions + ''; + }; + } + // self.nixosConfigurations.hetzner-1.config.serviceTests; + }; } diff --git a/checks/lib.nix b/checks/lib.nix deleted file mode 100644 index d38cd21..0000000 --- a/checks/lib.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ pkgs, lib, ... }: -{ - mkLint = - { - name, - fileset, - checkInputs ? [ ], - script, - }: - pkgs.stdenvNoCC.mkDerivation { - inherit name; - - src = lib.fileset.toSource { - root = ../.; - fileset = lib.fileset.difference fileset ( - lib.fileset.fileFilter ( - file: file.type != "regular" || file.name == "hardware-configuration.nix" - ) ../. - ); - }; - - checkInputs = [ pkgs.nushell ] ++ checkInputs; - - checkPhase = '' - nu -c '${script}' | tee $out - ''; - - dontPatch = true; - dontConfigure = true; - dontBuild = true; - dontInstall = true; - dontFixup = true; - doCheck = true; - }; -} diff --git a/configuration/hardware-specific/hetzner/default.nix b/configuration/hardware-specific/hetzner/default.nix index 4d0408c..3b60011 100644 --- a/configuration/hardware-specific/hetzner/default.nix +++ b/configuration/hardware-specific/hetzner/default.nix @@ -2,6 +2,7 @@ imports = [ ./hardware-configuration.nix ./disko.nix + ./vm.nix ]; # Intel's special encrypted memory<->CPU feature. Hetzner's BIOS diff --git a/configuration/hardware-specific/hetzner/vm.nix b/configuration/hardware-specific/hetzner/vm.nix new file mode 100644 index 0000000..7ecf8f8 --- /dev/null +++ b/configuration/hardware-specific/hetzner/vm.nix @@ -0,0 +1,70 @@ +{ lib, ... }: +{ + virtualisation.vmVariant = { + users.users.tlater.password = "insecure"; + + # Disable graphical tty so -curses works + boot.kernelParams = [ "nomodeset" ]; + + networking.hostName = lib.mkForce "testvm"; + + services = { + # Sets the base domain for nginx to a local domain so that we can + # easily test locally with the VM. + nginx.domain = lib.mkForce "dev.local"; + + # Don't run this + batteryManager.enable = lib.mkForce false; + btrfs.autoScrub.enable = lib.mkForce false; + + openssh.hostKeys = lib.mkForce [ + { + type = "rsa"; + bits = 4096; + path = "/etc/staging.key"; + } + ]; + }; + + # Use the staging secrets + sops.defaultSopsFile = lib.mkOverride 99 ../../../keys/staging.yaml; + + systemd.network.networks."10-eth0" = { + matchConfig.Name = "eth0"; + gateway = [ "192.168.9.1" ]; + networkConfig = { + Address = "192.168.9.2/24"; + }; + }; + + # Both so we have a predictable key for the staging env, as well as + # to have a static key for decrypting the sops secrets for the + # staging env. + environment.etc."staging.key" = { + mode = "0400"; + source = ../../../keys/hosts/staging.key; + }; + + # Pretend the acme renew succeeds. + # + # TODO(tlater): Set up pebble to retrieve certs "properly" + # instead + systemd.services."acme-order-renew-tlater.net".script = '' + touch out/acme-success + ''; + + virtualisation = { + memorySize = 3941; + cores = 2; + graphics = false; + diskSize = 1024 * 20; + + qemu = { + networkingOptions = lib.mkForce [ + "-device virtio-net,netdev=n1" + "-netdev bridge,id=n1,br=br0,helper=$(which qemu-bridge-helper)" + ]; + }; + }; + }; +} diff --git a/configuration/hardware-specific/vm.nix b/configuration/hardware-specific/vm.nix deleted file mode 100644 index 70c1b58..0000000 --- a/configuration/hardware-specific/vm.nix +++ /dev/null @@ -1,69 +0,0 @@ -{ lib, ... }: -{ - users.users.tlater.password = "insecure"; - - # Disable graphical tty so -curses works - boot.kernelParams = [ "nomodeset" ]; - - networking.hostName = "testvm"; - - services = { - # Sets the base domain for nginx to a local domain so that we can - # easily test locally with the VM. - nginx.domain = "dev.local"; - - # Don't run this - batteryManager.enable = lib.mkForce false; - - openssh.hostKeys = lib.mkForce [ - { - type = "rsa"; - bits = 4096; - path = "/etc/staging.key"; - } - ]; - }; - - # Use the staging secrets - sops.defaultSopsFile = lib.mkOverride 99 ../../keys/staging.yaml; - - systemd.network.networks."10-eth0" = { - matchConfig.Name = "eth0"; - gateway = [ "192.168.9.1" ]; - networkConfig = { - Address = "192.168.9.2/24"; - }; - }; - - # Both so we have a predictable key for the staging env, as well as - # to have a static key for decrypting the sops secrets for the - # staging env. - environment.etc."staging.key" = { - mode = "0400"; - source = ../../keys/hosts/staging.key; - }; - - # Pretend the acme renew succeeds. - # - # TODO(tlater): Set up pebble to retrieve certs "properly" - # instead - systemd.services."acme-order-renew-tlater.net".script = '' - touch out/acme-success - ''; - - virtualisation.vmVariant = { - virtualisation = { - memorySize = 3941; - cores = 2; - graphics = false; - diskSize = 1024 * 20; - }; - - virtualisation.qemu = { - networkingOptions = lib.mkForce [ - "-device virtio-net,netdev=n1" - "-netdev bridge,id=n1,br=br0,helper=$(which qemu-bridge-helper)" - ]; - }; - }; -} diff --git a/dev-utils.nix b/dev-utils.nix new file mode 100644 index 0000000..bfa0d17 --- /dev/null +++ b/dev-utils.nix @@ -0,0 +1,43 @@ +{ self, ... }: +{ + # Systems on which to make dev utilities runnable; anything + # NixOS-related encodes its own system. + systems = [ "x86_64-linux" ]; + + perSystem = + { + inputs', + self', + pkgs, + lib, + ... + }: + { + apps = { + default = self'.apps.runVm; + + runVm = { + type = "app"; + program = lib.getExe self.nixosConfigurations.hetzner-1.config.system.build.vm; + meta.description = "Run the test VM"; + }; + }; + + devShells = { + default = pkgs.mkShell { + sopsPGPKeyDirs = [ + "./keys/hosts/" + "./keys/users/" + ]; + + packages = lib.attrValues { + inherit (inputs'.sops-nix.packages) sops-import-keys-hook sops-init-gpg-key; + inherit (pkgs) deploy-rs; + }; + }; + + minecraft = pkgs.mkShell { packages = lib.attrValues { inherit (pkgs) packwiz; }; }; + webserver = self'.packages.webserver.devShell; + }; + }; +} diff --git a/flake.lock b/flake.lock index b7ae789..3094d19 100644 --- a/flake.lock +++ b/flake.lock @@ -46,7 +46,9 @@ "deploy-rs", "flake-compat" ], - "flake-parts": "flake-parts", + "flake-parts": [ + "flake-parts" + ], "nix-test-runner": "nix-test-runner", "nixpkgs": [ "sonnenshift", @@ -154,18 +156,14 @@ }, "flake-parts": { "inputs": { - "nixpkgs-lib": [ - "sonnenshift", - "crate2nix", - "nixpkgs" - ] + "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1712014858, - "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "lastModified": 1769996383, + "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", + "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", "type": "github" }, "original": { @@ -266,6 +264,21 @@ "url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz" } }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1769909678, + "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "72716169fe93074c333e8d0173151350670b824c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, "pre-commit-hooks": { "inputs": { "flake-compat": [ @@ -309,6 +322,7 @@ "inputs": { "deploy-rs": "deploy-rs", "disko": "disko", + "flake-parts": "flake-parts", "flint": "flint", "foundryvtt": "foundryvtt", "nixpkgs": "nixpkgs", diff --git a/flake.nix b/flake.nix index 10b52ec..8680b59 100644 --- a/flake.nix +++ b/flake.nix @@ -1,8 +1,7 @@ { - description = "tlater.net host configuration"; - inputs = { nixpkgs.url = "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz"; + flake-parts.url = "github:hercules-ci/flake-parts"; ## Nix/OS utilities @@ -42,139 +41,49 @@ crate2nix.inputs = { flake-compat.follows = "deploy-rs/flake-compat"; devshell.inputs.flake-utils.follows = "deploy-rs/utils"; + flake-parts.follows = "flake-parts"; }; }; }; }; outputs = - { - self, - nixpkgs, - sops-nix, - ... - }@inputs: - let - system = "x86_64-linux"; - - vm = nixpkgs.lib.nixosSystem { - inherit system; - specialArgs.flake-inputs = inputs; - - modules = [ - ./configuration - ./configuration/hardware-specific/vm.nix + { flake-parts, ... }@inputs: + flake-parts.lib.mkFlake { inherit inputs; } ( + { self, ... }@args: + { + imports = [ + (flake-parts.lib.importApply ./flakeModules/deploy-rs.nix args) + ./checks + ./dev-utils.nix + ./pkgs ]; - }; - # deploy-rs unfortunately uses an `import nixpkgs`, and its - # library functions depend on an instantiated nixpkgs, so we - # can't get around multi-nixpkgs-eval. - inherit - (import nixpkgs { - inherit system; - overlays = [ - inputs.deploy-rs.overlays.default - (_: prev: { - deploy-rs = { - inherit (nixpkgs.legacyPackages.${system}) deploy-rs; - inherit (prev.deploy-rs) lib; - }; - }) - ]; - }) - deploy-rs - ; - in - { - ################## - # Configurations # - ################## - nixosConfigurations = { - # The actual system definition - hetzner-1 = nixpkgs.lib.nixosSystem { - inherit system; + flake.nixosConfigurations.hetzner-1 = inputs.nixpkgs.lib.nixosSystem { specialArgs.flake-inputs = inputs; - modules = [ ./configuration ./configuration/hardware-specific/hetzner ]; }; - }; - ############################ - # Deployment configuration # - ############################ - deploy.nodes = { - hetzner-1 = { + deploy.nodes.hetzner-1 = { hostname = "116.202.158.55"; profiles.system = { user = "root"; - path = deploy-rs.lib.activate.nixos self.nixosConfigurations.hetzner-1; + activation = "nixos"; + closure = self.nixosConfigurations.hetzner-1; + + sshUser = "tlater"; + sshOpts = [ + "-p" + "2222" + "-o" + "ForwardAgent=yes" + ]; }; - - sshUser = "tlater"; - sshOpts = [ - "-p" - "2222" - "-o" - "ForwardAgent=yes" - ]; }; - }; - - ######### - # Tests # - ######### - checks = import ./checks { flake-inputs = inputs; }; - - ########################### - # Garbage collection root # - ########################### - - packages.${system} = { - default = vm.config.system.build.vm; } - // import ./pkgs { pkgs = nixpkgs.legacyPackages.${system}; }; - - ################### - # Utility scripts # - ################### - apps.${system} = { - default = self.apps.${system}.run-vm; - - run-vm = { - type = "app"; - program = - (nixpkgs.legacyPackages.${system}.writeShellScript "" '' - ${vm.config.system.build.vm.outPath}/bin/run-testvm-vm - '').outPath; - }; - }; - - ########################### - # Development environment # - ########################### - devShells.${system} = { - default = nixpkgs.legacyPackages.${system}.mkShell { - sopsPGPKeyDirs = [ - "./keys/hosts/" - "./keys/users/" - ]; - - packages = nixpkgs.lib.attrValues { - inherit (sops-nix.packages.${system}) sops-import-keys-hook sops-init-gpg-key; - inherit (deploy-rs) deploy-rs; - }; - }; - - minecraft = nixpkgs.legacyPackages.${system}.mkShell { - packages = nixpkgs.lib.attrValues { inherit (nixpkgs.legacyPackages.${system}) packwiz; }; - }; - - webserver = self.packages.${system}.webserver.devShell; - }; - }; + ); } diff --git a/flakeModules/deploy-rs.nix b/flakeModules/deploy-rs.nix new file mode 100644 index 0000000..abee5a8 --- /dev/null +++ b/flakeModules/deploy-rs.nix @@ -0,0 +1,136 @@ +{ lib, ... }@exportingFlake: +let + inherit (lib) mkOption types; + + deploy-rs-for-system = + system: + (import exportingFlake.inputs.nixpkgs { + inherit system; + overlays = [ + exportingFlake.inputs.deploy-rs.overlays.default + (_final: prev: { + deploy-rs = { + inherit (exportingFlake.inputs.nixpkgs.legacyPackages.${system}) deploy-rs; + inherit (prev.deploy-rs) lib; + }; + }) + ]; + }).deploy-rs; +in +{ config, ... }: +let + cfg = config.deploy; +in +{ + options.deploy = + let + genericOptions = + let + mkGenericOption = + type: + mkOption { + type = types.nullOr type; + default = null; + }; + in + { + options = { + sshUser = mkGenericOption types.str; + user = mkGenericOption types.str; + sshOpts = mkGenericOption (types.listOf types.str); + fastConnection = mkGenericOption types.bool; + autoRollback = mkGenericOption types.bool; + magicRollback = mkGenericOption types.bool; + confirmTimeout = mkGenericOption types.int; + activationTimeout = mkGenericOption types.int; + tempPath = mkGenericOption types.str; + interactiveSudo = mkGenericOption types.bool; + }; + }; + + profileModule = + { config, ... }: + { + imports = [ genericOptions ]; + + options = { + activation = mkOption { + type = types.oneOf [ + (types.enum [ + "nixos" + "home-manager" + "darwin" + "noop" + ]) + ]; + }; + + closure = mkOption { type = types.raw; }; + + profilePath = mkOption { + type = types.nullOr types.str; + default = null; + }; + + path = mkOption { + type = types.raw; + internal = true; + }; + }; + + config = + let + inherit (config.closure.config.nixpkgs.hostPlatform) system; + deploy-rs = deploy-rs-for-system system; + in + lib.mkMerge [ + (lib.mkIf (lib.elem config.activation [ + "nixos" + "home-manager" + "darwin" + "noop" + ]) { path = deploy-rs.lib.activate.${config.activation} config.closure; }) + ]; + }; + + nodeModule = { + imports = [ genericOptions ]; + + options = { + hostname = mkOption { type = types.str; }; + + profilesOrder = mkOption { + type = types.listOf types.str; + default = [ ]; + }; + + profiles = mkOption { + type = types.attrsOf (types.submoduleWith { modules = [ profileModule ]; }); + + apply = lib.mapAttrs ( + _: profile: + lib.filterAttrs ( + name: val: + !(lib.elem name [ + "activation" + "closure" + ]) + && val != null + ) profile + ); + + default = { }; + }; + }; + }; + in + { + nodes = mkOption { + default = { }; + type = types.attrsOf (types.submoduleWith { modules = [ nodeModule ]; }); + apply = lib.mapAttrs (_: node: lib.filterAttrs (_: val: val != null) node); + }; + }; + + config = lib.mkIf (cfg.nodes != { }) { flake.deploy.nodes = cfg.nodes; }; +} diff --git a/pkgs/default.nix b/pkgs/default.nix index 31335a6..46a9b61 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -1,5 +1,10 @@ -{ pkgs }: -pkgs.lib.packagesFromDirectoryRecursive { - inherit (pkgs) callPackage; - directory = ./packages; +{ + perSystem = + { pkgs, ... }: + { + packages = pkgs.lib.packagesFromDirectoryRecursive { + inherit (pkgs) callPackage; + directory = ./packages; + }; + }; }