387 lines
12 KiB
Nix
387 lines
12 KiB
Nix
{lib, ...}:
|
|
with lib; rec {
|
|
## Create a NixOS module option.
|
|
##
|
|
## ```nix
|
|
## lib.mkOpt nixpkgs.lib.types.str "My default" "Description of my option."
|
|
## ```
|
|
##
|
|
#@ Type -> Any -> String
|
|
mkOpt = type: default: description:
|
|
mkOption {inherit type default description;};
|
|
|
|
## Create a NixOS module option without a description.
|
|
##
|
|
## ```nix
|
|
## lib.mkOpt' nixpkgs.lib.types.str "My default"
|
|
## ```
|
|
##
|
|
#@ Type -> Any -> String
|
|
mkOpt' = type: default: mkOpt type default null;
|
|
|
|
## Create a required NixOS module option (no default).
|
|
##
|
|
## ```nix
|
|
## lib.mkRequiredOpt nixpkgs.lib.types.str "Description of my option."
|
|
## ```
|
|
##
|
|
#@ Type -> String -> Option
|
|
mkRequiredOpt = type: description:
|
|
mkOption {
|
|
inherit type description;
|
|
default = null;
|
|
};
|
|
|
|
## Create a boolean NixOS module option.
|
|
##
|
|
## ```nix
|
|
## lib.mkBoolOpt true "Description of my option."
|
|
## ```
|
|
##
|
|
#@ Type -> Any -> String
|
|
mkBoolOpt = mkOpt types.bool;
|
|
|
|
## Create a boolean NixOS module option without a description.
|
|
##
|
|
## ```nix
|
|
## lib.mkBoolOpt true
|
|
## ```
|
|
##
|
|
#@ Type -> Any -> String
|
|
mkBoolOpt' = mkOpt' types.bool;
|
|
|
|
## Create a nullable NixOS module option.
|
|
##
|
|
## ```nix
|
|
## lib.mkNullableOpt types.str null "Description of my option."
|
|
## ```
|
|
##
|
|
#@ Type -> Any -> String
|
|
mkNullableOpt = type: default: description:
|
|
mkOpt (types.nullOr type) default description;
|
|
|
|
## Create a nullable NixOS module option without a description.
|
|
##
|
|
## ```nix
|
|
## lib.mkNullableOpt' types.str null
|
|
## ```
|
|
##
|
|
#@ Type -> Any -> String
|
|
mkNullableOpt' = type: default: mkNullableOpt type default null;
|
|
|
|
## Create an attrsOf NixOS module option.
|
|
##
|
|
## ```nix
|
|
## lib.mkOptAttrset types.str {} "Description of my option."
|
|
## ```
|
|
##
|
|
#@ Type -> Any -> String
|
|
mkOptAttrset = type: default: description:
|
|
mkOpt (types.attrsOf type) default description;
|
|
|
|
## Create an attrsOf NixOS module option without a description.
|
|
##
|
|
## ```nix
|
|
## lib.mkOptAttrset' types.str {}
|
|
## ```
|
|
##
|
|
#@ Type -> Any -> String
|
|
mkOptAttrset' = type: default: mkOptAttrset type default null;
|
|
|
|
## Create a store path that symlinks to a path outside the Nix store.
|
|
##
|
|
## This is useful for mutable/decrypted files that should not be copied into
|
|
## the store, matching Home Manager's mkOutOfStoreSymlink behavior.
|
|
mkOutOfStoreSymlink = pkgs: path:
|
|
pkgs.runCommandLocal "${baseNameOf path}" {} ''
|
|
ln -s ${lib.escapeShellArg path} $out
|
|
'';
|
|
|
|
## Create a listOf NixOS module option.
|
|
##
|
|
## ```nix
|
|
## lib.mkListOpt types.str [] "Description of my option."
|
|
## ```
|
|
##
|
|
#@ Type -> Any -> String
|
|
mkListOpt = type: default: description:
|
|
mkOpt (types.listOf type) default description;
|
|
|
|
## Create a listOf NixOS module option without a description.
|
|
##
|
|
## ```nix
|
|
## lib.mkListOpt' types.str []
|
|
## ```
|
|
##
|
|
#@ Type -> Any -> String
|
|
mkListOpt' = type: default: mkListOpt type default null;
|
|
|
|
## Quickly enable an option.
|
|
##
|
|
## ```nix
|
|
## services.nginx = enabled;
|
|
## ```
|
|
##
|
|
#@ true
|
|
enabled = {
|
|
enable = true;
|
|
};
|
|
|
|
## Quickly disable an option.
|
|
##
|
|
## ```nix
|
|
## services.nginx = enabled;
|
|
## ```
|
|
##
|
|
#@ false
|
|
disabled = {
|
|
enable = false;
|
|
};
|
|
|
|
## Recursively nest an attribute set under a list of keys.
|
|
##
|
|
## For example:
|
|
## ```nix
|
|
## mkNestedAttrs { enable = true; } [ "foo" "bar" ]
|
|
## ```
|
|
## returns:
|
|
## ```nix
|
|
## { foo = { bar = { enable = true; }; }; }
|
|
## ```
|
|
##
|
|
## @param attrs Attribute set to nest.
|
|
## @param keys List of keys (strings) to nest under.
|
|
## @return Nested attribute set.
|
|
mkNestedAttrs = attrs: keys:
|
|
if keys == []
|
|
then attrs
|
|
else {
|
|
"${lib.head keys}" = mkNestedAttrs attrs (lib.tail keys);
|
|
};
|
|
|
|
## Nest an attribute set under a dot-separated namespace string.
|
|
##
|
|
## For example:
|
|
## ```nix
|
|
## mkOptionsWithNamespace "foo.bar" { enable = true; }
|
|
## ```
|
|
## returns:
|
|
## ```nix
|
|
## { foo = { bar = { enable = true; }; }; }
|
|
## ```
|
|
##
|
|
## @param namespace A string like "foo.bar.baz".
|
|
## @param options An attribute set to nest under the namespace.
|
|
## @return A nested attribute set.
|
|
mkOptionsWithNamespace = namespace: options:
|
|
namespace
|
|
|> splitString "."
|
|
|> mkNestedAttrs options;
|
|
|
|
## Get a deeply nested attribute from a dot-separated path string.
|
|
##
|
|
## For example:
|
|
## ```nix
|
|
## getAttrByNamespace "foo.bar.baz" config
|
|
## ```
|
|
## is equivalent to:
|
|
## ```nix
|
|
## lib.getAttrFromPath [ "foo" "bar" "baz" ] config
|
|
## ```
|
|
##
|
|
## @param set The attribute set to query.
|
|
## @param namespace Dot-separated string path.
|
|
## @return The value at the nested path.
|
|
getAttrByNamespace = set: namespace:
|
|
namespace
|
|
|> splitString "."
|
|
|> (path: getAttrFromPath path set);
|
|
|
|
## Resolve the IP address of a given host from a devices attribute set.
|
|
##
|
|
## @param devices A set mapping hostnames to their configuration (must include `IP`).
|
|
## @param node The hostname to resolve.
|
|
## @return The IP address for the given node.
|
|
## @throws If the node is not defined in the devices set.
|
|
resolveHostIP = devices: node:
|
|
if builtins.hasAttr node devices
|
|
then devices.${node}.ip
|
|
else throw "Host '${node}' does not exist in the devices configuration.";
|
|
|
|
## Return whether a given host is a gateway from a devices attribute set.
|
|
##
|
|
## @param devices A set mapping hostnames to their configuration (must include `IP`).
|
|
## @param node The hostname to resolve.
|
|
## @return The IP address for the given node.
|
|
## @throws If the node is not defined in the devices set.
|
|
isGateway = devices: node:
|
|
if builtins.hasAttr node devices
|
|
then devices.${node}.gateway
|
|
else false;
|
|
|
|
## Resolve the hostname that hosts a specific database.
|
|
##
|
|
## @param databases A set mapping hostnames to lists of database names.
|
|
## @param dbName The name of the database to find.
|
|
## @return The hostname where the database is hosted.
|
|
## @throws If no host is found for the given database.
|
|
resolveDatabaseHost = databases: dbName: let
|
|
matchingHosts =
|
|
databases
|
|
|> filterAttrs (host: dbs: lib.elem dbName dbs)
|
|
|> attrNames;
|
|
in
|
|
if matchingHosts == []
|
|
then throw "No host found for database '${dbName}'."
|
|
else builtins.head matchingHosts;
|
|
|
|
## Resolve the IP address of the host that hosts a specific database.
|
|
##
|
|
## @param devices A set mapping hostnames to their configuration (must include `ip`).
|
|
## @param databases A set mapping hostnames to lists of database names.
|
|
## @param dbName The name of the database to find.
|
|
## @return The IP address of the host where the database is hosted.
|
|
## @throws If no host is found for the given database.
|
|
resolveDatabaseIP = devices: databases: dbName:
|
|
resolveHostIP devices (resolveDatabaseHost databases dbName);
|
|
|
|
## Check for configuration conflicts between mount and share declarations.
|
|
##
|
|
## @param shares A list of folder names that are shared.
|
|
## @param hostName The name of the current host.
|
|
## @param folder The folder being mounted or shared.
|
|
## @param host The host the folder is being mounted from.
|
|
## @return Null if valid; throws an error if a conflict is detected.
|
|
## @throws If the host is self-mounting or if the folder appears in both shares and mounts.
|
|
checkHostMountConflict = {
|
|
shares,
|
|
hostName,
|
|
}: folder: host:
|
|
if host == hostName
|
|
then throw "Conflict: Mount host '${host}' cannot be the same as this host '${hostName}' for folder '${folder}'."
|
|
else if builtins.elem folder shares
|
|
then throw "Conflict: Folder '${folder}' is listed in both shares and mounts. Please resolve."
|
|
else null;
|
|
|
|
## Attempt to read a JSON file and return null of not valid JSON.
|
|
##
|
|
## @param path path to the json file to read
|
|
##
|
|
## @return JSON if valid; returns {} if invalid JSON.
|
|
readJsonOrEmpty = path: let
|
|
result =
|
|
path
|
|
|> builtins.readFile
|
|
|> builtins.fromJSON
|
|
|> builtins.tryEval;
|
|
in
|
|
if result.success
|
|
then result.value
|
|
else {};
|
|
|
|
## Safe get for deeply nested keys
|
|
##
|
|
## @param pathStr dot-separated list for the nested attribute to retrieve
|
|
## @param attrs attribute list for getting the keys
|
|
## @return attribute value if found; otherwise null
|
|
getIn = pathStr: attrs: let
|
|
path = splitString "." pathStr;
|
|
go = attrs: p:
|
|
if p == []
|
|
then attrs
|
|
else let
|
|
key = builtins.head p;
|
|
rest = builtins.tail p;
|
|
in
|
|
if builtins.hasAttr key attrs
|
|
then go (attrs.${key}) rest
|
|
else null;
|
|
in
|
|
go attrs path;
|
|
|
|
## Convert a host-first service map into a flat service->attrs map.
|
|
##
|
|
## Input: { hostName = { serviceName = { ... }; ... }; ... }
|
|
## Output: { serviceName = { host = hostName; ... }; ... }
|
|
##
|
|
## @param hostServices Attribute set mapping host names to their service maps.
|
|
## @return Flattened map with service names as keys, augmented with host.
|
|
## @throws If duplicate service names exist across different hosts.
|
|
flattenHostServices = hostServices: let
|
|
perHostEntries =
|
|
mapAttrsToList (
|
|
host: services:
|
|
mapAttrsToList (
|
|
svcName: svc: {
|
|
name = svcName;
|
|
value = svc // {host = host;};
|
|
}
|
|
)
|
|
services
|
|
)
|
|
hostServices;
|
|
|
|
flattenedList = concatLists perHostEntries;
|
|
flattenedAttrs = listToAttrs flattenedList;
|
|
in
|
|
if length flattenedList != length (attrNames flattenedAttrs)
|
|
then throw "flattenHostServices: duplicate service names across hosts detected"
|
|
else flattenedAttrs;
|
|
|
|
## Check if a host has a specific service defined in the network-services config.
|
|
##
|
|
## @param networkServices The network-services attribute set (host-first shape).
|
|
## @param hostName The hostname to check.
|
|
## @param serviceName The service name to check for.
|
|
## @return true if the host has the specific service defined, false otherwise.
|
|
hostHasService = networkServices: hostName: serviceName:
|
|
networkServices ? ${hostName}
|
|
&& networkServices.${hostName} ? ${serviceName};
|
|
|
|
## Resolve the port for a service from the network-services map.
|
|
##
|
|
## @param networkServices The network-services attribute set (host-first shape).
|
|
## @param serviceName The name of the service.
|
|
## @param defaultPort The default port to use if not specified in network-services.
|
|
## @return The port (either from config or default).
|
|
resolveServicePort = networkServices: serviceName: defaultPort:
|
|
(flattenHostServices networkServices).${serviceName}.port or defaultPort;
|
|
|
|
## Resolve the host for a service from the network-services map.
|
|
##
|
|
## @param networkServices The network-services attribute set (host-first shape).
|
|
## @param serviceName The name of the service.
|
|
## @return The host name where the service is defined.
|
|
resolveServiceHost = networkServices: serviceName:
|
|
(flattenHostServices networkServices).${serviceName}.host;
|
|
|
|
## Create an impermanence persistence directory owned by a service user.
|
|
##
|
|
## ```nix
|
|
## mkPersistDir config "forgejo" "/var/lib/forgejo"
|
|
## ```
|
|
##
|
|
## @param config The module config (passed from the module).
|
|
## @param username The service username (must exist in config.users.users).
|
|
## @param directory The directory path to persist.
|
|
## @return An attrset for the impermanence.folders list.
|
|
mkPersistDir = config: username: directory: let
|
|
u = config.users.users.${username};
|
|
in {
|
|
inherit directory;
|
|
user = u.name;
|
|
group = u.group;
|
|
mode = "700";
|
|
};
|
|
|
|
## Create an impermanence persistence directory owned by root.
|
|
##
|
|
## ```nix
|
|
## mkPersistRootDir config "/var/lib/docker"
|
|
## ```
|
|
##
|
|
## @param config The module config (passed from the module).
|
|
## @param directory The directory path to persist.
|
|
## @return An attrset for the impermanence.folders list.
|
|
mkPersistRootDir = config: directory: mkPersistDir config "root" directory;
|
|
}
|