Compare commits

..

2 commits

Author SHA1 Message Date
e2f312b2c5
Improve wording in messages 2026-03-13 17:39:38 +01:00
e69e9c8491
Avoid usage of lib.types.anything 2026-03-13 17:25:12 +01:00
12 changed files with 37 additions and 33 deletions

View file

@ -11,8 +11,11 @@
genAttrs = f: names: lib.genAttrs names f; genAttrs = f: names: lib.genAttrs names f;
mkInvalidConfigMessage = subject: reason: "Invalid configuration for ${subject}: ${reason}.";
mkUnprotectedMessage = mkUnprotectedMessage =
name: "${name} should only be exposed on private networks; access control isn't yet configured"; name:
self.lib.mkInvalidConfigMessage name "the service must use a private domain until access control is configured";
relativePath = path: path |> toString |> lib.removePrefix "${self}/"; relativePath = path: path |> toString |> lib.removePrefix "${self}/";

View file

@ -58,7 +58,7 @@ in
systemd.user.services.ntfy-client = { systemd.user.services.ntfy-client = {
Install.WantedBy = [ "graphical-session.target" ]; Install.WantedBy = [ "graphical-session.target" ];
Unit = { Unit = {
Description = "ntfy client subscriber"; Description = "ntfy client subscription";
PartOf = [ "graphical-session.target" ]; PartOf = [ "graphical-session.target" ];
After = [ "graphical-session.target" ]; After = [ "graphical-session.target" ];
X-Restart-Triggers = [ config.xdg.configFile."ntfy/client.yml".source ]; X-Restart-Triggers = [ config.xdg.configFile."ntfy/client.yml".source ];

View file

@ -26,7 +26,7 @@ in
default = "${self}/users/${config.home.username}/@${osConfig.networking.hostName}/secrets.json"; default = "${self}/users/${config.home.username}/@${osConfig.networking.hostName}/secrets.json";
}; };
secretsData = lib.mkOption { secretsData = lib.mkOption {
type = lib.types.anything; type = lib.types.attrs;
default = cfg.secretsFile |> lib.readFile |> lib.strings.fromJSON; default = cfg.secretsFile |> lib.readFile |> lib.strings.fromJSON;
}; };
}; };
@ -43,7 +43,7 @@ in
|> lib.attrNames |> lib.attrNames
|> lib.map (secretPath: { |> lib.map (secretPath: {
assertion = cfg.secretsData |> lib.hasAttrByPath (secretPath |> lib.splitString "/"); assertion = cfg.secretsData |> lib.hasAttrByPath (secretPath |> lib.splitString "/");
message = "Sops secret `${secretPath}` is used in a module but not defined in secrets.json"; message = self.lib.mkInvalidConfigMessage "SOPS secret `${secretPath}`" "it is used in a module but not defined in `secrets.json`";
}) })
) )
++ ( ++ (
@ -51,7 +51,7 @@ in
|> lib.mapAttrsToListRecursive (path: _: path |> lib.concatStringsSep "/") |> lib.mapAttrsToListRecursive (path: _: path |> lib.concatStringsSep "/")
|> lib.map (secretPath: { |> lib.map (secretPath: {
assertion = config.sops.secrets |> lib.hasAttr secretPath; assertion = config.sops.secrets |> lib.hasAttr secretPath;
message = "Sops secret `${secretPath}` is defined in secrets.json but not used in any module"; message = self.lib.mkInvalidConfigMessage "SOPS secret `${secretPath}`" "it is defined in `secrets.json` but not used in any module";
}) })
); );
}; };

View file

@ -16,7 +16,7 @@ in
}; };
nodes = lib.mkOption { nodes = lib.mkOption {
type = lib.types.anything; type = lib.types.listOf lib.types.attrs;
default = default =
allHosts allHosts
|> lib.attrValues |> lib.attrValues
@ -31,7 +31,7 @@ in
readOnly = true; readOnly = true;
}; };
peers = lib.mkOption { peers = lib.mkOption {
type = lib.types.anything; type = lib.types.listOf lib.types.attrs;
default = cfg.nodes |> lib.filter (node: node.hostName != cfg.hostName); default = cfg.nodes |> lib.filter (node: node.hostName != cfg.hostName);
readOnly = true; readOnly = true;
}; };

