Move Home Manager module defaults from a centralized `desktop-home-defaults.nix` file into their respective `wisdom` slices. This change aligns with the updated documentation regarding module ownership, where defaults live with the option that owns them rather than in a shared defaults file. Additionally, update `docs/conventions.md` to reflect this new pattern for defining `mkEnableOption` defaults and move base git/encryption configurations into the core `wisdom` module. - Delete `modules/hosts/desktop-home-defaults.nix` - Update `modules/wisdom/default.nix` to include shared git and package defaults - Add `default = ...` to `mkEnableOption` in various `wisdom` modules - Update `docs/conventions.md` with new HM slice guidelines
10 KiB
Conventions
How the flake is wired
flake.nix runs import-tree on ./modules with a few extra filters: skip /package/, /packages/, and dms/home-manager/ (callPackage noise + DMS HM colocation). Don't add a global /home-manager/ filter — modules/ssh/home-manager/ needs to load.
Every scanned .nix should be a flake fragment:
{ self, inputs, ... }: {
flake.nixosModules.systemFoo = { config, lib, pkgs, ... }: { /* … */ };
}
Don't define the same flake.nixosModules.foo in two files — merge order isn't something to bet on.
Outputs I actually use: nixosConfigurations.*, nixosModules.*, homeManagerModules.*, lib.*, packages.*, navi / naviHive. modules/parts.nix declares supported systems and the flake.lib / flake.homeManagerModules option slots.
Option namespaces
Project options live under chiasson.* only:
| Prefix | For |
|---|---|
chiasson.system.* |
Machine policy — docker, flatpak, audio, gaming, deploy builder, … |
chiasson.desktop.* |
GUI — compositors, DMS, wallpapers, display manager |
chiasson.home.* |
Home Manager toggles (module exports are still wisdom*) |
chiasson.users.* |
Catalog, enabled, hostOverrides, extraModules |
chiasson.ssh.* |
SSH inventory |
Leave upstream services.*, networking.*, home.*, etc. alone.
Module shape
NixOS leaf — camelCase export, usually prefixed (systemDocker, desktopNiri):
{ ... }: {
flake.nixosModules.systemSomething = { config, lib, pkgs, ... }:
let cfg = config.chiasson.system.something;
in {
options.chiasson.system.something.enable = lib.mkEnableOption "…";
config = lib.mkIf cfg.enable { /* … */ };
};
}
Aggregates: nixosModules.system and nixosModules.desktop import their leaves. Host configs import those stacks and set options — they shouldn't reimplement whole subsystems.
Home — files under modules/wisdom/. Baseline is homeManagerModules.wisdom (chiasson.home.enable, wired via chiasson.desktop.homeManager.bundleWisdom). Other wisdom* slices auto-wire once per user via lib.wisdomCatalogExtraModules self (modules/desktop/options.nix); hosts only set matching chiasson.home.*.enable toggles — no re-import in home.nix.
User apps / dotfiles → wisdom. Daemons, firewall, kernel → NixOS. Sometimes both (LocalSend: HM installs, systemLocalsend opens the firewall).
Odd exports
"client-services"— shared setup for laptops/tablets/desktops (not the VMs). Turns on Bluetooth, lets Navi log in asbuilderto update the machine, enables printing and mounting USB drives, and sets SSH to password. Used ont2mbp,14900k,ideapad,uConsole. Skipped onnix-serverandr5500.systemUconsoleKernelBuilder— cross-build the uConsole kernel on x86_64, not for running on the Pi.systemFlatpak— allowlist sync; skips heavyflatpak updatewhen lists unchanged (runHeavyMaintenanceEverySwitch).
Desktop notes
chiasson.desktop.displayManager.variant:sddmvsdankgreeter(DMS + Hyprland/Niri tends toward DankGreeter).desktopWallpaperscopiesinputs.wallpapersinto the store; overridechiasson.desktop.wallpapers.sourceif needed.
Users & SSH
User definitions live in the catalog defaults block inside modules/system/users.nix (was previously split across four files in modules/system/users/; folded into one for cohesion). olivier, server, builder, etc. Pick who exists on a host with chiasson.users.enabled.
SSH model — read this once
SSH authentication has two independent sides:
| Side | Holds | Decides |
|---|---|---|
| Client (connecting from) | identity private keys | "Here's proof I own key K" |
| Server (connecting to) | authorized_keys |
"Do I trust key K?" |
A key is just a cryptographic identity — it's not bound to a host or a machine account name. The model is one key per user account: each catalog account (olivier, server, builder) has its own keypair in modules/ssh/inventory.nix, regardless of which machine that account runs on. Concretely: when olivier@14900k sshes to server@r5500, OpenSSH routes via a Match user server block (generated from chiasson.ssh.outbound.rbw.extraIdentities) → IdentityFile ~/.ssh/id_ed25519_server.pub (written from users.server.publicKey) to ask the rbw agent for the server private key; r5500's server authorized_keys holds the matching server pubkey. olivier's catalog default sets extraIdentities = [ "server" "builder" ] so ssh <account>@<host> Just Works for every catalog role. We also support from="ip,cidr" constraints per account to scope which source IPs may authenticate as it.
This repo enforces "one key per user": every user has one ed25519 keypair, the private key in Bitwarden, the public key inline in modules/ssh/inventory.nix. On SSH connect:
~/.ssh/config(managed by home-manager) setsIdentityAgent SSH_AUTH_SOCKandIdentityFile ~/.ssh/id_ed25519_<user>.pubfor every host block.- OpenSSH reads the
.pubfile as a filter against the agent (OpenSSH 7.3+ trick —.pubas IdentityFile tells the agent "give me the private key matching this pub"), so even with multiple keys loaded, only the matching one is tried. This is why the old "too many auth failures" symptom is gone by construction. - rbw holds the private key; HM-managed shells + the desktop session expose
SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/rbw/…declaratively (no activation scripts).
Bitwarden onboarding
For each new user (run on the user's primary machine):
# 1. Generate a keypair with rbw (creates a Bitwarden item + prints the pubkey).
rbw gen ssh-key olivier
# 2. Paste the printed `ssh-ed25519 AAAA… olivier` line into
# modules/lib/ssh-inventory.nix under users.olivier.publicKey.
# 3. Push the user to a host:
ssh root@nix-server nixos-rebuild switch --flake .#nix-server
# 4. Have the user ssh into the fleet from any client — their rbw key
# is the only thing offered, and every server already has it in
# authorized_keys.
Adding a host
Edit modules/ssh/inventory.nix and add:
hosts."newhost" = {
hostName = "192.168.2.X"; # IP — required for navi to deploy here
aliases = [ "newhost" ]; # what you type at the ssh prompt
};
Then add self.nixosModules.<host>Configuration to modules/deploy/navi.nix.
Adding a catalog user
- Generate a keypair:
rbw gen ssh-key <name>. - Add a
users.<name> = { publicKey = "ssh-ed25519 …"; };line underusersinmodules/ssh/inventory.nix. - Add a
<name> = { isNormalUser = true; … ssh.inbound.enable = true; ssh.inbound.authorizedHosts = "all"; }entry underchiasson.users.cataloginmodules/system/users.nix. - Enable on every host that needs that user, e.g.:
chiasson.users.enabled = [ "olivier" "<name>" ];
Passwords
Passwords aren't in the repo. They're in secrets/secrets.yaml (encrypted with sops). Each host that has olivier must declare that secret and set neededForUsers = true; sops-nix decrypts it at boot, and the catalog points hashedPasswordFile at that file. That's why the catalog is a proper module instead of a static list — it needs config.sops.secrets.… at eval time.
Host overrides
# configuration.nix — machine policy
chiasson.users.enabled = [ "olivier" ];
chiasson.users.hostOverrides.<name> = { /* optional */ };
Home-manager per-host slices (desktop/laptop hosts)
Desktop hosts also have home.nix exporting flake.nixosModules.<host>Home, wired from default.nix alongside *Configuration:
# default.nix
modules = [
self.nixosModules."14900kConfiguration"
self.nixosModules."14900kHome"
];
# home.nix — flake fragment, per-host chiasson.home.* toggles
{ self, inputs, ... }: {
flake.nixosModules."14900kHome" = { self, pkgs, ... }: {
imports = [ self.nixosModules.desktopHomeBase ];
chiasson.users.extraModules.olivier = [
{
chiasson.home.browsers.edge.enable = true;
# …
}
];
};
}
flake.nixosModules.desktopHomeBase expands lib.wisdomCatalogExtraModules plus shared desktop toggles. Host *Home modules append per-host chiasson.home overrides (and rare inline home.packages blocks). chiasson.users.extraModules concatenates lists from multiple modules (base + host), so both can set the same user key.
The integration block at the bottom of modules/system/users.nix turns the catalog + overrides into users.users + HM + inbound SSH. Don't hand-roll catalog users unless you're changing this module.
Adding things
New NixOS leaf: export flake.nixosModules.whatever, wire from system/default.nix or desktop/default.nix if it's global, or only from a host configuration.nix if it's not. nix flake check. Git-add new paths if eval uses the git tree.
New HM slice: add modules/wisdom/.../foo.nix exporting flake.homeManagerModules.wisdomFoo (import-tree picks it up; wisdomCatalogExtraModules includes every wisdom* export except wisdom / wisdomShellBash). Gate packages on chiasson.home.*.enable, and set the slice's own enable default (mkEnableOption "…" // { default = true/false }) in that same module — defaults live with the option that owns them, not in a shared defaults file. Hosts flip toggles in home.nix. Upstream HM deps stay imported unconditionally — use mkIf on cfg.enable for config (never config-dependent imports; that recurses).
Derivations: let inside a fragment, or flake.packages / flake.lib — not a bare mkDerivation file import-tree will try to load.
New host: hosts/<name>/default.nix with nixosSystem listing *Configuration (+ *Home for desktops), configuration.nix exporting *Configuration, hardware + _private/ as needed. Desktop/laptop hosts also get home.nix exporting *Home (import self.nixosModules.desktopHomeBase unless it's a special case). Register in modules/deploy/navi.nix if it should be in the fleet.
When editing
nix flake checkafter touchingmodules/.- Stage/commit new files if the flake is read from git.
- Small diffs;
mkEnableOption+mkIffor toggles. - Cross-module deps: assertions or
mkDefault, mention it in the option description. - New flake inputs in
flake.nixwithfollowswhere versions should track nixpkgs. client-servicesSSH defaults are for my LAN — don't copy to anything internet-facing without tightening.- If I'll forget the behavior in six months, update these docs in the same change.