Files
chiasson-nix/modules/system/users.nix
T
Olivier e2fd47aa58 feat(users): add declarative rbw configuration for olivier
Introduce rbw settings within olivier's home-manager module to
centralize the email and base_url configuration. This replaces
manual per-host configuration with a declarative approach.
2026-07-15 18:58:54 -03:00

229 lines
9.0 KiB
Nix

# 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 = { pkgs, ... }: {
home.username = "olivier";
home.homeDirectory = "/home/olivier";
home.stateVersion = "25.11";
programs.home-manager.enable = true;
# Declarative rbw config — replaces per-host
# `rbw config set email|base_url`. Neither value is a
# secret (URL + login email), so plaintext is fine, same
# as the public keys committed in ssh-inventory.nix.
programs.rbw = {
enable = true;
settings = {
email = "olivierchiasson@hotmail.fr";
base_url = "https://bitwarden.chiasson.cloud";
};
};
};
};
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;
ssh.inbound.authorizedHosts = "all";
};
};
# 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;
})
];
};
}