README.md

nixconfig #

Personal NixOS configuration using Flakes, flake-parts, and import-tree.

Structure #

nixconfig/
├── flake.nix              # Entry point — wires flake-parts + import-tree
├── modules/
│   ├── aggregators/       # Role bundles + aspect aggregators (base, workstation, server, shell, apps, desktop, services)
│   ├── features/          # Feature modules (base, desktop, dev, profiles, rescue, server, shell)
│   │   ├── base/          # core (nixos + HM), local, cachyosKernel, sops
│   │   ├── desktop/       # compositors, apps, bar, services, noctalia, tools
│   │   ├── dev/           # editors, tools, languages
│   │   ├── profiles/      # gaming, laptop, performance, virtualisation, mkVM
│   │   ├── rescue/        # minimal rescue shell
│   │   ├── server/        # media/, security/, share/, vpn/ + monitoring, nginx, sshd, serverCore
│   │   └── shell/         # zsh, starship, ssh, shell tools
│   ├── hosts/             # Per-host registry entries (+ hardware/disko/impermanence)
│   │   ├── nimeses/        # Desktop laptop (workstation)
│   │   ├── prometheus/     # Desktop PC / NVIDIA (workstation)
│   │   ├── hephaistos/     # Server — Tailscale-only (vaultwarden, croc)
│   │   ├── athena/         # Server — public (nimeses.com, full stack)
│   │   ├── hermes/         # Minimal desktop / kiosk (base)
│   │   └── icarus/         # finix (experimental, non-NixOS)
│   ├── lib/               # Registry + aspects, shared compositor options
│   └── meta/              # Flake-parts wiring, generators, deploy-rs nodes, nixpkgs, users, overlays
└── modules/MODULES.md     # Full module reference

All .nix files under modules/ are picked up automatically by import-tree — no manual imports needed when adding new modules.


First-time Installation #

1. Clone the repo #

git clone https://github.com/N1meses/nixconfig 

2. Generate hardware configuration #

nixos-generate-config --root /mnt --show-hardware-config > modules/hosts/<hostname>/hardware<Hostname>.nix

3. Create host file #

Create modules/hosts/<hostname>/<hostname>.nix — use an existing host as reference (e.g. nimeses.nix). A host is a single registry.hosts.<hostname> entry; host-specific inline config lives on the entry itself as nixosModule/homeModule (there is no separate configurations.* block anymore — that was collapsed into the registry):

{config, ...}: {
  registry.hosts.<hostname> = {
    username = "<username>";
    system = "x86_64-linux";
    stateVersion = "<current-nixos-version>";
    # domain = "example.com";     # primary FQDN (public or tailnet) if it serves anything
    # hostId = "deadbeef";        # required for ZFS
    # extraGroups = ["plugdev"];

    # One list of module names, each routed to whichever layer defines it:
    # flake.modules.nixos.<name> -> system, .homeManager.<name> -> home, both -> both.
    # Start from a role bundle (base / workstation / server), then add extras.
    # Validated against an enum of known names — a typo fails at eval with the full list.
    aspects = with config.flake.lib.aspects; [
      workstation            # or: server / base
      hardware<Hostname>
      # add feature module names here
    ];

    # Host-specific inline config (boot, hardware tweaks, extra packages, sops).
    # networking.hostName is set by the generator from the registry key.
    nixosModule = {pkgs, ...}: {
      users.users.<username>.initialPassword = "changeme";
    };

    homeModule = {pkgs, ...}: {
      # e.g. home.packages, features.compositors.*, ...
    };
  };
}

4. Install #

nixos-install --flake .#<hostname>  --option experimental-features "nix-command flakes"

or when rebuilding the first time after a graphical install

sudo nixos-rebuild switch --flake .#<hostname> --option "extra-experimental-features" "nix-command flakes"

After the first build, experimental features are enabled permanently via the core module — subsequent rebuilds don't need the flag.


Adding a New Host #

  1. Create modules/hosts/<hostname>/ directory
  2. Add hardware<Hostname>.nix (generated by nixos-generate-config)
  3. Add <hostname>.nix with registry.hosts.<hostname> — its aspects list plus any host-specific nixosModule/homeModule inline config
  4. git add the new files (import-tree only sees tracked files)
  5. Rebuild: nh os switch --flake .#<hostname>

Common Commands #

Rebuild #

nh os switch              # rebuild + switch current host (NixOS + HM)
nh os switch --flake .#<hostname>   # specific host

Remote deploy (deploy-rs) #

deploy.nodes is generated from the registry (modules/meta/deploy.nix) for every NixOS host, so remote pushes are rollback-safe:

deploy .#<host>                 # build locally, copy closure, activate on remote
deploy .#<host> --skip-checks   # skip the (slow) flake-check pre-flight

Magic rollback is on: if the new config breaks connectivity, the target auto-reverts to the previous generation on its own. Nodes set sshOpts = ["-o" "ControlPath=none"] so the confirmation step uses a real fresh login rather than a multiplexed master socket (otherwise a broken sshd/firewall could falsely confirm).

Maintenance #

nix flake update          # update all inputs
nix flake update <input>  # update specific input
nix flake check           # check for evaluation errors
nh clean all              # remove old generations

Inspection #

nix flake show            # list all flake outputs
nix repl .                # open repl with flake loaded

Hosts #

Host Role Bundle Domain
nimeses Desktop laptop workstation —
prometheus Desktop PC (NVIDIA) workstation —
hephaistos Server — Tailscale-only server hephaistos.tail4109e2.ts.net
athena Server — full public stack server nimeses.com
hermes Minimal desktop / kiosk base —
icarus finix (experimental, non-NixOS) — —

The five NixOS hosts are also deploy-rs targets (deploy .#<host>); icarus is a finix build, not a NixOS deploy node.


Module System #

Modules are registered as flake.modules.nixos.<name> or flake.modules.homeManager.<name>. A host selects them through a single aspects list in its registry.hosts.<host> entry:

aspects = with config.flake.lib.aspects; [ workstation niri git ];

Role bundles & aggregators. Names in aspects can also be aggregators — keys of flake.aspectInclude.<name> (in modules/aggregators/) that expand to a list of other aspects via transitive closure. The role bundles base, workstation, and server are the top-level ones (e.g. workstation pulls in base, desktop, niri, …), so most hosts list a bundle plus a few extras. An aggregator key needs no backing module — it contributes only its members.

The generators (modules/meta/generation.nix for NixOS, modules/meta/homeModules.nix for home) route each resolved name to whichever layer defines it:

Name defined in… Applied to
flake.modules.nixos.<name> only the system
flake.modules.homeManager.<name> only home
both both

This means a compositor (nixos session + HM config) can't be half-wired, and a standalone homeConfigurations.<host> build automatically ignores nixos-only names (keeping the home side portable). aspects is enum-typed against the set of known module names, so a typo fails at eval and the error lists every valid name. The with config.flake.lib.aspects; scope lets names be written bare instead of quoted.

Listed = enabled — naming a module in aspects is sufficient to enable it. No extra .enable = true needed except for:

  • Compositor selection (features.compositors.niri.enable)
  • Server domain (features.server.domain)
  • Per-monitor config (features.compositors.monitors.<name>)

See modules/MODULES.md for the full module reference.