style(nix): apply style fixes #53

Merged
alexion merged 1 commits from apply-nix-style-fix into main 2026-09-16 00:18:54 -04:00
42 changed files with 389 additions and 289 deletions

View File

@@ -109,31 +109,36 @@ in
inherit (cfg.reverseProxy) trustedSources; inherit (cfg.reverseProxy) trustedSources;
}; };
volumes = lib.mapAttrs (_urlPath: volume: { volumes = lib.mapAttrs (_urlPath: volume: {
inherit (volume) path access flags readOnly; inherit (volume)
path
access
flags
readOnly
;
}) cfg.volumes; }) cfg.volumes;
}; };
mounts = mounts = {
{ ${cfg.stateDir} = {
${cfg.stateDir} = { directory = cfg.statePath;
directory = cfg.statePath; owner = config.registry."stable-uid".${config.networking.hostName}.copyparty;
owner = config.registry."stable-uid".${config.networking.hostName}.copyparty; group = "storage";
group = "storage"; mode = "0770";
mode = "0770"; };
}; ${"/run/secrets/${cfg.passwordSecret}"} = {
${"/run/secrets/${cfg.passwordSecret}"} = { file = config.sops.secrets.${cfg.passwordSecret}.path;
file = config.sops.secrets.${cfg.passwordSecret}.path; readOnly = true;
readOnly = true; };
}; }
// lib.mapAttrs' (
_urlPath: volume:
lib.nameValuePair volume.path {
directory = volume.hostPath;
owner = "root";
group = "storage";
mode = "2770";
} }
// lib.mapAttrs' (_urlPath: volume: ) cfg.volumes;
lib.nameValuePair volume.path {
directory = volume.hostPath;
owner = "root";
group = "storage";
mode = "2770";
}
) cfg.volumes;
limits = { limits = {
memory = lib.mkDefault "2G"; memory = lib.mkDefault "2G";
cpu = lib.mkDefault "200%"; cpu = lib.mkDefault "200%";

View File

@@ -88,43 +88,47 @@ in
guests.palworld = { guests.palworld = {
interior.modules.services.games.palworld = { interior.modules.services.games.palworld = {
enable = true; enable = true;
inherit (cfg) serverName port publicIP autoStart; inherit (cfg)
serverName
port
publicIP
autoStart
;
inherit steamDir installDir stateDir; inherit steamDir installDir stateDir;
passwordFile = "/run/secrets/${cfg.passwordSecret}"; passwordFile = "/run/secrets/${cfg.passwordSecret}";
adminPasswordFile = "/run/secrets/${cfg.adminPasswordSecret}"; adminPasswordFile = "/run/secrets/${cfg.adminPasswordSecret}";
}; };
mounts = mounts = {
{ ${steamDir} = {
${steamDir} = { directory = cfg.steamPath;
directory = cfg.steamPath; owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd; group = "storage";
group = "storage"; mode = "0770";
mode = "0770";
};
${installDir} = {
directory = cfg.installPath;
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
group = "storage";
mode = "0770";
};
${stateDir} = {
directory = cfg.statePath;
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
group = "storage";
mode = "0770";
};
${"/run/secrets/${cfg.passwordSecret}"} = {
file = config.sops.secrets.${cfg.passwordSecret}.path;
readOnly = true;
};
}
// lib.optionalAttrs (cfg.adminPasswordSecret != cfg.passwordSecret) {
${"/run/secrets/${cfg.adminPasswordSecret}"} = {
file = config.sops.secrets.${cfg.adminPasswordSecret}.path;
readOnly = true;
};
}; };
${installDir} = {
directory = cfg.installPath;
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
group = "storage";
mode = "0770";
};
${stateDir} = {
directory = cfg.statePath;
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
group = "storage";
mode = "0770";
};
${"/run/secrets/${cfg.passwordSecret}"} = {
file = config.sops.secrets.${cfg.passwordSecret}.path;
readOnly = true;
};
}
// lib.optionalAttrs (cfg.adminPasswordSecret != cfg.passwordSecret) {
${"/run/secrets/${cfg.adminPasswordSecret}"} = {
file = config.sops.secrets.${cfg.adminPasswordSecret}.path;
readOnly = true;
};
};
limits = { limits = {
memory = lib.mkDefault "8G"; memory = lib.mkDefault "8G";
@@ -133,20 +137,19 @@ in
}; };
}; };
sops.secrets = sops.secrets = {
{ ${cfg.passwordSecret} = {
${cfg.passwordSecret} = { owner = "root";
owner = "root"; group = "storage";
group = "storage"; mode = "0440";
mode = "0440";
};
}
// lib.optionalAttrs (cfg.adminPasswordSecret != cfg.passwordSecret) {
${cfg.adminPasswordSecret} = {
owner = "root";
group = "storage";
mode = "0440";
};
}; };
}
// lib.optionalAttrs (cfg.adminPasswordSecret != cfg.passwordSecret) {
${cfg.adminPasswordSecret} = {
owner = "root";
group = "storage";
mode = "0440";
};
};
}; };
} }

View File

@@ -34,7 +34,7 @@
registry.shell-shortcut.neogaia = { registry.shell-shortcut.neogaia = {
vpn-on = "sudo systemctl start wireguard-wg-home.target"; vpn-on = "sudo systemctl start wireguard-wg-home.target";
vpn-off = ''sudo systemctl stop 'wireguard-wg-home*.service' wireguard-wg-home.target''; vpn-off = "sudo systemctl stop 'wireguard-wg-home*.service' wireguard-wg-home.target";
}; };
modules.monorepo.enable = true; modules.monorepo.enable = true;

View File

@@ -1,4 +1,4 @@
{ ... }: _:
# neogaia's disk layout for disko: one NVMe disk, GPT, with an EFI system # neogaia's disk layout for disko: one NVMe disk, GPT, with an EFI system
# partition and a LUKS container holding btrfs subvolumes. # partition and a LUKS container holding btrfs subvolumes.
# No swap partition, since swap is zram. # No swap partition, since swap is zram.

View File

