From 55fbe82a425114c79aa7ba018a4e561a45a8c9df Mon Sep 17 00:00:00 2001 From: Jelle Spreeuwenberg Date: Fri, 24 Apr 2026 21:57:43 +0200 Subject: [PATCH] refactor: orion and base --- modules/data.nix | 152 +------------ modules/features/meta.nix | 386 -------------------------------- modules/flake-parts.nix | 14 +- modules/hosts/orion/default.nix | 80 +++---- modules/lib.nix | 374 ------------------------------- modules/lib/helpers.nix | 72 ++++++ modules/lib/schema.nix | 201 +++++++++++++++++ 7 files changed, 307 insertions(+), 972 deletions(-) delete mode 100644 modules/features/meta.nix delete mode 100644 modules/lib.nix create mode 100644 modules/lib/helpers.nix create mode 100644 modules/lib/schema.nix diff --git a/modules/data.nix b/modules/data.nix index 50f96f1..40f1f86 100644 --- a/modules/data.nix +++ b/modules/data.nix @@ -1,194 +1,46 @@ { lib, ... }: let - userSpecs = { + accounts = { kiri = { realName = "Jelle Spreeuwenberg"; - homeDirectory = "/home/kiri"; emails = { personal = { address = "mail@jelles.net"; primary = true; - scope = "personal"; type = "mxrouting"; }; old = { address = "mail@jellespreeuwenberg.nl"; - scope = null; type = "mxrouting"; }; uni = { address = "j.spreeuwenberg@student.tue.nl"; - scope = null; type = "office365"; }; work = { address = "jelle.spreeuwenberg@yookr.org"; - scope = "work"; type = "office365"; }; }; - sourceControl = { }; }; ergon = { realName = "Jelle Spreeuwenberg"; - homeDirectory = "/home/ergon"; emails = { personal = { address = "mail@jelles.net"; - scope = "personal"; type = "mxrouting"; }; work = { address = "jelle.spreeuwenberg@yookr.org"; primary = true; - scope = "work"; type = "office365"; }; }; - sourceControl.projectScope = "work"; }; }; - accounts = lib.mapAttrs (name: spec: spec // { inherit name; }) userSpecs; - - repo = { - contact.email = "mail@jelles.net"; - - desktop = { - browser = { - command = "vivaldi"; - packagePath = [ "vivaldi" ]; - }; - - fileManager = { - command = "nautilus"; - packagePath = [ "nautilus" ]; - }; - - terminal = { - command = "kitty"; - desktopId = "kitty.desktop"; - packagePath = [ "kitty" ]; - }; - }; - - 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 = { - displayName = "Kanagawa Wave"; - name = "kanagawa-wave"; - 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"; - - 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"; - }; - }; - }; - }; - }; in { - options.meta.lib.repo = lib.mkOption { - type = lib.types.attrs; - description = "Internal shared repository metadata."; - internal = true; - readOnly = true; - }; - - options.meta.lib.accounts = lib.mkOption { - type = lib.types.attrs; - description = "Canonical account attrsets shared by host definitions."; - internal = true; - readOnly = true; - }; - - config.meta.lib.repo = repo; - config.meta.lib.accounts = accounts; + repo.accounts = accounts; } diff --git a/modules/features/meta.nix b/modules/features/meta.nix deleted file mode 100644 index 43e4f08..0000000 --- a/modules/features/meta.nix +++ /dev/null @@ -1,386 +0,0 @@ -{ lib, ... }: -let - nonEmptyStrType = lib.types.addCheck lib.types.str (value: lib.stringLength value > 0); - - mkNullableOption = - type: - lib.mkOption { - type = lib.types.nullOr type; - default = null; - }; - - 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; - - 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; - scope = null; - type = "mxrouting"; - }; - - emailProviderType = lib.types.enum [ - "mxrouting" - "office365" - ]; - - 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 ( - { ... }: - { - options = { - address = lib.mkOption { - type = lib.types.str; - }; - - primary = lib.mkOption { - type = lib.types.bool; - default = false; - }; - - type = lib.mkOption { - type = emailProviderType; - }; - - scope = mkNullableOption sourceControlScopeType; - }; - } - ); - - sourceControlHostKeyType = lib.types.submodule ( - { ... }: - { - options = { - publicKey = lib.mkOption { - type = lib.types.nullOr nonEmptyStrType; - default = null; - }; - - privateKeyPath = mkNullableOption nonEmptyStrType; - }; - } - ); - - sourceControlHostUserType = lib.types.submodule ( - { ... }: - { - options = { - personal = mkNullableOption sourceControlHostKeyType; - work = mkNullableOption sourceControlHostKeyType; - }; - } - ); - - hostSourceControlType = lib.types.submodule ( - { ... }: - { - options.users = lib.mkOption { - type = lib.types.attrsOf sourceControlHostUserType; - default = { }; - }; - } - ); - - sourceControlType = lib.types.submodule ( - { ... }: - { - options = { - projectScope = lib.mkOption { - 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."; - }; - }; - } - ); - - 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 { - type = lib.types.str; - }; - - realName = lib.mkOption { - type = lib.types.str; - }; - - homeDirectory = lib.mkOption { - type = lib.types.str; - }; - - nixosConfigurationPath = lib.mkOption { - type = lib.types.str; - default = "${config.homeDirectory}/.config/nixos"; - }; - - emails = lib.mkOption { - 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; - sourceControl.scopes = lib.genAttrs (requiredSourceControlScopes config) (scope: { - email = scopeEmailAddress scope config; - }); - }; - } - ); - - displayModeType = lib.types.submodule ( - { ... }: - { - options = { - width = lib.mkOption { - type = lib.types.int; - }; - - height = lib.mkOption { - type = lib.types.int; - }; - - refresh = lib.mkOption { - type = lib.types.float; - }; - }; - } - ); - - displayType = lib.types.submodule ( - { ... }: - { - options = { - primary = lib.mkOption { - type = lib.types.bool; - default = false; - }; - - x = lib.mkOption { - type = lib.types.int; - }; - - y = lib.mkOption { - type = lib.types.int; - }; - - scale = lib.mkOption { - type = lib.types.nullOr lib.types.float; - default = null; - }; - - mode = lib.mkOption { - type = lib.types.nullOr displayModeType; - default = null; - }; - }; - } - ); - - mouseInputType = lib.types.submodule ( - { ... }: - { - options = { - 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; - }; - } - ); - - touchpadInputType = lib.types.submodule ( - { ... }: - { - options = { - 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; - }; - } - ); - - inputType = lib.types.submodule ( - { ... }: - { - options = { - mouse = lib.mkOption { - type = mouseInputType; - default = { }; - }; - - touchpad = lib.mkOption { - type = touchpadInputType; - default = { }; - }; - }; - } - ); - - hostType = lib.types.submodule ( - { ... }: - { - options = { - name = lib.mkOption { - type = lib.types.str; - }; - - displays = lib.mkOption { - type = lib.types.attrsOf displayType; - default = { }; - }; - - input = lib.mkOption { - type = inputType; - default = { }; - }; - - users = lib.mkOption { - type = lib.types.attrsOf userType; - default = { }; - }; - - sourceControl = lib.mkOption { - type = hostSourceControlType; - default = { }; - }; - }; - } - ); - - mkUserEmailAssertions = - userName: user: - [ - { - assertion = hasSinglePrimaryEmail user; - message = "User `${userName}` must define exactly one primary email entry."; - } - ] - ++ (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); -in -{ - flake.modules.nixos.meta = - { config, ... }: - { - options.meta.host = lib.mkOption { - type = hostType; - }; - - config.assertions = lib.flatten (lib.mapAttrsToList mkUserEmailAssertions 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.optionals (config.meta.user != null) ( - mkUserEmailAssertions config.meta.user.name config.meta.user - ); - }; -} diff --git a/modules/flake-parts.nix b/modules/flake-parts.nix index eb6092b..e9f96ca 100644 --- a/modules/flake-parts.nix +++ b/modules/flake-parts.nix @@ -3,12 +3,6 @@ config, ... }: -let - hostNames = builtins.attrNames ( - inputs.nixpkgs.lib.filterAttrs (_: type: type == "directory") (builtins.readDir ./hosts) - ); - nixosModules = config.flake.modules.nixos; -in { imports = [ inputs.flake-parts.flakeModules.modules @@ -17,13 +11,13 @@ in systems = [ "x86_64-linux" ]; - flake.nixosConfigurations = inputs.nixpkgs.lib.genAttrs hostNames ( - name: + flake.nixosConfigurations = builtins.mapAttrs ( + _: machine: inputs.nixpkgs.lib.nixosSystem { specialArgs = { inherit inputs; }; - modules = [ nixosModules.${name} ]; + modules = [ (machine.buildFunction machine) ]; } - ); + ) config.repo.machines; perSystem = { pkgs, ... }: diff --git a/modules/hosts/orion/default.nix b/modules/hosts/orion/default.nix index 4dc647c..e5d0b69 100644 --- a/modules/hosts/orion/default.nix +++ b/modules/hosts/orion/default.nix @@ -1,31 +1,41 @@ { inputs, config, + lib, ... }: let nixosModules = config.flake.modules.nixos; - homeModules = config.flake.modules.homeManager; - metaLib = config.meta.lib; + hmModules = config.flake.modules.homeManager; in { - flake.modules.nixos.orion-admin = + repo.machines.orion = { + buildFunction = config.repo.helpers.mkHost; + module = nixosModules.orion; + + accounts = lib.getAttrs [ "kiri" ] config.repo.accounts; + + stateVersion = "24.05"; + hmStateVersion = "24.05"; + }; + + flake.modules.nixos.orion = + { ... }: { - config, - lib, - pkgs, - ... - }: - let - terminal = metaLib.resolveRepoTerminal { - inherit pkgs; - }; - in - { - assertions = metaLib.mkTerminalAssertions { - inherit terminal; - requireTerminfo = true; - }; + imports = [ + nixosModules.host-base + nixosModules.sops-host-ssh-key + nixosModules.openssh + nixosModules.caddy + nixosModules.server-firewall + nixosModules.ssh-agent-auth + nixosModules.vaultwarden + nixosModules.radicale + nixosModules.actual + nixosModules.gitea + ./_hardware.nix + ./_disk.nix + ]; users.users.kiri = { linger = true; @@ -33,39 +43,5 @@ in "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAU2LydkXRTtNFY7oyX8JQURwXLVhB71DeK8XzrXeFX1 openpgp:0xA490D93A" ]; }; - - environment.systemPackages = [ - ] - ++ lib.optional terminal.hasTerminfo (lib.getOutput "terminfo" terminal.package); }; - - flake.modules.nixos.orion = metaLib.mkHost { - name = "orion"; - users = { - kiri = { - account = metaLib.accounts.kiri; - homeImports = [ - homeModules.shell - homeModules.git - homeModules.syncthing - ]; - }; - }; - - imports = [ - nixosModules.host-base - nixosModules.sops-host-ssh-key - nixosModules.openssh - nixosModules.caddy - nixosModules.server-firewall - nixosModules.ssh-agent-auth - nixosModules.orion-admin - nixosModules.vaultwarden - nixosModules.radicale - nixosModules.actual - nixosModules.gitea - ./_hardware.nix - ./_disk.nix - ]; - }; } diff --git a/modules/lib.nix b/modules/lib.nix deleted file mode 100644 index f69efa4..0000000 --- a/modules/lib.nix +++ /dev/null @@ -1,374 +0,0 @@ -{ - config, - lib, - ... -}: -let - normalizeHostUser = - spec: - { - homeImports = [ ]; - needsPassword = false; - stateVersion = null; - } - // spec; - - mkHost = - { - name, - displays ? { }, - input ? { }, - sourceControl ? { }, - users ? { }, - imports ? [ ], - stateVersion ? "24.05", - }: - { - config, - pkgs, - ... - }: - let - hostUserSpecs = lib.mapAttrs (_: spec: normalizeHostUser spec) users; - hostUsers = lib.mapAttrs (_: spec: spec.account) hostUserSpecs; - passwordUserSpecs = lib.filterAttrs (_: spec: spec.needsPassword) hostUserSpecs; - in - { - meta.host = { - inherit - displays - input - name - sourceControl - ; - 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 = userName; - home = spec.account.homeDirectory; - isNormalUser = true; - shell = pkgs.zsh; - extraGroups = [ - "wheel" - "networkmanager" - ]; - } - // lib.optionalAttrs spec.needsPassword { - hashedPasswordFile = config.sops.secrets."hashed-password-${userName}".path; - } - ) 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 = if spec.stateVersion == null then stateVersion else spec.stateVersion; - }; - }) hostUserSpecs; - }; - - mkCaddyReverseProxy = - { - domain, - port, - extraHeaders ? [ ], - extraConfigText ? "", - }: - let - headerLines = map (header: " header_up ${header.name} ${header.value}") extraHeaders; - extraConfigLines = map (line: " ${line}") ( - lib.filter (line: line != "") (lib.splitString "\n" extraConfigText) - ); - bodyLines = headerLines ++ extraConfigLines; - body = lib.concatStringsSep "\n" bodyLines; - in - { - services.caddy.virtualHosts.${domain}.extraConfig = - if body == "" then - "reverse_proxy :${toString port}" - else - '' - reverse_proxy :${toString port} { - ${body} - } - ''; - }; - - resolvePackagePath = - { - pkgs, - path, - }: - lib.attrByPath path null pkgs; - - resolveRepoTerminal = - { - pkgs, - }: - let - terminal = config.meta.lib.repo.desktop.terminal; - package = resolvePackagePath { - inherit pkgs; - path = terminal.packagePath; - }; - in - { - inherit - package - ; - inherit (terminal) - command - desktopId - packagePath - ; - - hasPackage = package != null; - - hasDesktopEntry = - package != null && builtins.pathExists "${package}/share/applications/${terminal.desktopId}"; - - hasTerminfo = package != null && lib.elem "terminfo" package.outputs; - }; - - mkTerminalAssertions = - { - terminal, - requireDesktopEntry ? false, - requireTerminfo ? false, - }: - lib.flatten [ - [ - { - assertion = terminal.hasPackage; - 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 requireDesktopEntry { - assertion = terminal.hasDesktopEntry; - message = "Terminal package `${lib.showAttrPath terminal.packagePath}` must provide `${terminal.desktopId}`."; - }) - (lib.optional requireTerminfo { - assertion = terminal.hasTerminfo; - message = "Terminal package `${lib.showAttrPath terminal.packagePath}` must provide a `terminfo` output."; - }) - ]; - - 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 - ); - - 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"; - }; - }; - - 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; - }; - }; - }; - - 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" ]; - }; - }; - - 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 -{ - options.meta.lib.mkHost = lib.mkOption { - type = lib.types.raw; - description = "Internal host 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."; - internal = true; - readOnly = true; - }; - - options.meta.lib.resolvePackagePath = lib.mkOption { - type = lib.types.raw; - description = "Internal helper to resolve package attr paths against the local pkgs set."; - internal = true; - readOnly = true; - }; - - options.meta.lib.resolveRepoTerminal = lib.mkOption { - type = lib.types.raw; - description = "Internal helper to resolve and validate the repo-standard terminal."; - 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; - }; - - config.meta.lib = { - inherit - mkInputProfiles - mkCaddyReverseProxy - mkTerminalAssertions - mkHost - resolvePackagePath - resolveRepoTerminal - ; - }; -} diff --git a/modules/lib/helpers.nix b/modules/lib/helpers.nix new file mode 100644 index 0000000..1ef3157 --- /dev/null +++ b/modules/lib/helpers.nix @@ -0,0 +1,72 @@ +{ lib, config, ... }: +let + nixosModules = config.flake.modules.nixos; + hmModules = config.flake.modules.homeManager; + + mkHost = + machine: + { config, pkgs, ... }: + { + imports = [ + nixosModules.host-base + nixosModules.meta + machine.module + ]; + meta.machine = machine; + + networking.hostName = machine.name; + system.stateVersion = machine.stateVersion; + + # TODO: Move this + programs.zsh.enable = true; + + users.users = lib.mapAttrs ( + _: account: { + inherit (account) name extraGroups; + isNormalUser = true; + home = account.homeDirectory; + # TODO: Move this + shell = pkgs.zsh; + } + ); + + home-manager.users = lib.mapAttrs ( + _: account: { + imports = [ + hmModules.meta + account.baseModule + ]; + meta = { + inherit machine account; + }; + home.homeDirectory = account.homeDirectory; + home.stateVersion = machine.hmStateVersion; + } + ); + }; + + mkWorkstationHost = + machine: + { ... }: + { + imports = [ + (mkHost machine) + nixosModules.workstation-base + ]; + + home-manager.users = lib.mapAttrs ( + _: account: { + imports = [ account.workstationModule ]; + } + ); + }; +in +{ + options.repo.helpers = lib.mkOption { + type = lib.types.attrsOf lib.types.raw; + internal = true; + readOnly = true; + }; + + config.repo.helpers = { inherit mkHost mkWorkstationHost; }; +} diff --git a/modules/lib/schema.nix b/modules/lib/schema.nix new file mode 100644 index 0000000..f030ba9 --- /dev/null +++ b/modules/lib/schema.nix @@ -0,0 +1,201 @@ +{ lib, ... }: +let + # Account types + emailProviderType = lib.types.enum [ + "mxrouting" + "office365" + ]; + + emailType = lib.types.submodule ( + { ... }: + { + options = { + address = lib.mkOption { + type = lib.types.str; + }; + + primary = lib.mkOption { + type = lib.types.bool; + default = false; + }; + + type = lib.mkOption { + type = emailProviderType; + }; + }; + } + ); + + accountType = lib.types.submodule ( + { name, config, ... }: + { + options = { + name = lib.mkOption { + type = lib.types.str; + default = name; + readOnly = true; + }; + + realName = lib.mkOption { + type = lib.types.str; + }; + + homeDirectory = lib.mkOption { + type = lib.types.str; + default = "/home/${config.name}"; + }; + + nixosConfigurationPath = lib.mkOption { + type = lib.types.str; + default = "${config.homeDirectory}/.config/nixos"; + }; + + emails = lib.mkOption { + type = lib.types.attrsOf emailType; + default = { }; + }; + + extraGroups = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + + baseModule = lib.mkOption { + type = lib.types.deferredModule; + default = { }; + }; + + workstationModule = lib.mkOption { + type = lib.types.deferredModule; + default = { }; + }; + + primaryEmail = lib.mkOption { + type = lib.types.nullOr emailType; + readOnly = true; + description = "Derived primary email entry for this user."; + default = + let + emails = builtins.attrValues config.emails; + in + lib.findFirst (email: email.primary) null emails; + }; + }; + } + ); + + # Machine types + displayType = lib.types.submodule ( + { ... }: + { + options = { + primary = lib.mkOption { + type = lib.types.bool; + default = false; + }; + + x = lib.mkOption { + type = lib.types.int; + default = 0; + }; + + y = lib.mkOption { + type = lib.types.int; + default = 0; + }; + + scale = lib.mkOption { + type = lib.types.nullOr lib.types.float; + default = 1.0; + }; + + width = lib.mkOption { + type = lib.types.int; + }; + + height = lib.mkOption { + type = lib.types.int; + }; + + refresh = lib.mkOption { + type = lib.types.float; + }; + }; + } + ); + + machineType = lib.types.submodule ( + { name, config, ... }: + { + options = { + name = lib.mkOption { + type = lib.types.str; + default = name; + readOnly = true; + }; + + module = lib.mkOption { + type = lib.types.deferredModule; + default = { }; + }; + + buildFunction = lib.mkOption { + type = lib.types.functionTo lib.types.deferredModule; + }; + + stateVersion = lib.mkOption { + type = lib.types.str; + }; + + hmStateVersion = lib.mkOption { + type = lib.types.str; + default = config.stateVersion; + }; + + displays = lib.mkOption { + type = lib.types.attrsOf displayType; + default = { }; + }; + + accounts = lib.mkOption { + type = lib.types.attrsOf accountType; + default = { }; + }; + }; + } + ); +in +{ + options.repo = { + accounts = lib.mkOption { + type = lib.types.attrsOf accountType; + default = { }; + }; + machines = lib.mkOption { + type = lib.types.attrsOf machineType; + default = { }; + }; + }; + + config.flake.modules.nixos.meta = + { config, ... }: + { + options.meta.machine = lib.mkOption { + type = machineType; + }; + }; + + config.flake.modules.homeManager.meta = + { config, ... }: + { + options.meta = { + machine = lib.mkOption { + type = machineType; + }; + + account = lib.mkOption { + type = accountType; + }; + }; + }; +}