From 503c1fe9bcdae5cfe93c41b2c8f6091dcc810d83 Mon Sep 17 00:00:00 2001 From: Jelle Spreeuwenberg Date: Wed, 22 Apr 2026 03:29:19 +0200 Subject: [PATCH] refactor: simplify host composition and shared feature config --- AGENTS.md | 2 +- modules/features/git.nix | 4 - modules/features/meta.nix | 73 ++++++++--- modules/features/neovim/_kanagawa-theme.nix | 45 +++++++ modules/features/neovim/default.nix | 57 ++------- modules/features/niri/default.nix | 12 +- modules/features/qbittorrent-client.nix | 10 +- modules/features/source-control.nix | 81 ++++--------- modules/features/terminal.nix | 60 ++++----- modules/features/vicinae.nix | 50 +++++--- modules/features/workstation-base.nix | 1 - modules/hosts/orion/default.nix | 2 +- modules/hosts/polaris/default.nix | 14 +-- modules/hosts/zenith/default.nix | 7 +- modules/lib.nix | 128 +++++++++++++++----- modules/users.nix | 19 ++- 16 files changed, 327 insertions(+), 238 deletions(-) create mode 100644 modules/features/neovim/_kanagawa-theme.nix diff --git a/AGENTS.md b/AGENTS.md index fa81857..29b035b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,7 +45,7 @@ In this repo, `flake.nix` imports `./modules` recursively via `inputs.import-tre - Reusable Home Manager concerns are published as `flake.modules.homeManager.`. - Hosts are aspects too. `orion`, `polaris`, and `zenith` are `nixos` aspects assembled from smaller aspects. - Host modules should use `config.meta.lib.mkHost` to define `meta.host`, base imports, hostname, and state version. -- Per-user Home Manager composition should use `config.meta.lib.mkHostUser` so `meta.host` and `meta.user` stay available to Home Manager features. +- Per-host user declarations should use `config.meta.lib.mkHostUser` so host-local defaults stay close to the host and `mkHost` can wire `meta.host` and `meta.user` into Home Manager consistently. - Features may rely on the `meta` contract. Existing modules already read `config.meta.host`, `config.meta.user`, and `config.meta.lib`. ## Preferred Aspect Patterns diff --git a/modules/features/git.nix b/modules/features/git.nix index 78ea63d..d436fb6 100644 --- a/modules/features/git.nix +++ b/modules/features/git.nix @@ -3,12 +3,10 @@ flake.modules.homeManager.git = { config, - lib, ... }: let user = config.meta.user; - usesScopedIdentity = user != null && user.sourceControl.profiles != { }; in { programs.git = { @@ -20,8 +18,6 @@ ]; settings = { init.defaultBranch = "main"; - } - // lib.optionalAttrs (!usesScopedIdentity) { user = { name = user.realName; email = user.primaryEmail.address; diff --git a/modules/features/meta.nix b/modules/features/meta.nix index 666cac2..8c38fe1 100644 --- a/modules/features/meta.nix +++ b/modules/features/meta.nix @@ -1,5 +1,7 @@ { lib, ... }: let + nonEmptyStrType = lib.types.addCheck lib.types.str (value: lib.stringLength value > 0); + mkNullableOption = type: lib.mkOption { @@ -10,9 +12,26 @@ let hasSinglePrimaryEmail = user: builtins.length (lib.filter (email: email.primary) (builtins.attrValues user.emails)) == 1; + scopeEmailCount = + scope: user: + builtins.length (lib.filter (email: email.scope == scope) (builtins.attrValues user.emails)); + + hasAtMostOneScopedEmail = scope: user: scopeEmailCount scope user <= 1; + + requiredSourceControlScopes = + user: + lib.unique [ + "personal" + user.sourceControl.projectScope + ]; + + hasRequiredScopedEmail = + scope: user: scopeEmailCount scope user == 1; + primaryEmailFallback = { address = ""; primary = false; + scope = null; type = ""; }; @@ -53,6 +72,8 @@ let type = lib.mkOption { type = lib.types.str; }; + + scope = mkNullableOption sourceControlScopeType; }; } ); @@ -62,13 +83,11 @@ let { options = { publicKey = lib.mkOption { - type = lib.types.str; - }; - - privateKeyPath = lib.mkOption { - type = lib.types.nullOr lib.types.str; + type = lib.types.nullOr nonEmptyStrType; default = null; }; + + privateKeyPath = mkNullableOption nonEmptyStrType; }; } ); @@ -93,22 +112,10 @@ let } ); - sourceControlProfileType = lib.types.submodule ( - { ... }: - { - options = { }; - } - ); - sourceControlType = lib.types.submodule ( { ... }: { options = { - profiles = lib.mkOption { - type = lib.types.attrsOf sourceControlProfileType; - default = { }; - }; - projectScope = lib.mkOption { type = sourceControlScopeType; default = "personal"; @@ -305,7 +312,22 @@ in config.assertions = lib.mapAttrsToList (userName: user: { assertion = hasSinglePrimaryEmail user; message = "User `${userName}` must define exactly one primary email entry."; - }) config.meta.host.users; + }) config.meta.host.users + ++ lib.flatten ( + lib.mapAttrsToList (userName: user: + (map (scope: { + assertion = hasAtMostOneScopedEmail scope user; + message = "User `${userName}` may define at most one `${scope}` scoped email entry."; + }) [ + "personal" + "work" + ]) + ++ map (scope: { + assertion = hasRequiredScopedEmail scope user; + message = "User `${userName}` must define exactly one `${scope}` scoped email entry."; + }) (requiredSourceControlScopes user) + ) config.meta.host.users + ); }; flake.modules.homeManager.meta = @@ -326,6 +348,19 @@ in config.assertions = lib.optional (config.meta.user != null) { assertion = hasSinglePrimaryEmail config.meta.user; message = "User `${config.meta.user.name}` must define exactly one primary email entry."; - }; + } + ++ lib.optionals (config.meta.user != null) ( + (map (scope: { + assertion = hasAtMostOneScopedEmail scope config.meta.user; + message = "User `${config.meta.user.name}` may define at most one `${scope}` scoped email entry."; + }) [ + "personal" + "work" + ]) + ++ map (scope: { + assertion = hasRequiredScopedEmail scope config.meta.user; + message = "User `${config.meta.user.name}` must define exactly one `${scope}` scoped email entry."; + }) (requiredSourceControlScopes config.meta.user) + ); }; } diff --git a/modules/features/neovim/_kanagawa-theme.nix b/modules/features/neovim/_kanagawa-theme.nix new file mode 100644 index 0000000..5af73f4 --- /dev/null +++ b/modules/features/neovim/_kanagawa-theme.nix @@ -0,0 +1,45 @@ +{ themeName }: +'' + require("kanagawa").setup({ + colors = { + theme = { + all = { + ui = { + bg_gutter = "none" + } + } + } + }, + + overrides = function(colors) + local theme = colors.theme + + local makeDiagnosticColor = function(color) + local c = require("kanagawa.lib.color") + return { fg = color, bg = c(color):blend(theme.ui.bg, 0.95):to_hex() } + end + + return { + TelescopeTitle = { fg = theme.ui.special, bold = true }, + TelescopePromptNormal = { bg = theme.ui.bg_p1 }, + TelescopePromptBorder = { fg = theme.ui.bg_p1, bg = theme.ui.bg_p1 }, + TelescopeResultsNormal = { fg = theme.ui.fg_dim, bg = theme.ui.bg_m1 }, + TelescopeResultsBorder = { fg = theme.ui.bg_m1, bg = theme.ui.bg_m1 }, + TelescopePreviewNormal = { bg = theme.ui.bg_dim }, + TelescopePreviewBorder = { bg = theme.ui.bg_dim, fg = theme.ui.bg_dim }, + + Pmenu = { fg = theme.ui.shade0, bg = theme.ui.bg_p1 }, -- add `blend = vim.o.pumblend` to enable transparency + PmenuSel = { fg = "NONE", bg = theme.ui.bg_p2 }, + PmenuSbar = { bg = theme.ui.bg_m1 }, + PmenuThumb = { bg = theme.ui.bg_p2 }, + + DiagnosticVirtualTextHint = makeDiagnosticColor(theme.diag.hint), + DiagnosticVirtualTextInfo = makeDiagnosticColor(theme.diag.info), + DiagnosticVirtualTextWarn = makeDiagnosticColor(theme.diag.warning), + DiagnosticVirtualTextError = makeDiagnosticColor(theme.diag.error), + } + end, + }) + + vim.cmd.colorscheme("${themeName}") +'' diff --git a/modules/features/neovim/default.nix b/modules/features/neovim/default.nix index e410240..7d0fa2c 100644 --- a/modules/features/neovim/default.nix +++ b/modules/features/neovim/default.nix @@ -1,3 +1,7 @@ +{ config, ... }: +let + repoTheme = config.meta.lib.repo.theme.kanagawa; +in { flake.modules.homeManager.neovim = { @@ -109,56 +113,13 @@ # Hostname/ConfigDir needed for nixd nixdExtras = { nixpkgs = "import ${pkgs.path} {}"; - nixos_options = ''(builtins.getFlake "path://${config.home.homeDirectory}/.config/nixos").nixosConfigurations.${config.meta.host.name}.options''; - home_manager_options = ''(builtins.getFlake "path://${config.home.homeDirectory}/.config/nixos").nixosConfigurations.${config.meta.host.name}.options.home-manager.users.type.getSubOptions []''; + nixos_options = ''(builtins.getFlake "path://${config.meta.user.nixosConfigurationPath}").nixosConfigurations.${config.meta.host.name}.options''; + home_manager_options = ''(builtins.getFlake "path://${config.meta.user.nixosConfigurationPath}").nixosConfigurations.${config.meta.host.name}.options.home-manager.users.type.getSubOptions []''; }; - # TODO: Put in separate theme file - themeSetup = # lua - '' - require("kanagawa").setup({ - colors = { - theme = { - all = { - ui = { - bg_gutter = "none" - } - } - } - }, - - overrides = function(colors) - local theme = colors.theme - - local makeDiagnosticColor = function(color) - local c = require("kanagawa.lib.color") - return { fg = color, bg = c(color):blend(theme.ui.bg, 0.95):to_hex() } - end - - return { - TelescopeTitle = { fg = theme.ui.special, bold = true }, - TelescopePromptNormal = { bg = theme.ui.bg_p1 }, - TelescopePromptBorder = { fg = theme.ui.bg_p1, bg = theme.ui.bg_p1 }, - TelescopeResultsNormal = { fg = theme.ui.fg_dim, bg = theme.ui.bg_m1 }, - TelescopeResultsBorder = { fg = theme.ui.bg_m1, bg = theme.ui.bg_m1 }, - TelescopePreviewNormal = { bg = theme.ui.bg_dim }, - TelescopePreviewBorder = { bg = theme.ui.bg_dim, fg = theme.ui.bg_dim }, - - Pmenu = { fg = theme.ui.shade0, bg = theme.ui.bg_p1 }, -- add `blend = vim.o.pumblend` to enable transparency - PmenuSel = { fg = "NONE", bg = theme.ui.bg_p2 }, - PmenuSbar = { bg = theme.ui.bg_m1 }, - PmenuThumb = { bg = theme.ui.bg_p2 }, - - DiagnosticVirtualTextHint = makeDiagnosticColor(theme.diag.hint), - DiagnosticVirtualTextInfo = makeDiagnosticColor(theme.diag.info), - DiagnosticVirtualTextWarn = makeDiagnosticColor(theme.diag.warning), - DiagnosticVirtualTextError = makeDiagnosticColor(theme.diag.error), - } - end, - }) - - vim.cmd.colorscheme("kanagawa-wave") - ''; + themeSetup = import ./_kanagawa-theme.nix { + themeName = repoTheme.name; + }; }; diff --git a/modules/features/niri/default.nix b/modules/features/niri/default.nix index cd69307..168ea13 100644 --- a/modules/features/niri/default.nix +++ b/modules/features/niri/default.nix @@ -30,6 +30,7 @@ in ... }: let + repoTheme = metaLib.repo.theme.kanagawa; outputs = lib.mapAttrs ( _: display: { @@ -41,10 +42,10 @@ in // lib.optionalAttrs (display.primary or false) { "focus-at-startup" = true; } - // lib.optionalAttrs (display ? scale) { + // lib.optionalAttrs (display.scale != null) { inherit (display) scale; } - // lib.optionalAttrs (display ? mode) { + // lib.optionalAttrs (display.mode != null) { inherit (display) mode; } ) config.meta.host.displays; @@ -81,7 +82,6 @@ in spawn-at-startup = [ { command = [ "xwayland-satellite" ]; } { command = [ "noctalia-shell" ]; } - { command = [ "qbittorrent" ]; } ]; prefer-no-csd = true; hotkey-overlay.skip-at-startup = true; @@ -106,9 +106,9 @@ in border = { enable = true; width = 3; - active.color = "#7E9CD8"; - inactive.color = "#54546D"; - urgent.color = "#E82424"; + active.color = repoTheme.palette.niri.border.active; + inactive.color = repoTheme.palette.niri.border.inactive; + urgent.color = repoTheme.palette.niri.border.urgent; }; }; diff --git a/modules/features/qbittorrent-client.nix b/modules/features/qbittorrent-client.nix index 3d3c1bc..219096b 100644 --- a/modules/features/qbittorrent-client.nix +++ b/modules/features/qbittorrent-client.nix @@ -7,8 +7,16 @@ }; flake.modules.homeManager.qbittorrent-client = - { pkgs, ... }: + { + lib, + pkgs, + ... + }: { home.packages = [ pkgs.qbittorrent ]; + + programs.niri.settings.spawn-at-startup = lib.mkAfter [ + { command = [ "qbittorrent" ]; } + ]; }; } diff --git a/modules/features/source-control.nix b/modules/features/source-control.nix index 8517425..7c93789 100644 --- a/modules/features/source-control.nix +++ b/modules/features/source-control.nix @@ -14,40 +14,11 @@ in user = config.meta.user; sourceControl = user.sourceControl; hostSourceControlUsers = host.sourceControl.users; - hostUserSourceControl = - if lib.hasAttr user.name hostSourceControlUsers then hostSourceControlUsers.${user.name} else { }; - profileNames = builtins.attrNames sourceControl.profiles; - - parsedProfiles = map ( - name: - let - matches = builtins.match "(github|gitlab)-(personal|work)" name; - in - { - inherit matches name; - isValid = matches != null; - scope = if matches == null then null else builtins.elemAt matches 1; - } - ) profileNames; - - validProfiles = builtins.filter (profile: profile.isValid) parsedProfiles; - invalidProfileNames = map (profile: profile.name) ( - builtins.filter (profile: !profile.isValid) parsedProfiles - ); - - emailNamesForScope = { - personal = [ - "personal" - "main" - ]; - work = [ "work" ]; - }; + hostUserSourceControl = hostSourceControlUsers.${user.name} or { }; scopeEmails = scope: - map (name: user.emails.${name}) ( - builtins.filter (name: lib.hasAttr name user.emails) emailNamesForScope.${scope} - ); + lib.filter (email: email.scope == scope) (builtins.attrValues user.emails); emailForScope = scope: @@ -56,8 +27,14 @@ in in if builtins.length emails == 1 then (builtins.head emails).address else null; - scopeConfig = - scope: if lib.hasAttr scope hostUserSourceControl then hostUserSourceControl.${scope} else null; + scopeConfig = scope: hostUserSourceControl.${scope} or null; + + scopeHasSigningKey = + scope: + let + keyConfig = scopeConfig scope; + in + keyConfig != null && keyConfig.publicKey != null; privateKeyPathForScope = scope: @@ -69,7 +46,7 @@ in else keyConfig.privateKeyPath; - scopePublicKey = + publicKeyForScope = scope: let keyConfig = scopeConfig scope; @@ -81,23 +58,25 @@ in "personal" sourceControl.projectScope ] - ++ map (profile: profile.scope) validProfiles ); - missingKeyScopes = builtins.filter (scope: scopePublicKey scope == null) scopesInUse; invalidEmailScopes = builtins.filter (scope: emailForScope scope == null) scopesInUse; - allowedSignersLines = map (scope: "${emailForScope scope} ${scopePublicKey scope}") ( - builtins.filter (scope: emailForScope scope != null && scopePublicKey scope != null) scopesInUse + allowedSignersLines = map (scope: "${emailForScope scope} ${publicKeyForScope scope}") ( + builtins.filter (scope: emailForScope scope != null && scopeHasSigningKey scope) scopesInUse ); - gitConfigForScope = scope: { - gpg.ssh.allowedSignersFile = "${config.xdg.configHome}/git/allowed_signers"; - user = { - name = user.realName; - email = emailForScope scope; - signingKey = "${privateKeyPathForScope scope}.pub"; - }; - }; + gitConfigForScope = + scope: + lib.recursiveUpdate { + user = { + name = user.realName; + email = emailForScope scope; + }; + } + (lib.optionalAttrs (scopeHasSigningKey scope) { + gpg.ssh.allowedSignersFile = "${config.xdg.configHome}/git/allowed_signers"; + user.signingKey = "${privateKeyPathForScope scope}.pub"; + }); gitRoots = [ { @@ -114,17 +93,9 @@ in imports = [ homeModules.git ]; assertions = [ - { - assertion = invalidProfileNames == [ ]; - message = "Invalid source control profiles for `${user.name}`: ${lib.concatStringsSep ", " invalidProfileNames}. Expected `-` using github/gitlab and personal/work."; - } - { - assertion = missingKeyScopes == [ ]; - message = "Missing source control keys for `${user.name}` scopes: ${lib.concatStringsSep ", " missingKeyScopes}."; - } { assertion = invalidEmailScopes == [ ]; - message = "Expected exactly one email selected by name for `${user.name}` scopes: ${lib.concatStringsSep ", " invalidEmailScopes}. Personal uses `personal` or `main`; work uses `work`."; + message = "Expected exactly one scoped email for `${user.name}` source-control scopes: ${lib.concatStringsSep ", " invalidEmailScopes}."; } ]; diff --git a/modules/features/terminal.nix b/modules/features/terminal.nix index 8d0f783..6ea0df2 100644 --- a/modules/features/terminal.nix +++ b/modules/features/terminal.nix @@ -11,6 +11,8 @@ in ... }: let + repoTheme = metaLib.repo.theme.kanagawa; + palette = repoTheme.palette; terminal = metaLib.resolveUserTerminal { inherit pkgs; user = config.meta.user; @@ -44,43 +46,43 @@ in update_check_interval = 0; }; extraConfig = '' - ## name: Kanagawa + ## name: ${repoTheme.displayName} ## license: MIT ## author: Tommaso Laurenzi ## upstream: https://github.com/rebelot/kanagawa.nvim/ - background #1F1F28 - foreground #DCD7BA - selection_background #2D4F67 - selection_foreground #C8C093 - url_color #72A7BC - cursor #C8C093 + background ${palette.background} + foreground ${palette.foreground} + selection_background ${palette.selectionBackground} + selection_foreground ${palette.selectionForeground} + url_color ${palette.url} + cursor ${palette.cursor} - active_tab_background #1F1F28 - active_tab_foreground #C8C093 - inactive_tab_background #1F1F28 - inactive_tab_foreground #727169 + active_tab_background ${palette.background} + active_tab_foreground ${palette.selectionForeground} + inactive_tab_background ${palette.background} + inactive_tab_foreground ${palette.muted} - color0 #16161D - color1 #C34043 - color2 #76946A - color3 #C0A36E - color4 #7E9CD8 - color5 #957FB8 - color6 #6A9589 - color7 #C8C093 + color0 ${palette.terminal.color0} + color1 ${palette.terminal.color1} + color2 ${palette.terminal.color2} + color3 ${palette.terminal.color3} + color4 ${palette.terminal.color4} + color5 ${palette.terminal.color5} + color6 ${palette.terminal.color6} + color7 ${palette.terminal.color7} - color8 #727169 - color9 #E82424 - color10 #98BB6C - color11 #E6C384 - color12 #7FB4CA - color13 #938AA9 - color14 #7AA89F - color15 #DCD7BA + color8 ${palette.terminal.color8} + color9 ${palette.terminal.color9} + color10 ${palette.terminal.color10} + color11 ${palette.terminal.color11} + color12 ${palette.terminal.color12} + color13 ${palette.terminal.color13} + color14 ${palette.terminal.color14} + color15 ${palette.terminal.color15} - color16 #FFA066 - color17 #FF5D62 + color16 ${palette.terminal.color16} + color17 ${palette.terminal.color17} ''; }; }; diff --git a/modules/features/vicinae.nix b/modules/features/vicinae.nix index 45bc8a4..1a7383b 100644 --- a/modules/features/vicinae.nix +++ b/modules/features/vicinae.nix @@ -1,44 +1,58 @@ +{ config, ... }: +let + metaLib = config.meta.lib; +in { flake.modules.homeManager.vicinae = - { pkgs, inputs, ... }: + { + pkgs, + inputs, + ... + }: + let + repoTheme = metaLib.repo.theme.kanagawa; + palette = repoTheme.palette; + in { programs.vicinae = { enable = true; systemd.enable = true; - themes.kanagawa-wave = { + themes.${repoTheme.name} = { meta = { version = 1; - name = "Kanagawa Wave"; + name = repoTheme.displayName; description = "A dark theme inspired by the colors of the famous painting by Katsushika Hokusai."; variant = "dark"; inherits = "vicinae-dark"; }; colors = { core = { - background = "#1F1F28"; - foreground = "#DCD7BA"; - secondary_background = "#16161D"; - border = "#2A2A37"; - accent = "#7E9CD8"; + background = palette.background; + foreground = palette.foreground; + secondary_background = palette.secondaryBackground; + border = palette.border; + accent = palette.accents.blue; }; accents = { - blue = "#7E9CD8"; - green = "#98BB6C"; - magenta = "#D27E99"; - orange = "#FFA066"; - purple = "#957FB8"; - red = "#E82424"; - yellow = "#E6C384"; - cyan = "#7AA89F"; + inherit (palette.accents) + blue + cyan + green + magenta + orange + purple + red + yellow + ; }; input.border_focus = "colors.core.accent"; }; }; settings.theme = { - light.name = "kanagawa-wave"; - dark.name = "kanagawa-wave"; + light.name = repoTheme.name; + dark.name = repoTheme.name; }; extensions = with inputs.vicinae-extensions.packages.${pkgs.stdenv.hostPlatform.system}; [ diff --git a/modules/features/workstation-base.nix b/modules/features/workstation-base.nix index 58f413b..e5bd90f 100644 --- a/modules/features/workstation-base.nix +++ b/modules/features/workstation-base.nix @@ -15,7 +15,6 @@ in nixosModules.networking nixosModules.niri nixosModules.printing - nixosModules.qbittorrent-client nixosModules.sddm nixosModules.sops-admin-key-file nixosModules.standard-boot diff --git a/modules/hosts/orion/default.nix b/modules/hosts/orion/default.nix index 352d9a1..cec6ce1 100644 --- a/modules/hosts/orion/default.nix +++ b/modules/hosts/orion/default.nix @@ -44,7 +44,7 @@ in flake.modules.nixos.orion = metaLib.mkHost { name = "orion"; users = { - kiri = { + kiri = metaLib.mkHostUser { account = metaLib.users.kiri; homeImports = [ homeModules.shell diff --git a/modules/hosts/polaris/default.nix b/modules/hosts/polaris/default.nix index b7ea0ac..c26ac83 100644 --- a/modules/hosts/polaris/default.nix +++ b/modules/hosts/polaris/default.nix @@ -29,17 +29,8 @@ in mouse.accelSpeed = 0.4; }; - sourceControl.users = { - kiri.personal.publicKey = ""; - - ergon = { - personal.publicKey = ""; - work.publicKey = ""; - }; - }; - users = { - kiri = { + kiri = metaLib.mkHostUser { account = metaLib.users.kiri; needsPassword = true; homeImports = [ @@ -50,7 +41,7 @@ in ]; }; - ergon = { + ergon = metaLib.mkHostUser { account = metaLib.users.ergon; needsPassword = true; homeImports = [ @@ -62,6 +53,7 @@ in imports = [ nixosModules.workstation-base + nixosModules.qbittorrent-client nixosModules.steam ./_hardware.nix ] diff --git a/modules/hosts/zenith/default.nix b/modules/hosts/zenith/default.nix index ee7177c..851da96 100644 --- a/modules/hosts/zenith/default.nix +++ b/modules/hosts/zenith/default.nix @@ -31,8 +31,6 @@ in }; sourceControl.users = { - kiri.personal.publicKey = ""; - ergon = { personal.publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPdR3KP2U84i7f7MlRqcML/3YyMw8JL3hdm637SkMUwO ergon@zenith#personal"; work.publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIHJz5uHKm0/TiMNh/cmzrODHNZ8NgEEZe+47XnJwQGk ergon@zenith#work"; @@ -40,7 +38,7 @@ in }; users = { - kiri = { + kiri = metaLib.mkHostUser { account = metaLib.users.kiri; needsPassword = true; homeImports = [ @@ -51,7 +49,7 @@ in ]; }; - ergon = { + ergon = metaLib.mkHostUser { account = metaLib.users.ergon; needsPassword = true; homeImports = [ @@ -63,6 +61,7 @@ in imports = [ nixosModules.workstation-base + nixosModules.qbittorrent-client nixosModules.laptop-power { hardware.enableRedistributableFirmware = true; diff --git a/modules/lib.nix b/modules/lib.nix index d2d3d96..f335b38 100644 --- a/modules/lib.nix +++ b/modules/lib.nix @@ -4,6 +4,22 @@ ... }: let + mkHostUser = + { + account, + needsPassword ? false, + homeImports ? [ ], + stateVersion ? null, + }: + { + inherit + account + homeImports + needsPassword + stateVersion + ; + }; + mkHost = { name, @@ -20,22 +36,15 @@ let ... }: let - hostUsers = lib.mapAttrs (_: spec: spec.account) users; + hostUserSpecs = lib.mapAttrs (_: spec: mkHostUser spec) users; + hostUsers = lib.mapAttrs (_: spec: spec.account) hostUserSpecs; - userAssertions = lib.flatten ( - lib.mapAttrsToList (userName: spec: [ - { - assertion = userName == spec.account.name; - message = "Host `${name}` declares user `${userName}` with mismatched account name `${spec.account.name}`."; - } - { - assertion = builtins.isList spec.homeImports; - message = "Host `${name}` user `${userName}` must define `homeImports` as a list."; - } - ]) users - ); + 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 or false) users; + passwordUserSpecs = lib.filterAttrs (_: spec: spec.needsPassword) hostUserSpecs; in { assertions = userAssertions; @@ -76,23 +85,26 @@ let "networkmanager" ]; } - // lib.optionalAttrs (spec.needsPassword or false) { + // lib.optionalAttrs spec.needsPassword { hashedPasswordFile = config.sops.secrets."hashed-password-${userName}".path; } - ) users; + ) hostUserSpecs; - home-manager.users = lib.mapAttrs (_: spec: { - imports = spec.homeImports; - meta = { - host = config.meta.host; - user = spec.account; - }; - home = { - username = spec.account.name; - homeDirectory = spec.account.homeDirectory; - stateVersion = spec.stateVersion or stateVersion; - }; - }) users; + home-manager.users = lib.mapAttrs ( + _: spec: + { + imports = spec.homeImports; + meta = { + host = config.meta.host; + user = spec.account; + }; + home = { + username = spec.account.name; + homeDirectory = spec.account.homeDirectory; + stateVersion = if spec.stateVersion == null then stateVersion else spec.stateVersion; + }; + } + ) hostUserSpecs; }; mkCaddyReverseProxy = @@ -170,6 +182,8 @@ let }; kanagawa = { + displayName = "Kanagawa Wave"; + name = "kanagawa-wave"; gtkThemeName = "Kanagawa-BL-LB"; iconThemeName = "Kanagawa"; owner = "Fausto-Korpsvart"; @@ -177,6 +191,56 @@ let rev = "55ca4ba249eba21f861b9866b71ab41bb8930318"; hash = "sha256-UdMoMx2DoovcxSp/zBZ3PRv/Qpj+prd0uPm1gmdak2E="; version = "unstable-2025-10-23"; + + palette = { + background = "#1F1F28"; + foreground = "#DCD7BA"; + secondaryBackground = "#16161D"; + border = "#2A2A37"; + selectionBackground = "#2D4F67"; + selectionForeground = "#C8C093"; + url = "#72A7BC"; + cursor = "#C8C093"; + muted = "#727169"; + + accents = { + blue = "#7E9CD8"; + green = "#98BB6C"; + magenta = "#D27E99"; + orange = "#FFA066"; + purple = "#957FB8"; + red = "#E82424"; + yellow = "#E6C384"; + cyan = "#7AA89F"; + }; + + niri.border = { + active = "#7E9CD8"; + inactive = "#54546D"; + urgent = "#E82424"; + }; + + terminal = { + color0 = "#16161D"; + color1 = "#C34043"; + color2 = "#76946A"; + color3 = "#C0A36E"; + color4 = "#7E9CD8"; + color5 = "#957FB8"; + color6 = "#6A9589"; + color7 = "#C8C093"; + color8 = "#727169"; + color9 = "#E82424"; + color10 = "#98BB6C"; + color11 = "#E6C384"; + color12 = "#7FB4CA"; + color13 = "#938AA9"; + color14 = "#7AA89F"; + color15 = "#DCD7BA"; + color16 = "#FFA066"; + color17 = "#FF5D62"; + }; + }; }; }; }; @@ -392,6 +456,13 @@ in readOnly = true; }; + options.meta.lib.mkHostUser = lib.mkOption { + type = lib.types.raw; + description = "Internal host user constructor shared between flake-parts modules."; + internal = true; + readOnly = true; + }; + options.meta.lib.mkCaddyReverseProxy = lib.mkOption { type = lib.types.raw; description = "Internal Caddy reverse proxy helper shared between flake-parts modules."; @@ -447,6 +518,7 @@ in mkCaddyReverseProxy mkTerminalAssertions mkHost + mkHostUser repo resolvePackagePath resolveUserTerminal diff --git a/modules/users.nix b/modules/users.nix index 46741fa..eb64144 100644 --- a/modules/users.nix +++ b/modules/users.nix @@ -9,30 +9,29 @@ let personal = { address = "mail@jelles.net"; primary = true; + scope = "personal"; type = "mxrouting"; }; old = { address = "mail@jellespreeuwenberg.nl"; primary = false; + scope = null; type = "mxrouting"; }; uni = { address = "j.spreeuwenberg@student.tue.nl"; primary = false; + scope = null; type = "office365"; }; work = { address = "jelle.spreeuwenberg@yookr.org"; primary = false; + scope = "work"; type = "office365"; }; }; - sourceControl = { - profiles = { - github-personal = { }; - gitlab-personal = { }; - }; - }; + sourceControl = { }; }; ergon = { @@ -44,21 +43,17 @@ let personal = { address = "mail@jelles.net"; primary = false; + scope = "personal"; type = "mxrouting"; }; work = { address = "jelle.spreeuwenberg@yookr.org"; primary = true; + scope = "work"; type = "office365"; }; }; sourceControl = { - profiles = { - github-personal = { }; - github-work = { }; - gitlab-personal = { }; - }; - projectScope = "work"; }; };