refactor: orion and base
This commit is contained in:
+2
-150
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
}
|
||||
+4
-10
@@ -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, ... }:
|
||||
|
||||
@@ -1,57 +1,27 @@
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
users.users.kiri = {
|
||||
linger = true;
|
||||
openssh.authorizedKeys.keys = [
|
||||
"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
|
||||
@@ -59,7 +29,6 @@ in
|
||||
nixosModules.caddy
|
||||
nixosModules.server-firewall
|
||||
nixosModules.ssh-agent-auth
|
||||
nixosModules.orion-admin
|
||||
nixosModules.vaultwarden
|
||||
nixosModules.radicale
|
||||
nixosModules.actual
|
||||
@@ -67,5 +36,12 @@ in
|
||||
./_hardware.nix
|
||||
./_disk.nix
|
||||
];
|
||||
|
||||
users.users.kiri = {
|
||||
linger = true;
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAU2LydkXRTtNFY7oyX8JQURwXLVhB71DeK8XzrXeFX1 openpgp:0xA490D93A"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
-374
@@ -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
|
||||
;
|
||||
};
|
||||
}
|
||||
@@ -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; };
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user