feat(lib): mksystem builder and nixpkgs overlays

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-28 06:48:07 +02:00
commit 87bfc4cc31
2 changed files with 198 additions and 0 deletions

154
lib/mksystem.nix Normal file
View file

@ -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/<name>.nix` (or `hosts/<name>/default.nix`) by convention
# - imports `users/<user>/nixos.nix` if it exists
# - wires home-manager and imports `users/<user>/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/<name>/default.nix`
# (lets a host have its own subdirectory for hardware files etc.),
# fall back to a flat `hosts/<name>.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.<name>`. 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.<name>.* option
# namespace exists) but inert until `omni.packs.<name>.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;
}