blob: 68ed4fc99d7548cb6d1e59f6a8213d9e150f0c5a [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
5{ lib, config, ... }:
6let
7 cfg = config.clicks.services.headscale;
8in
9{
10 options.clicks.services.headscale = {
11 enable = lib.mkEnableOption "The headscale control server for tailscale";
12 url = lib.mkOption {
13 type = lib.types.str;
14 description = "The url users should connect to to register a new device";
15 };
16 addr = lib.mkOption {
17 type = lib.types.str;
18 description = "Where to host headscale";
19 default = "0.0.0.0";
20 };
21 port = lib.mkOption {
22 type = lib.types.int;
23 description = "Port to host headscale on";
24 default = 80;
25 };
26 oidc = {
27 enable = lib.mkEnableOption "Enable OIDC";
28 issuer = lib.mkOption {
29 type = lib.types.str;
30 description = "Issuer URL for your OIDC provider";
31 };
32 allowed_groups = lib.mkOption {
33 type = lib.types.nullOr (lib.types.listOf lib.types.str);
34 description = "List of groups to allow authentication from";
35 };
36 client_id = lib.mkOption {
37 type = lib.types.str;
38 description = "Client ID";
39 default = "headscale";
40 };
41 client_secret_path = lib.mkOption {
42 type = lib.types.str;
43 description = "Client secret file path";
44 };
45 };
46 database_password_path = lib.mkOption {
47 type = lib.types.str;
48 description = "Database password file path";
49 };
50 noise_private_key_path = lib.mkOption {
51 type = lib.types.nullOr lib.types.str;
52 description = "Noise private key file path";
53 default = null;
54 };
55 private_key_path = lib.mkOption {
56 type = lib.types.nullOr lib.types.str;
57 description = "Headscale private key file path";
58 default = null;
59 };
60 };
61
62 config = lib.mkIf cfg.enable {
63 clicks = {
64 services.postgres.enable = true;
65 services.postgres.databases.headscale = cfg.database_password_path;
66 services.postgres.secretRequiredGroups = [ "headscale" ];
67 };
68
69 services.headscale = {
70 enable = true;
71
72 address = cfg.addr;
73 port = cfg.port;
74
75 settings.db_type = "postgres";
76 settings.db_port = config.services.postgresql.settings.port;
77 settings.db_user = "headscale";
78 settings.db_password_file = cfg.database_password_path;
79 settings.db_name = "headscale";
80 settings.db_host = lib.clicks.constants.hosts.standard;
81
82 settings.server_url = "https://${cfg.url}";
83
84 settings.ip_prefixes = "100.64.0.0/10";
85
86 settings.noise.private_key_path = lib.mkIf (
87 cfg.noise_private_key_path != null
88 ) cfg.noise_private_key_path;
89 settings.private_key_path = lib.mkIf (cfg.private_key_path != null) cfg.private_key_path;
90
91 settings.dns_config = {
92 nameservers = [
93 "1.1.1.1"
94 "1.0.0.1"
95 ];
Skyler Grey4d386d12024-06-09 16:05:44 +000096 domains = [ cfg.url ];
Skyler Grey61f0f852024-06-09 00:02:53 +000097 override_local_dns = true;
98 base_domain = cfg.url;
99 };
100
101 settings.oidc = lib.mkIf cfg.oidc.enable {
102 only_start_if_oidc_is_available = true;
103
104 issuer = cfg.oidc.issuer;
105
106 client_id = cfg.oidc.client_id;
107 client_secret_path = cfg.oidc.client_secret_path;
108
109 allowed_groups = lib.mkIf (cfg.oidc.allowed_groups != null) cfg.oidc.allowed_groups;
110 strip_email_domain = true;
111 };
112 };
113
114 systemd.services.headscale.requires = [ "postgresql.service" ];
115 systemd.services.headscale.after = [ "postgresql.service" ];
116
117 networking.firewall.allowedTCPPorts = lib.mkIf (cfg.addr == "0.0.0.0") [ cfg.port ];
118 };
119}