refactor: simplify shared config contracts

This commit is contained in:
2026-04-22 04:41:09 +02:00
parent cf308a1371
commit a4af935e6e
14 changed files with 159 additions and 139 deletions
+12 -8
View File
@@ -1,11 +1,9 @@
{ lib, ... }:
let
users = {
userSpecs = {
kiri = {
name = "kiri";
realName = "Jelle Spreeuwenberg";
homeDirectory = "/home/kiri";
terminalPackagePath = [ "kitty" ];
emails = {
personal = {
address = "mail@jelles.net";
@@ -33,10 +31,8 @@ let
};
ergon = {
name = "ergon";
realName = "Jelle Spreeuwenberg";
homeDirectory = "/home/ergon";
terminalPackagePath = [ "kitty" ];
emails = {
personal = {
address = "mail@jelles.net";
@@ -54,6 +50,8 @@ let
};
};
accounts = lib.mapAttrs (name: spec: spec // { inherit name; }) userSpecs;
repo = {
contact.email = "mail@jelles.net";
@@ -67,6 +65,12 @@ let
command = "nautilus";
packagePath = [ "nautilus" ];
};
terminal = {
command = "kitty";
desktopId = "kitty.desktop";
packagePath = [ "kitty" ];
};
};
services = {
@@ -178,13 +182,13 @@ in
readOnly = true;
};
options.meta.lib.users = lib.mkOption {
options.meta.lib.accounts = lib.mkOption {
type = lib.types.attrs;
description = "Canonical user attrsets shared by host definitions.";
description = "Canonical account attrsets shared by host definitions.";
internal = true;
readOnly = true;
};
config.meta.lib.repo = repo;
config.meta.lib.users = users;
config.meta.lib.accounts = accounts;
}
+29 -5
View File
@@ -27,6 +27,13 @@ let
hasRequiredScopedEmail = scope: user: scopeEmailCount scope user == 1;
scopeEmailAddress =
scope: user:
let
emails = lib.filter (email: email.scope == scope) (builtins.attrValues user.emails);
in
if emails == [ ] then "" else (builtins.head emails).address;
primaryEmailFallback = {
address = "";
primary = false;
@@ -125,6 +132,22 @@ let
type = sourceControlScopeType;
default = "personal";
};
scopes = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule (
{ ... }:
{
options.email = lib.mkOption {
type = lib.types.str;
readOnly = true;
};
}
)
);
readOnly = true;
description = "Derived source-control identities keyed by scope.";
};
};
}
);
@@ -149,10 +172,6 @@ let
type = lib.types.str;
};
terminalPackagePath = lib.mkOption {
type = lib.types.listOf lib.types.str;
};
nixosConfigurationPath = lib.mkOption {
type = lib.types.str;
default = "${config.homeDirectory}/.config/nixos";
@@ -174,7 +193,12 @@ let
};
};
config.primaryEmail = primaryEmail;
config = {
primaryEmail = primaryEmail;
sourceControl.scopes = lib.genAttrs (requiredSourceControlScopes config) (scope: {
email = scopeEmailAddress scope config;
});
};
}
);
-1
View File
@@ -3,7 +3,6 @@
networking = {
firewall.enable = true;
firewall.allowPing = false;
nftables.enable = true;
};
};
+2 -4
View File
@@ -56,15 +56,13 @@ in
}
) config.meta.host.displays;
inputProfiles = metaLib.mkInputProfiles config.meta.host.input;
terminal = metaLib.resolveUserTerminal {
terminal = metaLib.resolveRepoTerminal {
inherit pkgs;
user = config.meta.user;
};
in
{
assertions = metaLib.mkTerminalAssertions {
inherit terminal;
user = config.meta.user;
};
home.sessionVariables.NIXOS_OZONE_WL = "1";
@@ -154,7 +152,7 @@ in
};
binds =
if terminal.hasMainProgram then
if terminal.hasPackage then
import ./_bindings.nix {
inherit
browserCommand
+48 -33
View File
@@ -6,6 +6,17 @@
let
homeModules = config.flake.modules.homeManager;
metaLib = config.meta.lib;
mkNoctaliaSettings =
{
lib,
terminalPackage,
}:
import ./_noctalia-config.nix {
inherit
lib
terminalPackage
;
};
mkPortableSettings =
baseSettings:
lib.recursiveUpdate baseSettings {
@@ -29,38 +40,56 @@ let
};
in
{
flake.modules.homeManager.noctalia =
flake.modules.homeManager.noctalia-base =
{
inputs,
config,
lib,
pkgs,
...
}:
let
terminal = metaLib.resolveUserTerminal {
terminal = metaLib.resolveRepoTerminal {
inherit pkgs;
user = config.meta.user;
};
baseSettings =
if terminal.hasMainProgram then
import ./_noctalia-config.nix {
inherit
lib
;
if terminal.hasPackage then
mkNoctaliaSettings {
inherit lib;
terminalPackage = terminal.package;
}
else
{ };
in
{
imports = [ inputs.noctalia.homeModules.default ];
assertions = metaLib.mkTerminalAssertions {
inherit terminal;
user = config.meta.user;
options.meta.lib.noctaliaBaseSettings = lib.mkOption {
type = lib.types.attrs;
internal = true;
readOnly = true;
};
config = {
meta.lib.noctaliaBaseSettings = baseSettings;
assertions = metaLib.mkTerminalAssertions {
inherit terminal;
};
};
};
flake.modules.homeManager.noctalia =
{
config,
inputs,
lib,
pkgs,
...
}:
{
imports = [
homeModules.noctalia-base
inputs.noctalia.homeModules.default
];
programs.noctalia-shell = {
enable = true;
package = lib.mkForce (
@@ -69,7 +98,7 @@ in
}
);
settings = baseSettings;
settings = config.meta.lib.noctaliaBaseSettings;
};
};
@@ -77,29 +106,15 @@ in
{
config,
lib,
pkgs,
...
}:
let
terminal = metaLib.resolveUserTerminal {
inherit pkgs;
user = config.meta.user;
};
baseSettings =
if terminal.hasMainProgram then
import ./_noctalia-config.nix {
inherit
lib
;
terminalPackage = terminal.package;
}
else
{ };
in
{
imports = [ homeModules.noctalia ];
programs.noctalia-shell.settings = lib.mkForce (
if terminal.hasMainProgram then mkPortableSettings baseSettings else { }
if config.meta.lib.noctaliaBaseSettings == { } then
{ }
else
mkPortableSettings config.meta.lib.noctaliaBaseSettings
);
};
}
+6 -9
View File
@@ -13,19 +13,19 @@ in
host = config.meta.host;
user = config.meta.user;
sourceControl = user.sourceControl;
sourceControlScopes = sourceControl.scopes;
hostSourceControlUsers = host.sourceControl.users;
hostUserSourceControl = hostSourceControlUsers.${user.name} or { };
scopeEmails = scope: lib.filter (email: email.scope == scope) (builtins.attrValues user.emails);
scopeConfig = scope: hostUserSourceControl.${scope} or null;
scopeIdentity = scope: sourceControlScopes.${scope} or null;
emailForScope =
scope:
let
emails = scopeEmails scope;
identity = scopeIdentity scope;
in
if builtins.length emails == 1 then (builtins.head emails).address else null;
scopeConfig = scope: hostUserSourceControl.${scope} or null;
if identity == null then null else identity.email;
scopeHasSigningKey =
scope:
@@ -51,10 +51,7 @@ in
in
if keyConfig == null then null else keyConfig.publicKey;
scopesInUse = lib.unique ([
"personal"
sourceControl.projectScope
]);
scopesInUse = builtins.attrNames sourceControlScopes;
allowedSignersLines = map (scope: "${emailForScope scope} ${publicKeyForScope scope}") (
builtins.filter (scope: emailForScope scope != null && scopeHasSigningKey scope) scopesInUse
+1 -4
View File
@@ -14,17 +14,14 @@ in
let
repoTheme = metaRepo.theme.kanagawa;
palette = repoTheme.palette;
terminal = metaLib.resolveUserTerminal {
terminal = metaLib.resolveRepoTerminal {
inherit pkgs;
user = config.meta.user;
};
in
{
assertions = metaLib.mkTerminalAssertions {
inherit terminal;
user = config.meta.user;
requireDesktopEntry = true;
requireKitty = true;
};
xdg.terminal-exec = {
+7
View File
@@ -29,6 +29,13 @@ in
environment.localBinInPath = true;
};
flake.modules.nixos.workstation-host = {
imports = [
nixosModules.workstation-base
nixosModules.qbittorrent-client
];
};
flake.modules.homeManager.workstation-base = {
imports = [
homeModules.ai
+1 -1
View File
@@ -12,7 +12,7 @@ in
{
imports = [
inputs.flake-parts.flakeModules.modules
./repo-data.nix
./data.nix
];
systems = [ "x86_64-linux" ];
+2 -4
View File
@@ -17,15 +17,13 @@ in
...
}:
let
terminal = metaLib.resolveUserTerminal {
terminal = metaLib.resolveRepoTerminal {
inherit pkgs;
user = config.meta.host.users.kiri;
};
in
{
assertions = metaLib.mkTerminalAssertions {
inherit terminal;
user = config.meta.host.users.kiri;
requireTerminfo = true;
};
@@ -45,7 +43,7 @@ in
name = "orion";
users = {
kiri = {
account = metaLib.users.kiri;
account = metaLib.accounts.kiri;
homeImports = [
homeModules.shell
homeModules.git
+12 -14
View File
@@ -7,6 +7,13 @@ let
nixosModules = config.flake.modules.nixos;
homeModules = config.flake.modules.homeManager;
metaLib = config.meta.lib;
workstationHomeImports = [ homeModules.workstation-base ];
kiriHomeImports = workstationHomeImports ++ [
homeModules.syncthing
homeModules.qbittorrent-client
homeModules.noctalia
];
ergonHomeImports = workstationHomeImports ++ [ homeModules.noctalia ];
in
{
flake.modules.nixos.polaris = metaLib.mkHost {
@@ -31,29 +38,20 @@ in
users = {
kiri = {
account = metaLib.users.kiri;
account = metaLib.accounts.kiri;
needsPassword = true;
homeImports = [
homeModules.workstation-base
homeModules.syncthing
homeModules.qbittorrent-client
homeModules.noctalia
];
homeImports = kiriHomeImports;
};
ergon = {
account = metaLib.users.ergon;
account = metaLib.accounts.ergon;
needsPassword = true;
homeImports = [
homeModules.workstation-base
homeModules.noctalia
];
homeImports = ergonHomeImports;
};
};
imports = [
nixosModules.workstation-base
nixosModules.qbittorrent-client
nixosModules.workstation-host
nixosModules.steam
./_hardware.nix
]
+13 -14
View File
@@ -7,6 +7,14 @@ let
nixosModules = config.flake.modules.nixos;
homeModules = config.flake.modules.homeManager;
metaLib = config.meta.lib;
workstationHomeImports = [ homeModules.workstation-base ];
portableNoctalia = homeModules.noctalia-portable;
kiriHomeImports = workstationHomeImports ++ [
homeModules.syncthing
homeModules.qbittorrent-client
portableNoctalia
];
ergonHomeImports = workstationHomeImports ++ [ portableNoctalia ];
in
{
flake.modules.nixos.zenith = metaLib.mkHost {
@@ -39,29 +47,20 @@ in
users = {
kiri = {
account = metaLib.users.kiri;
account = metaLib.accounts.kiri;
needsPassword = true;
homeImports = [
homeModules.workstation-base
homeModules.syncthing
homeModules.qbittorrent-client
homeModules.noctalia-portable
];
homeImports = kiriHomeImports;
};
ergon = {
account = metaLib.users.ergon;
account = metaLib.accounts.ergon;
needsPassword = true;
homeImports = [
homeModules.workstation-base
homeModules.noctalia-portable
];
homeImports = ergonHomeImports;
};
};
imports = [
nixosModules.workstation-base
nixosModules.qbittorrent-client
nixosModules.workstation-host
nixosModules.laptop-power
{
hardware.enableRedistributableFirmware = true;
+23 -39
View File
@@ -31,17 +31,9 @@ let
let
hostUserSpecs = lib.mapAttrs (_: spec: normalizeHostUser spec) users;
hostUsers = lib.mapAttrs (_: spec: spec.account) hostUserSpecs;
userAssertions = lib.mapAttrsToList (userName: spec: {
assertion = userName == spec.account.name;
message = "Host `${name}` declares user `${userName}` with mismatched account name `${spec.account.name}`.";
}) hostUserSpecs;
passwordUserSpecs = lib.filterAttrs (_: spec: spec.needsPassword) hostUserSpecs;
in
{
assertions = userAssertions;
meta.host = {
inherit
displays
@@ -69,7 +61,7 @@ let
users.users = lib.mapAttrs (
userName: spec:
{
name = spec.account.name;
name = userName;
home = spec.account.homeDirectory;
isNormalUser = true;
shell = pkgs.zsh;
@@ -131,67 +123,59 @@ let
}:
lib.attrByPath path null pkgs;
resolveUserTerminal =
resolveRepoTerminal =
{
pkgs,
user,
}:
let
terminal = config.meta.lib.repo.desktop.terminal;
package = resolvePackagePath {
inherit pkgs;
path = user.terminalPackagePath;
path = terminal.packagePath;
};
hasPackage = package != null;
hasMainProgram = hasPackage && package ? meta.mainProgram;
mainProgram = if hasMainProgram then package.meta.mainProgram else null;
desktopId = if mainProgram == null then null else "${mainProgram}.desktop";
in
{
inherit
desktopId
hasMainProgram
hasPackage
mainProgram
package
;
inherit (terminal)
command
desktopId
packagePath
;
hasPackage = package != null;
hasDesktopEntry =
desktopId != null && builtins.pathExists "${package}/share/applications/${desktopId}";
package != null && builtins.pathExists "${package}/share/applications/${terminal.desktopId}";
hasTerminfo = hasPackage && lib.elem "terminfo" package.outputs;
hasTerminfo = package != null && lib.elem "terminfo" package.outputs;
};
mkTerminalAssertions =
{
terminal,
user,
requireDesktopEntry ? false,
requireKitty ? false,
requireMainProgram ? true,
requireTerminfo ? false,
}:
lib.flatten [
[
{
assertion = terminal.hasPackage;
message = "Unknown terminal package `${lib.showAttrPath user.terminalPackagePath}` for user `${user.name}`.";
message = "Unknown terminal package `${lib.showAttrPath terminal.packagePath}` in `meta.lib.repo.desktop.terminal`.";
}
{
assertion = terminal.command == "kitty";
message = "The repo terminal is currently expected to be kitty.";
}
]
(lib.optional requireMainProgram {
assertion = terminal.hasMainProgram;
message = "Terminal package `${lib.showAttrPath user.terminalPackagePath}` must define `meta.mainProgram`.";
})
(lib.optional requireDesktopEntry {
assertion = terminal.hasDesktopEntry;
message = "Terminal package `${lib.showAttrPath user.terminalPackagePath}` must provide `${terminal.desktopId}`.";
})
(lib.optional requireKitty {
assertion = terminal.hasMainProgram && terminal.mainProgram == "kitty";
message = "The terminal feature currently only supports kitty-specific Home Manager configuration.";
message = "Terminal package `${lib.showAttrPath terminal.packagePath}` must provide `${terminal.desktopId}`.";
})
(lib.optional requireTerminfo {
assertion = terminal.hasTerminfo;
message = "Terminal package `${lib.showAttrPath user.terminalPackagePath}` must provide a `terminfo` output.";
message = "Terminal package `${lib.showAttrPath terminal.packagePath}` must provide a `terminfo` output.";
})
];
@@ -356,9 +340,9 @@ in
readOnly = true;
};
options.meta.lib.resolveUserTerminal = lib.mkOption {
options.meta.lib.resolveRepoTerminal = lib.mkOption {
type = lib.types.raw;
description = "Internal helper to resolve and validate user terminal metadata.";
description = "Internal helper to resolve and validate the repo-standard terminal.";
internal = true;
readOnly = true;
};
@@ -384,7 +368,7 @@ in
mkTerminalAssertions
mkHost
resolvePackagePath
resolveUserTerminal
resolveRepoTerminal
;
};
}