diff --git a/machines/gerd.nix b/machines/gerd.nix index e2d95f7..4846d08 100644 --- a/machines/gerd.nix +++ b/machines/gerd.nix @@ -32,6 +32,8 @@ ./gerd/services/drtvrss.nix ./gerd/services/vikunja.nix + ./gerd/services/headscale + ./gerd/services/monitoring ]; diff --git a/machines/gerd/services/headscale/default.nix b/machines/gerd/services/headscale/default.nix new file mode 100644 index 0000000..fa9fd45 --- /dev/null +++ b/machines/gerd/services/headscale/default.nix @@ -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; +} diff --git a/machines/gerd/services/headscale/headplane.nix b/machines/gerd/services/headscale/headplane.nix new file mode 100644 index 0000000..43bf8a3 --- /dev/null +++ b/machines/gerd/services/headscale/headplane.nix @@ -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 = ""; + redirect_uri = "https://${svc_domain}/admin/oidc/callback"; + + # headscale API key for authenticating users + headscale_api_key = ""; + + # 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" + ]; +} diff --git a/machines/gerd/services/headscale/headscale.nix b/machines/gerd/services/headscale/headscale.nix new file mode 100644 index 0000000..9421288 --- /dev/null +++ b/machines/gerd/services/headscale/headscale.nix @@ -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" + ]; +} diff --git a/secrets/default.nix b/secrets/default.nix index a45dce6..b1b93d0 100644 --- a/secrets/default.nix +++ b/secrets/default.nix @@ -72,6 +72,10 @@ # vikunja vikunja-env.file = ./vikunja/env.age; + + # headscale/headplan + headscale-authelia-secret.file = ./headscale/headscale-authelia-secret.age; + headplane-env.file = ./headscale/headplane-env.age; }; users.groups.secrets-lldap-bind-user-pass = {}; diff --git a/secrets/headscale/headplane-env.age b/secrets/headscale/headplane-env.age new file mode 100644 index 0000000..cd791a7 --- /dev/null +++ b/secrets/headscale/headplane-env.age @@ -0,0 +1,11 @@ +age-encryption.org/v1 +-> ssh-ed25519 QSDXqg U2hjU5FeVfd+/agny133FUmLJ47zqpww10DgzHbkKW8 +k6k+WH6fZ1UZjnOwe3LS6cXYVcucE+kBhuPRMcNFabI +-> X25519 JfLnC3qBptSdDwBRo1Zj/rDqKqWFvxWqBANIKhCIVlc +iuqSt9gdPjQVRuv9+iUub1zkecSUjZD1XKvtvkNAaOE +-> ssh-ed25519 n8n9DQ eVkQODcS4wG+5xliWzqfEcRKvkHaHTzz8BWXH/ld/3k +9q0TB7ZLluGIQhFjMcXUmXlGiof5/ACrrHQRWEq4jik +-> ssh-ed25519 BTp6UA hGuGXru5pgShG15hEH6B8fYF4x1SK6blgP+3NSfvklI +UZYG5ZfErwlBtux5fWh3CPrT/9oWoDjGXX1M0ojtY8c +--- NlksmWK4pF3hQkpCkZaCBWvEv7I9TB0zgYRiJpuLB18 +-ke(~"KktWv&~ݶ H)jQtRe/C_s./~21"!Ӂ7ICjKۼHo!{Yhzcgo?tÁJS @`mh|e3U!;s}Lb$-R8Vq:u&K\i8TALOOg3fT \ No newline at end of file diff --git a/secrets/headscale/headscale-authelia-secret.age b/secrets/headscale/headscale-authelia-secret.age new file mode 100644 index 0000000..5ddf367 Binary files /dev/null and b/secrets/headscale/headscale-authelia-secret.age differ diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 9577a0f..867d1d3 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -82,4 +82,8 @@ in # vikunja "vikunja/env.age".publicKeys = defaultAccess; + + # headscale/headplan + "headscale/headscale-authelia-secret.age".publicKeys = defaultAccess; + "headscale/headplane-env.age".publicKeys = defaultAccess; } diff --git a/shared/sources/default.nix b/shared/sources/default.nix index 12d5f5f..dff0c31 100644 --- a/shared/sources/default.nix +++ b/shared/sources/default.nix @@ -18,6 +18,17 @@ in sources // { url = "https://github.com/NixOS/nixpkgs/pull/412054.patch"; sha256 = "sha256-OExslGsrGGPWSJJFPkZgYV8DaPKq9YDlmozPf/bV6dE="; }) + # headscale: 0.25.1 -> 0.26.0, update nixos module and test accordingly #407644 + (pkgs.fetchpatch { + url = "https://github.com/NixOS/nixpkgs/pull/407644.patch"; + sha256 = "sha256-0fAB9DJ8KeocS/ZP4CPEt9VHkAu6gt025rSu3Skssqo="; + }) + + # # Revert "nixos/murmur: Get rid of global lib expansion" #413495 + # (pkgs.fetchpatch { + # url = "https://github.com/NixOS/nixpkgs/pull/413495.patch"; + # sha256 = "sha256-0QI2qn0vkFruk0sq15xLriz6iqvs2e9DjApVblsSULE="; + # }) ]; }; diff --git a/shared/sources/sources.json b/shared/sources/sources.json index 6949936..795861c 100644 --- a/shared/sources/sources.json +++ b/shared/sources/sources.json @@ -17,10 +17,10 @@ "homepage": "", "owner": "nix-community", "repo": "disko", - "rev": "a894f2811e1ee8d10c50560551e50d6ab3c392ba", - "sha256": "06gbwfkzm73xrf2brnlvg0g6dbjjry7xqmaar320dqwclq44jf83", + "rev": "58d6e5a83fff9982d57e0a0a994d4e5c0af441e4", + "sha256": "0h4br7anx158shym8a812wi43qhq5qqsv3xk7vxm6s6dawnp4azw", "type": "tarball", - "url": "https://github.com/nix-community/disko/archive/a894f2811e1ee8d10c50560551e50d6ab3c392ba.tar.gz", + "url": "https://github.com/nix-community/disko/archive/58d6e5a83fff9982d57e0a0a994d4e5c0af441e4.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "drasl": {