# 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:

```nix
{ 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`):

```nix
{ ... }: {
  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 as `builder` to update the machine, enables printing and mounting USB drives, and sets SSH to password. Used on `t2mbp`, `14900k`, `ideapad`, `uConsole`. Skipped on `nix-server` and `r5500`.
- `systemUconsoleKernelBuilder` — cross-build the uConsole kernel on x86_64, not for running on the Pi.
- `systemFlatpak` — allowlist sync; skips heavy `flatpak update` when lists unchanged (`runHeavyMaintenanceEverySwitch`).

**Desktop notes**

- `chiasson.desktop.displayManager.variant`: `sddm` vs `dankgreeter` (DMS + Hyprland/Niri tends toward DankGreeter).
- `desktopWallpapers` copies `inputs.wallpapers` into the store; override `chiasson.desktop.wallpapers.source` if 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:

1. `~/.ssh/config` (managed by home-manager) sets `IdentityAgent SSH_AUTH_SOCK` and `IdentityFile ~/.ssh/id_ed25519_<user>.pub` for every host block.
2. OpenSSH reads the `.pub` file as a **filter** against the agent (OpenSSH 7.3+ trick — `.pub` as 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.
3. 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):

```sh
# 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:

```nix
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

1. Generate a keypair: `rbw gen ssh-key <name>`.
2. Add a `users.<name> = { publicKey = "ssh-ed25519 …"; };` line under `users` in `modules/ssh/inventory.nix`.
3. Add a `<name> = { isNormalUser = true; … ssh.inbound.enable = true; ssh.inbound.authorizedHosts = "all"; }` entry under `chiasson.users.catalog` in `modules/system/users.nix`.
4. Enable on every host that needs that user, e.g.:

```nix
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

```nix
# 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`:

```nix
# 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). Every `wisdom*` slice auto-catalogs — including the bash shell — so the root module no longer manually `imports` it. Gate packages on `chiasson.home.*.enable`, and set the slice's own `enable` default with `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).

**Trivial single-package slice:** use `self.lib.wisdomSlice { path = "a.b"; default = true/false; description = "…"; packages = pkgs: [ … ]; }` instead of the `root`/`cfg` + `mkIf (root.enable && cfg.enable)` boilerplate. `packages` is a `pkgs -> [ drv ]` function responsible for its own `availableOn`/platform gating. Several such slices may share one `simple.nix` in their category dir (each still exporting its own `wisdom*` module), rather than one file per slice.

**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 check` after touching `modules/`.
- Stage/commit new files if the flake is read from git.
- Small diffs; `mkEnableOption` + `mkIf` for toggles.
- Cross-module deps: assertions or `mkDefault`, mention it in the option description.
- New flake inputs in `flake.nix` with `follows` where versions should track nixpkgs.
- `client-services` SSH 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.
