adds headscale/planescale support (for one user -> me)

This commit is contained in:
eyjhb 2025-06-05 17:45:41 +02:00
parent 96f4272724
commit 038865ba84
Signed by: eyjhb
GPG key ID: 609F508E3239F920
10 changed files with 267 additions and 3 deletions

View file

@ -0,0 +1,26 @@
{ config, pkgs, ... }:
let
svc_domain = "headscale.${config.mine.shared.settings.domain}";
in {
imports = [
./headscale.nix
./headplane.nix
];
mine.shared.meta.headscale = {
name = "";
description = "";
package = let
pkg = pkgs.headscale;
in {
name = pkg.pname;
version = pkg.version;
meta = pkg.meta;
};
};
mine.shared.settings.headscale.domain = svc_domain;
}

View file

@ -0,0 +1,126 @@
{ config, pkgs, ... }:
let
svc_domain = config.mine.shared.settings.headscale.domain;
sources = import ./../../../../shared/sources;
flake-compat = sources.flake-compat;
newpkgs = (import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/c2a03962b8e24e669fb37b7df10e7c79531ff1a4.tar.gz";
}) {});
headplanesrc = let
tmppkgs = (import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/ab7b6889ae9d484eed2876868209e33eb262511d.tar.gz";
}) {});
src = builtins.fetchTarball {
url = "https://github.com/tale/headplane/archive/2f316176c8c37ad63946d7075c727478f81303b2.tar.gz";
};
in tmppkgs.applyPatches {
src = src;
name = "headplane-patched";
patches = [
(tmppkgs.writeText "headplane-package-pnpm-hash.patch" ''
diff --git a/nix/package.nix b/nix/package.nix
index bb430d7..11349c4 100644
--- a/nix/package.nix
+++ b/nix/package.nix
@@ -23,7 +23,7 @@ stdenv.mkDerivation (finalAttrs: {
pnpmDeps = pnpm_10.fetchDeps {
inherit (finalAttrs) pname version src;
- hash = "sha256-OOWgYaGwa5PtWhFEEkRCojCDmkPIR6tJ5cfFMOLND3I=";
+ hash = "sha256-xjjkqbgjYaAGYAmlTFE+Lq3Hp6myZKaW3br0YTDNhQA=";
};
'')
];
};
headplane = import flake-compat { src = headplanesrc; };
in {
imports = [
headplane.defaultNix.nixosModules.headplane
];
services.headplane = {
enable = true;
agent.enable = false;
settings = {
server = {
host = "127.0.0.1";
port = 53874;
cookie_secret = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; # replaced in env
cookie_secure = true;
};
headscale = {
url = "https://${svc_domain}";
config_strict = false;
};
oidc = {
issuer = "https://${config.mine.shared.settings.authelia.domain}";
client_id = "headplane";
client_secret = "<from_env>";
redirect_uri = "https://${svc_domain}/admin/oidc/callback";
# headscale API key for authenticating users
headscale_api_key = "<from_env>";
# default to state directory
user_storage_file = "/var/lib/headplane/users.json";
# set to the default authelia auth method
token_endpoint_auth_method = "client_secret_basic";
# disable authenticating with headscale api key
disable_api_key_login = true;
};
};
};
# headplane module does not allow setting package,
# so we have to add headplane to pkgs
nixpkgs.overlays = [
(self: super: {
headplane = headplane.defaultNix.packages.x86_64-linux.headplane;
})
];
systemd.services.headplane.serviceConfig = {
# setup state directory
StateDirectory = "headplane";
# load configs from env file
EnvironmentFile = [ config.age.secrets.headplane-env.path ];
};
# setup for oidc
services.authelia.instances.main.settings.identity_providers.oidc.clients = [{
client_id = "headplane";
client_name = "Headplane";
client_secret = "$pbkdf2-sha512$310000$h7Te42JTu4Xsqz/8CGan7Q$qDd183LHmEsgNvVAI8Xf.1DpRMeS8DqNmDpkkjkxgRR/lZYQgAkXYzL2MyvLqNFFSVKAdMTsD/Jxk72g9fxnew";
consent_mode = "implicit";
redirect_uris = [ "https://${svc_domain}/admin/oidc/callback" ];
scopes = [
"openid"
"profile"
"email"
];
}];
# nginx
services.nginx.virtualHosts."${svc_domain}".locations."/admin" = {
proxyPass = "http://127.0.0.1:${builtins.toString config.services.headplane.settings.server.port}";
priority = 5;
};
# persistence
environment.persistence.root.directories = [
"/var/lib/headplane"
];
}

View file

@ -0,0 +1,80 @@
{ config, pkgs, ... }:
let
svc_domain = config.mine.shared.settings.headscale.domain;
svc_domain_dns_magic_domain = "ts.${config.mine.shared.settings.domain}";
grpc_port = 50443;
in {
services.headscale = {
enable = true;
settings = {
server_url = "https://${svc_domain}";
# grpc
grpc_listen_addr = "127.0.0.1:${builtins.toString grpc_port}";
grpc_allow_insecure = true;
# dns
dns.base_domain = svc_domain_dns_magic_domain;
dns.nameservers.global = [ "1.1.1.1" ];
# acl -> save in database
policy.mode = "database";
# auth
oidc = {
only_start_if_oidc_is_available = true;
issuer = "https://${config.mine.shared.settings.authelia.domain}";
client_id = "headscale";
client_secret_path = "$CREDENTIALS_DIRECTORY/authelia-secret";
};
# debug
log.level = "debug";
};
};
systemd.services.headscale.serviceConfig.LoadCredential = [
"authelia-secret:${config.age.secrets.headscale-authelia-secret.path}"
];
# setup for oidc
services.authelia.instances.main.settings.identity_providers.oidc.clients = [{
client_id = "headscale";
client_name = "Headscale";
client_secret = "$pbkdf2-sha512$310000$/xiUR1oDvUEn4OKEu31COw$Z.IMgW3Qb2.mgCyEq1UUDC0i7cX2GOiywUjY4MsNv4ixQCP1jFO7njctCW2mVCqvkgfylDpsWRM3z.uXTs89IA";
consent_mode = "implicit";
redirect_uris = [ "https://${svc_domain}/oidc/callback" ];
scopes = [
"openid"
"profile"
"email"
];
}];
# nginx
services.nginx.virtualHosts."${svc_domain}" = {
forceSSL = true;
enableACME = true;
# headscale gRPC API
locations."/headscale" = {
extraConfig = ''
grpc_pass 127.0.0.1:${builtins.toString grpc_port};
'';
priority = 0;
};
# fallback to headscale
locations."/" = {
proxyPass = "http://127.0.0.1:${builtins.toString config.services.headscale.port}";
proxyWebsockets = true;
};
};
# persistence
environment.persistence.root.directories = [
"/var/lib/headscale"
];
}