Compare commits

...

4 Commits

Author SHA1 Message Date
Olivier 524abb9ceb Flake lock update 2026-07-15 10:38:27 -03:00
Olivier 6291076494 chore(14900k): disable handbrake due to ffmpeg-full incompatibility
Comment out handbrake installation because the current ffmpeg-full-8.1.2
version in nixpkgs contains a broken A01-mov-read patch that prevents
successful builds. This is a temporary measure until nixpkgs is updated
or the system is switched to a stable channel.
2026-07-15 10:38:08 -03:00
Olivier f3d358ce12 feat(14900k): add MediaTek MT7927 WiFi and Bluetooth support
Add a private module to enable MediaTek MT7927 (Filogic 380) support.
This includes a firmware path fix for Bluetooth and a systemd service
to force-load patched out-of-tree kernel modules, bypassing in-tree
drivers that lack support for this hardware on nixos-unstable.
2026-07-15 10:37:43 -03:00
Olivier 645e1f0f31 Refactor ssh&users 2026-07-15 10:02:04 -03:00
16 changed files with 863 additions and 603 deletions
+69 -7
View File
@@ -63,13 +63,75 @@ User apps / dotfiles → wisdom. Daemons, firewall, kernel → NixOS. Sometimes
- `desktopWallpapers` copies `inputs.wallpapers` into the store; override `chiasson.desktop.wallpapers.source` if needed.
## Users
## Users & SSH
User definitions live in `usersCatalogDefaults` (`modules/system/users/catalog-default.nix`) — olivier, server, builder, etc. Pick who exists on a host with `chiasson.users.enabled`.
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/lib/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/lib/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/lib/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/lib/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.
On a host:
### Host overrides
```nix
# configuration.nix — machine policy
@@ -77,6 +139,8 @@ 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
@@ -86,7 +150,7 @@ modules = [
self.nixosModules."14900kHome"
];
# home.nix — flake fragment, per-host `chiasson.home.*` toggles
# home.nix — flake fragment, per-host chiasson.home.* toggles
{ self, inputs, ... }: {
flake.nixosModules."14900kHome" = { self, pkgs, ... }: {
imports = [ self.nixosModules.desktopHomeBase ];
@@ -102,9 +166,7 @@ modules = [
`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.
`usersHomeIntegration` turns that into `users.users` + HM. Don't hand-roll catalog users unless you're changing the users module itself.
SSH: `sshInbound` on NixOS, outbound/rbw under `modules/ssh/home-manager/`.
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
Generated
+169 -123
View File
@@ -38,11 +38,11 @@
]
},
"locked": {
"lastModified": 1782389081,
"narHash": "sha256-60aWPXGfLy3fVDTgwd1noQ2W+gryGy0EnNB0jLjfsUY=",
"lastModified": 1783991957,
"narHash": "sha256-QekvkvkOF0iWAV680BcTZYWyW/jiokq7jfIm1wIKIY4=",
"ref": "refs/heads/main",
"rev": "0feb09ad82ab1800ce5727bb94191deb500ad184",
"revCount": 123,
"rev": "dff91dce874c9d517643331eac59bcd6ba29926e",
"revCount": 132,
"type": "git",
"url": "https://git.chiasson.cloud/Olivier/cursor-nixos-flake"
},
@@ -79,11 +79,11 @@
]
},
"locked": {
"lastModified": 1782185186,
"narHash": "sha256-URMC6ppMqe9WQOdH/oTeOqUcJggJGJaNOrhdzwSKyYo=",
"lastModified": 1783265113,
"narHash": "sha256-Ra65IQsez9QqXcFLp0PJ2f2y4+4X4CAkzmH/x/OXkY8=",
"owner": "AvengeMedia",
"repo": "dgop",
"rev": "28558a7e79ab5c81b6d832fe48f40ea499866496",
"rev": "15d96577fd8950a39bbb40ee9c3ac266be23f5c3",
"type": "github"
},
"original": {
@@ -117,17 +117,17 @@
},
"dms": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": [
"nixpkgs"
],
"quickshell": "quickshell"
]
},
"locked": {
"lastModified": 1777431599,
"narHash": "sha256-g6r/Gx8PTDzO3jCNzzySA+Ff1lmLF9nDlMCNyyoQjoE=",
"lastModified": 1783529634,
"narHash": "sha256-zdHsPGPE5MVi/y+uIt548XScTfZjQzdF21dME7ISEJM=",
"owner": "AvengeMedia",
"repo": "DankMaterialShell",
"rev": "eb5afcdc40ea5446c27e18552ff4a19f9daf9484",
"rev": "bdfd565b72cbb416441a9ab828efcac4fd516c70",
"type": "github"
},
"original": {
@@ -140,11 +140,11 @@
"dms-plugin-ambient-sound": {
"flake": false,
"locked": {
"lastModified": 1781888428,
"narHash": "sha256-uHPPeEYc5ix9EIBBsEHlle7QdGDZqv7aG/A/22Y+CSQ=",
"lastModified": 1783948872,
"narHash": "sha256-C9IBE5k1dQzOVgGRdVpLIP6pvjjVys18CXjymh8PzFc=",
"owner": "hthienloc",
"repo": "dms-ambient-sound",
"rev": "5ddf16c151450029d352eedb70d811439c1d51d2",
"rev": "3d987f1a39e02e21d0bf9b33b42094367a69f771",
"type": "github"
},
"original": {
@@ -236,11 +236,11 @@
"dms-plugin-emoji-launcher": {
"flake": false,
"locked": {
"lastModified": 1776309741,
"narHash": "sha256-NQ14YenDiNK2VqXQ3z7jAkatbSRtYJHhOhvv7AJlUD8=",
"lastModified": 1783139331,
"narHash": "sha256-fmIddCvACwO8wbAtLBMtDnEXXQJjb7+o2s4jW3f8VIU=",
"owner": "devnullvoid",
"repo": "dms-emoji-launcher",
"rev": "1c0a7d337a52b48f9499060076703a35e8dd4f4f",
"rev": "8ff394e3ddfcb2fd755ed2e7b4c6f01f3e26e596",
"type": "github"
},
"original": {
@@ -252,11 +252,11 @@
"dms-plugin-ephemera": {
"flake": false,
"locked": {
"lastModified": 1777606672,
"narHash": "sha256-G6oanR4zHbNSV+A94mNcB4EHK+S2ieo4dY8bwUEkv2I=",
"lastModified": 1783707727,
"narHash": "sha256-+i7rbacYd7gfXcJtFuzV9GoIDSH8JFsf6H2FXPGMRpQ=",
"owner": "nicolasgarcia214",
"repo": "Ephemera",
"rev": "eaadf6d933a8449f36219935520a8e9f944b4aa0",
"rev": "172783fccb37e99756f999daa737ced42aeefe4e",
"type": "github"
},
"original": {
@@ -268,11 +268,11 @@
"dms-plugin-home-assistant": {
"flake": false,
"locked": {
"lastModified": 1780998572,
"narHash": "sha256-YScGw1b4OHX3s7f+JUCoCuK+BcWGDLgpYxgSI+N3EPI=",
"lastModified": 1783907889,
"narHash": "sha256-woLXMu5/t/mADIG1svWNifuo3ypZMJ94ee44+kBCXK4=",
"owner": "xxyangyoulin",
"repo": "dms-plugin-hass",
"rev": "0d3cd45f6a094582db5f9209b3dc1f72c1cfb067",
"rev": "3cef9b5a66f2d2010ee5e2fe49456b0aed32c003",
"type": "github"
},
"original": {
@@ -300,11 +300,11 @@
"dms-plugin-ocr-scanner": {
"flake": false,
"locked": {
"lastModified": 1781670158,
"narHash": "sha256-n0oe+K1zUqCfEMyvtz+CYiZ+lBQv/Piqb19J/Cwqua4=",
"lastModified": 1782019482,
"narHash": "sha256-9BNQl7oP5n3vJiBgdXaHsUewkJnJiiHCvSID1tWCeKk=",
"owner": "hthienloc",
"repo": "dms-ocr-scanner",
"rev": "dd94a72c2fc11fcca10bd8edb59803734c2154f5",
"rev": "268a7fca154edaf2f05ed12700fa968a0f8b75bc",
"type": "github"
},
"original": {
@@ -316,11 +316,11 @@
"dms-plugin-official": {
"flake": false,
"locked": {
"lastModified": 1780332225,
"narHash": "sha256-QkQPqP7Wmo5DLRyKNSY5NuOau4LSaSfz3DYdHDLxluA=",
"lastModified": 1783532508,
"narHash": "sha256-17qZKvH0FDQEdX07HEfUjLLvOUEHGmXMVZ0zRtKoTZA=",
"owner": "AvengeMedia",
"repo": "dms-plugins",
"rev": "f4583449f12920e0a2f16808b00a860c27f0173d",
"rev": "5e4038806d8f4ca1fcfd1116c211cc9f1e36a074",
"type": "github"
},
"original": {
@@ -364,11 +364,11 @@
"dms-plugin-wallpaper-carousel": {
"flake": false,
"locked": {
"lastModified": 1779087303,
"narHash": "sha256-/0t6ykbirNgSB2gY1wpq8jbntnuUgME+kLDfwjLhfRg=",
"lastModified": 1782824647,
"narHash": "sha256-t70CBhiEBYHa9HvPpCZnfA8eCOWhUlXc6SUa9VprFNE=",
"owner": "motor-dev",
"repo": "wallpaperCarousel",
"rev": "bca1f457763d51c8001f8edcc89df3e619420163",
"rev": "c08fbb92c39d4d778bb08e520cfd96e395594440",
"type": "github"
},
"original": {
@@ -380,11 +380,11 @@
"dms-plugin-web-search": {
"flake": false,
"locked": {
"lastModified": 1773893074,
"narHash": "sha256-c6mVBTlkJdfvMuMvPjXGeOEWBtb0mdmIcPNzgmMxGwE=",
"lastModified": 1783139331,
"narHash": "sha256-UqFgAjW2A75dtlvOZkq4Vv/v/DROoc/ouCXBaVlksPI=",
"owner": "devnullvoid",
"repo": "dms-web-search",
"rev": "52f9ec482dc86d9c5ff0110a5d57401112191a38",
"rev": "821f5b437ea96739ce1cbc85ce324fb55e8884bb",
"type": "github"
},
"original": {
@@ -396,11 +396,11 @@
"dms-plugin-widget-group": {
"flake": false,
"locked": {
"lastModified": 1781887621,
"narHash": "sha256-3uMtYcR4/drkAbjAopxWaNCeGv1PpwovPhq4OnU866s=",
"lastModified": 1782507283,
"narHash": "sha256-+ApvThHcyP5Ivd+5nDrkZpKyoq9N1Aet+AJNKBPWfmY=",
"owner": "rdannenbring",
"repo": "widget-group",
"rev": "a82424ffdaddda90fcfe8d571d902c22fb894bb2",
"rev": "cd748fb105375629bf32378c7d05d10e50c51281",
"type": "github"
},
"original": {
@@ -426,6 +426,22 @@
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "NixOS",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
@@ -439,7 +455,7 @@
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"flake-compat_2": {
"flake-compat_3": {
"locked": {
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
@@ -459,11 +475,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1778716662,
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
"lastModified": 1782949081,
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
"type": "github"
},
"original": {
@@ -560,11 +576,11 @@
"nixpkgs-lib": "nixpkgs-lib_3"
},
"locked": {
"lastModified": 1778716662,
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
"lastModified": 1782949081,
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
"type": "github"
},
"original": {
@@ -640,11 +656,11 @@
"uv2nix": "uv2nix"
},
"locked": {
"lastModified": 1783286111,
"narHash": "sha256-t0NRlftLETETXef9nGI0wvD0YvouudXsKQMpyOpCSc4=",
"lastModified": 1783989509,
"narHash": "sha256-UwiWbdWHCoHyvsqAdOmKMOfvkMwDAcVqmH7zJGPcJ3g=",
"owner": "NousResearch",
"repo": "hermes-agent",
"rev": "c9a150d6407b700b4fb2f693415ba143b56a40eb",
"rev": "b663d50a6a0101d5214112b24ffe20924af32beb",
"type": "github"
},
"original": {
@@ -660,11 +676,11 @@
]
},
"locked": {
"lastModified": 1782423922,
"narHash": "sha256-qPNd6lUohHP5gcJhqQ7rLV87RwIx0xYR2A4Frb9Zjc4=",
"lastModified": 1783963347,
"narHash": "sha256-r376E2XpakiXwModDHIxlvB6qLq4iFVEq730vxOO4JY=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "5d320ab301cfaaca7d32514f13815d19d109f5f4",
"rev": "a45a7c451455a51ae740ec3bce4024b312809c29",
"type": "github"
},
"original": {
@@ -681,11 +697,11 @@
]
},
"locked": {
"lastModified": 1781497404,
"narHash": "sha256-9GAF8sSsnkyCVCWkomXR0T+zdSxyUlfPt6neQidimdg=",
"lastModified": 1782839684,
"narHash": "sha256-vzs4SBgPsK4aNzlJR2PpFwtARazXMOxZonQnDz0YHxk=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "1285cd3d6882a9847f2d56ed5541b3350c8a6162",
"rev": "2a37d71bbe69e1522ddabf03a4cea0374958bdbe",
"type": "github"
},
"original": {
@@ -709,14 +725,30 @@
"type": "github"
}
},
"mediatek-mt7927-dkms": {
"flake": false,
"locked": {
"lastModified": 1783265495,
"narHash": "sha256-GXbwhZbvfWE+w9TgMReAsAFL/WkbCokzEiIChmX2d00=",
"owner": "jetm",
"repo": "mediatek-mt7927-dkms",
"rev": "ad1f3e4d19fe540aaa1f449ddba86c65db9bfc82",
"type": "github"
},
"original": {
"owner": "jetm",
"repo": "mediatek-mt7927-dkms",
"type": "github"
}
},
"mobile-nixos": {
"flake": false,
"locked": {
"lastModified": 1772289954,
"narHash": "sha256-iDdtwk/dFb6AsXMtcOpZixxXl6C1HNUPe6cglxxHO7M=",
"lastModified": 1783912271,
"narHash": "sha256-CzwmiKxuh1u+H8hDnrVeUl/fL59PvsgLbr2l0FhqWK0=",
"owner": "mobile-nixos",
"repo": "mobile-nixos",
"rev": "1a9e0af79dc7b5e29ed772f1a8a76fcbd9d45fdf",
"rev": "2c132754323fc1915e8d21dcfc0ef68ab084c6fb",
"type": "github"
},
"original": {
@@ -725,22 +757,41 @@
"type": "github"
}
},
"mt7927": {
"inputs": {
"mediatek-mt7927-dkms": "mediatek-mt7927-dkms",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1784045889,
"narHash": "sha256-ObYysms1WamPJPdVgrIdCLW/+YXHSewjPlhYmmPJJtM=",
"owner": "cmspam",
"repo": "mt7927-nixos",
"rev": "90d453fe6ee8166a83e924131a5da9962e751c03",
"type": "github"
},
"original": {
"owner": "cmspam",
"repo": "mt7927-nixos",
"type": "github"
}
},
"navi": {
"inputs": {
"crane": "crane",
"flake-parts": "flake-parts_3",
"nix-github-actions": "nix-github-actions",
"nixos-anywhere": "nixos-anywhere",
"nixpkgs": "nixpkgs_2",
"nixpkgs": "nixpkgs_3",
"stable": "stable",
"tofunix": "tofunix"
},
"locked": {
"lastModified": 1781964794,
"narHash": "sha256-DpmkR7ifgSLj7890ayIsri3eioB7V8KhA6ys3ivXAPw=",
"lastModified": 1783089845,
"narHash": "sha256-KwjncG/BgYouGBDyTSaIKc5Qs/81y6aCOb/uS6nWemw=",
"owner": "cafkafk",
"repo": "navi",
"rev": "f6e15457cebefb3a8aa6e30e1961288676c9c7f1",
"rev": "120d278511ea610b4e29c62a3e72673f90702d18",
"type": "github"
},
"original": {
@@ -810,7 +861,7 @@
},
"nixcord": {
"inputs": {
"flake-compat": "flake-compat",
"flake-compat": "flake-compat_2",
"flake-parts": "flake-parts_6",
"nixpkgs": [
"nixpkgs"
@@ -818,11 +869,11 @@
"nixpkgs-nixcord": "nixpkgs-nixcord"
},
"locked": {
"lastModified": 1782436508,
"narHash": "sha256-F91/VTVJmeERWwiJMHfEjkCEXWcNECSOiBsil3TlX9k=",
"lastModified": 1783966657,
"narHash": "sha256-3UpTBZgZa/GhBALXqk5hfVsVhcFGERqjb3W2BuaD4sc=",
"owner": "KaylorBen",
"repo": "nixcord",
"rev": "9c530d99735c236db405f6a1ed58de0872ff9a19",
"rev": "0fd2e5db348f04e65447a773ddf55eb5b4e0b64d",
"type": "github"
},
"original": {
@@ -915,18 +966,18 @@
"nixos-raspberrypi": {
"inputs": {
"argononed": "argononed",
"flake-compat": "flake-compat_2",
"flake-compat": "flake-compat_3",
"nixos-images": "nixos-images_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1779023229,
"narHash": "sha256-MInilg7B/06c34SwOuGSBho4l0H1EZcmvxTkSWCs5pE=",
"lastModified": 1783459375,
"narHash": "sha256-S2pb+Mtv0qTeRr+6J5ESgelNjG2YKl4WqKjdPrJx5rM=",
"owner": "nvmd",
"repo": "nixos-raspberrypi",
"rev": "06c6e3513e1ee64b651913193fc6ac38aa4963f5",
"rev": "c845922f260b306d4529a4535bbb6e7f8cfe2887",
"type": "github"
},
"original": {
@@ -970,11 +1021,11 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1777168982,
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
"lastModified": 1782614948,
"narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
"rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c",
"type": "github"
},
"original": {
@@ -1000,11 +1051,11 @@
},
"nixpkgs-lib_3": {
"locked": {
"lastModified": 1777168982,
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
"lastModified": 1782614948,
"narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
"rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c",
"type": "github"
},
"original": {
@@ -1015,11 +1066,11 @@
},
"nixpkgs-nixcord": {
"locked": {
"lastModified": 1780453794,
"narHash": "sha256-bXMRa9VTsHSPXL4Cw8R6JJLQeY3Y/IP4+YJCYVmQ7FY=",
"lastModified": 1782999065,
"narHash": "sha256-5Dgj5+pIQYZKrXUGaLCk7CKfN3MmpwIhO94++WVxvng=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338",
"rev": "80d591ed473cfc46329932c2aadac9b435342c7c",
"type": "github"
},
"original": {
@@ -1030,6 +1081,22 @@
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1784007870,
"narHash": "sha256-djcLt/JJphyNt4eDY9XTly+/WbCK5lqWq9lSgCmJkkQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "18b9261cb3294b6d2a06d03f96872827b8fe2698",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1750134718,
"narHash": "sha256-v263g4GbxXv87hMXMCpjkIxd/viIF7p3JpJrwgKdNiI=",
@@ -1045,13 +1112,13 @@
"type": "github"
}
},
"nixpkgs_3": {
"nixpkgs_4": {
"locked": {
"lastModified": 1781577229,
"narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=",
"lastModified": 1784007870,
"narHash": "sha256-djcLt/JJphyNt4eDY9XTly+/WbCK5lqWq9lSgCmJkkQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f",
"rev": "18b9261cb3294b6d2a06d03f96872827b8fe2698",
"type": "github"
},
"original": {
@@ -1061,7 +1128,7 @@
"type": "github"
}
},
"nixpkgs_4": {
"nixpkgs_5": {
"locked": {
"lastModified": 1780336545,
"narHash": "sha256-vhVhuXzFrIOfcssC/9hDHx7MHzDKjF3keHuREOQqQiQ=",
@@ -1125,11 +1192,11 @@
]
},
"locked": {
"lastModified": 1782441318,
"narHash": "sha256-e+EWubnfN/CqogQ3qFL7i5Yg8ST7hg/19WS+BAOYFb8=",
"lastModified": 1783991529,
"narHash": "sha256-fHexrPkcBAX/4uFGF31MSBLglZqVFZkPXOn9M/ywoZ8=",
"owner": "nix-community",
"repo": "NUR",
"rev": "548388638a8f389437bc81e14122667244a36912",
"rev": "cc8dbe7122d71b860dc7e9e303d98dae5895e424",
"type": "github"
},
"original": {
@@ -1233,28 +1300,6 @@
"type": "github"
}
},
"quickshell": {
"inputs": {
"nixpkgs": [
"dms",
"nixpkgs"
]
},
"locked": {
"lastModified": 1776854048,
"narHash": "sha256-lLbV66V3RMNp1l8/UelmR4YzoJ5ONtgvEtiUMJATH/o=",
"ref": "refs/heads/master",
"rev": "783c953987dc56ff0601abe6845ed96f1d00495a",
"revCount": 806,
"type": "git",
"url": "https://git.outfoxxed.me/quickshell/quickshell"
},
"original": {
"rev": "783c953987dc56ff0601abe6845ed96f1d00495a",
"type": "git",
"url": "https://git.outfoxxed.me/quickshell/quickshell"
}
},
"root": {
"inputs": {
"cursor": "cursor",
@@ -1284,11 +1329,12 @@
"home-manager": "home-manager",
"import-tree": "import-tree",
"mobile-nixos": "mobile-nixos",
"mt7927": "mt7927",
"navi": "navi",
"nix-monitor": "nix-monitor",
"nixcord": "nixcord",
"nixos-raspberrypi": "nixos-raspberrypi",
"nixpkgs": "nixpkgs_3",
"nixpkgs": "nixpkgs_4",
"nixvirt": "nixvirt",
"nur": "nur",
"oom-hardware": "oom-hardware",
@@ -1310,11 +1356,11 @@
]
},
"locked": {
"lastModified": 1782165805,
"narHash": "sha256-478kKQBvK6SYTOdN2h9jhKJv94nbXRbFMfuL1WshErg=",
"lastModified": 1783174389,
"narHash": "sha256-aCWC8ngycU7OdJrU2+Je3qf+1a2ykuBvpPhZT/9tXMc=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "56b24064fdcaedca53553b1a6d607fd23b613a24",
"rev": "f1406619a3884cd5c47992a70b8b35c9c0fcb4c9",
"type": "github"
},
"original": {
@@ -1331,11 +1377,11 @@
"systems": "systems_3"
},
"locked": {
"lastModified": 1782031037,
"narHash": "sha256-a7oWSyS7SN81UOqVt481yIEMDsMpaJ7GNdV6Eaz5Yqg=",
"lastModified": 1783838433,
"narHash": "sha256-Zb8+v76qSPYDlUea1y30YzgdEoDjA97vRmeqfPAqwZs=",
"owner": "Gerg-L",
"repo": "spicetify-nix",
"rev": "9cb27462cfd20edac174353f1e95bc03aa888863",
"rev": "c6ab7371e40abd405313af9bddafd5f37cc94f83",
"type": "github"
},
"original": {
@@ -1464,11 +1510,11 @@
"t2linux-patches": {
"flake": false,
"locked": {
"lastModified": 1782419564,
"narHash": "sha256-Rz6BsjpS9SETgcSkTz/QYuncR5CB0TM61SCzTiIcRI0=",
"lastModified": 1783622056,
"narHash": "sha256-Sx0ZzYLDPs1DswThy8rWfNcS05ilHcP0DXz8nO6+FOc=",
"owner": "t2linux",
"repo": "linux-t2-patches",
"rev": "ff59395cc61769dc255b5eab6faac27e8ce441f7",
"rev": "b3fac289b2e46797c6e403078df1567214920324",
"type": "github"
},
"original": {
@@ -1565,7 +1611,7 @@
},
"wrapper-modules": {
"inputs": {
"nixpkgs": "nixpkgs_4"
"nixpkgs": "nixpkgs_5"
},
"locked": {
"lastModified": 1782135443,
@@ -1589,11 +1635,11 @@
]
},
"locked": {
"lastModified": 1782144240,
"narHash": "sha256-RgCWSv7AJZCwPhCzz+J0lvwp1WBz9ouvCnnlmvu0xfw=",
"lastModified": 1783982026,
"narHash": "sha256-tFP35Y/BDWkDwFVoCab0gN00yKjGJU/IWpVxl68tGEI=",
"owner": "0xc000022070",
"repo": "zen-browser-flake",
"rev": "d1693556428967f8b4eef128feb090421ddcaf15",
"rev": "8cbde27feab69507f13a41a01ee45c2b632a8f43",
"type": "github"
},
"original": {
+3
View File
@@ -203,6 +203,9 @@
hermes = {
url = "github:NousResearch/hermes-agent";
};
mt7927 = {
url = "github:cmspam/mt7927-nixos";
};
};
outputs = inputs:
+1 -1
View File
@@ -93,7 +93,7 @@ let
// lib.optionalAttrs (name == "nix-server") {
targetPort = 22;
}
) ssh.activeHosts;
) ssh.activeHosts /* nix-deploy target list IPs only */;
metaNixpkgs = import inputs.nixpkgs {
system = "x86_64-linux";
@@ -0,0 +1,107 @@
{ pkgs, inputs, ... }:
# All MediaTek MT7927 / MT6639 (Filogic 380) WiFi 7 + Bluetooth support for this
# host in one place: the upstream flake module, the hardware enablement, and the
# workarounds needed to actually make it function on nixos-unstable.
#
# Workaround background: on current nixos-unstable, `boot.extraModulePackages`
# modules placed in `extra/` are NOT indexed by `depmod` (modules.dep /
# modules.alias contain zero `extra/` entries). As a result `modprobe`/udev
# resolve mt7925e / btusb to the stock in-tree drivers, which do not support the
# MediaTek MT7927 (14c3:7927). We therefore force the patched out-of-tree modules
# to load by explicit path, blacklist the in-tree collisions so udev cannot bind
# them first, and fix the BT firmware path the driver expects.
let
# The upstream mt7927 flake installs the BT firmware under
# `mediatek/mt6639/`, but the patched btmtk driver requests it from
# `mediatek/mt7927/`. Provide it at the expected path.
mt7927BtFirmwareFix = pkgs.runCommand "mt7927-bt-firmware-fix" { } ''
src="${inputs.mt7927.packages.${pkgs.system}.firmware}/lib/firmware/mediatek/mt6639/BT_RAM_CODE_MT6639_2_1_hdr.bin"
install -Dm644 "$src" \
"$out/lib/firmware/mediatek/mt7927/BT_RAM_CODE_MT6639_2_1_hdr.bin"
'';
in
{
imports = [
inputs.mt7927.nixosModules.default
];
hardware.mediatek-mt7927 = {
enable = true;
enableWifi = true;
enableBluetooth = true;
disableAspm = true; # Highly recommended to fix upload speeds
};
hardware.firmware = [ mt7927BtFirmwareFix ];
boot.blacklistedKernelModules = [
# Whole mt76 stack (patched extra/ copy replaces it entirely).
"mt76"
"mt76_connac_lib"
"mt792x_lib"
"mt7921_common"
"mt7921e"
"mt7925_common"
"mt7925e"
# Patched bluetooth.
"btusb"
"btmtk"
];
systemd.services.mt7927-force = {
description = "Force-load patched MediaTek MT7927 (Filogic 380) kernel modules";
wantedBy = [ "multi-user.target" ];
before = [ "network.target" "NetworkManager.service" ];
path = [ pkgs.kmod ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
KVER="$(uname -r)"
EXTRA="/run/booted-system/kernel-modules/lib/modules/$KVER/extra"
# Load in-tree core dependencies (these ARE indexed, so modprobe works).
modprobe mac80211 cfg80211 rfkill led-class bluetooth || true
# Defensive: drop any in-tree mt76 modules that may have been pulled in
# (e.g. via boot.kernelModules) so the patched copies can bind instead.
rmmod mt7925e mt7921e mt7925_common mt7921_common mt792x_lib mt76_connac_lib mt76 2>/dev/null || true
insmod_x() {
local mod="$1"
local name
name="''${mod##*/}"; name="''${name%.ko}"
if lsmod | grep -q "^$name "; then
echo "mt7927-force: $name already loaded, skipping"
else
echo "mt7927-force: insmod $mod"
insmod "$mod" || echo "mt7927-force: FAILED to insmod $mod"
fi
}
# Patched mt76 stack (full replacement, dependency order).
insmod_x "$EXTRA/mt76/mt76.ko"
insmod_x "$EXTRA/mt76/mt76-connac-lib.ko"
insmod_x "$EXTRA/mt76/mt792x-lib.ko"
insmod_x "$EXTRA/mt76/mt7921/mt7921-common.ko"
insmod_x "$EXTRA/mt76/mt7925/mt7925-common.ko"
insmod_x "$EXTRA/mt76/mt7921/mt7921e.ko"
insmod_x "$EXTRA/mt76/mt7925/mt7925e.ko"
# Patched bluetooth. The patched btmtk/btusb need symbols from the
# in-tree bluetooth core (bluetooth.ko) and the btrtl/btintel/btbcm
# helpers, so load those first (they are indexed and modprobe-able).
for m in bluetooth btrtl btintel btbcm rfkill ecdh_generic ecc crc16; do
if modprobe "$m" 2>/dev/null; then
echo "mt7927-force: modprobe $m ok"
else
echo "mt7927-force: modprobe $m FAILED (continuing)"
fi
done
insmod_x "$EXTRA/bluetooth/btmtk.ko"
insmod_x "$EXTRA/bluetooth/btusb.ko"
'';
};
}
+4 -2
View File
@@ -23,6 +23,7 @@
./_private/streaming-display.nix
./_private/ai.nix
./_private/media-disk.nix
./_private/mediatek-mt7927.nix
./_private/jellyfin-nfs-export.nix
./_private/moonfin-flatpak.nix
];
@@ -44,7 +45,6 @@
libvdpau-va-gl
];
};
environment.sessionVariables = {
LIBVA_DRIVER_NAME = "iHD";
# Force VA-API to use the Intel iGPU render node (otherwise libva may pick NVIDIA and iHD fails).
@@ -100,7 +100,9 @@
yt-dlp
# Native install (avoid flatpak sandbox issues for QSV/VAAPI).
handbrake
# handbrake # TEMP removed 2026-07-13 — depends on broken ffmpeg-full-8.1.2
# # (A01-mov-read patch fails against 8.1.2 source). Re-enable
# # when nixpkgs regenerates the patch or when switching to stable.
qbittorrent
-4
View File
@@ -20,9 +20,5 @@
}
];
programs.npm = {
enable = true;
globalPackages = ["freebuff"];
};
};
}
+4 -3
View File
@@ -1,6 +1,7 @@
{ ... }: {
flake.lib.rbwSshSocket =
let
# Bash/Fish init snippets — used by wisdom/shells/{bash,fish}.nix.
bashSnippet = ''
if [ -z "''${SSH_AUTH_SOCK:-}" ]; then
if [ -n "''${XDG_RUNTIME_DIR:-}" ]; then
@@ -21,10 +22,10 @@
end
end
'';
in
{
in {
inherit bashSnippet fishSnippet;
# Used by home-manager modules to expose SSH_AUTH_SOCK to GUI apps
# and HM-managed shells declaratively (no activation script needed).
sessionVariable = "$XDG_RUNTIME_DIR/rbw/ssh-agent-socket";
activationPath = "/run/user/$(id -u)/rbw/ssh-agent-socket";
};
}
+110 -85
View File
@@ -1,131 +1,156 @@
{ lib, ... }: {
flake.lib.sshInventory =
let
# Hosts: pure network endpoints. No public keys live here anymore —
# keys are per USER (see `users` below), one key per person.
hosts = {
"14900k" = {
hostName = "192.168.2.25";
aliases = [ "14900k" "nixdesk" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILwUevBGnf+Y/sL1ZsB4bt0c50a89iqwPRoYUGP4UHsL 14900k";
};
ideapad = {
hostName = "192.168.2.229";
aliases = [ "ideapad" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQwaaI90xIMjZ46EcMyO8kBwGCxf7qVL75IYhw8Ssze ideapad";
};
t2mbp = {
hostName = "192.168.2.15";
aliases = [ "t2mbp" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMhVWB9YXl/FuQvufle4VWUas/QM8qCKoRd5a83Tt3S6 t2mbp";
};
uConsole = {
"uConsole" = {
hostName = "192.168.2.99";
aliases = [ "uConsole" "uconsole" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAVPR0lRAcywPR7iTchM3+eO7NCdXAR6NPzYXxalr+dP uConsole";
};
# Scratchpad / recovery target — hostName isn't an IP, so `activeHosts`
# excludes it from navi deployments. Leave it in `hosts` for inventory
# visibility while recovery is in progress.
test = {
hostName = "test";
aliases = [ "test" ];
publicKey = null;
};
nix-server = {
"nix-server" = {
hostName = "192.168.2.238";
aliases = [ "nix-server" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL3KDicMjtOFR6LfZrFzfAD1gdYUdwv6ZM4PSgtmIuzd nix-server";
};
r5500 = {
hostName = "192.168.2.100";
aliases = [ "r5500" ];
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK7iWCEtkYDLZFRF3w1gzyAok5VCAGUOwu4iWZdMjf3D r5500";
};
};
mkIdentityFileName = hostName: ".ssh/id_ed25519_${lib.strings.toLower hostName}.pub";
activeHosts = builtins.removeAttrs hosts (
builtins.filter (name: hosts.${name}.publicKey == null) (builtins.attrNames hosts)
);
# Per-user public keys. One keypair per person, generated once with
# `ssh-keygen -t ed25519 -C <comment>` (or `rbw gen ssh-key <user>`),
# private key stored in Bitwarden under the same name. Paste the
# `ssh-ed25519 AAAA… <comment>` line here.
users = {
olivier = {
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICpORzDw8kEOobjywl7bQaEp6Tez1c/ZP+7VBDsTcM8I olivier";
};
server = {
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICsksvNGOt/ebJtpRi9ocLBjwyl16VSaf+BsL2Hz5PRy server";
};
builder = {
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIILBtfW1cq9mG6uAjAfpZuVa1EWDSTPSCThapDXXlLgV builder";
};
};
mkIdentityFiles = selectedHosts:
builtins.listToAttrs (
builtins.map
(hostName: {
name = mkIdentityFileName hostName;
value.text = "${selectedHosts.${hostName}.publicKey}\n";
})
(builtins.attrNames selectedHosts)
);
# Hosts SSH is actually safe to deploy/scan to: anything whose hostName
# parses as an IPv4 dotted-quad. Filters out scratchpad/non-routable
# entries (e.g. `hostName = "test"`).
activeHosts = lib.filterAttrs
(_: h: lib.match "[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+" h.hostName != null)
hosts;
# Gitea git-over-SSH listens on port 222. System SSH (nix deploy, server@…) uses port 22
# via the catalog `nix-server` Host block — never list nix-server or 192.168.2.238 here.
giteaSshBlock = identityAgent: ''
Host git.chiasson.cloud gitea
HostName 192.168.2.238
Port 222
User git
IdentityAgent ${identityAgent}
IdentitiesOnly no
Match host nix-server,192.168.2.238 user git
HostName 192.168.2.238
Port 222
User git
IdentityAgent ${identityAgent}
IdentitiesOnly no
'';
# .pub filename convention. OpenSSH 7.3+ accepts a `.pub` file as
# IdentityFile to filter `IdentityAgent` (rbw) keys without storing the
# private key on disk — keep this trick.
mkIdentityFileName = userName: ".ssh/id_ed25519_${lib.strings.toLower userName}.pub";
mkSshConfigTemplate =
{
selectedHosts ? activeHosts,
user ? null,
identityAgent ? "__RBW_SSH_SOCK__",
# Catalog accounts the operator may authenticate AS via
# `ssh <account>@<host>`. Each entry gets a `Match user <account>`
# block that overrides IdentityFile so OpenSSH asks the rbw agent
# for that account's private key. `cfg.user` is already covered
# by the Host blocks, so don't list it here.
extraIdentities ? [ ],
identityAgent ? "SSH_AUTH_SOCK",
}:
let
hostBlocks = builtins.map
(hostName:
let
entry = selectedHosts.${hostName};
hostPatterns = builtins.concatStringsSep " " (entry.aliases ++ [ entry.hostName ]);
userLine = if user == null then "" else " User ${user}\n";
portLine =
if hostName == "nix-server" then
" Port 22\n"
else
"";
in
''
Host ${hostPatterns}
HostName ${entry.hostName}
${userLine}${portLine} IdentityFile ~/${mkIdentityFileName hostName}
IdentityAgent ${identityAgent}
IdentitiesOnly yes
'')
(builtins.attrNames selectedHosts);
in
builtins.concatStringsSep "\n" (
[
(giteaSshBlock identityAgent)
]
++ hostBlocks
++ [
''
Host *
# Match blocks for cross-account SSH. Filter out cfg.user since
# the matching pub is already loaded via the Host block's
# IdentityFile line — re-emitting it would just duplicate.
matchBlocks = lib.concatStringsSep "\n" (map
(n: ''
Match user ${n}
IdentityFile ~/${mkIdentityFileName n}
IdentitiesOnly yes
IdentityAgent none
''
]
);
'')
(builtins.filter (n: n != user) extraIdentities));
# Per-host block. `IdentityFile` (when `user` is set) is a .pub file
# pointing at the agent's matching private key. With IdentitiesOnly
# yes, only agent keys matching that pub are tried — fixes the old
# "too many auth failures" symptom by design.
hostBlock = hostName:
let
e = selectedHosts.${hostName};
hostPatterns = lib.concatStringsSep " " (e.aliases ++ [ e.hostName ]);
idLine = if user == null then "" else " IdentityFile ~/${mkIdentityFileName user}\n";
in ''
Host ${hostPatterns}
HostName ${e.hostName}
${idLine} IdentityAgent ${identityAgent}
IdentitiesOnly yes
'';
# Gitea block: always emitted. `User git`, port 222. olivier's
# public key (passed via `IdentityFile`) is what gitea accepts
# because it gets pushed into gitea's authorized_keys through
# gitea's own UI for that account.
giteaBlock = ''
Host git.chiasson.cloud gitea
HostName 192.168.2.238
Port 222
User git
${if user == null then "" else " IdentityFile ~/${mkIdentityFileName user}\n"} IdentityAgent ${identityAgent}
IdentitiesOnly yes
'';
hostBlocks = lib.concatStringsSep "\n" (map hostBlock (lib.attrNames selectedHosts));
in
# Filter empty sections so an absent Match block (no extraIdentities)
# doesn't add leading whitespace to the rendered config.
lib.concatStringsSep "\n" (lib.filter (s: s != "") [
matchBlocks
giteaBlock
hostBlocks
''
Host *
IdentitiesOnly yes
IdentityAgent none
''
]);
# Per-user line for `users.users.<u>.openssh.authorizedKeys.keys`.
# `selection` is either "all" or a list of source identifiers for
# sshd's `from=` constraint (IP, CIDR, or hostname if reverse-DNS works).
# Returns null if inventory is missing the user's pubkey — caller is
# expected to assert and fail the build rather than silently lock out.
mkAuthorizedKeyLine = userName: selection:
let
entry = users.${userName} or null;
in
if entry == null then null
else if (entry.publicKey or "") == "" then null
else if selection == "all" then entry.publicKey
else ''from="${lib.concatStringsSep "," selection}" ${entry.publicKey}'';
in
{
inherit hosts activeHosts mkIdentityFiles mkSshConfigTemplate;
authorizedKeys = lib.unique (
builtins.map (entry: entry.publicKey) (builtins.attrValues activeHosts)
);
identityFiles = mkIdentityFiles activeHosts;
sshConfigTemplate = mkSshConfigTemplate { };
inherit hosts users activeHosts;
mkSshConfigTemplate = mkSshConfigTemplate;
mkAuthorizedKeyLine = mkAuthorizedKeyLine;
mkIdentityFileName = mkIdentityFileName;
};
}
+137 -92
View File
@@ -1,96 +1,141 @@
{ self, ... }: {
flake.homeManagerModules.sshOutboundRbw = {
config,
lib,
pkgs,
...
}:
let
cfg = config.chiasson.ssh.outbound.rbw;
inventory = self.lib.sshInventory;
selectedHostNames =
if cfg.hosts == [ "all" ] then
builtins.attrNames inventory.activeHosts
else
cfg.hosts;
missing = builtins.filter (name: !(builtins.hasAttr name inventory.hosts)) selectedHostNames;
selectedHosts = builtins.listToAttrs (
builtins.map (name: {
inherit name;
value = inventory.hosts.${name};
}) selectedHostNames
);
sshConfigTemplate = inventory.mkSshConfigTemplate {
selectedHosts = selectedHosts;
user = cfg.user;
};
in
{
options.chiasson.ssh.outbound.rbw = {
enable = lib.mkEnableOption "Generated `~/.ssh/config` + rbw agent socket.";
user = lib.mkOption {
type = lib.types.str;
default = config.home.username;
description = "`User` in generated `Host` blocks.";
};
hosts = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "all" ];
description = "Inventory hosts to emit (or `[ \"all\" ]`).";
};
manageSshConfig = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Write `~/.ssh/config` from the template.";
};
};
config = lib.mkIf cfg.enable (lib.mkMerge [
{
assertions = [
{
assertion = missing == [ ];
message = "ssh.outbound.rbw: unknown host keys: ${builtins.concatStringsSep ", " missing}";
}
];
}
{
home.packages = with pkgs; [ rbw pinentry-gtk2 ];
home.sessionVariables.SSH_AUTH_SOCK = self.lib.rbwSshSocket.sessionVariable;
home.file = inventory.mkIdentityFiles selectedHosts;
programs.ssh.enable = lib.mkIf cfg.manageSshConfig false;
home.activation.rbwSshConfig = lib.mkIf cfg.manageSshConfig (lib.hm.dag.entryAfter [ "writeBoundary" ] ''
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
RBW_SSH_SOCK="${self.lib.rbwSshSocket.activationPath}"
cat > "$HOME/.ssh/config" <<'EOF'
${sshConfigTemplate}
EOF
sed -i "s|__RBW_SSH_SOCK__|$RBW_SSH_SOCK|g" "$HOME/.ssh/config"
chmod 600 "$HOME/.ssh/config"
'');
systemd.user.services.rbw-agent-bootstrap = {
Unit = {
Description = "Bootstrap rbw SSH agent";
PartOf = [ "graphical-session.target" ];
After = [ "graphical-session.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${pkgs.bash}/bin/bash -lc '${pkgs.rbw}/bin/rbw unlocked >/dev/null 2>&1 || true'";
RemainAfterExit = true;
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
flake.homeManagerModules.sshOutboundRbw =
{ config, lib, pkgs, ... }:
let
cfg = config.chiasson.ssh.outbound.rbw;
inventory = self.lib.sshInventory;
# Resolve which hosts to emit blocks for, and which user identity to
# use as the IdentityFile filter against the rbw agent.
selectedHostNames =
if cfg.hosts == [ "all" ] then
builtins.attrNames inventory.activeHosts
else
cfg.hosts;
missingHosts = builtins.filter (name: !(builtins.hasAttr name inventory.hosts)) selectedHostNames;
# All identities this HM user may authenticate AS: cfg.user
# extraIdentities. `cfg.user` can be null (e.g. user not in inventory
# yet) — in that case the Host-block IdentityFile line is suppressed
# and cross-account SSH is wired from extraIdentities alone.
effectiveIdentities = lib.filter (n: n != null) ([ cfg.user ] ++ cfg.extraIdentities);
defaultUserHasKey =
cfg.user == null
|| (inventory.users.${cfg.user}.publicKey or "") != "";
# Each extra identity must have a pubkey in the inventory. Dropping
# it silently would produce a dead Match block — fail the build.
missingExtraKeys = lib.filter
(n: (inventory.users.${n}.publicKey or "") == "")
cfg.extraIdentities;
in
{
options.chiasson.ssh.outbound.rbw = {
enable = lib.mkEnableOption "Generated `~/.ssh/config` + rbw agent socket.";
user = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = config.home.username;
description = ''
Inventory user whose public key is written to `~/.ssh/id_ed25519_<user>.pub`
and used as `IdentityFile` to filter the rbw agent. Set `null`
to disable the per-user .pub file / IdentityFile line (e.g.
when the user isn't in the inventory yet).
'';
};
hosts = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "all" ];
description = "Inventory hosts to emit (or `[ \"all\" ]`).";
};
manageSshConfig = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Write `~/.ssh/config` (disable for manual management).";
};
# Cross-account SSH on the operator's laptop/desktop. Each entry
# adds a `~/.ssh/id_ed25519_<account>.pub` filter file and a
# `Match user <account>` block in `~/.ssh/config` so
# `ssh <account>@<host>` picks the right key from the rbw agent.
# `cfg.user` is already wired by the Host blocks — don't list it here.
extraIdentities = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "server" "builder" ];
description = ''
Catalog accounts this HM user may authenticate AS, in addition
to `cfg.user`. Each entry must have a publicKey pasted into
`modules/lib/ssh-inventory.nix`. Typical for the operator's
primary laptop: `[ "server" "builder" ]` so fleet/ops commands
like `ssh server@r5500` or `ssh builder@nix-server` work
without touching `/etc/passwd` on each host.
'';
};
};
home.activation.rbwPinentryConfig = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
${pkgs.rbw}/bin/rbw config set pinentry "${pkgs.pinentry-gtk2}/bin/pinentry-gtk-2" >/dev/null 2>&1 || true
'';
}
]);
};
config = lib.mkIf cfg.enable (lib.mkMerge [
{
assertions = [
{
assertion = missingHosts == [ ];
message = "ssh.outbound.rbw: unknown host keys: ${builtins.concatStringsSep ", " missingHosts}";
}
{
assertion = defaultUserHasKey;
message = "ssh.outbound.rbw: no publicKey for inventory user `${cfg.user}` in `modules/lib/ssh-inventory.nix`.";
}
{
assertion = missingExtraKeys == [ ];
message = "ssh.outbound.rbw: extraIdentities has entries with no publicKey in `modules/lib/ssh-inventory.nix`: ${builtins.concatStringsSep ", " missingExtraKeys}";
}
# `enable=true` with neither a default user nor any extras gives the
# agent no IdentityFile at all; with IdentitiesOnly yes the user
# would silently be unable to authenticate as any catalog user.
{
assertion = effectiveIdentities != [ ];
message = "ssh.outbound.rbw: enabled but no identities wired. Set `chiasson.ssh.outbound.rbw.user` (defaults to `home.username`) or list catalog accounts under `extraIdentities`.";
}
];
}
(lib.mkMerge [
{
home.packages = with pkgs; [ rbw pinentry-gtk2 ];
home.sessionVariables.SSH_AUTH_SOCK = self.lib.rbwSshSocket.sessionVariable;
# Write a `.pub` file per identity this HM user may act as
# (cfg.user extraIdentities). OpenSSH reads each one via
# IdentityFile/Match to filter agent keys — they never hold
# private key material, so 0644 (HM's default) is fine.
# OpenSSH's StrictModes only rejects group/other-writable files.
home.file = lib.listToAttrs (map
(n: {
name = inventory.mkIdentityFileName n;
value.text = "${inventory.users.${n}.publicKey}\n";
})
effectiveIdentities);
programs.ssh.enable = lib.mkIf cfg.manageSshConfig false;
}
(lib.mkIf cfg.manageSshConfig {
home.file.".ssh/config".text = inventory.mkSshConfigTemplate {
selectedHosts = builtins.listToAttrs (map (n: { name = n; value = inventory.hosts.${n}; }) selectedHostNames);
user = cfg.user;
inherit (cfg) extraIdentities;
};
})
{
systemd.user.services.rbw-agent-bootstrap = {
Unit = {
Description = "Bootstrap rbw SSH agent";
PartOf = [ "graphical-session.target" ];
After = [ "graphical-session.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${pkgs.bash}/bin/bash -lc '${pkgs.rbw}/bin/rbw unlocked >/dev/null 2>&1 || true'";
RemainAfterExit = true;
};
Install.WantedBy = [ "graphical-session.target" ];
};
home.activation.rbwPinentryConfig = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
${pkgs.rbw}/bin/rbw config set pinentry "${pkgs.pinentry-gtk2}/bin/pinentry-gtk-2" >/dev/null 2>&1 || true
'';
}
])
]);
};
}
+44 -28
View File
@@ -1,35 +1,51 @@
{ self, ... }: {
flake.nixosModules.sshInbound = {
config,
lib,
...
}:
let
cfg = config.chiasson.ssh.inbound;
inventory = self.lib.sshInventory;
in
{
options.chiasson.ssh.inbound = {
enable = lib.mkEnableOption "Apply SSH inventory public keys to `authorized_keys`.";
userAuthorizedHosts = lib.mkOption {
type = lib.types.attrsOf (lib.types.either (lib.types.enum [ "all" ]) (lib.types.listOf lib.types.str));
default = { };
example = {
olivier = "all";
admin = [ "14900k" "t2mbp" ];
flake.nixosModules.sshInbound =
{ config, lib, ... }:
let
cfg = config.chiasson.ssh.inbound;
inventory = self.lib.sshInventory;
# Every inbound-enabled user must have a publicKey pasted into the
# inventory — silently dropping the key would lock the user out.
missingKeys = lib.filter
(name:
let line = inventory.mkAuthorizedKeyLine name (cfg.userAuthorizedHosts.${name} or "all");
in line == null)
(lib.attrNames cfg.userAuthorizedHosts);
in
{
options.chiasson.ssh.inbound = {
enable = lib.mkEnableOption "Write user-specific SSH inventory public keys into `authorized_keys`.";
userAuthorizedHosts = lib.mkOption {
type = lib.types.attrsOf (lib.types.either (lib.types.enum [ "all" ]) (lib.types.listOf lib.types.str));
default = { };
example = {
olivier = "all";
server = [ "192.168.2.25" "192.168.2.15" ];
};
description = ''
Catalog users whose inventory public key is installed in
`authorized_keys`. The value is either `"all"` (no `from=`
restriction) or a list of source identifiers appended to
sshd's `from=` constraint. List entries should be IPs or CIDRs;
hostnames require reverse-DNS to resolve cleanly on the target.
'';
};
description = ''
Catalog users that receive the SSH inventory public keys in `authorized_keys`.
'';
};
};
config = lib.mkIf cfg.enable {
users.users = lib.mapAttrs
(_user: _selection: {
openssh.authorizedKeys.keys = inventory.authorizedKeys;
config = lib.mkMerge [
{
assertions = [{
assertion = cfg.enable -> missingKeys == [ ];
message = "chiasson.ssh.inbound: users enabled but missing public key in `modules/lib/ssh-inventory.nix`: ${lib.concatStringsSep ", " missingKeys}";
}];
}
(lib.mkIf cfg.enable {
users.users = lib.mapAttrs
(name: selection: {
openssh.authorizedKeys.keys = [ (inventory.mkAuthorizedKeyLine name selection) ];
})
cfg.userAuthorizedHosts;
})
cfg.userAuthorizedHosts;
];
};
};
}
+215
View File
@@ -0,0 +1,215 @@
# Users & SSH inbound: options + catalog defaults + integration. Single module
# that used to be split across `modules/system/users/{default,catalog-options,
# catalog-default,home-integration}.nix`. Per-host overrides / extra HM modules
# work the same way they always have; only the file structure changed.
{ self, ... }:
{
flake.nixosModules.users =
{ config, options, lib, ... }:
let
cfg = config.chiasson.users;
usersLib = self.lib.usersMerge lib;
inventory = self.lib.sshInventory;
olivierEnabled = lib.elem "olivier" cfg.enabled;
# Merge catalog + per-host overrides into the final user attrs.
selectedUsers = lib.listToAttrs (map
(name: {
inherit name;
value = lib.recursiveUpdate cfg.catalog.${name} (cfg.hostOverrides.${name} or { });
})
cfg.enabled);
names = usersLib.hmWiredNames selectedUsers;
missing = usersLib.missingEnabledNames cfg.catalog cfg.enabled;
stray = usersLib.strayHomeUserKeys cfg.extraModules cfg.enabled;
hmAvailable = lib.hasAttrByPath [ "home-manager" "users" ] options;
inboundUsersAttr = usersLib.inboundHostsAttr selectedUsers;
hmUsersAttr = lib.listToAttrs (map
(name: {
inherit name;
value = usersLib.mkHmUserModule {
inherit name;
user = selectedUsers.${name};
hostExtraModules = cfg.extraModules.${name} or [ ];
};
})
names);
# Fish shell wiring: HM's fish module declares its package, but
# /etc/passwd + /etc/shells need updating so login shells work.
# Only fires for HM users that actually enable fish.
hmFishUsers =
if !hmAvailable then { }
else
lib.filterAttrs
(name: hmUser: (hmUser.programs.fish.enable or false) && builtins.elem name names)
config.home-manager.users;
in
{
imports = [
self.nixosModules.sshInbound
{ _module.args = { inherit self usersLib; }; }
];
####################
# Options
####################
options.chiasson.users = {
catalog = lib.mkOption {
type = lib.types.attrs;
default = { };
description = "User records; defaults below. Override with `hostOverrides` or `mkForce`.";
};
enabled = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Catalog names to materialize as `users.users` on this machine.";
};
hostOverrides = lib.mkOption {
type = lib.types.attrs;
default = { };
description = "`recursiveUpdate`'d onto catalog users on this host.";
};
# Standard `attrsOf (listOf …)` already concatenates lists across
# module definitions; no custom merge function needed.
extraModules = lib.mkOption {
type = lib.types.attrsOf (lib.types.listOf lib.types.unspecified);
default = { };
description = ''
Per-user Home Manager extraModules keyed by catalog name. Lists
from multiple modules (desktopHomeBase + this host's home.nix)
are concatenated.
'';
};
homeManager.autoWire = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Auto-create Home Manager users from the catalog.";
};
};
####################
# Catalog defaults + integration
####################
# Nix won't accept both `config.X = …` and `config = …` in the same
# attrset (the path-attr partially defines `config`, then the explicit
# `config = …` is a redefinition error). All `config.` writes must
# therefore live INSIDE the single mkMerge block below.
config = lib.mkMerge [
####################
# Catalog defaults
####################
{
chiasson.users.catalog = {
olivier = {
isNormalUser = true;
description = "Olivier";
extraGroups = [
"networkmanager" "wheel" "docker" "fuse" "uinput" "kvm"
# `video` lets brightnessctl/light udev rules own /sys/backlight without sudo.
# Harmless on headless hosts (no devices).
"video"
# DRI render + input for gamescope / Steam on Wayland, no sudo.
"render" "input"
];
hashedPasswordFile = lib.mkIf olivierEnabled (
config.sops.secrets."users/olivier/hashedPassword".path
);
homeManager = {
enable = true;
module = { ... }: {
home.username = "olivier";
home.homeDirectory = "/home/olivier";
home.stateVersion = "25.11";
programs.home-manager.enable = true;
};
};
ssh.inbound.enable = true;
ssh.inbound.authorizedHosts = "all";
ssh.outbound.rbw.enable = true;
ssh.outbound.rbw.hosts = [ "all" ];
# olivier's laptop: the HM SSH module writes the matching
# `.pub` filter for each of these and emits a `Match user`
# block, so `ssh server@r5500` / `ssh builder@nix-server`
# route the right private key from the rbw agent.
ssh.outbound.rbw.extraIdentities = [ "server" "builder" ];
};
server = {
isNormalUser = true;
description = "Server user";
extraGroups = [ "wheel" ];
homeManager = { enable = false; module = null; };
ssh.inbound.enable = true;
ssh.inbound.authorizedHosts = "all";
ssh.outbound.rbw.enable = false;
ssh.outbound.rbw.hosts = [ "all" ];
};
builder = {
isNormalUser = true;
description = "Navi fleet deploy (push + activate only)";
extraGroups = [ ];
createHome = false;
homeManager = { enable = false; module = null; };
ssh.inbound.enable = true;
};
};
# Pull in olivier's hashed password from sops; `neededForUsers`
# makes nixos decrypt it at boot so the user record can read it.
sops.secrets."users/olivier/hashedPassword" = lib.mkIf olivierEnabled {
neededForUsers = true;
};
}
####################
# Integration
####################
{
assertions = [
{
assertion = missing == [ ];
message = "chiasson.users.enabled references unknown catalog names: ${lib.concatStringsSep ", " missing}";
}
{
assertion = stray == [ ];
message = "chiasson.users.extraModules has keys not in chiasson.users.enabled: ${lib.concatStringsSep ", " stray}";
}
];
# NixOS user accounts (no HM, raw).
users.users = lib.mapAttrs
(name: user: usersLib.mkNixosUser name user)
selectedUsers;
}
(lib.optionalAttrs hmAvailable {
"home-manager".useGlobalPkgs = lib.mkIf (cfg.homeManager.autoWire && names != [ ]) true;
"home-manager".sharedModules = lib.mkIf (cfg.homeManager.autoWire && names != [ ]) [
self.homeManagerModules.sshOutboundRbw
];
"home-manager".users = lib.mkIf (cfg.homeManager.autoWire && names != [ ]) hmUsersAttr;
})
# Wire user-specific ACLs into the inbound module — fail the build
# if a user has no matching pubkey.
(lib.mkIf (inboundUsersAttr != { }) {
chiasson.ssh.inbound.enable = true;
chiasson.ssh.inbound.userAuthorizedHosts = inboundUsersAttr;
})
# Fish-shell wiring: HM knows the package; we sync /etc/passwd +
# /etc/shells with `mkForce`. `mkNixosUser` doesn't emit `shell`, so
# the force is uncontested.
(lib.mkIf (hmFishUsers != { }) {
environment.shells = lib.mkAfter (lib.mapAttrsToList
(_: hmUser: lib.getExe hmUser.programs.fish.package)
hmFishUsers);
users.users = lib.mapAttrs
(name: hmUser: { shell = lib.mkForce (lib.getExe hmUser.programs.fish.package); })
hmFishUsers;
})
];
};
}
-105
View File
@@ -1,105 +0,0 @@
# Shared user definitions for all hosts that import `nixosModules.users`.
# Module (not bare attrset) so catalog entries can use `config.*` for sops paths etc.
{ ... }: {
flake.nixosModules.usersCatalogDefaults =
{ config, lib, ... }:
let
olivierEnabled = lib.elem "olivier" config.chiasson.users.enabled;
in
{
config.sops.secrets."users/olivier/hashedPassword" = lib.mkIf olivierEnabled {
neededForUsers = true;
};
config.chiasson.users.catalog = {
olivier = {
isNormalUser = true;
description = "Olivier";
extraGroups = [
"networkmanager"
"wheel"
"docker"
"fuse"
"uinput"
"kvm"
# `video` is required for the brightnessctl/light udev rules to grant write access
# to /sys/class/backlight/*/brightness without sudo. Harmless on hosts without a
# backlight (servers, desktop towers): the group simply has no devices to own.
"video"
# DRI render nodes and input devices for gamescope / Steam on Wayland (no sudo).
"render"
"input"
];
# Declared in this module when olivier is in `chiasson.users.enabled`.
# With `neededForUsers`, `.path` is under /run/secrets-for-users/… (sops-nix README).
hashedPasswordFile = lib.mkIf olivierEnabled (
config.sops.secrets."users/olivier/hashedPassword".path
);
homeManager = {
enable = true;
module =
{ ... }:
{
home.username = "olivier";
home.homeDirectory = "/home/olivier";
home.stateVersion = "25.11";
programs.home-manager.enable = true;
};
};
ssh = {
inbound = {
enable = true;
authorizedHosts = "all";
};
outbound = {
rbw = {
enable = true;
hosts = "all";
};
};
};
};
server = {
isNormalUser = true;
description = "Server user";
extraGroups = [ "wheel" ];
homeManager = {
enable = false;
module = null;
};
ssh = {
inbound = {
enable = true;
authorizedHosts = "all";
};
outbound = {
rbw = {
enable = false;
hosts = "all";
};
};
};
};
builder = {
isNormalUser = true;
description = "Navi fleet deploy (push + activate only)";
extraGroups = [ ];
createHome = false;
homeManager = {
enable = false;
module = null;
};
ssh.inbound.enable = true;
};
};
};
}
-55
View File
@@ -1,55 +0,0 @@
{ ... }: {
flake.nixosModules.usersCatalogOptions =
{ lib, ... }:
{
options.chiasson.users = {
catalog = lib.mkOption {
type = lib.types.attrs;
default = { };
description = ''
User records merged from `usersCatalogDefaults`; override with `hostOverrides` or `mkForce`.
'';
};
enabled = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Catalog names to materialize as `users.users` on this machine.";
};
hostOverrides = lib.mkOption {
type = lib.types.attrs;
default = { };
description = ''
`recursiveUpdate`d onto catalog users.
'';
};
extraModules = lib.mkOption {
type =
(lib.types.attrsOf (lib.types.listOf lib.types.unspecified))
// {
merge =
loc: defs:
let
values = map (d: d.value) defs;
names = lib.unique (lib.concatLists (map builtins.attrNames values));
in
lib.genAttrs names (
name: lib.concatLists (map (v: v.${name} or [ ]) values)
);
};
default = { };
description = ''
Per-user Home Manager `extraModules` keyed by catalog user name.
Keys must match `chiasson.users.enabled`. Lists from multiple modules
are concatenated (e.g. `desktop-home-base.nix` + host `home.nix`).
'';
};
homeManager = {
autoWire = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Create HM users from the catalog when true.";
};
};
};
};
}
-27
View File
@@ -1,27 +0,0 @@
# Catalog → NixOS `users.users` + Home Manager + SSH inbound.
{ self, ... }: {
flake.nixosModules.users =
{ config, lib, ... }:
let
usersLib = self.lib.usersMerge lib;
selectUsers =
c:
let
uc = c.chiasson.users;
in
usersLib.selectedUsersAttr {
catalog = uc.catalog;
enabled = uc.enabled;
hostOverrides = uc.hostOverrides;
};
in
{
imports = [
self.nixosModules.sshInbound
self.nixosModules.usersCatalogOptions
self.nixosModules.usersCatalogDefaults
{ _module.args = { inherit self usersLib selectUsers; }; }
self.nixosModules.usersHomeIntegration
];
};
}
-71
View File
@@ -1,71 +0,0 @@
{ ... }: {
flake.nixosModules.usersHomeIntegration =
{ config, options, lib, self, usersLib, selectUsers, ... }:
let
cfg = config.chiasson.users;
selected = selectUsers config;
missing = usersLib.missingEnabledNames cfg.catalog cfg.enabled;
stray = usersLib.strayHomeUserKeys cfg.extraModules cfg.enabled;
names = usersLib.hmWiredNames selected;
hmAvailable = lib.hasAttrByPath [ "home-manager" "users" ] options;
hmUsersAttr = lib.listToAttrs (
map (name: {
inherit name;
value = usersLib.mkHmUserModule {
inherit name;
user = selected.${name};
hostExtraModules = cfg.extraModules.${name} or [ ];
};
}) names
);
inboundUsersAttr = usersLib.inboundHostsAttr selected;
# HM configures fish in ~/.config/fish but no longer sets /etc/passwd or /etc/shells.
hmFishUsers =
if !hmAvailable then { }
else
lib.filterAttrs (
name: hmUser: (hmUser.programs.fish.enable or false) && builtins.elem name names
) config.home-manager.users;
in
{
config = lib.mkMerge [
{
assertions = [
{
assertion = missing == [ ];
message = "chiasson.users.enabled references unknown catalog users: ${builtins.concatStringsSep ", " missing}";
}
{
assertion = stray == [ ];
message = "chiasson.users.extraModules has keys not in chiasson.users.enabled: ${builtins.concatStringsSep ", " stray}";
}
];
}
{
users.users = lib.mapAttrs (name: user: usersLib.mkNixosUser name user) selected;
}
(lib.optionalAttrs hmAvailable {
"home-manager".useGlobalPkgs = lib.mkIf (cfg.homeManager.autoWire && names != [ ]) true;
"home-manager".sharedModules = lib.mkIf (cfg.homeManager.autoWire && names != [ ]) [ self.homeManagerModules.sshOutboundRbw ];
"home-manager".users = lib.mkIf (cfg.homeManager.autoWire && names != [ ]) hmUsersAttr;
})
(lib.mkIf (inboundUsersAttr != { }) {
chiasson.ssh.inbound.enable = true;
chiasson.ssh.inbound.userAuthorizedHosts = inboundUsersAttr;
})
(lib.mkIf (hmFishUsers != { }) {
environment.shells = lib.mkAfter (
lib.mapAttrsToList (
_: hmUser: lib.getExe hmUser.programs.fish.package
) hmFishUsers
);
users.users = lib.mapAttrs (
name: hmUser: {
shell = lib.mkForce (lib.getExe hmUser.programs.fish.package);
}
) hmFishUsers;
})
];
};
}