diff --git a/modules/features/bitwarden.nix b/modules/features/bitwarden.nix index d47cdda..09c90a5 100644 --- a/modules/features/bitwarden.nix +++ b/modules/features/bitwarden.nix @@ -1,22 +1,26 @@ -{ ... }: +{ + config, + ... +}: +let + metaLib = config.meta.lib; +in { flake.modules.homeManager.bitwarden = { config, - lib, pkgs, ... }: let user = config.meta.user; - primaryEmail = builtins.head (lib.filter (email: email.primary) (builtins.attrValues user.emails)); in { programs.rbw = { enable = true; settings = { - base_url = "https://vault.jelles.net"; - email = primaryEmail.address; + base_url = metaLib.repo.services.vaultwarden.url; + email = user.primaryEmail.address; pinentry = pkgs.pinentry-gnome3; }; }; diff --git a/modules/features/ergon-workstation.nix b/modules/features/ergon-workstation.nix deleted file mode 100644 index 63acca9..0000000 --- a/modules/features/ergon-workstation.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ config, ... }: -let - homeModules = config.flake.modules.homeManager; -in -{ - flake.modules.homeManager.ergon-workstation = { - imports = [ homeModules.workstation-base ]; - }; -} diff --git a/modules/features/git.nix b/modules/features/git.nix index f97aac7..78ea63d 100644 --- a/modules/features/git.nix +++ b/modules/features/git.nix @@ -8,7 +8,6 @@ }: let user = config.meta.user; - primaryEmail = builtins.head (lib.filter (email: email.primary) (builtins.attrValues user.emails)); usesScopedIdentity = user != null && user.sourceControl.profiles != { }; in { @@ -25,7 +24,7 @@ // lib.optionalAttrs (!usesScopedIdentity) { user = { name = user.realName; - email = primaryEmail.address; + email = user.primaryEmail.address; }; }; }; diff --git a/modules/features/input.nix b/modules/features/input.nix index 0eae387..3724602 100644 --- a/modules/features/input.nix +++ b/modules/features/input.nix @@ -1,3 +1,10 @@ +{ + config, + ... +}: +let + metaLib = config.meta.lib; +in { flake.modules.nixos.input = { @@ -6,64 +13,15 @@ ... }: let - hostInput = config.meta.host.input; - - hasAnyConfiguredValue = attrs: lib.any (value: value != null) (lib.attrValues attrs); - libinputScrollMethodMap = { - edge = "edge"; - "no-scroll" = "none"; - "on-button-down" = "button"; - "two-finger" = "twofinger"; - }; - libinputClickMethodMap = { - "button-areas" = "buttonareas"; - clickfinger = "clickfinger"; - }; - - hasMouseConfig = hasAnyConfiguredValue hostInput.mouse; - hasTouchpadConfig = hasAnyConfiguredValue hostInput.touchpad; - hasPointerConfig = hasMouseConfig || hasTouchpadConfig; - - mouseConfig = lib.filterAttrs (_: value: value != null) { - accelProfile = hostInput.mouse.accelProfile; - accelSpeed = - if hostInput.mouse.accelSpeed == null then null else toString hostInput.mouse.accelSpeed; - leftHanded = hostInput.mouse.leftHanded; - middleEmulation = hostInput.mouse.middleEmulation; - naturalScrolling = hostInput.mouse.naturalScrolling; - scrollMethod = - if hostInput.mouse.scrollMethod == null then - null - else - libinputScrollMethodMap.${hostInput.mouse.scrollMethod}; - }; - - touchpadConfig = lib.filterAttrs (_: value: value != null) { - accelProfile = hostInput.touchpad.accelProfile; - accelSpeed = - if hostInput.touchpad.accelSpeed == null then null else toString hostInput.touchpad.accelSpeed; - clickMethod = - if hostInput.touchpad.clickMethod == null then - null - else - libinputClickMethodMap.${hostInput.touchpad.clickMethod}; - disableWhileTyping = hostInput.touchpad.disableWhileTyping; - leftHanded = hostInput.touchpad.leftHanded; - middleEmulation = hostInput.touchpad.middleEmulation; - naturalScrolling = hostInput.touchpad.naturalScrolling; - scrollMethod = - if hostInput.touchpad.scrollMethod == null then - null - else - libinputScrollMethodMap.${hostInput.touchpad.scrollMethod}; - tapping = hostInput.touchpad.tapping; - }; + inputProfiles = metaLib.mkInputProfiles config.meta.host.input; in { - services.libinput = lib.mkIf hasPointerConfig { + services.libinput = lib.mkIf inputProfiles.hasPointerConfig { enable = true; - mouse = mouseConfig; - touchpad = touchpadConfig; + inherit (inputProfiles.libinput) + mouse + touchpad + ; }; }; } diff --git a/modules/features/kiri-workstation.nix b/modules/features/kiri-workstation.nix deleted file mode 100644 index 731c033..0000000 --- a/modules/features/kiri-workstation.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ config, ... }: -let - homeModules = config.flake.modules.homeManager; -in -{ - flake.modules.homeManager.kiri-workstation = { - imports = [ - homeModules.workstation-base - homeModules.syncthing - homeModules.qbittorrent-client - ]; - }; -} diff --git a/modules/features/meta.nix b/modules/features/meta.nix index 4488b2b..666cac2 100644 --- a/modules/features/meta.nix +++ b/modules/features/meta.nix @@ -3,10 +3,41 @@ let mkNullableOption = type: lib.mkOption { - inherit type; + type = lib.types.nullOr type; default = null; }; + hasSinglePrimaryEmail = + user: builtins.length (lib.filter (email: email.primary) (builtins.attrValues user.emails)) == 1; + + primaryEmailFallback = { + address = ""; + primary = false; + type = ""; + }; + + sourceControlScopeType = lib.types.enum [ + "personal" + "work" + ]; + + pointerAccelProfileType = lib.types.enum [ + "adaptive" + "flat" + ]; + + pointerScrollMethodType = lib.types.enum [ + "no-scroll" + "two-finger" + "edge" + "on-button-down" + ]; + + touchpadClickMethodType = lib.types.enum [ + "button-areas" + "clickfinger" + ]; + emailType = lib.types.submodule ( { ... }: { @@ -79,10 +110,7 @@ let }; projectScope = lib.mkOption { - type = lib.types.enum [ - "personal" - "work" - ]; + type = sourceControlScopeType; default = "personal"; }; }; @@ -91,6 +119,10 @@ let userType = lib.types.submodule ( { config, ... }: + let + primaryEmails = lib.filter (email: email.primary) (builtins.attrValues config.emails); + primaryEmail = if primaryEmails == [ ] then primaryEmailFallback else builtins.head primaryEmails; + in { options = { name = lib.mkOption { @@ -118,11 +150,19 @@ let type = lib.types.attrsOf emailType; }; + primaryEmail = lib.mkOption { + type = emailType; + readOnly = true; + description = "Derived primary email entry for this user."; + }; + sourceControl = lib.mkOption { type = sourceControlType; default = { }; }; }; + + config.primaryEmail = primaryEmail; } ); @@ -179,28 +219,12 @@ let { ... }: { options = { - accelProfile = mkNullableOption ( - lib.types.nullOr ( - lib.types.enum [ - "adaptive" - "flat" - ] - ) - ); - accelSpeed = mkNullableOption (lib.types.nullOr lib.types.float); - leftHanded = mkNullableOption (lib.types.nullOr lib.types.bool); - middleEmulation = mkNullableOption (lib.types.nullOr lib.types.bool); - naturalScrolling = mkNullableOption (lib.types.nullOr lib.types.bool); - scrollMethod = mkNullableOption ( - lib.types.nullOr ( - lib.types.enum [ - "no-scroll" - "two-finger" - "edge" - "on-button-down" - ] - ) - ); + accelProfile = mkNullableOption (pointerAccelProfileType); + accelSpeed = mkNullableOption lib.types.float; + leftHanded = mkNullableOption lib.types.bool; + middleEmulation = mkNullableOption lib.types.bool; + naturalScrolling = mkNullableOption lib.types.bool; + scrollMethod = mkNullableOption pointerScrollMethodType; }; } ); @@ -209,38 +233,15 @@ let { ... }: { options = { - accelProfile = mkNullableOption ( - lib.types.nullOr ( - lib.types.enum [ - "adaptive" - "flat" - ] - ) - ); - accelSpeed = mkNullableOption (lib.types.nullOr lib.types.float); - clickMethod = mkNullableOption ( - lib.types.nullOr ( - lib.types.enum [ - "button-areas" - "clickfinger" - ] - ) - ); - disableWhileTyping = mkNullableOption (lib.types.nullOr lib.types.bool); - leftHanded = mkNullableOption (lib.types.nullOr lib.types.bool); - middleEmulation = mkNullableOption (lib.types.nullOr lib.types.bool); - naturalScrolling = mkNullableOption (lib.types.nullOr lib.types.bool); - scrollMethod = mkNullableOption ( - lib.types.nullOr ( - lib.types.enum [ - "no-scroll" - "two-finger" - "edge" - "on-button-down" - ] - ) - ); - tapping = mkNullableOption (lib.types.nullOr lib.types.bool); + accelProfile = mkNullableOption (pointerAccelProfileType); + accelSpeed = mkNullableOption lib.types.float; + clickMethod = mkNullableOption touchpadClickMethodType; + disableWhileTyping = mkNullableOption lib.types.bool; + leftHanded = mkNullableOption lib.types.bool; + middleEmulation = mkNullableOption lib.types.bool; + naturalScrolling = mkNullableOption lib.types.bool; + scrollMethod = mkNullableOption pointerScrollMethodType; + tapping = mkNullableOption lib.types.bool; }; } ); @@ -294,23 +295,37 @@ let ); in { - flake.modules.nixos.meta = { - options.meta.host = lib.mkOption { - type = hostType; - }; - }; - - flake.modules.homeManager.meta = { - options.meta = { - host = lib.mkOption { - type = lib.types.nullOr hostType; - default = null; + flake.modules.nixos.meta = + { config, ... }: + { + options.meta.host = lib.mkOption { + type = hostType; }; - user = lib.mkOption { - type = lib.types.nullOr userType; - default = null; + config.assertions = lib.mapAttrsToList (userName: user: { + assertion = hasSinglePrimaryEmail user; + message = "User `${userName}` must define exactly one primary email entry."; + }) config.meta.host.users; + }; + + flake.modules.homeManager.meta = + { config, ... }: + { + options.meta = { + host = lib.mkOption { + type = lib.types.nullOr hostType; + default = null; + }; + + user = lib.mkOption { + type = lib.types.nullOr userType; + default = null; + }; + }; + + 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."; }; }; - }; } diff --git a/modules/features/niri/default.nix b/modules/features/niri/default.nix index 1cdcf9c..cd69307 100644 --- a/modules/features/niri/default.nix +++ b/modules/features/niri/default.nix @@ -30,8 +30,6 @@ in ... }: let - hostInput = config.meta.host.input; - outputs = lib.mapAttrs ( _: display: { @@ -50,44 +48,17 @@ in inherit (display) mode; } ) config.meta.host.displays; - terminalPackage = metaLib.resolvePackagePath { + inputProfiles = metaLib.mkInputProfiles config.meta.host.input; + terminal = metaLib.resolveUserTerminal { inherit pkgs; - path = config.meta.user.terminalPackagePath; - }; - hasMainProgram = terminalPackage != null && terminalPackage ? meta.mainProgram; - - mouseSettings = lib.filterAttrs (_: value: value != null) { - accel-profile = hostInput.mouse.accelProfile; - accel-speed = hostInput.mouse.accelSpeed; - left-handed = hostInput.mouse.leftHanded; - middle-emulation = hostInput.mouse.middleEmulation; - natural-scroll = hostInput.mouse.naturalScrolling; - scroll-method = hostInput.mouse.scrollMethod; - }; - - touchpadSettings = lib.filterAttrs (_: value: value != null) { - accel-profile = hostInput.touchpad.accelProfile; - accel-speed = hostInput.touchpad.accelSpeed; - click-method = hostInput.touchpad.clickMethod; - dwt = hostInput.touchpad.disableWhileTyping; - left-handed = hostInput.touchpad.leftHanded; - middle-emulation = hostInput.touchpad.middleEmulation; - natural-scroll = hostInput.touchpad.naturalScrolling; - scroll-method = hostInput.touchpad.scrollMethod; - tap = hostInput.touchpad.tapping; + user = config.meta.user; }; in { - assertions = [ - { - assertion = terminalPackage != null; - message = "Unknown terminal package `${lib.showAttrPath config.meta.user.terminalPackagePath}` for user `${config.meta.user.name}`."; - } - { - assertion = hasMainProgram; - message = "Terminal package `${lib.showAttrPath config.meta.user.terminalPackagePath}` must define `meta.mainProgram`."; - } - ]; + assertions = metaLib.mkTerminalAssertions { + inherit terminal; + user = config.meta.user; + }; home.sessionVariables.NIXOS_OZONE_WL = "1"; @@ -167,20 +138,20 @@ in xkb.options = "caps:escape"; }; } - // lib.optionalAttrs (mouseSettings != { }) { - mouse = mouseSettings; + // lib.optionalAttrs (inputProfiles.niri.mouse != { }) { + mouse = inputProfiles.niri.mouse; } - // lib.optionalAttrs (touchpadSettings != { }) { - touchpad = touchpadSettings; + // lib.optionalAttrs (inputProfiles.niri.touchpad != { }) { + touchpad = inputProfiles.niri.touchpad; }; binds = - if hasMainProgram then + if terminal.hasMainProgram then import ./_bindings.nix { inherit lib - terminalPackage ; + terminalPackage = terminal.package; } else { }; diff --git a/modules/features/noctalia.nix b/modules/features/noctalia.nix index 6d53f08..1f61dab 100644 --- a/modules/features/noctalia.nix +++ b/modules/features/noctalia.nix @@ -38,18 +38,17 @@ in ... }: let - terminalPackage = metaLib.resolvePackagePath { + terminal = metaLib.resolveUserTerminal { inherit pkgs; - path = config.meta.user.terminalPackagePath; + user = config.meta.user; }; - hasMainProgram = terminalPackage != null && terminalPackage ? meta.mainProgram; baseSettings = - if hasMainProgram then + if terminal.hasMainProgram then import ./_noctalia-config.nix { inherit lib - terminalPackage ; + terminalPackage = terminal.package; } else { }; @@ -57,16 +56,10 @@ in { imports = [ inputs.noctalia.homeModules.default ]; - assertions = [ - { - assertion = terminalPackage != null; - message = "Unknown terminal package `${lib.showAttrPath config.meta.user.terminalPackagePath}` for user `${config.meta.user.name}`."; - } - { - assertion = hasMainProgram; - message = "Terminal package `${lib.showAttrPath config.meta.user.terminalPackagePath}` must define `meta.mainProgram`."; - } - ]; + assertions = metaLib.mkTerminalAssertions { + inherit terminal; + user = config.meta.user; + }; programs.noctalia-shell = { enable = true; @@ -88,18 +81,17 @@ in ... }: let - terminalPackage = metaLib.resolvePackagePath { + terminal = metaLib.resolveUserTerminal { inherit pkgs; - path = config.meta.user.terminalPackagePath; + user = config.meta.user; }; - hasMainProgram = terminalPackage != null && terminalPackage ? meta.mainProgram; baseSettings = - if hasMainProgram then + if terminal.hasMainProgram then import ./_noctalia-config.nix { inherit lib - terminalPackage ; + terminalPackage = terminal.package; } else { }; @@ -107,7 +99,7 @@ in { imports = [ homeModules.noctalia ]; programs.noctalia-shell.settings = lib.mkForce ( - if hasMainProgram then mkPortableSettings baseSettings else { } + if terminal.hasMainProgram then mkPortableSettings baseSettings else { } ); }; } diff --git a/modules/features/personal-productivity.nix b/modules/features/personal-productivity.nix deleted file mode 100644 index 6850516..0000000 --- a/modules/features/personal-productivity.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ config, ... }: -let - homeModules = config.flake.modules.homeManager; -in -{ - flake.modules.homeManager.personal-productivity = { - imports = [ - homeModules.bitwarden - homeModules.email - homeModules.pim - ]; - }; -} diff --git a/modules/features/pim.nix b/modules/features/pim.nix index de7fa7f..978f160 100644 --- a/modules/features/pim.nix +++ b/modules/features/pim.nix @@ -1,3 +1,10 @@ +{ + config, + ... +}: +let + metaLib = config.meta.lib; +in { flake.modules.homeManager.pim = { @@ -44,7 +51,7 @@ }; remote = { - url = "https://radicale.jelles.net/"; + url = metaLib.repo.services.radicale.url; type = "caldav"; userName = config.home.username; passwordCommand = [ diff --git a/modules/features/services/actual.nix b/modules/features/services/actual.nix index a3a8e4e..fe0da19 100644 --- a/modules/features/services/actual.nix +++ b/modules/features/services/actual.nix @@ -1,6 +1,7 @@ { config, ... }: let metaLib = config.meta.lib; + service = metaLib.repo.services.actual; in { flake.modules.nixos.actual = @@ -11,15 +12,17 @@ in enable = true; openFirewall = false; settings = { - port = 3000; - hostname = "127.0.0.1"; + inherit (service) port; + hostname = service.host; }; }; } (metaLib.mkCaddyReverseProxy { - domain = "finance.jelles.net"; - port = 3000; + inherit (service) + domain + port + ; }) ]; } diff --git a/modules/features/services/caddy.nix b/modules/features/services/caddy.nix index 533fa1d..7e7ba98 100644 --- a/modules/features/services/caddy.nix +++ b/modules/features/services/caddy.nix @@ -1,8 +1,12 @@ +{ config, ... }: +let + metaLib = config.meta.lib; +in { flake.modules.nixos.caddy = { services.caddy = { enable = true; - email = "mail@jelles.net"; + email = metaLib.repo.contact.email; openFirewall = true; }; }; diff --git a/modules/features/services/deluge.nix b/modules/features/services/deluge.nix deleted file mode 100644 index 1d48022..0000000 --- a/modules/features/services/deluge.nix +++ /dev/null @@ -1,18 +0,0 @@ -{ - flake.modules.nixos.deluge-service = - { ... }: - { - sops.secrets.deluge-auth-file = { }; - - services.deluge = { - enable = true; - declarative = false; - }; - }; - - flake.modules.homeManager.deluge-client = - { pkgs, ... }: - { - home.packages = [ pkgs.deluge ]; - }; -} diff --git a/modules/features/services/gitea.nix b/modules/features/services/gitea.nix index 5269957..b935a23 100644 --- a/modules/features/services/gitea.nix +++ b/modules/features/services/gitea.nix @@ -1,6 +1,7 @@ { config, ... }: let metaLib = config.meta.lib; + service = metaLib.repo.services.gitea; in { flake.modules.nixos.gitea = @@ -12,10 +13,10 @@ in settings = { server = { - DOMAIN = "git.jelles.net"; - ROOT_URL = "https://git.jelles.net/"; - HTTP_PORT = 3001; - HTTP_ADDR = "127.0.0.1"; + DOMAIN = service.domain; + ROOT_URL = service.url; + HTTP_PORT = service.port; + HTTP_ADDR = service.host; START_SSH_SERVER = false; SSH_PORT = 22; @@ -31,8 +32,10 @@ in } (metaLib.mkCaddyReverseProxy { - domain = "git.jelles.net"; - port = 3001; + inherit (service) + domain + port + ; }) ]; } diff --git a/modules/features/services/qbittorrent.nix b/modules/features/services/qbittorrent.nix deleted file mode 100644 index 83cf1f2..0000000 --- a/modules/features/services/qbittorrent.nix +++ /dev/null @@ -1,11 +0,0 @@ -{ ... }: -{ - flake.modules.nixos.qbittorrent = { - services.qbittorrent = { - enable = true; - openFirewall = true; - torrentingPort = 43864; - webuiPort = 8123; - }; - }; -} diff --git a/modules/features/services/radicale.nix b/modules/features/services/radicale.nix index 4d376a0..97c0dfe 100644 --- a/modules/features/services/radicale.nix +++ b/modules/features/services/radicale.nix @@ -1,6 +1,7 @@ { config, ... }: let metaLib = config.meta.lib; + service = metaLib.repo.services.radicale; in { flake.modules.nixos.radicale = @@ -10,7 +11,7 @@ in services.radicale = { enable = true; settings = { - server.hosts = [ "127.0.0.1:5232" ]; + server.hosts = [ "${service.host}:${toString service.port}" ]; auth = { type = "htpasswd"; @@ -24,8 +25,10 @@ in } (metaLib.mkCaddyReverseProxy { - domain = "radicale.jelles.net"; - port = 5232; + inherit (service) + domain + port + ; extraHeaders = [ { name = "X-Script-Name"; diff --git a/modules/features/services/vaultwarden.nix b/modules/features/services/vaultwarden.nix index e1168dd..49ed919 100644 --- a/modules/features/services/vaultwarden.nix +++ b/modules/features/services/vaultwarden.nix @@ -1,6 +1,7 @@ { config, ... }: let metaLib = config.meta.lib; + service = metaLib.repo.services.vaultwarden; in { flake.modules.nixos.vaultwarden = @@ -11,17 +12,19 @@ in enable = true; backupDir = "/var/backup/vaultwarden"; config = { - DOMAIN = "https://vault.jelles.net"; + DOMAIN = service.url; SIGNUPS_ALLOWED = false; - ROCKET_PORT = 8100; + ROCKET_PORT = service.port; ROCKET_LOG = "critical"; }; }; } (metaLib.mkCaddyReverseProxy { - domain = "vault.jelles.net"; - port = 8100; + inherit (service) + domain + port + ; }) ]; } diff --git a/modules/features/terminal.nix b/modules/features/terminal.nix index f8a4bb9..8d0f783 100644 --- a/modules/features/terminal.nix +++ b/modules/features/terminal.nix @@ -11,38 +11,22 @@ in ... }: let - terminalPackage = metaLib.resolvePackagePath { + terminal = metaLib.resolveUserTerminal { inherit pkgs; - path = config.meta.user.terminalPackagePath; + user = config.meta.user; }; - hasTerminalPackage = terminalPackage != null; - hasMainProgram = hasTerminalPackage && terminalPackage ? meta.mainProgram; - terminalDesktopId = if hasMainProgram then "${terminalPackage.meta.mainProgram}.desktop" else null; in { - assertions = [ - { - assertion = hasTerminalPackage; - message = "Unknown terminal package `${lib.showAttrPath config.meta.user.terminalPackagePath}` for user `${config.meta.user.name}`."; - } - { - assertion = hasMainProgram; - message = "Terminal package `${lib.showAttrPath config.meta.user.terminalPackagePath}` must define `meta.mainProgram`."; - } - { - assertion = - hasMainProgram && builtins.pathExists "${terminalPackage}/share/applications/${terminalDesktopId}"; - message = "Terminal package `${lib.showAttrPath config.meta.user.terminalPackagePath}` must provide `${terminalDesktopId}`."; - } - { - assertion = hasMainProgram && terminalPackage.meta.mainProgram == "kitty"; - message = "The terminal feature currently only supports kitty-specific Home Manager configuration."; - } - ]; + assertions = metaLib.mkTerminalAssertions { + inherit terminal; + user = config.meta.user; + requireDesktopEntry = true; + requireKitty = true; + }; xdg.terminal-exec = { enable = true; - settings.default = lib.optional (terminalDesktopId != null) terminalDesktopId; + settings.default = lib.optional (terminal.desktopId != null) terminal.desktopId; }; programs.kitty = { diff --git a/modules/features/theme.nix b/modules/features/theme.nix index e10269c..39d434f 100644 --- a/modules/features/theme.nix +++ b/modules/features/theme.nix @@ -1,3 +1,10 @@ +{ + config, + ... +}: +let + metaLib = config.meta.lib; +in { flake.modules.nixos.theme = { @@ -5,10 +12,12 @@ ... }: let - cursorTheme = { - name = "phinger-cursors-light"; - package = pkgs.phinger-cursors; - size = 24; + repoTheme = metaLib.repo.theme; + cursorTheme = repoTheme.cursor // { + package = metaLib.resolvePackagePath { + inherit pkgs; + path = repoTheme.cursor.packagePath; + }; }; in { @@ -25,21 +34,25 @@ flake.modules.homeManager.theme = { config, pkgs, ... }: let - cursorTheme = { - name = "phinger-cursors-light"; - package = pkgs.phinger-cursors; - size = 24; + repoTheme = metaLib.repo.theme; + cursorTheme = repoTheme.cursor // { + package = metaLib.resolvePackagePath { + inherit pkgs; + path = repoTheme.cursor.packagePath; + }; }; kanagawaThemeSrc = pkgs.fetchFromGitHub { - owner = "Fausto-Korpsvart"; - repo = "Kanagawa-GKT-Theme"; - rev = "55ca4ba249eba21f861b9866b71ab41bb8930318"; - hash = "sha256-UdMoMx2DoovcxSp/zBZ3PRv/Qpj+prd0uPm1gmdak2E="; + inherit (repoTheme.kanagawa) + hash + owner + repo + rev + ; }; kanagawaOverride = { - version = "unstable-2025-10-23"; + version = repoTheme.kanagawa.version; src = kanagawaThemeSrc; }; in @@ -59,14 +72,14 @@ "sftp://orion Orion VPS" ]; theme = { - name = "Kanagawa-BL-LB"; + name = repoTheme.kanagawa.gtkThemeName; package = pkgs.kanagawa-gtk-theme.overrideAttrs (_: kanagawaOverride); }; gtk4.theme = { inherit (config.gtk.theme) name package; }; iconTheme = { - name = "Kanagawa"; + name = repoTheme.kanagawa.iconThemeName; package = pkgs.kanagawa-icon-theme.overrideAttrs (_: kanagawaOverride); }; }; diff --git a/modules/features/workstation-base.nix b/modules/features/workstation-base.nix index 7420d4d..58f413b 100644 --- a/modules/features/workstation-base.nix +++ b/modules/features/workstation-base.nix @@ -33,15 +33,17 @@ in flake.modules.homeManager.workstation-base = { imports = [ homeModules.ai + homeModules.bitwarden homeModules.clipboard homeModules.dev-tools + homeModules.email homeModules.local-apps homeModules.mpv homeModules.neovim homeModules.nh homeModules.niri homeModules.nix - homeModules.personal-productivity + homeModules.pim homeModules.podman homeModules.shell homeModules.sops diff --git a/modules/flake-parts.nix b/modules/flake-parts.nix index 0de2a00..c3a4732 100644 --- a/modules/flake-parts.nix +++ b/modules/flake-parts.nix @@ -4,6 +4,11 @@ ... }: let + hostNames = [ + "orion" + "polaris" + "zenith" + ]; nixosModules = config.flake.modules.nixos; in { @@ -11,22 +16,13 @@ in systems = [ "x86_64-linux" ]; - flake.nixosConfigurations = { - orion = inputs.nixpkgs.lib.nixosSystem { + flake.nixosConfigurations = inputs.nixpkgs.lib.genAttrs hostNames ( + name: + inputs.nixpkgs.lib.nixosSystem { specialArgs = { inherit inputs; }; - modules = [ nixosModules.orion ]; - }; - - polaris = inputs.nixpkgs.lib.nixosSystem { - specialArgs = { inherit inputs; }; - modules = [ nixosModules.polaris ]; - }; - - zenith = inputs.nixpkgs.lib.nixosSystem { - specialArgs = { inherit inputs; }; - modules = [ nixosModules.zenith ]; - }; - }; + modules = [ nixosModules.${name} ]; + } + ); perSystem = { pkgs, ... }: diff --git a/modules/hosts/orion/default.nix b/modules/hosts/orion/default.nix index e8bc5f1..352d9a1 100644 --- a/modules/hosts/orion/default.nix +++ b/modules/hosts/orion/default.nix @@ -17,22 +17,17 @@ in ... }: let - terminalPackage = metaLib.resolvePackagePath { + terminal = metaLib.resolveUserTerminal { inherit pkgs; - path = config.meta.host.users.kiri.terminalPackagePath; + user = config.meta.host.users.kiri; }; in { - assertions = [ - { - assertion = terminalPackage != null; - message = "Unknown terminal package `${lib.showAttrPath config.meta.host.users.kiri.terminalPackagePath}` for user `kiri`."; - } - { - assertion = terminalPackage != null && lib.elem "terminfo" terminalPackage.outputs; - message = "Terminal package `${lib.showAttrPath config.meta.host.users.kiri.terminalPackagePath}` must provide a `terminfo` output for `orion`."; - } - ]; + assertions = metaLib.mkTerminalAssertions { + inherit terminal; + user = config.meta.host.users.kiri; + requireTerminfo = true; + }; users.users.kiri = { linger = true; @@ -43,15 +38,20 @@ in environment.systemPackages = [ ] - ++ lib.optional (terminalPackage != null && lib.elem "terminfo" terminalPackage.outputs) ( - lib.getOutput "terminfo" terminalPackage - ); + ++ lib.optional terminal.hasTerminfo (lib.getOutput "terminfo" terminal.package); }; flake.modules.nixos.orion = metaLib.mkHost { name = "orion"; users = { - inherit (metaLib.users) kiri; + kiri = { + account = metaLib.users.kiri; + homeImports = [ + homeModules.shell + homeModules.git + homeModules.syncthing + ]; + }; }; imports = [ @@ -66,15 +66,6 @@ in nixosModules.radicale nixosModules.actual nixosModules.gitea - (metaLib.mkHostUser { - account = metaLib.users.kiri; - needsPassword = false; - homeImports = [ - homeModules.shell - homeModules.git - homeModules.syncthing - ]; - }) ./_hardware.nix ./_disk.nix ]; diff --git a/modules/hosts/polaris/default.nix b/modules/hosts/polaris/default.nix index 7b7acd1..b7ea0ac 100644 --- a/modules/hosts/polaris/default.nix +++ b/modules/hosts/polaris/default.nix @@ -39,31 +39,30 @@ in }; users = { - inherit (metaLib.users) - ergon - kiri - ; + kiri = { + account = metaLib.users.kiri; + needsPassword = true; + homeImports = [ + homeModules.workstation-base + homeModules.syncthing + homeModules.qbittorrent-client + homeModules.noctalia + ]; + }; + + ergon = { + account = metaLib.users.ergon; + needsPassword = true; + homeImports = [ + homeModules.workstation-base + homeModules.noctalia + ]; + }; }; imports = [ nixosModules.workstation-base nixosModules.steam - (metaLib.mkHostUser { - account = metaLib.users.kiri; - needsPassword = true; - homeImports = [ - homeModules.kiri-workstation - homeModules.noctalia - ]; - }) - (metaLib.mkHostUser { - account = metaLib.users.ergon; - needsPassword = true; - homeImports = [ - homeModules.ergon-workstation - homeModules.noctalia - ]; - }) ./_hardware.nix ] ++ (with inputs.nixos-hardware.nixosModules; [ diff --git a/modules/hosts/zenith/default.nix b/modules/hosts/zenith/default.nix index 59bbd5e..ee7177c 100644 --- a/modules/hosts/zenith/default.nix +++ b/modules/hosts/zenith/default.nix @@ -40,31 +40,30 @@ in }; users = { - inherit (metaLib.users) - ergon - kiri - ; + kiri = { + account = metaLib.users.kiri; + needsPassword = true; + homeImports = [ + homeModules.workstation-base + homeModules.syncthing + homeModules.qbittorrent-client + homeModules.noctalia-portable + ]; + }; + + ergon = { + account = metaLib.users.ergon; + needsPassword = true; + homeImports = [ + homeModules.workstation-base + homeModules.noctalia-portable + ]; + }; }; imports = [ nixosModules.workstation-base nixosModules.laptop-power - (metaLib.mkHostUser { - account = metaLib.users.kiri; - needsPassword = true; - homeImports = [ - homeModules.kiri-workstation - homeModules.noctalia-portable - ]; - }) - (metaLib.mkHostUser { - account = metaLib.users.ergon; - needsPassword = true; - homeImports = [ - homeModules.ergon-workstation - homeModules.noctalia-portable - ]; - }) { hardware.enableRedistributableFirmware = true; services.fwupd.enable = true; diff --git a/modules/lib.nix b/modules/lib.nix index 9db522d..d2d3d96 100644 --- a/modules/lib.nix +++ b/modules/lib.nix @@ -15,20 +15,84 @@ let stateVersion ? "24.05", }: { + config, + pkgs, + ... + }: + let + hostUsers = lib.mapAttrs (_: spec: spec.account) users; + + 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 + ); + + passwordUserSpecs = lib.filterAttrs (_: spec: spec.needsPassword or false) users; + in + { + assertions = userAssertions; + meta.host = { inherit displays input name sourceControl - users ; + users = hostUsers; }; inherit imports; networking.hostName = name; system.stateVersion = stateVersion; + + programs.zsh.enable = true; + + sops.secrets = lib.mapAttrs' ( + userName: _: + lib.nameValuePair "hashed-password-${userName}" { + neededForUsers = true; + } + ) passwordUserSpecs; + + users.users = lib.mapAttrs ( + userName: spec: + { + name = spec.account.name; + home = spec.account.homeDirectory; + isNormalUser = true; + shell = pkgs.zsh; + extraGroups = [ + "wheel" + "networkmanager" + ]; + } + // lib.optionalAttrs (spec.needsPassword or false) { + hashedPasswordFile = config.sops.secrets."hashed-password-${userName}".path; + } + ) 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 = spec.stateVersion or stateVersion; + }; + }) users; }; mkCaddyReverseProxy = @@ -65,62 +129,259 @@ let }: lib.attrByPath path null pkgs; - mkHostUser = + repo = { + contact.email = "mail@jelles.net"; + + services = { + actual = { + domain = "finance.jelles.net"; + host = "127.0.0.1"; + port = 3000; + url = "https://finance.jelles.net"; + }; + + gitea = { + domain = "git.jelles.net"; + host = "127.0.0.1"; + port = 3001; + url = "https://git.jelles.net/"; + }; + + radicale = { + domain = "radicale.jelles.net"; + host = "127.0.0.1"; + port = 5232; + url = "https://radicale.jelles.net/"; + }; + + vaultwarden = { + domain = "vault.jelles.net"; + host = "127.0.0.1"; + port = 8100; + url = "https://vault.jelles.net"; + }; + }; + + theme = { + cursor = { + name = "phinger-cursors-light"; + packagePath = [ "phinger-cursors" ]; + size = 24; + }; + + kanagawa = { + gtkThemeName = "Kanagawa-BL-LB"; + iconThemeName = "Kanagawa"; + owner = "Fausto-Korpsvart"; + repo = "Kanagawa-GKT-Theme"; + rev = "55ca4ba249eba21f861b9866b71ab41bb8930318"; + hash = "sha256-UdMoMx2DoovcxSp/zBZ3PRv/Qpj+prd0uPm1gmdak2E="; + version = "unstable-2025-10-23"; + }; + }; + }; + + resolveUserTerminal = { - account, - homeImports, - needsPassword ? false, - stateVersion ? "24.05", - }: - { - config, pkgs, - ... + user, }: let - name = account.name; - primaryEmails = lib.filter (email: email.primary) (builtins.attrValues account.emails); + package = resolvePackagePath { + inherit pkgs; + path = user.terminalPackagePath; + }; + 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 { - assertions = [ + inherit + desktopId + hasMainProgram + hasPackage + mainProgram + package + ; + + hasDesktopEntry = + desktopId != null && builtins.pathExists "${package}/share/applications/${desktopId}"; + + hasTerminfo = hasPackage && lib.elem "terminfo" package.outputs; + }; + + mkTerminalAssertions = + { + terminal, + user, + requireDesktopEntry ? false, + requireKitty ? false, + requireMainProgram ? true, + requireTerminfo ? false, + }: + lib.flatten [ + [ { - assertion = builtins.length primaryEmails == 1; - message = "User ${name} must define exactly one primary email entry."; + assertion = terminal.hasPackage; + message = "Unknown terminal package `${lib.showAttrPath user.terminalPackagePath}` for user `${user.name}`."; } - ]; + ] + (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."; + }) + (lib.optional requireTerminfo { + assertion = terminal.hasTerminfo; + message = "Terminal package `${lib.showAttrPath user.terminalPackagePath}` must provide a `terminfo` output."; + }) + ]; - programs.zsh.enable = true; + mapConfiguredAttrs = + { + settings, + schema, + }: + lib.filterAttrs (_: value: value != null) ( + lib.mapAttrs ( + _: spec: + let + value = lib.getAttrFromPath spec.path settings; + in + if value == null then + null + else if spec ? values then + spec.values.${value} + else if spec ? transform then + spec.transform value + else + value + ) schema + ); - sops.secrets = lib.optionalAttrs needsPassword { - "hashed-password-${name}".neededForUsers = true; + mkInputProfiles = + input: + let + scrollMethodValues = { + edge = { + libinput = "edge"; + niri = "edge"; + }; + "no-scroll" = { + libinput = "none"; + niri = "no-scroll"; + }; + "on-button-down" = { + libinput = "button"; + niri = "on-button-down"; + }; + "two-finger" = { + libinput = "twofinger"; + niri = "two-finger"; + }; }; - users.users.${name} = { - name = account.name; - home = account.homeDirectory; - isNormalUser = true; - shell = pkgs.zsh; - extraGroups = [ - "wheel" - "networkmanager" - ]; - } - // lib.optionalAttrs needsPassword { - hashedPasswordFile = config.sops.secrets."hashed-password-${name}".path; + mouse = input.mouse; + touchpad = input.touchpad; + + libinputMouse = mapConfiguredAttrs { + settings = mouse; + schema = { + accelProfile.path = [ "accelProfile" ]; + accelSpeed = { + path = [ "accelSpeed" ]; + transform = toString; + }; + leftHanded.path = [ "leftHanded" ]; + middleEmulation.path = [ "middleEmulation" ]; + naturalScrolling.path = [ "naturalScrolling" ]; + scrollMethod = { + path = [ "scrollMethod" ]; + values = lib.mapAttrs (_: value: value.libinput) scrollMethodValues; + }; + }; }; - home-manager.users.${name} = { - imports = homeImports; - meta = { - host = config.meta.host; - user = account; + libinputTouchpad = mapConfiguredAttrs { + settings = touchpad; + schema = { + accelProfile.path = [ "accelProfile" ]; + accelSpeed = { + path = [ "accelSpeed" ]; + transform = toString; + }; + clickMethod = { + path = [ "clickMethod" ]; + values = { + "button-areas" = "buttonareas"; + clickfinger = "clickfinger"; + }; + }; + disableWhileTyping.path = [ "disableWhileTyping" ]; + leftHanded.path = [ "leftHanded" ]; + middleEmulation.path = [ "middleEmulation" ]; + naturalScrolling.path = [ "naturalScrolling" ]; + scrollMethod = { + path = [ "scrollMethod" ]; + values = lib.mapAttrs (_: value: value.libinput) scrollMethodValues; + }; + tapping.path = [ "tapping" ]; }; - home = { - username = account.name; - homeDirectory = account.homeDirectory; - inherit stateVersion; + }; + + niriMouse = mapConfiguredAttrs { + settings = mouse; + schema = { + "accel-profile".path = [ "accelProfile" ]; + "accel-speed".path = [ "accelSpeed" ]; + "left-handed".path = [ "leftHanded" ]; + "middle-emulation".path = [ "middleEmulation" ]; + "natural-scroll".path = [ "naturalScrolling" ]; + "scroll-method" = { + path = [ "scrollMethod" ]; + values = lib.mapAttrs (_: value: value.niri) scrollMethodValues; + }; }; }; + + niriTouchpad = mapConfiguredAttrs { + settings = touchpad; + schema = { + "accel-profile".path = [ "accelProfile" ]; + "accel-speed".path = [ "accelSpeed" ]; + "click-method".path = [ "clickMethod" ]; + dwt.path = [ "disableWhileTyping" ]; + "left-handed".path = [ "leftHanded" ]; + "middle-emulation".path = [ "middleEmulation" ]; + "natural-scroll".path = [ "naturalScrolling" ]; + "scroll-method" = { + path = [ "scrollMethod" ]; + values = lib.mapAttrs (_: value: value.niri) scrollMethodValues; + }; + tap.path = [ "tapping" ]; + }; + }; + in + { + hasPointerConfig = libinputMouse != { } || libinputTouchpad != { }; + + libinput = { + mouse = libinputMouse; + touchpad = libinputTouchpad; + }; + + niri = { + mouse = niriMouse; + touchpad = niriTouchpad; + }; }; in { @@ -138,16 +399,30 @@ in readOnly = true; }; - options.meta.lib.mkHostUser = lib.mkOption { + options.meta.lib.resolvePackagePath = lib.mkOption { type = lib.types.raw; - description = "Internal helper for explicit per-host user assembly."; + description = "Internal helper to resolve package attr paths against the local pkgs set."; internal = true; readOnly = true; }; - options.meta.lib.resolvePackagePath = lib.mkOption { + options.meta.lib.resolveUserTerminal = lib.mkOption { type = lib.types.raw; - description = "Internal helper to resolve package attr paths against the local pkgs set."; + description = "Internal helper to resolve and validate user terminal metadata."; + internal = true; + readOnly = true; + }; + + options.meta.lib.mkTerminalAssertions = lib.mkOption { + type = lib.types.raw; + description = "Internal helper for terminal-related assertions shared across modules."; + internal = true; + readOnly = true; + }; + + options.meta.lib.mkInputProfiles = lib.mkOption { + type = lib.types.raw; + description = "Internal helper to normalize host input metadata for libinput and Niri."; internal = true; readOnly = true; }; @@ -159,12 +434,22 @@ in readOnly = true; }; + options.meta.lib.repo = lib.mkOption { + type = lib.types.attrs; + description = "Internal shared repository metadata."; + internal = true; + readOnly = true; + }; + config.meta.lib = { inherit + mkInputProfiles mkCaddyReverseProxy + mkTerminalAssertions mkHost - mkHostUser + repo resolvePackagePath + resolveUserTerminal ; }; }