{ 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 ; }; }