| # SPDX-FileCopyrightText: 2024 Clicks Codes |
| # |
| # SPDX-License-Identifier: GPL-3.0-only |
| |
| { |
| lib, |
| config, |
| pkgs, |
| ... |
| }: |
| 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"; |
| }; |
| 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"; |
| }; |
| }; |
| database_password_path = lib.mkOption { |
| type = lib.types.str; |
| description = "Database password 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.postgres.enable = true; |
| services.postgres.databases.headscale = cfg.database_password_path; |
| services.postgres.secretRequiredGroups = [ "headscale" ]; |
| services.nginx.enable = true; |
| services.nginx.hosts.${cfg.domain} = { |
| 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; |
| }; |
| }; |
| |
| services.headscale = { |
| enable = true; |
| |
| address = cfg.addr; |
| port = cfg.port; |
| |
| settings.db_type = "postgres"; |
| settings.db_port = config.services.postgresql.settings.port; |
| settings.db_user = "headscale"; |
| settings.db_password_file = cfg.database_password_path; |
| settings.db_name = "headscale"; |
| settings.db_host = lib.clicks.constants.hosts.standard; |
| |
| settings.server_url = "https://${cfg.domain}"; |
| |
| settings.ip_prefixes = "100.64.0.0/10"; |
| |
| settings.noise.private_key_path = lib.mkIf ( |
| cfg.noise_private_key_path != null |
| ) cfg.noise_private_key_path; |
| settings.private_key_path = lib.mkIf (cfg.private_key_path != null) cfg.private_key_path; |
| |
| settings.dns_config = { |
| nameservers = [ |
| "1.1.1.1" |
| "1.0.0.1" |
| ]; |
| domains = [ cfg.domain ]; |
| override_local_dns = true; |
| base_domain = cfg.domain; |
| }; |
| |
| settings.oidc = lib.mkIf cfg.oidc.enable { |
| only_start_if_oidc_is_available = 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; |
| strip_email_domain = true; |
| }; |
| |
| settings.acl_policy_path = lib.mkIf (cfg.acl != null) ( |
| pkgs.writers.writeJSON "tailscale-acls.json" cfg.acl |
| ); |
| }; |
| |
| systemd.services.headscale.requires = [ "postgresql.service" ] ++ |
| (if config.clicks.services.nginx.enable then [ "nginx.service" ] else []); |
| systemd.services.headscale.after = [ "postgresql.service" ] ++ |
| (if config.clicks.services.nginx.enable then [ "nginx.service" ] else []); |
| }; |
| } |