View file

@ -59,7 +59,7 @@ in
}; };
dnsServers = lib.mkOption { dnsServers = lib.mkOption {
type = lib.types.anything; type = lib.types.listOf lib.types.nonEmptyStr;
default = default =
allHosts allHosts
|> lib.attrValues |> lib.attrValues

View file

@ -1,5 +1,6 @@
{ {
config, config,
self,
lib, lib,
allHosts, allHosts,
... ...
@ -38,7 +39,7 @@ in
|> lib.filter (name: name != "system") |> lib.filter (name: name != "system")
|> lib.map (name: { |> lib.map (name: {
assertion = cfg.collect.metrics.${name} -> config.services.${name}.enable; assertion = cfg.collect.metrics.${name} -> config.services.${name}.enable;
message = "Alloy cannot collect `${name}` metrics without the `${name}` service"; message = self.lib.mkInvalidConfigMessage "Alloy metric collection for `${name}`" "the `${name}` service is not enabled";
}); });
services.alloy = { services.alloy = {

View file

@ -91,11 +91,11 @@ in
|> lib.concatMap (vHost: [ |> lib.concatMap (vHost: [
{ {
assertion = (vHost.port == null) || (vHost.files == null); assertion = (vHost.port == null) || (vHost.files == null);
message = "Caddy virtual host `${vHost.domain}` cannot set both `port` and `files`"; message = self.lib.mkInvalidConfigMessage "Caddy virtual host `${vHost.domain}`" "`port` and `files` cannot be set at the same time";
} }
{ {
assertion = (vHost.port != null) || (vHost.files != null) || (vHost.extraConfig != null); assertion = (vHost.port != null) || (vHost.files != null) || (vHost.extraConfig != null);
message = "Caddy virtual host `${vHost.domain}` must set at least one of `port`, `files` or `extraConfig`"; message = self.lib.mkInvalidConfigMessage "Caddy virtual host `${vHost.domain}`" "one of `port`, `files` or `extraConfig` must be set";
} }
]); ]);

View file

@ -62,7 +62,7 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
assertions = lib.singleton { assertions = lib.singleton {
assertion = netCfg.overlay.isLighthouse -> cfg.advertise.address != null; assertion = netCfg.overlay.isLighthouse -> cfg.advertise.address != null;
message = "`${netCfg.hostName}` is a Nebula lighthouse, but `underlay.isPublic` or `overlay.advertise.address` are not set. Lighthouses must be publicly reachable."; message = self.lib.mkInvalidConfigMessage "Nebula lighthouse `${netCfg.hostName}`" "`underlay.isPublic` must be enabled or `services.nebula.advertise.address` must be set so the host is publicly reachable";
}; };
sops.secrets."nebula/host-key" = lib.mkIf (cfg.privateKeyFile == null) { sops.secrets."nebula/host-key" = lib.mkIf (cfg.privateKeyFile == null) {

View file

@ -96,10 +96,10 @@ in
expr = ''absent_over_time(up{instance="${hostName}", job="node"}[2m])''; expr = ''absent_over_time(up{instance="${hostName}", job="node"}[2m])'';
labels.severity = "critical"; labels.severity = "critical";
annotations = { annotations = {
summary = "${hostName} is DOWN"; summary = "Host ${hostName} is down";
summary_resolved = "${hostName} is up again"; summary_resolved = "Host ${hostName} is up again";
description = "No metrics received for over 2 minutes."; description = "Prometheus has not received node metrics from ${hostName} for 2 minutes.";
description_resolved = "Metrics are being received again."; description_resolved = "Prometheus is receiving node metrics from ${hostName} again.";
}; };
}) })
) )
@ -109,20 +109,20 @@ in
expr = ''up{job=~"prometheus|alertmanager"} == 0''; expr = ''up{job=~"prometheus|alertmanager"} == 0'';
for = "2m"; for = "2m";
annotations = { annotations = {
summary = "{{ $labels.job | title }} on {{ $labels.instance }} is DOWN"; summary = "Service {{ $labels.job | title }} on {{ $labels.instance }} is down";
summary_resolved = "{{ $labels.job | title }} on {{ $labels.instance }} is up again"; summary_resolved = "Service {{ $labels.job | title }} on {{ $labels.instance }} is up again";
description = "Unresponsive for over 2 minutes."; description = "Prometheus has not received scrape data for 2 minutes.";
description_resolved = "Responding normally."; description_resolved = "Prometheus is receiving scrape data again.";
}; };
} }
{ {
alert = "CominDeploymentFailed"; alert = "CominDeploymentFailed";
expr = ''comin_deployment_info{status!="done"}''; expr = ''comin_deployment_info{status!="done"}'';
annotations = { annotations = {
summary = "{{ $labels.instance }} deployment failed"; summary = "Deployment on {{ $labels.instance }} failed";
summary_resolved = "{{ $labels.instance }} deployment recovered"; summary_resolved = "Deployment on {{ $labels.instance }} succeeded again";
description = "Deployment is not reaching \"done\" status."; description = "Comin reports a deployment status other than \"done\".";
description_resolved = "Deployment completed successfully."; description_resolved = "Comin reports the deployment status as \"done\" again.";
}; };
} }
{ {
@ -130,10 +130,10 @@ in
expr = "count(count by (commit_id) (comin_deployment_info)) > 1"; expr = "count(count by (commit_id) (comin_deployment_info)) > 1";
for = "10m"; for = "10m";
annotations = { annotations = {
summary = "Hosts are running different commits"; summary = "Deployment commits are out of sync";
summary_resolved = "All hosts are running the same commit again"; summary_resolved = "Deployment commits are in sync again";
description = "Possibly a failed deployment or incompatible configurations."; description = "Comin reports different deployed commits across hosts.";
description_resolved = "All hosts are in sync."; description_resolved = "Comin reports the same deployed commit across all hosts again.";
}; };
} }
]; ];

