| # SPDX-FileCopyrightText: 2024 Clicks Codes |
| # |
| # SPDX-License-Identifier: GPL-3.0-only |
| |
| { |
| lib, |
| config, |
| pkgs, |
| system, |
| inputs, |
| ... |
| }: |
| let |
| cfg = config.clicks.services.headscale; |
| in |
| { |
| options.clicks.services.headscale = { |
| enable = lib.mkEnableOption "The headscale control server for tailscale"; |
| domain = lib.mkOption { |
| type = lib.types.str; |
| description = "The domain of the url users should connect to to register a new device"; |
| }; |
| server_url = lib.mkOption { |
| type = lib.types.str; |
| description = "The domain of the url users should connect to to register a new device"; |
| }; |
| addr = lib.mkOption { |
| type = lib.types.str; |
| description = "Where to host headscale"; |
| default = "127.0.0.1"; |
| }; |
| port = lib.mkOption { |
| type = lib.types.int; |
| description = "Port to host headscale on"; |
| default = 1024; |
| }; |
| oidc = { |
| enable = lib.mkEnableOption "Enable OIDC"; |
| issuer = lib.mkOption { |
| type = lib.types.str; |
| description = "Issuer URL for your OIDC provider"; |
| }; |
| allowed_groups = lib.mkOption { |
| type = lib.types.nullOr (lib.types.listOf lib.types.str); |
| description = "List of groups to allow authentication from"; |
| }; |
| client_id = lib.mkOption { |
| type = lib.types.str; |
| description = "Client ID"; |
| default = "headscale"; |
| }; |
| client_secret_path = lib.mkOption { |
| type = lib.types.str; |
| description = "Client secret file path"; |
| }; |
| }; |
| noise_private_key_path = lib.mkOption { |
| type = lib.types.nullOr lib.types.str; |
| description = "Noise private key file path"; |
| default = null; |
| }; |
| private_key_path = lib.mkOption { |
| type = lib.types.nullOr lib.types.str; |
| description = "Headscale private key file path"; |
| default = null; |
| }; |
| acl = lib.mkOption { |
| type = lib.types.nullOr (lib.types.attrsOf lib.types.anything); |
| description = "ACL rules for headscale to enforce"; |
| default = null; |
| }; |
| }; |
| |
| config = lib.mkIf cfg.enable { |
| clicks = { |
| services.nginx.enable = true; |
| services.nginx.hosts.${cfg.server_url} = { |
| service = lib.clicks.nginx.http.reverseProxy cfg.addr cfg.port; |
| www = false; |
| # TODO: disable http when we have changed a1d2's reverse proxy config to allow us to terminate HTTPS |
| enableHttp = true; |
| }; |
| |
| storage.impermanence.persist.directories = [ "/var/lib/headscale" ]; |
| }; |
| |
| services.headscale = { |
| enable = true; |
| |
| address = cfg.addr; |
| port = cfg.port; |
| |
| package = lib.recursiveUpdate inputs.headscale.packages.${system}.headscale ({ meta.mainProgram = "headscale"; }); |
| |
| settings.server_url = "https://${cfg.server_url}"; |
| |
| settings.noise.private_key_path = lib.mkIf ( |
| cfg.noise_private_key_path != null |
| ) cfg.noise_private_key_path; |
| |
| settings.dns = { |
| nameservers.global = [ |
| "1.1.1.1" |
| "1.0.0.1" |
| "2606:4700:4700::1111" |
| "2606:4700:4700::1001" |
| ]; |
| base_domain = cfg.domain; |
| }; |
| |
| settings.oidc = lib.mkIf cfg.oidc.enable { |
| only_start_if_oidc_is_available = true; |
| strip_email_domain = true; |
| |
| issuer = cfg.oidc.issuer; |
| |
| client_id = cfg.oidc.client_id; |
| client_secret_path = cfg.oidc.client_secret_path; |
| |
| allowed_groups = lib.mkIf (cfg.oidc.allowed_groups != null) cfg.oidc.allowed_groups; |
| }; |
| |
| settings.policy = lib.mkIf (cfg.acl != null) { |
| mode = "file"; |
| path = (pkgs.writers.writeJSON "tailscale-acls.json" cfg.acl); |
| }; |
| }; |
| }; |
| } |