refactor: simplify module composition

This commit is contained in:
2026-04-22 02:35:26 +02:00
parent 3b6c42ebe3
commit 5eec5689f4
25 changed files with 615 additions and 448 deletions
+9 -5
View File
@@ -1,22 +1,26 @@
{ ... }: {
config,
...
}:
let
metaLib = config.meta.lib;
in
{ {
flake.modules.homeManager.bitwarden = flake.modules.homeManager.bitwarden =
{ {
config, config,
lib,
pkgs, pkgs,
... ...
}: }:
let let
user = config.meta.user; user = config.meta.user;
primaryEmail = builtins.head (lib.filter (email: email.primary) (builtins.attrValues user.emails));
in in
{ {
programs.rbw = { programs.rbw = {
enable = true; enable = true;
settings = { settings = {
base_url = "https://vault.jelles.net"; base_url = metaLib.repo.services.vaultwarden.url;
email = primaryEmail.address; email = user.primaryEmail.address;
pinentry = pkgs.pinentry-gnome3; pinentry = pkgs.pinentry-gnome3;
}; };
}; };
-9
View File
@@ -1,9 +0,0 @@
{ config, ... }:
let
homeModules = config.flake.modules.homeManager;
in
{
flake.modules.homeManager.ergon-workstation = {
imports = [ homeModules.workstation-base ];
};
}
+1 -2
View File
@@ -8,7 +8,6 @@
}: }:
let let
user = config.meta.user; user = config.meta.user;
primaryEmail = builtins.head (lib.filter (email: email.primary) (builtins.attrValues user.emails));
usesScopedIdentity = user != null && user.sourceControl.profiles != { }; usesScopedIdentity = user != null && user.sourceControl.profiles != { };
in in
{ {
@@ -25,7 +24,7 @@
// lib.optionalAttrs (!usesScopedIdentity) { // lib.optionalAttrs (!usesScopedIdentity) {
user = { user = {
name = user.realName; name = user.realName;
email = primaryEmail.address; email = user.primaryEmail.address;
}; };
}; };
}; };
+13 -55
View File
@@ -1,3 +1,10 @@
{
config,
...
}:
let
metaLib = config.meta.lib;
in
{ {
flake.modules.nixos.input = flake.modules.nixos.input =
{ {
@@ -6,64 +13,15 @@
... ...
}: }:
let let
hostInput = config.meta.host.input; inputProfiles = metaLib.mkInputProfiles 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;
};
in in
{ {
services.libinput = lib.mkIf hasPointerConfig { services.libinput = lib.mkIf inputProfiles.hasPointerConfig {
enable = true; enable = true;
mouse = mouseConfig; inherit (inputProfiles.libinput)
touchpad = touchpadConfig; mouse
touchpad
;
}; };
}; };
} }
-13
View File
@@ -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
];
};
}
+76 -61
View File
@@ -3,10 +3,41 @@ let
mkNullableOption = mkNullableOption =
type: type:
lib.mkOption { lib.mkOption {
inherit type; type = lib.types.nullOr type;
default = null; 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 ( emailType = lib.types.submodule (
{ ... }: { ... }:
{ {
@@ -79,10 +110,7 @@ let
}; };
projectScope = lib.mkOption { projectScope = lib.mkOption {
type = lib.types.enum [ type = sourceControlScopeType;
"personal"
"work"
];
default = "personal"; default = "personal";
}; };
}; };
@@ -91,6 +119,10 @@ let
userType = lib.types.submodule ( userType = lib.types.submodule (
{ config, ... }: { config, ... }:
let
primaryEmails = lib.filter (email: email.primary) (builtins.attrValues config.emails);
primaryEmail = if primaryEmails == [ ] then primaryEmailFallback else builtins.head primaryEmails;
in
{ {
options = { options = {
name = lib.mkOption { name = lib.mkOption {
@@ -118,11 +150,19 @@ let
type = lib.types.attrsOf emailType; type = lib.types.attrsOf emailType;
}; };
primaryEmail = lib.mkOption {
type = emailType;
readOnly = true;
description = "Derived primary email entry for this user.";
};
sourceControl = lib.mkOption { sourceControl = lib.mkOption {
type = sourceControlType; type = sourceControlType;
default = { }; default = { };
}; };
}; };
config.primaryEmail = primaryEmail;
} }
); );
@@ -179,28 +219,12 @@ let
{ ... }: { ... }:
{ {
options = { options = {
accelProfile = mkNullableOption ( accelProfile = mkNullableOption (pointerAccelProfileType);
lib.types.nullOr ( accelSpeed = mkNullableOption lib.types.float;
lib.types.enum [ leftHanded = mkNullableOption lib.types.bool;
"adaptive" middleEmulation = mkNullableOption lib.types.bool;
"flat" naturalScrolling = mkNullableOption lib.types.bool;
] scrollMethod = mkNullableOption pointerScrollMethodType;
)
);
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"
]
)
);
}; };
} }
); );
@@ -209,38 +233,15 @@ let
{ ... }: { ... }:
{ {
options = { options = {
accelProfile = mkNullableOption ( accelProfile = mkNullableOption (pointerAccelProfileType);
lib.types.nullOr ( accelSpeed = mkNullableOption lib.types.float;
lib.types.enum [ clickMethod = mkNullableOption touchpadClickMethodType;
"adaptive" disableWhileTyping = mkNullableOption lib.types.bool;
"flat" leftHanded = mkNullableOption lib.types.bool;
] middleEmulation = mkNullableOption lib.types.bool;
) naturalScrolling = mkNullableOption lib.types.bool;
); scrollMethod = mkNullableOption pointerScrollMethodType;
accelSpeed = mkNullableOption (lib.types.nullOr lib.types.float); tapping = mkNullableOption lib.types.bool;
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);
}; };
} }
); );
@@ -294,13 +295,22 @@ let
); );
in in
{ {
flake.modules.nixos.meta = { flake.modules.nixos.meta =
{ config, ... }:
{
options.meta.host = lib.mkOption { options.meta.host = lib.mkOption {
type = hostType; type = hostType;
}; };
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 = { flake.modules.homeManager.meta =
{ config, ... }:
{
options.meta = { options.meta = {
host = lib.mkOption { host = lib.mkOption {
type = lib.types.nullOr hostType; type = lib.types.nullOr hostType;
@@ -312,5 +322,10 @@ in
default = null; 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.";
};
}; };
} }
+13 -42
View File
@@ -30,8 +30,6 @@ in
... ...
}: }:
let let
hostInput = config.meta.host.input;
outputs = lib.mapAttrs ( outputs = lib.mapAttrs (
_: display: _: display:
{ {
@@ -50,44 +48,17 @@ in
inherit (display) mode; inherit (display) mode;
} }
) config.meta.host.displays; ) config.meta.host.displays;
terminalPackage = metaLib.resolvePackagePath { inputProfiles = metaLib.mkInputProfiles config.meta.host.input;
terminal = metaLib.resolveUserTerminal {
inherit pkgs; inherit pkgs;
path = config.meta.user.terminalPackagePath; user = config.meta.user;
};
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;
}; };
in in
{ {
assertions = [ assertions = metaLib.mkTerminalAssertions {
{ inherit terminal;
assertion = terminalPackage != null; user = config.meta.user;
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`.";
}
];
home.sessionVariables.NIXOS_OZONE_WL = "1"; home.sessionVariables.NIXOS_OZONE_WL = "1";
@@ -167,20 +138,20 @@ in
xkb.options = "caps:escape"; xkb.options = "caps:escape";
}; };
} }
// lib.optionalAttrs (mouseSettings != { }) { // lib.optionalAttrs (inputProfiles.niri.mouse != { }) {
mouse = mouseSettings; mouse = inputProfiles.niri.mouse;
} }
// lib.optionalAttrs (touchpadSettings != { }) { // lib.optionalAttrs (inputProfiles.niri.touchpad != { }) {
touchpad = touchpadSettings; touchpad = inputProfiles.niri.touchpad;
}; };
binds = binds =
if hasMainProgram then if terminal.hasMainProgram then
import ./_bindings.nix { import ./_bindings.nix {
inherit inherit
lib lib
terminalPackage
; ;
terminalPackage = terminal.package;
} }
else else
{ }; { };
+13 -21
View File
@@ -38,18 +38,17 @@ in
... ...
}: }:
let let
terminalPackage = metaLib.resolvePackagePath { terminal = metaLib.resolveUserTerminal {
inherit pkgs; inherit pkgs;
path = config.meta.user.terminalPackagePath; user = config.meta.user;
}; };
hasMainProgram = terminalPackage != null && terminalPackage ? meta.mainProgram;
baseSettings = baseSettings =
if hasMainProgram then if terminal.hasMainProgram then
import ./_noctalia-config.nix { import ./_noctalia-config.nix {
inherit inherit
lib lib
terminalPackage
; ;
terminalPackage = terminal.package;
} }
else else
{ }; { };
@@ -57,16 +56,10 @@ in
{ {
imports = [ inputs.noctalia.homeModules.default ]; imports = [ inputs.noctalia.homeModules.default ];
assertions = [ assertions = metaLib.mkTerminalAssertions {
{ inherit terminal;
assertion = terminalPackage != null; user = config.meta.user;
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`.";
}
];
programs.noctalia-shell = { programs.noctalia-shell = {
enable = true; enable = true;
@@ -88,18 +81,17 @@ in
... ...
}: }:
let let
terminalPackage = metaLib.resolvePackagePath { terminal = metaLib.resolveUserTerminal {
inherit pkgs; inherit pkgs;
path = config.meta.user.terminalPackagePath; user = config.meta.user;
}; };
hasMainProgram = terminalPackage != null && terminalPackage ? meta.mainProgram;
baseSettings = baseSettings =
if hasMainProgram then if terminal.hasMainProgram then
import ./_noctalia-config.nix { import ./_noctalia-config.nix {
inherit inherit
lib lib
terminalPackage
; ;
terminalPackage = terminal.package;
} }
else else
{ }; { };
@@ -107,7 +99,7 @@ in
{ {
imports = [ homeModules.noctalia ]; imports = [ homeModules.noctalia ];
programs.noctalia-shell.settings = lib.mkForce ( programs.noctalia-shell.settings = lib.mkForce (
if hasMainProgram then mkPortableSettings baseSettings else { } if terminal.hasMainProgram then mkPortableSettings baseSettings else { }
); );
}; };
} }
@@ -1,13 +0,0 @@
{ config, ... }:
let
homeModules = config.flake.modules.homeManager;
in
{
flake.modules.homeManager.personal-productivity = {
imports = [
homeModules.bitwarden
homeModules.email
homeModules.pim
];
};
}
+8 -1
View File
@@ -1,3 +1,10 @@
{
config,
...
}:
let
metaLib = config.meta.lib;
in
{ {
flake.modules.homeManager.pim = flake.modules.homeManager.pim =
{ {
@@ -44,7 +51,7 @@
}; };
remote = { remote = {
url = "https://radicale.jelles.net/"; url = metaLib.repo.services.radicale.url;
type = "caldav"; type = "caldav";
userName = config.home.username; userName = config.home.username;
passwordCommand = [ passwordCommand = [
+7 -4
View File
@@ -1,6 +1,7 @@
{ config, ... }: { config, ... }:
let let
metaLib = config.meta.lib; metaLib = config.meta.lib;
service = metaLib.repo.services.actual;
in in
{ {
flake.modules.nixos.actual = flake.modules.nixos.actual =
@@ -11,15 +12,17 @@ in
enable = true; enable = true;
openFirewall = false; openFirewall = false;
settings = { settings = {
port = 3000; inherit (service) port;
hostname = "127.0.0.1"; hostname = service.host;
}; };
}; };
} }
(metaLib.mkCaddyReverseProxy { (metaLib.mkCaddyReverseProxy {
domain = "finance.jelles.net"; inherit (service)
port = 3000; domain
port
;
}) })
]; ];
} }
+5 -1
View File
@@ -1,8 +1,12 @@
{ config, ... }:
let
metaLib = config.meta.lib;
in
{ {
flake.modules.nixos.caddy = { flake.modules.nixos.caddy = {
services.caddy = { services.caddy = {
enable = true; enable = true;
email = "mail@jelles.net"; email = metaLib.repo.contact.email;
openFirewall = true; openFirewall = true;
}; };
}; };
-18
View File
@@ -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 ];
};
}
+9 -6
View File
@@ -1,6 +1,7 @@
{ config, ... }: { config, ... }:
let let
metaLib = config.meta.lib; metaLib = config.meta.lib;
service = metaLib.repo.services.gitea;
in in
{ {
flake.modules.nixos.gitea = flake.modules.nixos.gitea =
@@ -12,10 +13,10 @@ in
settings = { settings = {
server = { server = {
DOMAIN = "git.jelles.net"; DOMAIN = service.domain;
ROOT_URL = "https://git.jelles.net/"; ROOT_URL = service.url;
HTTP_PORT = 3001; HTTP_PORT = service.port;
HTTP_ADDR = "127.0.0.1"; HTTP_ADDR = service.host;
START_SSH_SERVER = false; START_SSH_SERVER = false;
SSH_PORT = 22; SSH_PORT = 22;
@@ -31,8 +32,10 @@ in
} }
(metaLib.mkCaddyReverseProxy { (metaLib.mkCaddyReverseProxy {
domain = "git.jelles.net"; inherit (service)
port = 3001; domain
port
;
}) })
]; ];
} }
-11
View File
@@ -1,11 +0,0 @@
{ ... }:
{
flake.modules.nixos.qbittorrent = {
services.qbittorrent = {
enable = true;
openFirewall = true;
torrentingPort = 43864;
webuiPort = 8123;
};
};
}
+6 -3
View File
@@ -1,6 +1,7 @@
{ config, ... }: { config, ... }:
let let
metaLib = config.meta.lib; metaLib = config.meta.lib;
service = metaLib.repo.services.radicale;
in in
{ {
flake.modules.nixos.radicale = flake.modules.nixos.radicale =
@@ -10,7 +11,7 @@ in
services.radicale = { services.radicale = {
enable = true; enable = true;
settings = { settings = {
server.hosts = [ "127.0.0.1:5232" ]; server.hosts = [ "${service.host}:${toString service.port}" ];
auth = { auth = {
type = "htpasswd"; type = "htpasswd";
@@ -24,8 +25,10 @@ in
} }
(metaLib.mkCaddyReverseProxy { (metaLib.mkCaddyReverseProxy {
domain = "radicale.jelles.net"; inherit (service)
port = 5232; domain
port
;
extraHeaders = [ extraHeaders = [
{ {
name = "X-Script-Name"; name = "X-Script-Name";
+7 -4
View File
@@ -1,6 +1,7 @@
{ config, ... }: { config, ... }:
let let
metaLib = config.meta.lib; metaLib = config.meta.lib;
service = metaLib.repo.services.vaultwarden;
in in
{ {
flake.modules.nixos.vaultwarden = flake.modules.nixos.vaultwarden =
@@ -11,17 +12,19 @@ in
enable = true; enable = true;
backupDir = "/var/backup/vaultwarden"; backupDir = "/var/backup/vaultwarden";
config = { config = {
DOMAIN = "https://vault.jelles.net"; DOMAIN = service.url;
SIGNUPS_ALLOWED = false; SIGNUPS_ALLOWED = false;
ROCKET_PORT = 8100; ROCKET_PORT = service.port;
ROCKET_LOG = "critical"; ROCKET_LOG = "critical";
}; };
}; };
} }
(metaLib.mkCaddyReverseProxy { (metaLib.mkCaddyReverseProxy {
domain = "vault.jelles.net"; inherit (service)
port = 8100; domain
port
;
}) })
]; ];
} }
+9 -25
View File
@@ -11,38 +11,22 @@ in
... ...
}: }:
let let
terminalPackage = metaLib.resolvePackagePath { terminal = metaLib.resolveUserTerminal {
inherit pkgs; 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 in
{ {
assertions = [ assertions = metaLib.mkTerminalAssertions {
{ inherit terminal;
assertion = hasTerminalPackage; user = config.meta.user;
message = "Unknown terminal package `${lib.showAttrPath config.meta.user.terminalPackagePath}` for user `${config.meta.user.name}`."; requireDesktopEntry = true;
} requireKitty = true;
{ };
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.";
}
];
xdg.terminal-exec = { xdg.terminal-exec = {
enable = true; enable = true;
settings.default = lib.optional (terminalDesktopId != null) terminalDesktopId; settings.default = lib.optional (terminal.desktopId != null) terminal.desktopId;
}; };
programs.kitty = { programs.kitty = {
+28 -15
View File
@@ -1,3 +1,10 @@
{
config,
...
}:
let
metaLib = config.meta.lib;
in
{ {
flake.modules.nixos.theme = flake.modules.nixos.theme =
{ {
@@ -5,10 +12,12 @@
... ...
}: }:
let let
cursorTheme = { repoTheme = metaLib.repo.theme;
name = "phinger-cursors-light"; cursorTheme = repoTheme.cursor // {
package = pkgs.phinger-cursors; package = metaLib.resolvePackagePath {
size = 24; inherit pkgs;
path = repoTheme.cursor.packagePath;
};
}; };
in in
{ {
@@ -25,21 +34,25 @@
flake.modules.homeManager.theme = flake.modules.homeManager.theme =
{ config, pkgs, ... }: { config, pkgs, ... }:
let let
cursorTheme = { repoTheme = metaLib.repo.theme;
name = "phinger-cursors-light"; cursorTheme = repoTheme.cursor // {
package = pkgs.phinger-cursors; package = metaLib.resolvePackagePath {
size = 24; inherit pkgs;
path = repoTheme.cursor.packagePath;
};
}; };
kanagawaThemeSrc = pkgs.fetchFromGitHub { kanagawaThemeSrc = pkgs.fetchFromGitHub {
owner = "Fausto-Korpsvart"; inherit (repoTheme.kanagawa)
repo = "Kanagawa-GKT-Theme"; hash
rev = "55ca4ba249eba21f861b9866b71ab41bb8930318"; owner
hash = "sha256-UdMoMx2DoovcxSp/zBZ3PRv/Qpj+prd0uPm1gmdak2E="; repo
rev
;
}; };
kanagawaOverride = { kanagawaOverride = {
version = "unstable-2025-10-23"; version = repoTheme.kanagawa.version;
src = kanagawaThemeSrc; src = kanagawaThemeSrc;
}; };
in in
@@ -59,14 +72,14 @@
"sftp://orion Orion VPS" "sftp://orion Orion VPS"
]; ];
theme = { theme = {
name = "Kanagawa-BL-LB"; name = repoTheme.kanagawa.gtkThemeName;
package = pkgs.kanagawa-gtk-theme.overrideAttrs (_: kanagawaOverride); package = pkgs.kanagawa-gtk-theme.overrideAttrs (_: kanagawaOverride);
}; };
gtk4.theme = { gtk4.theme = {
inherit (config.gtk.theme) name package; inherit (config.gtk.theme) name package;
}; };
iconTheme = { iconTheme = {
name = "Kanagawa"; name = repoTheme.kanagawa.iconThemeName;
package = pkgs.kanagawa-icon-theme.overrideAttrs (_: kanagawaOverride); package = pkgs.kanagawa-icon-theme.overrideAttrs (_: kanagawaOverride);
}; };
}; };
+3 -1
View File
@@ -33,15 +33,17 @@ in
flake.modules.homeManager.workstation-base = { flake.modules.homeManager.workstation-base = {
imports = [ imports = [
homeModules.ai homeModules.ai
homeModules.bitwarden
homeModules.clipboard homeModules.clipboard
homeModules.dev-tools homeModules.dev-tools
homeModules.email
homeModules.local-apps homeModules.local-apps
homeModules.mpv homeModules.mpv
homeModules.neovim homeModules.neovim
homeModules.nh homeModules.nh
homeModules.niri homeModules.niri
homeModules.nix homeModules.nix
homeModules.personal-productivity homeModules.pim
homeModules.podman homeModules.podman
homeModules.shell homeModules.shell
homeModules.sops homeModules.sops
+11 -15
View File
@@ -4,6 +4,11 @@
... ...
}: }:
let let
hostNames = [
"orion"
"polaris"
"zenith"
];
nixosModules = config.flake.modules.nixos; nixosModules = config.flake.modules.nixos;
in in
{ {
@@ -11,22 +16,13 @@ in
systems = [ "x86_64-linux" ]; systems = [ "x86_64-linux" ];
flake.nixosConfigurations = { flake.nixosConfigurations = inputs.nixpkgs.lib.genAttrs hostNames (
orion = inputs.nixpkgs.lib.nixosSystem { name:
inputs.nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs; }; specialArgs = { inherit inputs; };
modules = [ nixosModules.orion ]; modules = [ nixosModules.${name} ];
}; }
);
polaris = inputs.nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs; };
modules = [ nixosModules.polaris ];
};
zenith = inputs.nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs; };
modules = [ nixosModules.zenith ];
};
};
perSystem = perSystem =
{ pkgs, ... }: { pkgs, ... }:
+16 -25
View File
@@ -17,22 +17,17 @@ in
... ...
}: }:
let let
terminalPackage = metaLib.resolvePackagePath { terminal = metaLib.resolveUserTerminal {
inherit pkgs; inherit pkgs;
path = config.meta.host.users.kiri.terminalPackagePath; user = config.meta.host.users.kiri;
}; };
in in
{ {
assertions = [ assertions = metaLib.mkTerminalAssertions {
{ inherit terminal;
assertion = terminalPackage != null; user = config.meta.host.users.kiri;
message = "Unknown terminal package `${lib.showAttrPath config.meta.host.users.kiri.terminalPackagePath}` for user `kiri`."; requireTerminfo = true;
} };
{
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`.";
}
];
users.users.kiri = { users.users.kiri = {
linger = true; linger = true;
@@ -43,15 +38,20 @@ in
environment.systemPackages = [ environment.systemPackages = [
] ]
++ lib.optional (terminalPackage != null && lib.elem "terminfo" terminalPackage.outputs) ( ++ lib.optional terminal.hasTerminfo (lib.getOutput "terminfo" terminal.package);
lib.getOutput "terminfo" terminalPackage
);
}; };
flake.modules.nixos.orion = metaLib.mkHost { flake.modules.nixos.orion = metaLib.mkHost {
name = "orion"; name = "orion";
users = { users = {
inherit (metaLib.users) kiri; kiri = {
account = metaLib.users.kiri;
homeImports = [
homeModules.shell
homeModules.git
homeModules.syncthing
];
};
}; };
imports = [ imports = [
@@ -66,15 +66,6 @@ in
nixosModules.radicale nixosModules.radicale
nixosModules.actual nixosModules.actual
nixosModules.gitea nixosModules.gitea
(metaLib.mkHostUser {
account = metaLib.users.kiri;
needsPassword = false;
homeImports = [
homeModules.shell
homeModules.git
homeModules.syncthing
];
})
./_hardware.nix ./_hardware.nix
./_disk.nix ./_disk.nix
]; ];
+19 -20
View File
@@ -39,31 +39,30 @@ in
}; };
users = { users = {
inherit (metaLib.users) kiri = {
ergon account = metaLib.users.kiri;
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 = [ imports = [
nixosModules.workstation-base nixosModules.workstation-base
nixosModules.steam 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 ./_hardware.nix
] ]
++ (with inputs.nixos-hardware.nixosModules; [ ++ (with inputs.nixos-hardware.nixosModules; [
+19 -20
View File
@@ -40,31 +40,30 @@ in
}; };
users = { users = {
inherit (metaLib.users) kiri = {
ergon account = metaLib.users.kiri;
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 = [ imports = [
nixosModules.workstation-base nixosModules.workstation-base
nixosModules.laptop-power 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; hardware.enableRedistributableFirmware = true;
services.fwupd.enable = true; services.fwupd.enable = true;
+329 -44
View File
@@ -15,20 +15,84 @@ let
stateVersion ? "24.05", 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 = { meta.host = {
inherit inherit
displays displays
input input
name name
sourceControl sourceControl
users
; ;
users = hostUsers;
}; };
inherit imports; inherit imports;
networking.hostName = name; networking.hostName = name;
system.stateVersion = stateVersion; 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 = mkCaddyReverseProxy =
@@ -65,62 +129,259 @@ let
}: }:
lib.attrByPath path null pkgs; 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, pkgs,
... user,
}: }:
let let
name = account.name; package = resolvePackagePath {
primaryEmails = lib.filter (email: email.primary) (builtins.attrValues account.emails); 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 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 =
{ {
assertion = builtins.length primaryEmails == 1; terminal,
message = "User ${name} must define exactly one primary email entry."; 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.";
})
]; ];
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 { mkInputProfiles =
"hashed-password-${name}".neededForUsers = true; 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} = { mouse = input.mouse;
name = account.name; touchpad = input.touchpad;
home = account.homeDirectory;
isNormalUser = true; libinputMouse = mapConfiguredAttrs {
shell = pkgs.zsh; settings = mouse;
extraGroups = [ schema = {
"wheel" accelProfile.path = [ "accelProfile" ];
"networkmanager" accelSpeed = {
]; path = [ "accelSpeed" ];
} transform = toString;
// lib.optionalAttrs needsPassword { };
hashedPasswordFile = config.sops.secrets."hashed-password-${name}".path; leftHanded.path = [ "leftHanded" ];
middleEmulation.path = [ "middleEmulation" ];
naturalScrolling.path = [ "naturalScrolling" ];
scrollMethod = {
path = [ "scrollMethod" ];
values = lib.mapAttrs (_: value: value.libinput) scrollMethodValues;
};
};
}; };
home-manager.users.${name} = { libinputTouchpad = mapConfiguredAttrs {
imports = homeImports; settings = touchpad;
meta = { schema = {
host = config.meta.host; accelProfile.path = [ "accelProfile" ];
user = account; accelSpeed = {
path = [ "accelSpeed" ];
transform = toString;
}; };
home = { clickMethod = {
username = account.name; path = [ "clickMethod" ];
homeDirectory = account.homeDirectory; values = {
inherit stateVersion; "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 in
{ {
@@ -138,16 +399,30 @@ in
readOnly = true; readOnly = true;
}; };
options.meta.lib.mkHostUser = lib.mkOption { options.meta.lib.resolvePackagePath = lib.mkOption {
type = lib.types.raw; 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; internal = true;
readOnly = true; readOnly = true;
}; };
options.meta.lib.resolvePackagePath = lib.mkOption { options.meta.lib.resolveUserTerminal = lib.mkOption {
type = lib.types.raw; 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; internal = true;
readOnly = true; readOnly = true;
}; };
@@ -159,12 +434,22 @@ in
readOnly = true; readOnly = true;
}; };
options.meta.lib.repo = lib.mkOption {
type = lib.types.attrs;
description = "Internal shared repository metadata.";
internal = true;
readOnly = true;
};
config.meta.lib = { config.meta.lib = {
inherit inherit
mkInputProfiles
mkCaddyReverseProxy mkCaddyReverseProxy
mkTerminalAssertions
mkHost mkHost
mkHostUser repo
resolvePackagePath resolvePackagePath
resolveUserTerminal
; ;
}; };
} }