View file

@ -31,7 +31,7 @@ in
default = [ ]; default = [ ];
}; };
extraConfig = lib.mkOption { extraConfig = lib.mkOption {
type = lib.types.attrsOf lib.types.anything; type = lib.types.attrs;
default = { }; default = { };
}; };
}; };

View file

@ -26,7 +26,7 @@ in
systemd.services = { systemd.services = {
"healthcheck-ping@" = { "healthcheck-ping@" = {
description = "Pings healthcheck (%i)"; description = "Ping Healthchecks for %i";
serviceConfig.Type = "oneshot"; serviceConfig.Type = "oneshot";
scriptArgs = "%i"; scriptArgs = "%i";
script = '' script = ''

View file

@ -22,7 +22,7 @@ in
default = "${self}/hosts/${config.networking.hostName}/secrets.json"; default = "${self}/hosts/${config.networking.hostName}/secrets.json";
}; };
secretsData = lib.mkOption { secretsData = lib.mkOption {
type = lib.types.anything; type = lib.types.attrs;
default = cfg.secretsFile |> lib.readFile |> lib.strings.fromJSON; default = cfg.secretsFile |> lib.readFile |> lib.strings.fromJSON;
}; };
}; };
@ -41,7 +41,7 @@ in
|> lib.attrNames |> lib.attrNames
|> lib.map (secretPath: { |> lib.map (secretPath: {
assertion = cfg.secretsData |> lib.hasAttrByPath (secretPath |> lib.splitString "/"); assertion = cfg.secretsData |> lib.hasAttrByPath (secretPath |> lib.splitString "/");
message = "Sops secret `${secretPath}` is used in a module but not defined in secrets.json"; message = self.lib.mkInvalidConfigMessage "SOPS secret `${secretPath}`" "it is used in a module but not defined in `secrets.json`";
}) })
) )
++ ( ++ (
@ -49,7 +49,7 @@ in
|> lib.mapAttrsToListRecursive (path: _: path |> lib.concatStringsSep "/") |> lib.mapAttrsToListRecursive (path: _: path |> lib.concatStringsSep "/")
|> lib.map (secretPath: { |> lib.map (secretPath: {
assertion = config.sops.secrets |> lib.hasAttr secretPath; assertion = config.sops.secrets |> lib.hasAttr secretPath;
message = "Sops secret `${secretPath}` is defined in secrets.json but not used in any module"; message = self.lib.mkInvalidConfigMessage "SOPS secret `${secretPath}`" "it is defined in `secrets.json` but not used in any module";
}) })
); );
}; };