nixos/headplane: address review feedback

This commit is contained in:
StealthBadger747
2026-04-27 21:41:07 -07:00
parent 9ffc6aa12e
commit d97d82b37e
2 changed files with 201 additions and 173 deletions
+200 -173
View File
@@ -14,16 +14,31 @@ let
;
inherit (lib.attrsets) filterAttrsRecursive;
cfg = config.services.headplane;
settingsFile = (pkgs.formats.yaml { }).generate "headplane-config.yaml" (
# Headplane config can't have `null` values.
filterAttrsRecursive (n: v: v != null) cfg.settings
settingsFormat = pkgs.formats.yaml { };
filterSettings = lib.converge (
filterAttrsRecursive (
_: v:
!lib.elem v [
{ }
null
]
)
);
agentSettings = cfg.settings.integration.agent;
settings = cfg.settings // {
integration = cfg.settings.integration // {
agent = if agentSettings == null || !agentSettings.enabled then null else agentSettings;
};
};
settingsFile = settingsFormat.generate "headplane-config.yaml" (filterSettings settings);
in
{
options.services.headplane = {
enable = mkEnableOption "Headplane";
package = mkPackageOption pkgs "headplane" { };
agent.package = mkPackageOption pkgs "headplane-agent" { };
debug = mkEnableOption "Enable debug loggin";
settings = mkOption {
@@ -157,74 +172,74 @@ in
type = types.submodule {
options = {
agent = mkOption {
type = types.submodule {
options = {
enabled = mkOption {
type = types.bool;
default = false;
description = ''
The Headplane agent allows retrieving information about nodes.
This allows the UI to display version, OS, and connectivity data.
You will see the Headplane agent in your Tailnet as a node when it connects.
'';
};
type = types.nullOr (
types.submodule {
options = {
enabled = mkOption {
type = types.bool;
default = false;
description = ''
The Headplane agent allows retrieving information about nodes.
This allows the UI to display version, OS, and connectivity data.
You will see the Headplane agent in your Tailnet as a node when it connects.
'';
};
executable_path = mkOption {
type = types.path;
readOnly = true;
default = "${cfg.settings.integration.agent.package}/bin/hp_agent";
defaultText = lib.literalExpression ''"''\${config.services.headplane.settings.integration.agent.package}/bin/hp_agent"'';
description = ''
Path to the headplane agent binary.
'';
};
executable_path = mkOption {
type = types.path;
readOnly = true;
default = "${cfg.agent.package}/bin/hp_agent";
defaultText = lib.literalExpression ''"''${config.services.headplane.agent.package}/bin/hp_agent"'';
description = ''
Path to the headplane agent binary.
'';
};
pre_authkey_path = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to a file containing the agent preauth key.
To connect to your Tailnet, you need to generate a pre-auth key.
This can be done via the web UI or through the `headscale` CLI.
'';
example = "config.sops.secrets.agent_pre_authkey.path";
};
pre_authkey_path = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to a file containing the agent preauth key.
To connect to your Tailnet, you need to generate a pre-auth key.
This can be done via the web UI or through the `headscale` CLI.
'';
example = "config.sops.secrets.agent_pre_authkey.path";
};
host_name = mkOption {
type = types.str;
default = "headplane-agent";
description = "Optionally change the name of the agent in the Tailnet";
};
host_name = mkOption {
type = types.str;
default = "headplane-agent";
description = "Optionally change the name of the agent in the Tailnet";
};
cache_ttl = mkOption {
type = types.ints.positive;
default = 180000;
description = ''
How long to cache agent information (in milliseconds).
If you want data to update faster, reduce the TTL, but this will increase the frequency of requests to Headscale.
'';
};
cache_ttl = mkOption {
type = types.ints.positive;
default = 180000;
description = ''
How long to cache agent information (in milliseconds).
If you want data to update faster, reduce the TTL, but this will increase the frequency of requests to Headscale.
'';
};
cache_path = mkOption {
type = types.path;
default = "/var/lib/headplane/agent_cache.json";
description = "Where to store the agent cache.";
};
cache_path = mkOption {
type = types.path;
default = "/var/lib/headplane/agent_cache.json";
description = "Where to store the agent cache.";
};
work_dir = mkOption {
type = types.path;
default = "/var/lib/headplane/agent";
description = ''
Do not change this unless you are running a custom deployment.
The work_dir represents where the agent will store its data to be able to automatically reauthenticate with your Tailnet.
It needs to be writable by the user running the Headplane process.
'';
work_dir = mkOption {
type = types.path;
default = "/var/lib/headplane/agent";
description = ''
Do not change this unless you are running a custom deployment.
The work_dir represents where the agent will store its data to be able to automatically reauthenticate with your Tailnet.
It needs to be writable by the user running the Headplane process.
'';
};
};
package = mkPackageOption pkgs "headplane-agent" { };
};
};
default = { };
}
);
default = null;
description = "Agent configuration for the Headplane agent.";
};
@@ -253,128 +268,127 @@ in
};
oidc = mkOption {
type = types.submodule {
options = {
issuer = mkOption {
type = types.nullOr types.str;
default = null;
description = "URL to OpenID issuer.";
example = "https://provider.example.com/issuer-url";
};
type = types.nullOr (
types.submodule {
options = {
issuer = mkOption {
type = types.str;
description = "URL to OpenID issuer.";
example = "https://provider.example.com/issuer-url";
};
client_id = mkOption {
type = types.nullOr types.str;
default = null;
description = "The client ID for the OIDC client.";
example = "your-client-id";
};
client_id = mkOption {
type = types.str;
description = "The client ID for the OIDC client.";
example = "your-client-id";
};
client_secret_path = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to a file containing the OIDC client secret.
'';
example = "config.sops.secrets.oidc_client_secret.path";
};
client_secret_path = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to a file containing the OIDC client secret.
'';
example = "config.sops.secrets.oidc_client_secret.path";
};
disable_api_key_login = mkOption {
type = types.bool;
default = false;
description = "Whether to disable API key login.";
};
disable_api_key_login = mkOption {
type = types.bool;
default = false;
description = "Whether to disable API key login.";
};
token_endpoint_auth_method = mkOption {
type = types.enum [
"client_secret_post"
"client_secret_basic"
"client_secret_jwt"
];
default = "client_secret_post";
description = "The token endpoint authentication method.";
};
token_endpoint_auth_method = mkOption {
type = types.enum [
"client_secret_post"
"client_secret_basic"
"client_secret_jwt"
];
default = "client_secret_post";
description = "The token endpoint authentication method.";
};
headscale_api_key_path = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to a file containing the Headscale API key.
'';
example = "config.sops.secrets.headscale_api_key.path";
};
headscale_api_key_path = mkOption {
type = types.path;
description = ''
Path to a file containing the Headscale API key.
'';
example = "config.sops.secrets.headscale_api_key.path";
};
redirect_uri = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
This should point to your publicly accessible URL
for your Headplane instance with /admin/oidc/callback.
'';
example = "https://headscale.example.com/admin/oidc/callback";
};
redirect_uri = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
This should point to your publicly accessible URL
for your Headplane instance with /admin/oidc/callback.
'';
example = "https://headscale.example.com/admin/oidc/callback";
};
user_storage_file = mkOption {
type = types.path;
default = "/var/lib/headplane/users.json";
description = ''
Path to a file containing the users and their permissions for Headplane.
'';
example = "/var/lib/headplane/users.json";
};
user_storage_file = mkOption {
type = types.path;
default = "/var/lib/headplane/users.json";
description = ''
Path to a file containing the users and their permissions for Headplane.
'';
example = "/var/lib/headplane/users.json";
};
profile_picture_source = mkOption {
type = types.enum [
"oidc"
"gravatar"
];
default = "oidc";
description = "Source for user profile pictures.";
};
profile_picture_source = mkOption {
type = types.enum [
"oidc"
"gravatar"
];
default = "oidc";
description = "Source for user profile pictures.";
};
strict_validation = mkOption {
type = types.bool;
default = true;
description = "Enable strict validation of OIDC configuration.";
};
strict_validation = mkOption {
type = types.bool;
default = true;
description = "Enable strict validation of OIDC configuration.";
};
scope = mkOption {
type = types.str;
default = "openid email profile";
description = "OIDC scope to request.";
};
scope = mkOption {
type = types.str;
default = "openid email profile";
description = "OIDC scope to request.";
};
extra_params = mkOption {
type = types.nullOr (types.attrsOf types.str);
default = null;
description = "Extra parameters to send to the OIDC provider.";
example = {
prompt = "consent";
extra_params = mkOption {
type = types.nullOr (types.attrsOf types.str);
default = null;
description = "Extra parameters to send to the OIDC provider.";
example = {
prompt = "consent";
};
};
authorization_endpoint = mkOption {
type = types.nullOr types.str;
default = null;
description = "Custom authorization endpoint URL.";
example = "https://provider.example.com/authorize";
};
token_endpoint = mkOption {
type = types.nullOr types.str;
default = null;
description = "Custom token endpoint URL.";
example = "https://provider.example.com/token";
};
userinfo_endpoint = mkOption {
type = types.nullOr types.str;
default = null;
description = "Custom userinfo endpoint URL.";
example = "https://provider.example.com/userinfo";
};
};
authorization_endpoint = mkOption {
type = types.nullOr types.str;
default = null;
description = "Custom authorization endpoint URL.";
example = "https://provider.example.com/authorize";
};
token_endpoint = mkOption {
type = types.nullOr types.str;
default = null;
description = "Custom token endpoint URL.";
example = "https://provider.example.com/token";
};
userinfo_endpoint = mkOption {
type = types.nullOr types.str;
default = null;
description = "Custom userinfo endpoint URL.";
example = "https://provider.example.com/userinfo";
};
};
};
default = { };
}
);
default = null;
description = "OIDC Configuration for authentication.";
};
};
@@ -384,6 +398,19 @@ in
};
config = mkIf cfg.enable {
assertions = [
{
assertion =
cfg.settings.integration.agent == null
|| !cfg.settings.integration.agent.enabled
|| cfg.settings.integration.agent.pre_authkey_path != null;
message = ''
services.headplane.settings.integration.agent.pre_authkey_path must be set
when services.headplane.settings.integration.agent.enabled is true.
'';
}
];
environment = {
systemPackages = [ cfg.package ];
etc."headplane/config.yaml".source = "${settingsFile}";
@@ -16,6 +16,7 @@ buildGoModule (finalAttrs: {
};
vendorHash = "sha256-MvrqKMD+A+qBZmzQv+T9920U5uJop+pjfJpZdm2ZqEA=";
subPackages = [ "cmd/hp_agent" ];
ldflags = [
"-s"