@@ -1,4 +1,4 @@
{ ... }: _:
# pikachu's install layout for disko: one NVMe boot disk with an EFI system partition and ext4 root. # pikachu's install layout for disko: one NVMe boot disk with an EFI system partition and ext4 root.
# The existing 8 TB ZFS mirror is imported by name and is never declared here. # The existing 8 TB ZFS mirror is imported by name and is never declared here.
{ {

View File

@@ -8,7 +8,10 @@
# Disk layout is in ./disk.nix. # Disk layout is in ./disk.nix.
# `fileSystems` are derived from it, none declared here. # `fileSystems` are derived from it, none declared here.
{ {
imports = [ ./disk.nix ] ++ lib.optional (builtins.pathExists ./hardware-configuration.nix) ./hardware-configuration.nix; imports = [
./disk.nix
]
++ lib.optional (builtins.pathExists ./hardware-configuration.nix) ./hardware-configuration.nix;
nixpkgs.hostPlatform = "x86_64-linux"; nixpkgs.hostPlatform = "x86_64-linux";

View File

@@ -1,4 +1,4 @@
{ ... }: _:
# zeus's disk layout for disko: two NVMe disks with one EFI system partition # zeus's disk layout for disko: two NVMe disks with one EFI system partition
# and two LUKS containers joined into one btrfs filesystem. # and two LUKS containers joined into one btrfs filesystem.
# No swap partition, since swap is zram. # No swap partition, since swap is zram.

View File

@@ -10,7 +10,8 @@ let
user = config.user.name; user = config.user.name;
in in
{ {
options.modules.desktop.apps.alacritty.enable = lib.mkEnableOption "Alacritty as the desktop terminal"; options.modules.desktop.apps.alacritty.enable =
lib.mkEnableOption "Alacritty as the desktop terminal";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home-manager.users.${user}.home.packages = [ pkgs.alacritty ]; home-manager.users.${user}.home.packages = [ pkgs.alacritty ];

View File

@@ -10,7 +10,8 @@ let
user = config.user.name; user = config.user.name;
in in
{ {
options.modules.desktop.apps.obsidian.enable = lib.mkEnableOption "Obsidian as the desktop note-taking vault"; options.modules.desktop.apps.obsidian.enable =
lib.mkEnableOption "Obsidian as the desktop note-taking vault";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home-manager.users.${user}.home.packages = [ pkgs.obsidian ]; home-manager.users.${user}.home.packages = [ pkgs.obsidian ];

View File

@@ -10,7 +10,8 @@ let
user = config.user.name; user = config.user.name;
in in
{ {
options.modules.desktop.apps.vencord.enable = lib.mkEnableOption "Vesktop, a Discord client with Vencord built in"; options.modules.desktop.apps.vencord.enable =
lib.mkEnableOption "Vesktop, a Discord client with Vencord built in";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home-manager.users.${user}.home.packages = [ pkgs.vesktop ]; home-manager.users.${user}.home.packages = [ pkgs.vesktop ];

View File

@@ -67,22 +67,21 @@ in
# UWSM launches the session without the shell profile that would carry # UWSM launches the session without the shell profile that would carry
# them, so a variable the compositor or its children must see is set # them, so a variable the compositor or its children must see is set
# here rather than through home-manager's sessionVariables. # here rather than through home-manager's sessionVariables.
env = env = [
[ # Chromium and Electron apps read this to select native Wayland;
# Chromium and Electron apps read this to select native Wayland; # nixpkgs wrappers (Obsidian's included) gate their Wayland flags on
# nixpkgs wrappers (Obsidian's included) gate their Wayland flags on # it, so without it they run under XWayland and blur at this DPI.
# it, so without it they run under XWayland and blur at this DPI. "NIXOS_OZONE_WL,1"
"NIXOS_OZONE_WL,1" ]
] # Bibata ships XCursor only.
# Bibata ships XCursor only. # The hyprcursor variables name the same theme, which Hyprland
# The hyprcursor variables name the same theme, which Hyprland # resolves through its XCursor fallback.
# resolves through its XCursor fallback. ++ lib.optionals (cursor != null) [
++ lib.optionals (cursor != null) [ "XCURSOR_THEME,${cursor.name}"
"XCURSOR_THEME,${cursor.name}" "XCURSOR_SIZE,${toString cursor.size}"
"XCURSOR_SIZE,${toString cursor.size}" "HYPRCURSOR_THEME,${cursor.name}"
"HYPRCURSOR_THEME,${cursor.name}" "HYPRCURSOR_SIZE,${toString cursor.size}"
"HYPRCURSOR_SIZE,${toString cursor.size}" ];
];
input = { input = {
kb_layout = "us"; kb_layout = "us";

View File

@@ -45,7 +45,8 @@ let
''; '';
in in
{ {
options.modules.desktop.session.hyprland.hyprlock.enable = lib.mkEnableOption "the hyprlock lock screen"; options.modules.desktop.session.hyprland.hyprlock.enable =
lib.mkEnableOption "the hyprlock lock screen";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home-manager.users.${user} = { home-manager.users.${user} = {

View File

@@ -10,7 +10,8 @@ let
cfg = config.modules.desktop.session.login; cfg = config.modules.desktop.session.login;
in in
{ {
options.modules.desktop.session.login.enable = lib.mkEnableOption "greetd with the tuigreet text greeter"; options.modules.desktop.session.login.enable =
lib.mkEnableOption "greetd with the tuigreet text greeter";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
services.greetd = { services.greetd = {

View File

@@ -25,7 +25,8 @@ let
''; '';
in in
{ {
options.modules.desktop.utilities.clipboard.enable = lib.mkEnableOption "cliphist clipboard history"; options.modules.desktop.utilities.clipboard.enable =
lib.mkEnableOption "cliphist clipboard history";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home-manager.users.${user} = { home-manager.users.${user} = {

View File

@@ -10,7 +10,8 @@ let
user = config.user.name; user = config.user.name;
in in
{ {
options.modules.desktop.utilities.osd.enable = lib.mkEnableOption "the SwayOSD volume and brightness popup"; options.modules.desktop.utilities.osd.enable =
lib.mkEnableOption "the SwayOSD volume and brightness popup";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
environment.systemPackages = [ pkgs.swayosd ]; environment.systemPackages = [ pkgs.swayosd ];

View File

@@ -41,7 +41,8 @@ let
''; '';
in in
{ {
options.modules.desktop.utilities.recording.enable = lib.mkEnableOption "wf-recorder screen recording"; options.modules.desktop.utilities.recording.enable =
lib.mkEnableOption "wf-recorder screen recording";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home-manager.users.${user} = { home-manager.users.${user} = {

View File

@@ -32,7 +32,8 @@ let
''; '';
in in
{ {
options.modules.desktop.utilities.screenshot.enable = lib.mkEnableOption "screenshots via grimblast and satty"; options.modules.desktop.utilities.screenshot.enable =
lib.mkEnableOption "screenshots via grimblast and satty";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home-manager.users.${user} = { home-manager.users.${user} = {

View File

@@ -5,7 +5,7 @@
}: }:
let let
cfg = config.modules.host; cfg = config.modules.host;
user = config.user; inherit (config) user;
hostSecrets = ../secrets + "/${config.networking.hostName}.yaml"; hostSecrets = ../secrets + "/${config.networking.hostName}.yaml";
passwordSecret = "${user.name}-password"; passwordSecret = "${user.name}-password";
in in

View File

@@ -104,8 +104,7 @@ let
isWorkstation = elem currentHost cfg.hosts.workstations; isWorkstation = elem currentHost cfg.hosts.workstations;
defaultAuthorizedKeys = lib.flatten ( defaultAuthorizedKeys = lib.flatten (
clientKeysFor workstationNames clientKeysFor workstationNames ++ optional isServer (clientKeysFor serverNames)
++ optional isServer (clientKeysFor serverNames)
); );
outboundTargetNames = outboundTargetNames =
@@ -120,7 +119,8 @@ let
in in
filter (name: name != currentHost) (targetNamesIn groupTargets); filter (name: name != currentHost) (targetNamesIn groupTargets);
sshSettingsFor = name: sshSettingsFor =
name:
let let
target = cfg.targets.${name}; target = cfg.targets.${name};
in in
@@ -131,7 +131,8 @@ let
} }
// optionalAttrs (target.port != 22) { Port = target.port; }; // optionalAttrs (target.port != 22) { Port = target.port; };
knownHostNamesFor = target: knownHostNamesFor =
target:
let let
names = unique (target.aliases ++ [ target.hostName ]); names = unique (target.aliases ++ [ target.hostName ]);
withPort = name: if target.port == 22 then name else "[${name}]:${toString target.port}"; withPort = name: if target.port == 22 then name else "[${name}]:${toString target.port}";
@@ -142,7 +143,8 @@ let
concatLists ( concatLists (
mapAttrsToList ( mapAttrsToList (
targetName: target: targetName: target:
imap0 (i: key: imap0 (
i: key:
nameValuePair "${targetName}-${toString i}" { nameValuePair "${targetName}-${toString i}" {
hostNames = knownHostNamesFor target; hostNames = knownHostNamesFor target;
publicKey = keyWithoutComment key; publicKey = keyWithoutComment key;
@@ -290,58 +292,56 @@ in
}; };
}; };
config = mkIf cfg.enable ( config = mkIf cfg.enable (mkMerge [
mkMerge [ {
{ services.openssh.enable = true;
services.openssh.enable = true;
users.users.${user}.openssh.authorizedKeys.keys = users.users.${user}.openssh.authorizedKeys.keys =
if cfg.authorizedKeys != null then if cfg.authorizedKeys != null then
cfg.authorizedKeys cfg.authorizedKeys
else else
defaultAuthorizedKeys ++ cfg.extraAuthorizedKeys; defaultAuthorizedKeys ++ cfg.extraAuthorizedKeys;
programs.ssh.knownHosts = knownHosts; programs.ssh.knownHosts = knownHosts;
home-manager.users.${user}.programs.ssh = { home-manager.users.${user}.programs.ssh = {
enable = true; enable = true;
enableDefaultConfig = false; enableDefaultConfig = false;
settings = settings =
mapAttrs (name: _: sshSettingsFor name) (genAttrs outboundTargetNames (name: name)) mapAttrs (name: _: sshSettingsFor name) (genAttrs outboundTargetNames (name: name))
// cfg.extraSettings; // cfg.extraSettings;
}; };
} }
(mkIf cfg.hostKeys.restore { (mkIf cfg.hostKeys.restore {
assertions = [ assertions = [
{ {
assertion = cfg.hostKeys.sopsFile != null; assertion = cfg.hostKeys.sopsFile != null;
message = "modules.network.ssh.hostKeys.restore requires modules.network.ssh.hostKeys.sopsFile to name the encrypted host keys."; message = "modules.network.ssh.hostKeys.restore requires modules.network.ssh.hostKeys.sopsFile to name the encrypted host keys.";
} }
]; ];
sops.secrets = genAttrs (map hostKeySecret cfg.hostKeys.types) (_: { sops.secrets = genAttrs (map hostKeySecret cfg.hostKeys.types) (_: {
inherit (cfg.hostKeys) sopsFile; inherit (cfg.hostKeys) sopsFile;
mode = "0400"; mode = "0400";
restartUnits = [ "sshd.service" ]; restartUnits = [ "sshd.service" ];
}); });
services.openssh.hostKeys = [ ]; services.openssh.hostKeys = [ ];
services.openssh.extraConfig = concatStringsSep "" ( services.openssh.extraConfig = concatStringsSep "" (
map (type: "HostKey ${config.sops.secrets.${hostKeySecret type}.path}\n") cfg.hostKeys.types map (type: "HostKey ${config.sops.secrets.${hostKeySecret type}.path}\n") cfg.hostKeys.types
); );
}) })
(mkIf (cfg.userKey.sopsFile != null) { (mkIf (cfg.userKey.sopsFile != null) {
sops.secrets.${userKeySecret} = { sops.secrets.${userKeySecret} = {
inherit (cfg.userKey) sopsFile; inherit (cfg.userKey) sopsFile;
mode = "0400"; mode = "0400";
owner = user; owner = user;
}; };
home-manager.users.${user}.programs.ssh.settings."*".IdentityFile = home-manager.users.${user}.programs.ssh.settings."*".IdentityFile =
config.sops.secrets.${userKeySecret}.path; config.sops.secrets.${userKeySecret}.path;
}) })
] ]);
);
} }

View File

@@ -75,10 +75,11 @@ in
]; ];
# networkd owns these interfaces so NetworkManager does not contend for them. # networkd owns these interfaces so NetworkManager does not contend for them.
networking.networkmanager.unmanaged = networking.networkmanager.unmanaged = [
[ "interface-name:${cfg.trunk}" ] "interface-name:${cfg.trunk}"
++ map (id: "interface-name:${bridgeName id}") cfg.ids ]
++ map (id: "interface-name:${vlanName id}") cfg.ids; ++ map (id: "interface-name:${bridgeName id}") cfg.ids
++ map (id: "interface-name:${vlanName id}") cfg.ids;
systemd.network = { systemd.network = {
enable = true; enable = true;

View File

@@ -88,16 +88,18 @@ in
networking.wireguard.interfaces.${cfg.interface} = { networking.wireguard.interfaces.${cfg.interface} = {
ips = [ cfg.address ]; ips = [ cfg.address ];
mtu = cfg.mtu; inherit (cfg) mtu;
privateKeyFile = config.sops.secrets.${cfg.privateKeySecret}.path; privateKeyFile = config.sops.secrets.${cfg.privateKeySecret}.path;
peers = [ peers = [
({ (
inherit (cfg.peer) publicKey endpoint allowedIPs; {
} inherit (cfg.peer) publicKey endpoint allowedIPs;
// lib.optionalAttrs (cfg.peer.persistentKeepalive != null) { }
inherit (cfg.peer) persistentKeepalive; // lib.optionalAttrs (cfg.peer.persistentKeepalive != null) {
}) inherit (cfg.peer) persistentKeepalive;
}
)
]; ];
}; };
}; };

View File

@@ -45,14 +45,14 @@ in
users.users.${cfg.user} = { users.users.${cfg.user} = {
isSystemUser = true; isSystemUser = true;
uid = config.registry."stable-uid".${config.networking.hostName}.actualbudget; uid = config.registry."stable-uid".${config.networking.hostName}.actualbudget;
group = cfg.group; inherit (cfg) group;
}; };
services.actual = { services.actual = {
enable = true; enable = true;
openFirewall = true; openFirewall = true;
user = cfg.user; inherit (cfg) user;
group = cfg.group; inherit (cfg) group;
settings = { settings = {
inherit (cfg) port dataDir; inherit (cfg) port dataDir;
}; };

View File

@@ -40,13 +40,16 @@ let
renderAccess = permission: accounts: " ${permission}: ${lib.concatStringsSep ", " accounts}"; renderAccess = permission: accounts: " ${permission}: ${lib.concatStringsSep ", " accounts}";
hasFlag = name: flags: lib.any (flag: lib.hasPrefix "${name}:" flag || lib.hasPrefix "${name}=" flag) flags; hasFlag =
name: flags: lib.any (flag: lib.hasPrefix "${name}:" flag || lib.hasPrefix "${name}=" flag) flags;
defaultPermissionFlags = flags: defaultPermissionFlags =
flags:
lib.optionals (!(hasFlag "chmod_d" flags)) [ "chmod_d: 2770" ] lib.optionals (!(hasFlag "chmod_d" flags)) [ "chmod_d: 2770" ]
++ lib.optionals (!(hasFlag "chmod_f" flags)) [ "chmod_f: 660" ]; ++ lib.optionals (!(hasFlag "chmod_f" flags)) [ "chmod_f: 660" ];
renderFlags = flags: lib.concatMapStringsSep "\n" (flag: " ${flag}") (flags ++ defaultPermissionFlags flags); renderFlags =
flags: lib.concatMapStringsSep "\n" (flag: " ${flag}") (flags ++ defaultPermissionFlags flags);
renderVolume = urlPath: volume: '' renderVolume = urlPath: volume: ''
[${urlPath}] [${urlPath}]
@@ -54,8 +57,8 @@ let
accs: accs:
${lib.concatStringsSep "\n" (lib.mapAttrsToList renderAccess volume.access)} ${lib.concatStringsSep "\n" (lib.mapAttrsToList renderAccess volume.access)}
${lib.optionalString (volume.flags != [ ]) '' ${lib.optionalString (volume.flags != [ ]) ''
flags: flags:
${renderFlags volume.flags} ${renderFlags volume.flags}
''} ''}
''; '';
@@ -173,7 +176,7 @@ in
users.users.${cfg.user} = { users.users.${cfg.user} = {
isSystemUser = true; isSystemUser = true;
uid = config.registry."stable-uid".${config.networking.hostName}.copyparty; uid = config.registry."stable-uid".${config.networking.hostName}.copyparty;
group = cfg.group; inherit (cfg) group;
home = cfg.stateDir; home = cfg.stateDir;
}; };
@@ -202,7 +205,10 @@ in
PrivateTmp = true; PrivateTmp = true;
ProtectSystem = "strict"; ProtectSystem = "strict";
ProtectHome = true; ProtectHome = true;
ReadWritePaths = [ cfg.stateDir ] ++ map (volume: volume.path) (lib.filter (volume: !volume.readOnly) (lib.attrValues cfg.volumes)); ReadWritePaths = [
cfg.stateDir
]
++ map (volume: volume.path) (lib.filter (volume: !volume.readOnly) (lib.attrValues cfg.volumes));
}; };
}; };

View File

@@ -134,19 +134,18 @@
appId = 2394010; appId = 2394010;
inherit (service) autoStart; inherit (service) autoStart;
executable = "${startScript}"; executable = "${startScript}";
arguments = arguments = [
[ "-useperfthreads"
"-useperfthreads" "-NoAsyncLoadingThread"
"-NoAsyncLoadingThread" "-UseMultithreadForDS"
"-UseMultithreadForDS" "-port=${toString service.port}"
"-port=${toString service.port}" "-publicport=${toString service.port}"
"-publicport=${toString service.port}" "-servername=${service.serverName}"
"-servername=${service.serverName}" "-players=${toString service.maxPlayers}"
"-players=${toString service.maxPlayers}" ]
] ++ lib.optional service.publicLobby "-publiclobby"
++ lib.optional service.publicLobby "-publiclobby" ++ lib.optionals (service.publicIP != null) [ "-publicip=${service.publicIP}" ]
++ lib.optionals (service.publicIP != null) [ "-publicip=${service.publicIP}" ] ++ lib.optional service.rcon.enable "-rcon";
++ lib.optional service.rcon.enable "-rcon";
udpPorts = [ service.port ]; udpPorts = [ service.port ];
tcpPorts = [ service.port ]; tcpPorts = [ service.port ];
}; };

View File

@@ -7,7 +7,9 @@
let let
cfg = config.modules.services.homepage; cfg = config.modules.services.homepage;
mkRegistryPath = registryName: host: entry: "registry.${registryName}.${host}.${entry}"; mkRegistryPath =
registryName: host: entry:
"registry.${registryName}.${host}.${entry}";
homepageEntries = lib.concatLists ( homepageEntries = lib.concatLists (
lib.mapAttrsToList ( lib.mapAttrsToList (
@@ -19,7 +21,8 @@ let
) registry.homepage ) registry.homepage
); );
mkHref = item: mkHref =
item:
if item.homepage.href != null then if item.homepage.href != null then
item.homepage.href item.homepage.href
else if (item.composite.route or null) != null then else if (item.composite.route or null) != null then
@@ -32,7 +35,8 @@ let
else else
throw "${mkRegistryPath "homepage" item.host item.entry} needs href or a matching route entry"; throw "${mkRegistryPath "homepage" item.host item.entry} needs href or a matching route entry";
mkServiceAttrs = item: mkServiceAttrs =
item:
lib.filterAttrs (_: value: value != null) { lib.filterAttrs (_: value: value != null) {
href = mkHref item; href = mkHref item;
inherit (item.homepage) description icon; inherit (item.homepage) description icon;
@@ -40,15 +44,15 @@ let
sortedEntries = lib.sort (left: right: left.homepage.name < right.homepage.name) homepageEntries; sortedEntries = lib.sort (left: right: left.homepage.name < right.homepage.name) homepageEntries;
insertEntry = acc: item: insertEntry =
acc: item:
acc acc
// { // {
${item.homepage.tab} = (acc.${item.homepage.tab} or { }) ${item.homepage.tab} = (acc.${item.homepage.tab} or { }) // {
// { ${item.homepage.section} = (acc.${item.homepage.tab}.${item.homepage.section} or [ ]) ++ [
${item.homepage.section} = (acc.${item.homepage.tab}.${item.homepage.section} or [ ]) ++ [ { ${item.homepage.name} = mkServiceAttrs item; }
{ ${item.homepage.name} = mkServiceAttrs item; } ];
]; };
};
}; };
entriesByTab = lib.foldl' insertEntry { } sortedEntries; entriesByTab = lib.foldl' insertEntry { } sortedEntries;
@@ -80,13 +84,15 @@ let
]; ];
}; };
orderedSections = tab: sections: orderedSections =
tab: sections:
let let
sectionOrder = sectionOrderByTab.${tab} or [ ]; sectionOrder = sectionOrderByTab.${tab} or [ ];
in in
sectionOrder ++ lib.filter (section: !lib.elem section sectionOrder) (lib.attrNames sections); sectionOrder ++ lib.filter (section: !lib.elem section sectionOrder) (lib.attrNames sections);
mkTab = tab: mkTab =
tab:
let let
sections = entriesByTab.${tab}; sections = entriesByTab.${tab};
in in
@@ -148,7 +154,9 @@ in
openFirewall = true; openFirewall = true;
inherit (cfg) listenPort; inherit (cfg) listenPort;
environmentFiles = [ cfg.environmentFile ]; environmentFiles = [ cfg.environmentFile ];
allowedHosts = lib.concatStringsSep "," (if cfg.allowedHosts == null then derivedAllowedHosts else cfg.allowedHosts); allowedHosts = lib.concatStringsSep "," (
if cfg.allowedHosts == null then derivedAllowedHosts else cfg.allowedHosts
);
services = homepageServices; services = homepageServices;
widgets = [ widgets = [
{ {

View File

@@ -50,7 +50,7 @@ in
user = "navidrome"; user = "navidrome";
group = "storage"; group = "storage";
openFirewall = true; openFirewall = true;
environmentFile = cfg.environmentFile; inherit (cfg) environmentFile;
settings = { settings = {
Address = "0.0.0.0"; Address = "0.0.0.0";
Port = 4533; Port = 4533;

View File

@@ -7,15 +7,20 @@
let let
cfg = config.modules.services.reverse-proxy; cfg = config.modules.services.reverse-proxy;
mkRegistryPath = registryName: host: entry: "registry.${registryName}.${host}.${entry}"; mkRegistryPath =
registryName: host: entry:
"registry.${registryName}.${host}.${entry}";
mkBackend = address: port: "http://${address}:${toString port}"; mkBackend = address: port: "http://${address}:${toString port}";
mkVirtualHost = host: entry: mode: mkVirtualHost =
host: entry: mode:
let let
compositeEntry = registry.join host entry; compositeEntry = registry.join host entry;
route = compositeEntry.route; inherit (compositeEntry) route;
address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}"); address =
compositeEntry.address
or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}");
isHttp = mode == "http"; isHttp = mode == "http";
port = if isHttp then route.http.port else route.port; port = if isHttp then route.http.port else route.port;
in in
@@ -63,7 +68,11 @@ let
domain: virtualHosts: domain: virtualHosts:
if lib.length virtualHosts > 1 then if lib.length virtualHosts > 1 then
[ [
"duplicate route domain ${domain} in ${lib.concatMapStringsSep " and " (virtualHost: mkRegistryPath virtualHost.registryName virtualHost.host virtualHost.entry) virtualHosts}" "duplicate route domain ${domain} in ${
lib.concatMapStringsSep " and " (
virtualHost: mkRegistryPath virtualHost.registryName virtualHost.host virtualHost.entry
) virtualHosts
}"
] ]
else else
[ ] [ ]
@@ -76,13 +85,21 @@ let
else else
lib.listToAttrs generatedVirtualHostList; lib.listToAttrs generatedVirtualHostList;
mkUdpStream = host: entry: port: mkUdpStream =
host: entry: port:
let let
compositeEntry = registry.join host entry; compositeEntry = registry.join host entry;
address = compositeEntry.address or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}"); address =
compositeEntry.address
or (throw "no matching entry in registry.address for ${mkRegistryPath "route" host entry}");
in in
{ {
inherit host entry port address; inherit
host
entry
port
address
;
}; };
generatedUdpStreams = lib.concatLists ( generatedUdpStreams = lib.concatLists (

View File

@@ -10,7 +10,8 @@ let
user = config.user.name; user = config.user.name;
in in
{ {
options.modules.shell.bazel.enable = lib.mkEnableOption "Bazel for the primary user's shell environment"; options.modules.shell.bazel.enable =
lib.mkEnableOption "Bazel for the primary user's shell environment";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home-manager.users.${user}.home.packages = [ pkgs.bazel ]; home-manager.users.${user}.home.packages = [ pkgs.bazel ];

View File

@@ -107,8 +107,7 @@ in
# Rendered by home-manager into ~/.config/fish/config.fish. # Rendered by home-manager into ~/.config/fish/config.fish.
interactiveShellInit = lib.concatStringsSep "\n" ( interactiveShellInit = lib.concatStringsSep "\n" (
(lib.mapAttrsToList exportEnvVar envVars) (lib.mapAttrsToList exportEnvVar envVars) ++ [ (builtins.readFile ./config.fish) ]
++ [ (builtins.readFile ./config.fish) ]
); );
}; };
}; };

View File

@@ -10,7 +10,8 @@ let
user = config.user.name; user = config.user.name;
in in
{ {
options.modules.shell.herdr.enable = lib.mkEnableOption "Herdr, a terminal multiplexer for coding agents"; options.modules.shell.herdr.enable =
lib.mkEnableOption "Herdr, a terminal multiplexer for coding agents";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
registry.shell-shortcut.${config.networking.hostName}.hs = "herdr --session"; registry.shell-shortcut.${config.networking.hostName}.hs = "herdr --session";

View File

@@ -11,7 +11,8 @@ let
user = config.user.name; user = config.user.name;
in in
{ {
options.modules.shell.nvim.enable = lib.mkEnableOption "Neovim, configured declaratively via nixvim"; options.modules.shell.nvim.enable =
lib.mkEnableOption "Neovim, configured declaratively via nixvim";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
registry.shell-shortcut.${config.networking.hostName} = { registry.shell-shortcut.${config.networking.hostName} = {

View File

@@ -18,8 +18,7 @@ let
in in
{ {
options.modules.shell.pi = { options.modules.shell.pi = {
enable = lib.mkEnableOption '' enable = lib.mkEnableOption "Pi, a terminal coding agent, configured through home-manager";
Pi, a terminal coding agent, configured through home-manager'';
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {

View File

@@ -10,7 +10,8 @@ let
user = config.user.name; user = config.user.name;
in in
{ {
options.modules.shell.python.enable = lib.mkEnableOption "Python tools for the primary user's shell environment"; options.modules.shell.python.enable =
lib.mkEnableOption "Python tools for the primary user's shell environment";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
registry.env-var.${config.networking.hostName}.PYTHON_TOOLCHAIN_ROOT = "${pkgs.python3}"; registry.env-var.${config.networking.hostName}.PYTHON_TOOLCHAIN_ROOT = "${pkgs.python3}";

View File

@@ -20,7 +20,8 @@ let
}; };
in in
{ {
options.modules.shell.rust.enable = lib.mkEnableOption "Rust tools for the primary user's shell environment"; options.modules.shell.rust.enable =
lib.mkEnableOption "Rust tools for the primary user's shell environment";
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
registry.env-var.${config.networking.hostName}.RUST_TOOLCHAIN_ROOT = "${toolchain}"; registry.env-var.${config.networking.hostName}.RUST_TOOLCHAIN_ROOT = "${toolchain}";

View File

@@ -6,14 +6,16 @@
message = "Route domains must be unique."; message = "Route domains must be unique.";
} }
{ {
assertion = entries: assertion =
entries:
lib.registry.predicates.uniqueValue (entry: entry.value) ( lib.registry.predicates.uniqueValue (entry: entry.value) (
lib.concatMap (entry: map (port: entry // { value = port; }) entry.value.udp.ports) entries lib.concatMap (entry: map (port: entry // { value = port; }) entry.value.udp.ports) entries
); );
message = "Route UDP ports must be unique."; message = "Route UDP ports must be unique.";
} }
{ {
assertion = entries: lib.all (entry: entry.value.port == null || entry.value.http.port == null) entries; assertion =
entries: lib.all (entry: entry.value.port == null || entry.value.http.port == null) entries;
message = "Route entries must not set both port and http.port."; message = "Route entries must not set both port and http.port.";
} }
]; ];

View File

@@ -7,12 +7,14 @@ let
unique unique
; ;
serviceNamesByUid = entries: serviceNamesByUid =
entries:
mapAttrs (_uid: uidEntries: unique (map (entry: entry.entry) uidEntries)) ( mapAttrs (_uid: uidEntries: unique (map (entry: entry.entry) uidEntries)) (
groupBy (entry: toString entry.value) entries groupBy (entry: toString entry.value) entries
); );
duplicateUids = entries: duplicateUids =
entries:
filterAttrs (_uid: serviceNames: builtins.length serviceNames > 1) (serviceNamesByUid entries); filterAttrs (_uid: serviceNames: builtins.length serviceNames > 1) (serviceNamesByUid entries);
in in

View File

@@ -78,7 +78,7 @@
{ self, nixpkgs, ... }@inputs: { self, nixpkgs, ... }@inputs:
import ./src/dotfiles { import ./src/dotfiles {
inherit inputs self; inherit inputs self;
lib = nixpkgs.lib; inherit (nixpkgs) lib;
root = ./configs/nixos; root = ./configs/nixos;
system = "x86_64-linux"; system = "x86_64-linux";
}; };

View File

@@ -10,19 +10,21 @@ in
{ {
fix = { fix = {
type = "app"; type = "app";
program = lib.getExe (pkgs.writeShellApplication { program = lib.getExe (
name = "fix"; pkgs.writeShellApplication {
runtimeInputs = [ name = "fix";
formatter.config.build.wrapper runtimeInputs = [
pkgs.statix formatter.config.build.wrapper
pkgs.deadnix pkgs.statix
]; pkgs.deadnix
text = '' ];
treefmt text = ''
statix fix . treefmt
deadnix --edit --exclude configs/nixos/hosts/*/hardware-configuration.nix -- . statix fix .
treefmt deadnix --edit --exclude configs/nixos/hosts/*/hardware-configuration.nix -- .
''; treefmt
}); '';
}
);
}; };
} }

View File

@@ -17,16 +17,13 @@ let
directoryMounts = lib.filterAttrs (_: mount: mount.directory != null) cfg.mounts; directoryMounts = lib.filterAttrs (_: mount: mount.directory != null) cfg.mounts;
directoryScript = lib.concatStringsSep "\n" ( directoryScript = lib.concatStringsSep "\n" (
lib.mapAttrsToList ( lib.mapAttrsToList (_: mount: ''
_: mount: ${config.system.path}/bin/install -d \
'' ${lib.optionalString (mount.owner != null) "-o ${lib.escapeShellArg (toString mount.owner)}"} \
${config.system.path}/bin/install -d \ ${lib.optionalString (mount.group != null) "-g ${lib.escapeShellArg mount.group}"} \
${lib.optionalString (mount.owner != null) "-o ${lib.escapeShellArg (toString mount.owner)}"} \ ${lib.optionalString (mount.mode != null) "-m ${lib.escapeShellArg mount.mode}"} \
${lib.optionalString (mount.group != null) "-g ${lib.escapeShellArg mount.group}"} \ ${lib.escapeShellArg mount.directory}
${lib.optionalString (mount.mode != null) "-m ${lib.escapeShellArg mount.mode}"} \ '') directoryMounts
${lib.escapeShellArg mount.directory}
''
) directoryMounts
); );
in in
{ {
@@ -171,14 +168,20 @@ let
assertions = assertions =
let let
hasDirectorySettings = mount: mount.owner != null || mount.group != null || mount.mode != null; hasDirectorySettings = mount: mount.owner != null || mount.group != null || mount.mode != null;
hasCompleteDirectorySettings = mount: mount.owner != null && mount.group != null && mount.mode != null; hasCompleteDirectorySettings =
mount: mount.owner != null && mount.group != null && mount.mode != null;
hasOneSource = mount: (mount.directory != null) != (mount.file != null); hasOneSource = mount: (mount.directory != null) != (mount.file != null);
mountsWithInvalidSource = lib.attrNames (lib.filterAttrs (_: mount: !hasOneSource mount) cfg.mounts); mountsWithInvalidSource = lib.attrNames (
lib.filterAttrs (_: mount: !hasOneSource mount) cfg.mounts
);
fileMountsWithHostSettings = lib.attrNames ( fileMountsWithHostSettings = lib.attrNames (
lib.filterAttrs (_: mount: mount.file != null && hasDirectorySettings mount) cfg.mounts lib.filterAttrs (_: mount: mount.file != null && hasDirectorySettings mount) cfg.mounts
); );
directoryMountsWithPartialHostSettings = lib.attrNames ( directoryMountsWithPartialHostSettings = lib.attrNames (
lib.filterAttrs (_: mount: mount.directory != null && hasDirectorySettings mount && !hasCompleteDirectorySettings mount) cfg.mounts lib.filterAttrs (
_: mount:
mount.directory != null && hasDirectorySettings mount && !hasCompleteDirectorySettings mount
) cfg.mounts
); );
in in
[ [
@@ -203,20 +206,23 @@ let
{ {
assertion = lib.elem cfg.vlan config.modules.network.vlans.ids; assertion = lib.elem cfg.vlan config.modules.network.vlans.ids;
message = '' message = ''
guests.${name}.vlan = ${toString cfg.vlan} is not among its host's modules.network.vlans.ids (${lib.concatMapStringsSep ", " toString config.modules.network.vlans.ids}). Declare the VLAN on the host or correct the guest's placement. guests.${name}.vlan = ${toString cfg.vlan} is not among its host's modules.network.vlans.ids (${
lib.concatMapStringsSep ", " toString config.modules.network.vlans.ids
}). Declare the VLAN on the host or correct the guest's placement.
''; '';
} }
]; ];
registry.address.${config.networking.hostName}.${name} = registry.address.${config.networking.hostName}.${name} = lib.mkIf (cfg.address != null) cfg.address;
lib.mkIf (cfg.address != null) cfg.address;
registry.route.${config.networking.hostName}.${name} = registry.route.${config.networking.hostName}.${name} =
lib.mkIf cfg.reverseProxy.enable cfg.reverseProxy.route; lib.mkIf cfg.reverseProxy.enable cfg.reverseProxy.route;
systemd.services."container@${name}" = systemd.services."container@${name}" =
let let
bridgeDevice = "sys-subsystem-net-devices-${lib.replaceStrings [ "-" ] [ "\\x2d" ] (util.bridgeName cfg.vlan)}.device"; bridgeDevice = "sys-subsystem-net-devices-${
lib.replaceStrings [ "-" ] [ "\\x2d" ] (util.bridgeName cfg.vlan)
}.device";
in in
{ {
after = [ bridgeDevice ]; after = [ bridgeDevice ];
@@ -268,10 +274,11 @@ let
}; };
}; };
}; };
in
let
files = util.collectNixFiles root; files = util.collectNixFiles root;
guestFiles = lib.filterAttrs (file: type: type == "regular" && lib.hasSuffix ".nix" file) (builtins.readDir root); guestFiles = lib.filterAttrs (file: type: type == "regular" && lib.hasSuffix ".nix" file) (
builtins.readDir root
);
names = map (file: lib.removeSuffix ".nix" file) (lib.attrNames guestFiles); names = map (file: lib.removeSuffix ".nix" file) (lib.attrNames guestFiles);
in in
{ {

View File

@@ -26,7 +26,8 @@ let
guestModules guestModules
# TODO: refactor # TODO: refactor
registryModule (root + "/${name}") registryModule
(root + "/${name}")
{ {
networking.hostName = name; networking.hostName = name;
@@ -34,12 +35,8 @@ let
} }
]; ];
}; };
in
let
directories = lib.filterAttrs (_name: type: type == "directory") (builtins.readDir root); directories = lib.filterAttrs (_name: type: type == "directory") (builtins.readDir root);
names = lib.attrNames directories; names = lib.attrNames directories;
in in
lib.genAttrs names ( lib.genAttrs names (name: mkHost { inherit root name; })
name:
mkHost { inherit root name; }
)

View File

@@ -1,4 +1,8 @@
{ hosts, lib, root }: {
hosts,
lib,
root,
}:
let let
checkType = lib.types.submodule { checkType = lib.types.submodule {
options = { options = {
@@ -19,7 +23,8 @@ let
}; };
predicates = { predicates = {
uniqueValue = value: entries: builtins.length (lib.unique (map value entries)) == builtins.length entries; uniqueValue =
value: entries: builtins.length (lib.unique (map value entries)) == builtins.length entries;
}; };
registryLib = lib // { registryLib = lib // {
@@ -57,15 +62,18 @@ let
registryName = fileName: lib.removeSuffix ".nix" fileName; registryName = fileName: lib.removeSuffix ".nix" fileName;
registryOption = registry: lib.mkOption { registryOption =
type = lib.types.attrsOf (lib.types.attrsOf registry.type); registry:
default = { }; lib.mkOption {
description = '' type = lib.types.attrsOf (lib.types.attrsOf registry.type);
Lazily collected fleet-visible registry entries, nested by host and entry name. default = { };
''; description = ''
}; Lazily collected fleet-visible registry entries, nested by host and entry name.
'';
};
mkRegistry = path: mkRegistry =
path:
let let
evaluated = lib.evalModules { evaluated = lib.evalModules {
specialArgs.lib = registryLib; specialArgs.lib = registryLib;
@@ -79,15 +87,21 @@ let
inherit (evaluated.config) type replicate checks; inherit (evaluated.config) type replicate checks;
}; };
definitions = lib.mapAttrs' ( definitions =
name: type: lib.mapAttrs'
let (
path = root + "/${name}"; name: _type:
in let
lib.nameValuePair (registryName name) (mkRegistry path) path = root + "/${name}";
) (lib.filterAttrs (name: type: type == "regular" && lib.hasSuffix ".nix" name) (builtins.readDir root)); in
lib.nameValuePair (registryName name) (mkRegistry path)
)
(
lib.filterAttrs (name: type: type == "regular" && lib.hasSuffix ".nix" name) (builtins.readDir root)
);
flattenRegistryEntries = registryName: data: flattenRegistryEntries =
registryName: data:
lib.concatLists ( lib.concatLists (
lib.mapAttrsToList ( lib.mapAttrsToList (
host: entries: host: entries:
@@ -98,10 +112,12 @@ let
) data ) data
); );
rawDeclarations = registryName: hosts: rawDeclarations =
registryName: hosts:
lib.mapAttrs (_hostName: host: host.config.registry.${registryName} or { }) hosts; lib.mapAttrs (_hostName: host: host.config.registry.${registryName} or { }) hosts;
replicatedEntries = registryName: raw: replicatedEntries =
registryName: raw:
let let
entries = lib.concatLists ( entries = lib.concatLists (
lib.concatLists ( lib.concatLists (
@@ -126,18 +142,21 @@ let
else else
throw "conflicting replicated registry entry ${entry.path} does not match ${acc.${entry.entryName}.path}" throw "conflicting replicated registry entry ${entry.path} does not match ${acc.${entry.entryName}.path}"
else else
acc // { acc
// {
${entry.entryName} = entry; ${entry.entryName} = entry;
} }
) { } entries; ) { } entries;
replicatedData = registryName: hosts: raw: replicatedData =
registryName: hosts: raw:
let let
entries = lib.mapAttrs (_entryName: entry: entry.value) (replicatedEntries registryName raw); entries = lib.mapAttrs (_entryName: entry: entry.value) (replicatedEntries registryName raw);
in in
lib.mapAttrs (_hostName: _host: entries) hosts; lib.mapAttrs (_hostName: _host: entries) hosts;
validationAssertions = data: validationAssertions =
data:
lib.concatLists ( lib.concatLists (
lib.mapAttrsToList ( lib.mapAttrsToList (
registryName: registry: registryName: registry:
@@ -151,7 +170,8 @@ let
) definitions ) definitions
); );
assertValid = data: assertValid =
data:
let let
assertions = validationAssertions data; assertions = validationAssertions data;
failures = lib.filter (assertion: !assertion.assertion) assertions; failures = lib.filter (assertion: !assertion.assertion) assertions;
@@ -161,9 +181,12 @@ let
); );
data; data;
join = registry: host: entry: join =
registry: host: entry:
lib.filterAttrs (_name: value: value != null) ( lib.filterAttrs (_name: value: value != null) (
lib.mapAttrs (registryName: _registry: (registry.${registryName}.${host} or { }).${entry} or null) definitions lib.mapAttrs (
registryName: _registry: (registry.${registryName}.${host} or { }).${entry} or null
) definitions
); );
collectData = collectData =
@@ -180,7 +203,8 @@ let
in in
assertValid data; assertValid data;
withOperations = registry: withOperations =
registry:
registry registry
// { // {
join = host: entry: join registry host entry; join = host: entry: join registry host entry;
@@ -190,8 +214,10 @@ in
types = lib.mapAttrs (_name: definition: definition.type) definitions; types = lib.mapAttrs (_name: definition: definition.type) definitions;
module = { module = {
options.registry = lib.mapAttrs (_name: registry: registryOption registry) definitions; options.registry = lib.mapAttrs (_name: registryOption) definitions;
}; };
collected = withOperations (collectData { inherit hosts; }); collected = withOperations (collectData {
inherit hosts;
});
} }

View File

@@ -28,5 +28,14 @@ in
hash = builtins.hashString "sha256" name; hash = builtins.hashString "sha256" name;
octet = i: builtins.substring (i * 2) 2 hash; octet = i: builtins.substring (i * 2) 2 hash;
in in
lib.concatStringsSep ":" ([ "02" ] ++ map octet [ 0 1 2 3 4 ]); lib.concatStringsSep ":" (
[ "02" ]
++ map octet [
0
1
2
3
4
]
);
} }