headplane: 0.6.1 -> 0.6.2 (#515299)
This commit is contained in:
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")
|
||||
'';
|
||||
}
|
||||
@@ -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=";
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user