blob: 2d104bd07861e0ede1352ad1c7a518395d1ba892 [file] [log] [blame]
Skyler Grey61f0f852024-06-09 00:02:53 +00001# SPDX-FileCopyrightText: 2024 Clicks Codes
2#
3# SPDX-License-Identifier: GPL-3.0-only
4
Skyler Grey107cb5e2024-06-10 17:15:45 +00005{
6 lib,
7 config,
8 pkgs,
9 ...
10}:
Skyler Grey61f0f852024-06-09 00:02:53 +000011let
12 cfg = config.clicks.services.headscale;
13in
14{
15 options.clicks.services.headscale = {
16 enable = lib.mkEnableOption "The headscale control server for tailscale";
Skyler Greybed35f12024-07-04 00:46:44 +000017 domain = lib.mkOption {
Skyler Grey61f0f852024-06-09 00:02:53 +000018 type = lib.types.str;
Skyler Greybed35f12024-07-04 00:46:44 +000019 description = "The domain of the url users should connect to to register a new device";
Skyler Grey61f0f852024-06-09 00:02:53 +000020 };
21 addr = lib.mkOption {
22 type = lib.types.str;
23 description = "Where to host headscale";
Skyler Grey14375fe2024-06-22 14:43:44 +000024 default = "127.0.0.1";
Skyler Grey61f0f852024-06-09 00:02:53 +000025 };
26 port = lib.mkOption {
27 type = lib.types.int;
28 description = "Port to host headscale on";
Skyler Grey14375fe2024-06-22 14:43:44 +000029 default = 1024;
Skyler Grey61f0f852024-06-09 00:02:53 +000030 };
31 oidc = {
32 enable = lib.mkEnableOption "Enable OIDC";
33 issuer = lib.mkOption {
34 type = lib.types.str;
35 description = "Issuer URL for your OIDC provider";
36 };
37 allowed_groups = lib.mkOption {
38 type = lib.types.nullOr (lib.types.listOf lib.types.str);
39 description = "List of groups to allow authentication from";
40 };
41 client_id = lib.mkOption {
42 type = lib.types.str;
43 description = "Client ID";
44 default = "headscale";
45 };
46 client_secret_path = lib.mkOption {
47 type = lib.types.str;
48 description = "Client secret file path";
49 };
50 };
51 database_password_path = lib.mkOption {
52 type = lib.types.str;
53 description = "Database password file path";
54 };
55 noise_private_key_path = lib.mkOption {
56 type = lib.types.nullOr lib.types.str;
57 description = "Noise private key file path";
58 default = null;
59 };
60 private_key_path = lib.mkOption {
61 type = lib.types.nullOr lib.types.str;
62 description = "Headscale private key file path";
63 default = null;
64 };
Skyler Grey107cb5e2024-06-10 17:15:45 +000065 acl = lib.mkOption {
66 type = lib.types.nullOr (lib.types.attrsOf lib.types.anything);
67 description = "ACL rules for headscale to enforce";
68 default = null;
69 };
Skyler Grey61f0f852024-06-09 00:02:53 +000070 };
71
72 config = lib.mkIf cfg.enable {
73 clicks = {
74 services.postgres.enable = true;
75 services.postgres.databases.headscale = cfg.database_password_path;
76 services.postgres.secretRequiredGroups = [ "headscale" ];
Skyler Grey14375fe2024-06-22 14:43:44 +000077 services.nginx.enable = true;
78 services.nginx.hosts.${cfg.domain} = {
79 service = lib.clicks.nginx.http.reverseProxy cfg.addr cfg.port;
80 www = false;
81 # TODO: disable http when we have changed a1d2's reverse proxy config to allow us to terminate HTTPS
82 enableHttp = true;
83 };
Skyler Grey61f0f852024-06-09 00:02:53 +000084 };
85
86 services.headscale = {
87 enable = true;
88
89 address = cfg.addr;
90 port = cfg.port;
91
92 settings.db_type = "postgres";
93 settings.db_port = config.services.postgresql.settings.port;
94 settings.db_user = "headscale";
95 settings.db_password_file = cfg.database_password_path;
96 settings.db_name = "headscale";
97 settings.db_host = lib.clicks.constants.hosts.standard;
98
Skyler Greybed35f12024-07-04 00:46:44 +000099 settings.server_url = "https://${cfg.domain}";
Skyler Grey61f0f852024-06-09 00:02:53 +0000100
101 settings.ip_prefixes = "100.64.0.0/10";
102
103 settings.noise.private_key_path = lib.mkIf (
104 cfg.noise_private_key_path != null
105 ) cfg.noise_private_key_path;
106 settings.private_key_path = lib.mkIf (cfg.private_key_path != null) cfg.private_key_path;
107
108 settings.dns_config = {
109 nameservers = [
110 "1.1.1.1"
111 "1.0.0.1"
112 ];
Skyler Greybed35f12024-07-04 00:46:44 +0000113 domains = [ cfg.domain ];
Skyler Grey61f0f852024-06-09 00:02:53 +0000114 override_local_dns = true;
Skyler Greybed35f12024-07-04 00:46:44 +0000115 base_domain = cfg.domain;
Skyler Grey61f0f852024-06-09 00:02:53 +0000116 };
117
118 settings.oidc = lib.mkIf cfg.oidc.enable {
119 only_start_if_oidc_is_available = true;
120
121 issuer = cfg.oidc.issuer;
122
123 client_id = cfg.oidc.client_id;
124 client_secret_path = cfg.oidc.client_secret_path;
125
126 allowed_groups = lib.mkIf (cfg.oidc.allowed_groups != null) cfg.oidc.allowed_groups;
127 strip_email_domain = true;
128 };
Skyler Grey107cb5e2024-06-10 17:15:45 +0000129
130 settings.acl_policy_path = lib.mkIf (cfg.acl != null) (
131 pkgs.writers.writeJSON "tailscale-acls.json" cfg.acl
132 );
Skyler Grey61f0f852024-06-09 00:02:53 +0000133 };
134
Skyler Grey14375fe2024-06-22 14:43:44 +0000135 systemd.services.headscale.requires = [ "postgresql.service" ] ++
136 (if config.clicks.services.nginx.enable then [ "nginx.service" ] else []);
137 systemd.services.headscale.after = [ "postgresql.service" ] ++
138 (if config.clicks.services.nginx.enable then [ "nginx.service" ] else []);
Skyler Grey61f0f852024-06-09 00:02:53 +0000139 };
140}