This repository manages system configurations for all of my macOS, NixOS, and Linux machines.
The flake follows the dendritic pattern on top of
flake-parts. flake.nix holds the
inputs, the concrete host outputs (nixosConfigurations,
darwinConfigurations, homeConfigurations), and the perSystem wiring
for overlays, devShell, treefmt, pre-commit, and checks.
vic/import-tree recursively pulls in
every .nix file under ./modules; each one self-registers as
a reusable module body (flake.<class>Modules.<name>). Hosts then
compose those modules by name.
modules/
├── _lib.nix # mkAspect helper (underscore-prefixed, skipped by import-tree)
├── shared/ # cross-class option modules + wiring
│ (primary-user, common-base, nixpkgs-wiring, identity, fonts)
├── nixos/ # flake.nixosModules.<name>
├── darwin/ # flake.darwinModules.<name>
├── home/ # flake.homeModules.<name> (includes assets/ for non-Nix files)
└── profiles/ # identity profiles registered across all three classes
Each module file inlines its body directly — a file like modules/nixos/hyprland.nix
both registers flake.nixosModules.hyprland and contains the full compositor
configuration. Cross-module references go through config.flake.<class>Modules.<name>
so hm.imports = [ config.flake.homeModules.hyprland ] in the NixOS module is
how the home-manager side of Hyprland is pulled in when Hyprland is enabled.
Non-Nix assets that aren't flake-parts modules live in secrets/,
pkgs/ (custom packages), and
modules/home/assets/{dotfiles,nvim,yazi}/ (source-path references for
the corresponding home modules).
nix-darwin and NixOS share identical shell/user/fonts/packages setup via
modules/nixos/default.nix and
modules/darwin/default.nix, with shared
option declarations (user, hm) and nixpkgs wiring factored into
modules/shared/.
The home-manager configuration is entirely decoupled from NixOS and
nix-darwin. All modules live in modules/home/. For each
NixOS/darwin host they are pulled in via the flake-parts hm alias (see
modules/shared/primary-user.nix),
which forwards config.hm.* to home-manager.users.${config.user.name}.*.
The same module tree is also exposed as homeConfigurations in
flake.nix (fanned out across x86_64-linux,
aarch64-linux, and aarch64-darwin) so it is fully usable as a
standalone configuration on any Linux or macOS system via the
home-manager CLI.
User "profiles" live in modules/profiles; these
modules configure contextual, identity-specific settings such as SSL
certificates or email addresses. Each profile is declared across all three
module classes in a single file via the mkAspect helper; currently only
personal.nix exists.
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --determinateSkip this step on NixOS, where nix is the package manager by default.
NixOS hosts are provisioned remotely with nixos-anywhere, which partitions the disk from the host's disko config, builds the system locally and copies it over SSH.
-
Pre-generate the host's SSH host key. sops-nix decrypts secrets with it during
nixos-install, and profile-personal's login password lives insecrets/users.yaml(mutableUsers = false, so there is no fallback). Without the key in place at install time the user ends up with no password.host=<hostname> keys=$(mktemp -d) install -d -m755 "$keys/etc/ssh" ssh-keygen -q -t ed25519 -N "" -C "root@$host" -f "$keys/etc/ssh/ssh_host_ed25519_key" ssh-to-age < "$keys/etc/ssh/ssh_host_ed25519_key.pub"
-
Enroll the key in sops. Add the printed age key as
&<hostname>underkeys:in.sops.yaml, reference it in everycreation_rulesentry the host needs (at minimumsecrets/users.yaml), then re-encrypt:sops updatekeys secrets/users.yaml git add -A
-
Boot the target into a NixOS installer ISO (disable Secure Boot in firmware if it won't boot). The minimal ISO has no NetworkManager; join Wi-Fi with
sudo systemctl start wpa_supplicant+wpa_cli. Set a password withpasswd, note the IP, and from the provisioning machine:ssh-copy-id nixos@<ip>
nixos-anywhere copies these
authorized_keysto root and reconnects as root, so key auth is required — a password alone isn't enough. -
Install. Run from an interactive terminal: disko prompts for the LUKS passphrase over the SSH tty.
nix run github:nix-community/nixos-anywhere -- \ --flake ".#$host" \ --target-host nixos@<ip> \ --extra-files "$keys" rm -rf "$keys"
Add
--no-substitute-on-destinationif the target has LAN access to the provisioning machine but no internet uplink. The installer ISO is detected, so nothing is downloaded on the target. Check the install output forsetting up secrets for users...with noCannot read ssh key/failed to decrypterrors after it. -
After first boot, enroll a FIDO2 key for LUKS unlock:
sudo systemd-cryptenroll --fido2-device=auto /dev/disk/by-partlabel/disk-main-luks
Recovering a host that installed without its secrets (e.g. no login
password): finish steps 1–2, boot the installer again, and rerun step 4 with
--disko-mode mount --phases disko,install,reboot. That unlocks and mounts
the existing disk instead of reformatting it (the log still prints
"Formatting hard drive with disko"; it only asks for the passphrase once),
places the host key and reinstalls.
Clone this repository into ~/.nixpkgs:
git clone https://github.com/kclejeune/system ~/.nixpkgsBootstrap a new system by using nh to activate the config:
nix run .#nh -- darwin switch .#kclejeune@aarch64-darwinnh auto-detects the host and installs nix-darwin or home-manager; override
with --darwin or --home-manager if needed.