refactor: shared config metadata

This commit is contained in:
2026-04-22 04:10:29 +02:00
parent 503c1fe9bc
commit cf308a1371
23 changed files with 362 additions and 344 deletions
+2 -2
View File
@@ -3,7 +3,7 @@
...
}:
let
metaLib = config.meta.lib;
metaRepo = config.meta.lib.repo;
in
{
flake.modules.homeManager.bitwarden =
@@ -19,7 +19,7 @@ in
programs.rbw = {
enable = true;
settings = {
base_url = metaLib.repo.services.vaultwarden.url;
base_url = metaRepo.services.vaultwarden.url;
email = user.primaryEmail.address;
pinentry = pkgs.pinentry-gnome3;
};
+24 -12
View File
@@ -1,20 +1,32 @@
{ config, ... }:
let
metaRepo = config.meta.lib.repo;
in
{
flake.modules.homeManager.local-apps =
{ pkgs, ... }:
let
browserPackage = config.meta.lib.resolvePackagePath {
inherit pkgs;
path = metaRepo.desktop.browser.packagePath;
};
in
{
home.sessionVariables.BROWSER = "vivaldi";
home.sessionVariables.BROWSER = metaRepo.desktop.browser.command;
home.packages = with pkgs; [
brave
vivaldi
postman
spotify
calcure
planify
unzip
gimp
dbeaver-bin
];
home.packages =
with pkgs;
[
brave
postman
spotify
calcure
planify
unzip
gimp
dbeaver-bin
]
++ [ browserPackage ];
programs.imv.enable = true;
programs.sioyek.enable = true;
+35 -39
View File
@@ -25,16 +25,20 @@ let
user.sourceControl.projectScope
];
hasRequiredScopedEmail =
scope: user: scopeEmailCount scope user == 1;
hasRequiredScopedEmail = scope: user: scopeEmailCount scope user == 1;
primaryEmailFallback = {
address = "";
primary = false;
scope = null;
type = "";
type = "mxrouting";
};
emailProviderType = lib.types.enum [
"mxrouting"
"office365"
];
sourceControlScopeType = lib.types.enum [
"personal"
"work"
@@ -67,10 +71,11 @@ let
primary = lib.mkOption {
type = lib.types.bool;
default = false;
};
type = lib.mkOption {
type = lib.types.str;
type = emailProviderType;
};
scope = mkNullableOption sourceControlScopeType;
@@ -300,6 +305,29 @@ let
};
}
);
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 =
@@ -309,25 +337,7 @@ in
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
++ lib.flatten (
lib.mapAttrsToList (userName: user:
(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)
) config.meta.host.users
);
config.assertions = lib.flatten (lib.mapAttrsToList mkUserEmailAssertions config.meta.host.users);
};
flake.modules.homeManager.meta =
@@ -345,22 +355,8 @@ in
};
};
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.";
}
++ lib.optionals (config.meta.user != null) (
(map (scope: {
assertion = hasAtMostOneScopedEmail scope config.meta.user;
message = "User `${config.meta.user.name}` may define at most one `${scope}` scoped email entry.";
}) [
"personal"
"work"
])
++ map (scope: {
assertion = hasRequiredScopedEmail scope config.meta.user;
message = "User `${config.meta.user.name}` must define exactly one `${scope}` scoped email entry.";
}) (requiredSourceControlScopes config.meta.user)
config.assertions = lib.optionals (config.meta.user != null) (
mkUserEmailAssertions config.meta.user.name config.meta.user
);
};
}
+2 -1
View File
@@ -1,4 +1,5 @@
{
browserCommand,
lib,
terminalPackage,
}:
@@ -8,7 +9,7 @@
hotkey-overlay.title = "Terminal";
};
"Mod+B" = {
action.spawn = "vivaldi";
action.spawn = browserCommand;
hotkey-overlay.title = "Browser";
};
"Mod+Space" = {
+16 -7
View File
@@ -5,6 +5,7 @@
}:
let
metaLib = config.meta.lib;
metaRepo = metaLib.repo;
in
{
flake.modules.nixos.niri =
@@ -30,7 +31,12 @@ in
...
}:
let
repoTheme = metaLib.repo.theme.kanagawa;
repoTheme = metaRepo.theme.kanagawa;
browserCommand = metaRepo.desktop.browser.command;
fileManagerPackage = metaLib.resolvePackagePath {
inherit pkgs;
path = metaRepo.desktop.fileManager.packagePath;
};
outputs = lib.mapAttrs (
_: display:
{
@@ -69,12 +75,14 @@ in
};
};
home.packages = with pkgs; [
playerctl
nautilus
brightnessctl
xwayland-satellite
];
home.packages =
with pkgs;
[
playerctl
brightnessctl
xwayland-satellite
]
++ [ fileManagerPackage ];
programs.niri.settings = {
inherit outputs;
@@ -149,6 +157,7 @@ in
if terminal.hasMainProgram then
import ./_bindings.nix {
inherit
browserCommand
lib
;
terminalPackage = terminal.package;
+2 -2
View File
@@ -3,7 +3,7 @@
...
}:
let
metaLib = config.meta.lib;
metaRepo = config.meta.lib.repo;
in
{
flake.modules.homeManager.pim =
@@ -51,7 +51,7 @@ in
};
remote = {
url = metaLib.repo.services.radicale.url;
url = metaRepo.services.radicale.url;
type = "caldav";
userName = config.home.username;
passwordCommand = [
+2 -1
View File
@@ -1,7 +1,8 @@
{ config, ... }:
let
metaRepo = config.meta.lib.repo;
metaLib = config.meta.lib;
service = metaLib.repo.services.actual;
service = metaRepo.services.actual;
in
{
flake.modules.nixos.actual =
+2 -2
View File
@@ -1,12 +1,12 @@
{ config, ... }:
let
metaLib = config.meta.lib;
metaRepo = config.meta.lib.repo;
in
{
flake.modules.nixos.caddy = {
services.caddy = {
enable = true;
email = metaLib.repo.contact.email;
email = metaRepo.contact.email;
openFirewall = true;
};
};
+2 -1
View File
@@ -1,7 +1,8 @@
{ config, ... }:
let
metaRepo = config.meta.lib.repo;
metaLib = config.meta.lib;
service = metaLib.repo.services.gitea;
service = metaRepo.services.gitea;
in
{
flake.modules.nixos.gitea =
+2 -1
View File
@@ -1,7 +1,8 @@
{ config, ... }:
let
metaRepo = config.meta.lib.repo;
metaLib = config.meta.lib;
service = metaLib.repo.services.radicale;
service = metaRepo.services.radicale;
in
{
flake.modules.nixos.radicale =
+2 -1
View File
@@ -1,7 +1,8 @@
{ config, ... }:
let
metaRepo = config.meta.lib.repo;
metaLib = config.meta.lib;
service = metaLib.repo.services.vaultwarden;
service = metaRepo.services.vaultwarden;
in
{
flake.modules.nixos.vaultwarden =
+18 -27
View File
@@ -16,9 +16,7 @@ in
hostSourceControlUsers = host.sourceControl.users;
hostUserSourceControl = hostSourceControlUsers.${user.name} or { };
scopeEmails =
scope:
lib.filter (email: email.scope == scope) (builtins.attrValues user.emails);
scopeEmails = scope: lib.filter (email: email.scope == scope) (builtins.attrValues user.emails);
emailForScope =
scope:
@@ -53,30 +51,30 @@ in
in
if keyConfig == null then null else keyConfig.publicKey;
scopesInUse = lib.unique (
[
"personal"
sourceControl.projectScope
]
);
scopesInUse = lib.unique ([
"personal"
sourceControl.projectScope
]);
invalidEmailScopes = builtins.filter (scope: emailForScope scope == null) scopesInUse;
allowedSignersLines = map (scope: "${emailForScope scope} ${publicKeyForScope scope}") (
builtins.filter (scope: emailForScope scope != null && scopeHasSigningKey scope) scopesInUse
);
gitConfigForScope =
scope:
lib.recursiveUpdate {
user = {
name = user.realName;
email = emailForScope scope;
};
}
(lib.optionalAttrs (scopeHasSigningKey scope) {
gpg.ssh.allowedSignersFile = "${config.xdg.configHome}/git/allowed_signers";
user.signingKey = "${privateKeyPathForScope scope}.pub";
});
lib.recursiveUpdate
{
user = {
name = user.realName;
email = emailForScope scope;
};
}
(
lib.optionalAttrs (scopeHasSigningKey scope) {
gpg.ssh.allowedSignersFile = "${config.xdg.configHome}/git/allowed_signers";
user.signingKey = "${privateKeyPathForScope scope}.pub";
}
);
gitRoots = [
{
@@ -92,13 +90,6 @@ in
{
imports = [ homeModules.git ];
assertions = [
{
assertion = invalidEmailScopes == [ ];
message = "Expected exactly one scoped email for `${user.name}` source-control scopes: ${lib.concatStringsSep ", " invalidEmailScopes}.";
}
];
xdg.configFile."git/allowed_signers".text = lib.concatStringsSep "\n" (
allowedSignersLines ++ [ "" ]
);
+2 -1
View File
@@ -1,6 +1,7 @@
{ config, ... }:
let
metaLib = config.meta.lib;
metaRepo = metaLib.repo;
in
{
flake.modules.homeManager.terminal =
@@ -11,7 +12,7 @@ in
...
}:
let
repoTheme = metaLib.repo.theme.kanagawa;
repoTheme = metaRepo.theme.kanagawa;
palette = repoTheme.palette;
terminal = metaLib.resolveUserTerminal {
inherit pkgs;
+3 -2
View File
@@ -4,6 +4,7 @@
}:
let
metaLib = config.meta.lib;
metaRepo = metaLib.repo;
in
{
flake.modules.nixos.theme =
@@ -12,7 +13,7 @@ in
...
}:
let
repoTheme = metaLib.repo.theme;
repoTheme = metaRepo.theme;
cursorTheme = repoTheme.cursor // {
package = metaLib.resolvePackagePath {
inherit pkgs;
@@ -34,7 +35,7 @@ in
flake.modules.homeManager.theme =
{ config, pkgs, ... }:
let
repoTheme = metaLib.repo.theme;
repoTheme = metaRepo.theme;
cursorTheme = repoTheme.cursor // {
package = metaLib.resolvePackagePath {
inherit pkgs;
+2 -2
View File
@@ -1,6 +1,6 @@
{ config, ... }:
let
metaLib = config.meta.lib;
metaRepo = config.meta.lib.repo;
in
{
flake.modules.homeManager.vicinae =
@@ -10,7 +10,7 @@ in
...
}:
let
repoTheme = metaLib.repo.theme.kanagawa;
repoTheme = metaRepo.theme.kanagawa;
palette = repoTheme.palette;
in
{
+24 -7
View File
@@ -1,7 +1,20 @@
{ config, ... }:
let
metaLib = config.meta.lib;
metaRepo = metaLib.repo;
in
{
flake.modules.homeManager.xdg =
{ config, pkgs, ... }:
let
browserPackage = metaLib.resolvePackagePath {
inherit pkgs;
path = metaRepo.desktop.browser.packagePath;
};
fileManagerPackage = metaLib.resolvePackagePath {
inherit pkgs;
path = metaRepo.desktop.fileManager.packagePath;
};
homeDir = config.home.homeDirectory;
localDir = "${homeDir}/.local";
mediaDir = "${homeDir}/media";
@@ -33,13 +46,17 @@
mimeApps = {
enable = true;
defaultApplicationPackages = with pkgs; [
sioyek
imv
vivaldi
neovim
nautilus
];
defaultApplicationPackages =
with pkgs;
[
sioyek
imv
neovim
]
++ [
browserPackage
fileManagerPackage
];
};
};
};
+7 -6
View File
@@ -4,15 +4,16 @@
...
}:
let
hostNames = [
"orion"
"polaris"
"zenith"
];
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 ];
imports = [
inputs.flake-parts.flakeModules.modules
./repo-data.nix
];
systems = [ "x86_64-linux" ];
+1 -1
View File
@@ -44,7 +44,7 @@ in
flake.modules.nixos.orion = metaLib.mkHost {
name = "orion";
users = {
kiri = metaLib.mkHostUser {
kiri = {
account = metaLib.users.kiri;
homeImports = [
homeModules.shell
+2 -2
View File
@@ -30,7 +30,7 @@ in
};
users = {
kiri = metaLib.mkHostUser {
kiri = {
account = metaLib.users.kiri;
needsPassword = true;
homeImports = [
@@ -41,7 +41,7 @@ in
];
};
ergon = metaLib.mkHostUser {
ergon = {
account = metaLib.users.ergon;
needsPassword = true;
homeImports = [
+2 -2
View File
@@ -38,7 +38,7 @@ in
};
users = {
kiri = metaLib.mkHostUser {
kiri = {
account = metaLib.users.kiri;
needsPassword = true;
homeImports = [
@@ -49,7 +49,7 @@ in
];
};
ergon = metaLib.mkHostUser {
ergon = {
account = metaLib.users.ergon;
needsPassword = true;
homeImports = [
+20 -157
View File
@@ -4,21 +4,14 @@
...
}:
let
mkHostUser =
normalizeHostUser =
spec:
{
account,
needsPassword ? false,
homeImports ? [ ],
stateVersion ? null,
}:
{
inherit
account
homeImports
needsPassword
stateVersion
;
};
homeImports = [ ];
needsPassword = false;
stateVersion = null;
}
// spec;
mkHost =
{
@@ -36,7 +29,7 @@ let
...
}:
let
hostUserSpecs = lib.mapAttrs (_: spec: mkHostUser spec) users;
hostUserSpecs = lib.mapAttrs (_: spec: normalizeHostUser spec) users;
hostUsers = lib.mapAttrs (_: spec: spec.account) hostUserSpecs;
userAssertions = lib.mapAttrsToList (userName: spec: {
@@ -90,21 +83,18 @@ let
}
) 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;
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 =
@@ -141,110 +131,6 @@ let
}:
lib.attrByPath path null pkgs;
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 = {
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";
};
};
};
};
};
resolveUserTerminal =
{
pkgs,
@@ -456,13 +342,6 @@ in
readOnly = true;
};
options.meta.lib.mkHostUser = lib.mkOption {
type = lib.types.raw;
description = "Internal host user 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.";
@@ -498,28 +377,12 @@ in
readOnly = true;
};
options.meta.lib.users = lib.mkOption {
type = lib.types.attrs;
description = "Canonical user attrsets shared by host definitions.";
internal = 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 = {
inherit
mkInputProfiles
mkCaddyReverseProxy
mkTerminalAssertions
mkHost
mkHostUser
repo
resolvePackagePath
resolveUserTerminal
;
+190
View File
@@ -0,0 +1,190 @@
{ lib, ... }:
let
users = {
kiri = {
name = "kiri";
realName = "Jelle Spreeuwenberg";
homeDirectory = "/home/kiri";
terminalPackagePath = [ "kitty" ];
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 = {
name = "ergon";
realName = "Jelle Spreeuwenberg";
homeDirectory = "/home/ergon";
terminalPackagePath = [ "kitty" ];
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";
};
};
repo = {
contact.email = "mail@jelles.net";
desktop = {
browser = {
command = "vivaldi";
packagePath = [ "vivaldi" ];
};
fileManager = {
command = "nautilus";
packagePath = [ "nautilus" ];
};
};
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.users = lib.mkOption {
type = lib.types.attrs;
description = "Canonical user attrsets shared by host definitions.";
internal = true;
readOnly = true;
};
config.meta.lib.repo = repo;
config.meta.lib.users = users;
}
-68
View File
@@ -1,68 +0,0 @@
{ ... }:
let
kiri = {
name = "kiri";
realName = "Jelle Spreeuwenberg";
homeDirectory = "/home/kiri";
terminalPackagePath = [ "kitty" ];
emails = {
personal = {
address = "mail@jelles.net";
primary = true;
scope = "personal";
type = "mxrouting";
};
old = {
address = "mail@jellespreeuwenberg.nl";
primary = false;
scope = null;
type = "mxrouting";
};
uni = {
address = "j.spreeuwenberg@student.tue.nl";
primary = false;
scope = null;
type = "office365";
};
work = {
address = "jelle.spreeuwenberg@yookr.org";
primary = false;
scope = "work";
type = "office365";
};
};
sourceControl = { };
};
ergon = {
name = "ergon";
realName = "Jelle Spreeuwenberg";
homeDirectory = "/home/ergon";
terminalPackagePath = [ "kitty" ];
emails = {
personal = {
address = "mail@jelles.net";
primary = false;
scope = "personal";
type = "mxrouting";
};
work = {
address = "jelle.spreeuwenberg@yookr.org";
primary = true;
scope = "work";
type = "office365";
};
};
sourceControl = {
projectScope = "work";
};
};
in
{
meta.lib.users = {
inherit
ergon
kiri
;
};
}