From 87bfc4cc31cd15d52b67513fedb3e944d445f639 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sun, 28 Jun 2026 06:48:07 +0200 Subject: [PATCH] feat(lib): mksystem builder and nixpkgs overlays Co-Authored-By: Claude Opus 4.8 --- lib/mksystem.nix | 154 +++++++++++++++++++++++++++++++++++++++++++++++ lib/overlays.nix | 44 ++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 lib/mksystem.nix create mode 100644 lib/overlays.nix diff --git a/lib/mksystem.nix b/lib/mksystem.nix new file mode 100644 index 0000000..e2a0042 --- /dev/null +++ b/lib/mksystem.nix @@ -0,0 +1,154 @@ +# mksystem — uniform NixOS host constructor. +# +# Inspired by mitchellh/nixos-config's lib/mksystem.nix, adapted for our +# context. Replaces the boilerplate of repeated `nixpkgs.lib.nixosSystem` +# calls in flake.nix with a single function that: +# +# - dispatches to nixpkgs.lib.nixosSystem +# - applies overlays uniformly +# - imports `hosts/.nix` (or `hosts//default.nix`) by convention +# - imports `users//nixos.nix` if it exists +# - wires home-manager and imports `users//home-manager.nix` +# - opt-in dev-env via `devEnv = true` (imports modules/dev-env) +# - exposes `currentSystemName`, `currentSystemUser`, `inputs` to every +# module via specialArgs so downstream code can introspect host context +# +# Usage in flake.nix: +# +# let +# overlays = import ./lib/overlays.nix { inherit inputs; }; +# mkSystem = import ./lib/mksystem.nix { inherit nixpkgs overlays inputs; }; +# in +# { +# nixosConfigurations = { +# my-laptop = mkSystem "my-laptop" { +# user = "padreug"; +# devEnv = true; +# }; +# +# # Multiple users on the same machine, no dev-env: +# lab-shared = mkSystem "lab-shared" { +# user = "padreug"; +# }; +# }; +# } +# +# Differences from mitchellh's version: +# +# - NixOS only (no darwin, no WSL — we don't need those right now and +# the dispatch is easy to add later if we do). +# - dev-env module is opt-in; not every host wants it. +# - `modules` parameter lets a host inject extra modules without +# creating a wrapper file. +# - Uses specialArgs (not _module.args inside a module) so the values +# are visible to imported file paths, which is required for things +# like `(let path = currentSystemName; in ./hosts/${path})`. + +{ + nixpkgs, + overlays ? [ ], + inputs, +}: + +name: +{ + system ? "x86_64-linux", + user, + devEnv ? false, + modules ? [ ], + # Extra args merged into the system-level specialArgs. Used by hosts + # whose configuration.nix expects values beyond the defaults (e.g. + # omni's configuration.nix takes `settings` from settings.nix). + extraSpecialArgs ? { }, + # Same, for home-manager.extraSpecialArgs. + extraHmArgs ? { }, +}: + +let + # Resolve the host's config file. Prefer `hosts//default.nix` + # (lets a host have its own subdirectory for hardware files etc.), + # fall back to a flat `hosts/.nix`. + hostDir = ../hosts/${name}; + hostFile = ../hosts + "/${name}.nix"; + hostConfig = + if builtins.pathExists hostDir then + hostDir + else if builtins.pathExists hostFile then + hostFile + else + throw "mksystem: no host config for '${name}' (looked at ${toString hostDir} and ${toString hostFile})"; + + userNixosConfig = ../users/${user}/nixos.nix; + userHMConfig = ../users/${user}/home-manager.nix; + + hasUserNixosConfig = builtins.pathExists userNixosConfig; + hasUserHMConfig = builtins.pathExists userHMConfig; +in + +nixpkgs.lib.nixosSystem { + # specialArgs flow into every module's function signature AND can be + # used in `imports = [ ... ]` paths. _module.args cannot, which is + # why we don't use it for these. + specialArgs = { + inherit inputs; + currentSystemName = name; + currentSystemUser = user; + } + // extraSpecialArgs; + + modules = [ + # Apply overlays first so subsequent modules see our pinned packages. + { nixpkgs.hostPlatform = system; } + { nixpkgs.overlays = overlays; } + { nixpkgs.config.allowUnfree = true; } + + # Per-host configuration. + hostConfig + ] + # Per-user system-level config (account, shell, sudo) — only if the + # user file exists. Skipping silently is fine; some hosts share users + # defined elsewhere (e.g. omni's users.nix). + ++ nixpkgs.lib.optional hasUserNixosConfig userNixosConfig + ++ [ + # sops-nix — declarative age-encrypted secrets, decrypted at + # activation. Module always included; host modules opt in by + # declaring `sops.secrets.`. Per-host wiring (defaultSopsFile, + # age.keyFile) lives in modules/secrets.nix. + inputs.sops-nix.nixosModules.sops + ] + ++ [ + # Opt-in packs — always imported (so the omni.packs..* option + # namespace exists) but inert until `omni.packs..enable = true`. + # Single wire-in point; the ISO bypasses mksystem and so stays + # pack-free. See modules/packs/default.nix. + ../modules/packs + ] + ++ [ + # Home-manager wiring. + inputs.home-manager.nixosModules.home-manager + { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + home-manager.extraSpecialArgs = { + inherit inputs; + currentSystemName = name; + currentSystemUser = user; + } + // extraHmArgs; + } + # Pre-create the home-manager activation script's expected + # directory tree. See modules/home-manager-bootstrap.nix for + # the full rationale and the bugs it fixes. Imported here + # rather than inlined so the live ISO (which bypasses + # mksystem) can import the same module and get the same + # bootstrap. + ../modules/home-manager-bootstrap.nix + ] + ++ nixpkgs.lib.optional hasUserHMConfig { + home-manager.users.${user} = import userHMConfig; + } + # dev-env is opt-in. + ++ nixpkgs.lib.optional devEnv ../modules/dev-env + # Caller-supplied extras. + ++ modules; +} diff --git a/lib/overlays.nix b/lib/overlays.nix new file mode 100644 index 0000000..38e7efa --- /dev/null +++ b/lib/overlays.nix @@ -0,0 +1,44 @@ +# Overlays applied uniformly across every host built by mksystem. +# +# Pin select fast-moving packages to nixpkgs-unstable while keeping +# everything else on the stable channel. This is the mitchellh pattern: +# stable for stability, unstable for the handful of things that move +# faster than we want to wait. +# +# To use, the flake.nix needs both inputs: +# +# inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; +# inputs.nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable"; +# +# Then: +# +# overlays = import ./lib/overlays.nix { inherit inputs; }; +# mkSystem = import ./lib/mksystem.nix { inherit nixpkgs overlays inputs; }; + +{ inputs }: + +[ + # Pinned-to-unstable packages. + # + # Add a package here when stable is too slow for your needs (claude-code + # ships weekly, gh fixes are frequent, direnv has churn). Remove when + # stable catches up. Keep the list small — every entry is a divergence + # from the rest of the stable closure. + ( + final: prev: + let + unstable = import inputs.nixpkgs-unstable { + system = prev.stdenv.hostPlatform.system; + config.allowUnfree = true; + }; + in + { + claude-code = unstable.claude-code; + pi-coding-agent = unstable.pi-coding-agent; + gh = unstable.gh; + direnv = unstable.direnv; + nix-direnv = unstable.nix-direnv; + } + ) + +]