Files
lux/modules/lib.nix
T

375 lines
9.6 KiB
Nix

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