headplane: 0.6.1 -> 0.6.2 (#515299)

This commit is contained in:
Edward Tjörnhammar
2026-05-11 11:57:14 +00:00
committed by GitHub
6 changed files with 223 additions and 63 deletions
@@ -251,6 +251,13 @@ of pulling the upstream container image from Docker Hub. If you want the old beh
- Ethercalc and its associated module have been removed, as the package is unmaintained and cannot be installed from source with npm now.
- `services.headplane` has been updated to 0.6.2, which introduces several changes to the configuration schema:
- `services.headplane.settings.oidc.redirect_uri` is deprecated. Use `services.headplane.settings.server.base_url` instead; the OIDC redirect URI is now automatically derived from it. Ensure `base_url` is the bare host URL without the `/admin` suffix.
- `services.headplane.settings.oidc.user_storage_file` is deprecated. Headplane 0.6.2 still accepts it to migrate the old JSON user database into the new internal SQL database.
- `services.headplane.settings.oidc.strict_validation` is deprecated and has no effect.
- `services.headplane.settings.oidc.token_endpoint_auth_method` now defaults to `null` (auto-detection), which typically falls back to `client_secret_basic`. Previous versions defaulted to `client_secret_post`.
- `services.headplane.settings.integration.agent.cache_ttl` is deprecated and has no effect in 0.6.2.
- `services.immich` no longer supports pgvecto.rs since the package has been removed from nixpkgs.
As a result, options `services.immich.database.enableVectors` and `services.immich.database.enableVectorchord` have been removed, and VectorChord is now always used.
If you have not completed the migration yet, ensure you completely remove the extension from your database before upgrading by following the [migration guide](https://github.com/NixOS/nixpkgs/blob/nixos-25.11/nixos/modules/services/web-apps/immich.md#migrating-from-pgvecto-rs-to-vectorchord-pre-2511-installations-module-services-immich-vectorchord-migration).
+134 -58
View File
@@ -64,6 +64,16 @@ in
description = "The port to listen on.";
};
base_url = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
The base URL for Headplane. Used for OIDC redirect callback URL
detection. Should not include the dashboard prefix (/admin).
'';
example = "https://headplane.example.com";
};
cookie_secret_path = mkOption {
type = types.nullOr types.path;
default = null;
@@ -84,16 +94,32 @@ in
'';
};
cookie_max_age = mkOption {
type = types.ints.positive;
default = 86400;
description = "The maximum age of the session cookie in seconds.";
};
cookie_domain = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Restrict the cookie to a specific domain.
This may not work as expected if not using a reverse proxy.
'';
example = "example.com";
};
data_path = mkOption {
type = types.path;
default = "/var/lib/headplane";
description = ''
The path to persist Headplane specific data.
All data going forward is stored in this directory, including the internal database and any cache related files.
Data formats prior to 0.6.1 will automatically be migrated.
'';
example = "/var/lib/headplane";
};
};
};
default = { };
@@ -185,6 +211,15 @@ in
'';
};
pre_authkey_path = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to a file containing a Headscale pre-auth key for the agent.
'';
example = lib.literalExpression "config.sops.secrets.headplane_pre_authkey.path";
};
executable_path = mkOption {
type = types.path;
readOnly = true;
@@ -195,36 +230,25 @@ in
'';
};
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 = lib.literalExpression "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";
description = "Optionally change the name of the agent in the Tailnet.";
};
cache_ttl = mkOption {
type = types.ints.positive;
default = 180000;
type = types.nullOr types.int;
default = null;
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.
Deprecated cache TTL for the agent. This option is accepted
by Headplane 0.6.2 but has no effect.
'';
};
cache_path = mkOption {
type = types.path;
default = "/var/lib/headplane/agent_cache.json";
description = "Where to store the agent cache.";
description = "The path to store the agent's cache.";
};
work_dir = mkOption {
@@ -271,6 +295,15 @@ in
type = types.nullOr (
types.submodule {
options = {
enabled = mkOption {
type = types.bool;
default = true;
description = ''
Explicitly control OIDC availability.
Set to false to define OIDC config without enabling it.
'';
};
issuer = mkOption {
type = types.str;
description = "URL to OpenID issuer.";
@@ -292,6 +325,16 @@ in
example = lib.literalExpression "config.sops.secrets.oidc_client_secret.path";
};
headscale_api_key_path = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Path to a file containing the Headscale API key.
Required for OIDC authentication.
'';
example = lib.literalExpression "config.sops.secrets.headscale_api_key.path";
};
disable_api_key_login = mkOption {
type = types.bool;
default = false;
@@ -299,42 +342,47 @@ in
};
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;
type = types.nullOr (
types.enum [
"client_secret_post"
"client_secret_basic"
"client_secret_jwt"
]
);
default = null;
description = ''
Path to a file containing the Headscale API key.
Required when `services.headplane.settings.oidc` is set.
The token endpoint authentication method.
If not set, Headplane will auto-detect the best method
and fall back to client_secret_basic.
'';
};
use_pkce = mkOption {
type = types.bool;
default = false;
description = ''
Whether to use PKCE when authenticating users.
Your OIDC provider must support PKCE and it must be enabled on the client.
'';
example = lib.literalExpression "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.
Deprecated OIDC redirect URI. Use services.headplane.settings.server.base_url
instead; Headplane derives the callback URL from it.
'';
example = "https://headscale.example.com/admin/oidc/callback";
example = "https://headplane.example.com/admin/oidc/callback";
};
user_storage_file = mkOption {
type = types.path;
default = "/var/lib/headplane/users.json";
strict_validation = mkOption {
type = types.nullOr types.bool;
default = null;
description = ''
Path to a file containing the users and their permissions for Headplane.
Deprecated OIDC validation setting. This option is accepted
by Headplane 0.6.2 but has no effect.
'';
example = "/var/lib/headplane/users.json";
};
profile_picture_source = mkOption {
@@ -346,12 +394,6 @@ in
description = "Source for user profile pictures.";
};
strict_validation = mkOption {
type = types.bool;
default = true;
description = "Enable strict validation of OIDC configuration.";
};
scope = mkOption {
type = types.str;
default = "openid email profile";
@@ -387,6 +429,16 @@ in
description = "Custom userinfo endpoint URL.";
example = "https://provider.example.com/userinfo";
};
user_storage_file = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Deprecated path to the pre-0.6.2 JSON user database.
Headplane uses this once to migrate users into its internal database.
'';
example = "/var/lib/headplane/users.json";
};
};
}
);
@@ -400,6 +452,33 @@ in
};
config = mkIf cfg.enable {
warnings =
lib.optionals (cfg.settings.oidc != null && cfg.settings.oidc.redirect_uri != null) [
''
services.headplane.settings.oidc.redirect_uri is deprecated by Headplane 0.6.2.
Use services.headplane.settings.server.base_url instead; Headplane derives
the OIDC callback URL from it.
''
]
++ lib.optionals (cfg.settings.oidc != null && cfg.settings.oidc.strict_validation != null) [
''
services.headplane.settings.oidc.strict_validation is deprecated and has no effect
in Headplane 0.6.2.
''
]
++ lib.optionals (cfg.settings.oidc != null && cfg.settings.oidc.user_storage_file != null) [
''
services.headplane.settings.oidc.user_storage_file is deprecated. Headplane 0.6.2
uses it only to migrate the pre-0.6.2 JSON user database into the internal database.
''
]
++ lib.optionals (agentSettings != null && agentSettings.cache_ttl != null) [
''
services.headplane.settings.integration.agent.cache_ttl is deprecated and has no
effect in Headplane 0.6.2.
''
];
assertions = [
{
assertion = config.services.headscale.enable;
@@ -422,23 +501,20 @@ in
via systemd `LoadCredential` or sops-nix.
'';
}
{
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.
'';
}
{
assertion = cfg.settings.oidc == null || cfg.settings.oidc.headscale_api_key_path != null;
message = ''
services.headplane.settings.oidc.headscale_api_key_path must be set
when services.headplane.settings.oidc is non-null. Headplane's OIDC
flow requires a Headscale API key to mint sessions; upstream config
validation rejects an OIDC block without it.
flow requires a Headscale API key to mint sessions.
'';
}
{
assertion =
agentSettings == null || !agentSettings.enabled || agentSettings.pre_authkey_path != null;
message = ''
services.headplane.settings.integration.agent.pre_authkey_path must be set
when the agent is enabled.
'';
}
];
+1
View File
@@ -726,6 +726,7 @@ in
imports = [ ./hbase.nix ];
_module.args.getPackage = pkgs: pkgs.hbase_2_5;
};
headplane = runTest ./headplane.nix;
headscale = runTest ./headscale.nix;
healthchecks = runTest ./web-apps/healthchecks.nix;
hedgedoc = runTest ./hedgedoc.nix;
+69
View File
@@ -0,0 +1,69 @@
{ lib, pkgs, ... }:
let
cookieSecret = pkgs.writeText "cookie-secret" "01234567890123456789012345678901";
oidcClientSecret = pkgs.writeText "oidc-client-secret" "client-secret";
headscaleApiKey = pkgs.writeText "headscale-api-key" "headscale-api-key";
in
{
name = "headplane";
meta.maintainers = with lib.maintainers; [
igor-ramazanov
stealthbadger747
];
nodes.machine = {
services.headscale = {
enable = true;
settings = {
server_url = "http://127.0.0.1";
ip_prefixes = [ "100.64.0.0/10" ];
dns = {
base_domain = "tailnet";
override_local_dns = false;
};
};
};
services.headplane = {
enable = true;
settings.server = {
cookie_secret_path = cookieSecret;
cookie_secure = false;
# Exercise the new 0.6.2 server-level options so the test fails
# if their option-name -> YAML-key mapping ever regresses.
base_url = "http://127.0.0.1:3000";
cookie_max_age = 3600;
cookie_domain = "127.0.0.1";
};
settings.oidc = {
enabled = false;
issuer = "https://provider.example.com/issuer-url";
client_id = "your-client-id";
client_secret_path = oidcClientSecret;
headscale_api_key_path = headscaleApiKey;
redirect_uri = "http://127.0.0.1:3000/admin/oidc/callback";
strict_validation = true;
user_storage_file = "/var/lib/headplane/users.json";
};
};
};
testScript = ''
machine.wait_for_unit("headscale.service")
machine.wait_for_unit("headplane.service")
machine.wait_until_succeeds("curl -sf http://127.0.0.1:3000/admin/login")
with subtest("rendered config contains new and deprecated-compatible options"):
config = machine.succeed("cat /etc/headplane/config.yaml")
for key in ["base_url:", "cookie_max_age:", "cookie_domain:", "redirect_uri:", "strict_validation:", "user_storage_file:"]:
assert key in config, f"{key} missing from rendered config"
with subtest("unsupported options are not rendered"):
config = machine.succeed("cat /etc/headplane/config.yaml")
for key in ["info_secret_path:", "subject_claims:", "allow_weak_rsa_keys:", "use_end_session:", "end_session_endpoint:", "post_logout_redirect_uri:"]:
assert key not in config, f"unsupported option {key} still rendered"
with subtest("session cookie is set on login page"):
machine.succeed("curl -sf -c /tmp/cj http://127.0.0.1:3000/admin/login")
machine.succeed("test -s /tmp/cj")
'';
}
+2 -2
View File
@@ -7,13 +7,13 @@ buildGoModule (finalAttrs: {
pname = "headplane-agent";
__structuredAttrs = true;
# Note, if you are upgrading this, you should upgrade headplane at the same time
version = "0.6.1";
version = "0.6.2";
src = fetchFromGitHub {
owner = "tale";
repo = "headplane";
tag = "v${finalAttrs.version}";
hash = "sha256-hsrnmEwKXJlPjV4aIfmS6GAE414ArVRGoPPpZGmV0x4=";
hash = "sha256-2C/Pn2M2aHADtoljSFg9hz6xOaZp6IRI77jjy+LDAgw=";
};
vendorHash = "sha256-MvrqKMD+A+qBZmzQv+T9920U5uJop+pjfJpZdm2ZqEA=";
+10 -3
View File
@@ -3,8 +3,10 @@
fetchFromGitHub,
fetchPnpmDeps,
git,
headplane-agent,
lib,
makeWrapper,
nixosTests,
nodejs_22,
pnpm_10,
pnpmConfigHook,
@@ -13,13 +15,13 @@
let
pname = "headplane";
# Note, if you are upgrading this, you should upgrade headplane-agent at the same time
version = "0.6.1";
pnpmDepsHash = "sha256-AYfEL3HSRg87I+Y0fkLthFSDWgHTg5u0DBpzn6KBn1Q=";
version = "0.6.2";
pnpmDepsHash = "sha256-CsmffCo9Se/4oiOqbcuhjPMuGmR2GL+YfcyWgzBTAh8=";
src = fetchFromGitHub {
owner = "tale";
repo = "headplane";
tag = "v${version}";
hash = "sha256-hsrnmEwKXJlPjV4aIfmS6GAE414ArVRGoPPpZGmV0x4=";
hash = "sha256-2C/Pn2M2aHADtoljSFg9hz6xOaZp6IRI77jjy+LDAgw=";
};
headplaneSshWasm = buildGoModule {
@@ -104,6 +106,11 @@ stdenv.mkDerivation (finalAttrs: {
runHook postInstall
'';
passthru = {
agent = headplane-agent;
tests = { inherit (nixosTests) headplane; };
};
meta = {
description = "Feature-complete Web UI for Headscale";
homepage = "https://github.com/tale/headplane";