{ 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; userAssertions = lib.mapAttrsToList (userName: spec: { assertion = userName == spec.account.name; message = "Host `${name}` declares user `${userName}` with mismatched account name `${spec.account.name}`."; }) hostUserSpecs; passwordUserSpecs = lib.filterAttrs (_: spec: spec.needsPassword) hostUserSpecs; in { assertions = userAssertions; meta.host = { inherit displays 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 = spec.account.name; 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; resolveUserTerminal = { pkgs, user, }: let 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 { 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 = 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."; }) ]; 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.resolveUserTerminal = lib.mkOption { type = lib.types.raw; 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; }; config.meta.lib = { inherit mkInputProfiles mkCaddyReverseProxy mkTerminalAssertions mkHost resolvePackagePath resolveUserTerminal ; }; }