blob: 69c4c39b36d22999235f4df061907dc3d6e42699 [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,
Samuel Shuertc6f63032024-12-31 11:09:23 -05009 system,
10 inputs,
Skyler Grey107cb5e2024-06-10 17:15:45 +000011 ...
12}:
Skyler Grey61f0f852024-06-09 00:02:53 +000013let
14 cfg = config.clicks.services.headscale;
15in
16{
17 options.clicks.services.headscale = {
18 enable = lib.mkEnableOption "The headscale control server for tailscale";
Skyler Greybed35f12024-07-04 00:46:44 +000019 domain = lib.mkOption {
Skyler Grey61f0f852024-06-09 00:02:53 +000020 type = lib.types.str;
Skyler Greybed35f12024-07-04 00:46:44 +000021 description = "The domain of the url users should connect to to register a new device";
Skyler Grey61f0f852024-06-09 00:02:53 +000022 };
Samuel Shuertc6f63032024-12-31 11:09:23 -050023 server_url = lib.mkOption {
24 type = lib.types.str;
25 description = "The domain of the url users should connect to to register a new device";
26 };
Skyler Grey61f0f852024-06-09 00:02:53 +000027 addr = lib.mkOption {
28 type = lib.types.str;
29 description = "Where to host headscale";
Skyler Grey14375fe2024-06-22 14:43:44 +000030 default = "127.0.0.1";
Skyler Grey61f0f852024-06-09 00:02:53 +000031 };
32 port = lib.mkOption {
33 type = lib.types.int;
34 description = "Port to host headscale on";
Skyler Grey14375fe2024-06-22 14:43:44 +000035 default = 1024;
Skyler Grey61f0f852024-06-09 00:02:53 +000036 };
37 oidc = {
38 enable = lib.mkEnableOption "Enable OIDC";
39 issuer = lib.mkOption {
40 type = lib.types.str;
41 description = "Issuer URL for your OIDC provider";
42 };
43 allowed_groups = lib.mkOption {
44 type = lib.types.nullOr (lib.types.listOf lib.types.str);
45 description = "List of groups to allow authentication from";
46 };
47 client_id = lib.mkOption {
48 type = lib.types.str;
49 description = "Client ID";
50 default = "headscale";
51 };
52 client_secret_path = lib.mkOption {
53 type = lib.types.str;
54 description = "Client secret file path";
55 };
56 };
Skyler Grey61f0f852024-06-09 00:02:53 +000057 noise_private_key_path = lib.mkOption {
58 type = lib.types.nullOr lib.types.str;
59 description = "Noise private key file path";
60 default = null;
61 };
62 private_key_path = lib.mkOption {
63 type = lib.types.nullOr lib.types.str;
64 description = "Headscale private key file path";
65 default = null;
66 };
Skyler Grey107cb5e2024-06-10 17:15:45 +000067 acl = lib.mkOption {
68 type = lib.types.nullOr (lib.types.attrsOf lib.types.anything);
69 description = "ACL rules for headscale to enforce";
70 default = null;
71 };
Skyler Grey61f0f852024-06-09 00:02:53 +000072 };
73
74 config = lib.mkIf cfg.enable {
75 clicks = {
Skyler Grey14375fe2024-06-22 14:43:44 +000076 services.nginx.enable = true;
Samuel Shuertc6f63032024-12-31 11:09:23 -050077 services.nginx.hosts.${cfg.server_url} = {
Skyler Grey14375fe2024-06-22 14:43:44 +000078 service = lib.clicks.nginx.http.reverseProxy cfg.addr cfg.port;
79 www = false;
80 # TODO: disable http when we have changed a1d2's reverse proxy config to allow us to terminate HTTPS
81 enableHttp = true;
82 };
Samuel Shuertc6f63032024-12-31 11:09:23 -050083
84 storage.impermanence.persist.directories = [ "/var/lib/headscale" ];
Skyler Grey61f0f852024-06-09 00:02:53 +000085 };
86
87 services.headscale = {
88 enable = true;
89
90 address = cfg.addr;
91 port = cfg.port;
92
Samuel Shuertc6f63032024-12-31 11:09:23 -050093 package = lib.recursiveUpdate inputs.headscale.packages.${system}.headscale ({ meta.mainProgram = "headscale"; });
Skyler Grey61f0f852024-06-09 00:02:53 +000094
Samuel Shuertc6f63032024-12-31 11:09:23 -050095 settings.server_url = "https://${cfg.server_url}";
Skyler Grey61f0f852024-06-09 00:02:53 +000096
97 settings.noise.private_key_path = lib.mkIf (
98 cfg.noise_private_key_path != null
99 ) cfg.noise_private_key_path;
Skyler Grey61f0f852024-06-09 00:02:53 +0000100
Samuel Shuertc6f63032024-12-31 11:09:23 -0500101 settings.dns = {
102 nameservers.global = [
Skyler Grey61f0f852024-06-09 00:02:53 +0000103 "1.1.1.1"
104 "1.0.0.1"
Samuel Shuertc6f63032024-12-31 11:09:23 -0500105 "2606:4700:4700::1111"
106 "2606:4700:4700::1001"
Skyler Grey61f0f852024-06-09 00:02:53 +0000107 ];
Skyler Greybed35f12024-07-04 00:46:44 +0000108 base_domain = cfg.domain;
Skyler Grey61f0f852024-06-09 00:02:53 +0000109 };
110
111 settings.oidc = lib.mkIf cfg.oidc.enable {
112 only_start_if_oidc_is_available = true;
Samuel Shuertc6f63032024-12-31 11:09:23 -0500113 strip_email_domain = true;
Skyler Grey61f0f852024-06-09 00:02:53 +0000114
115 issuer = cfg.oidc.issuer;
116
117 client_id = cfg.oidc.client_id;
118 client_secret_path = cfg.oidc.client_secret_path;
119
120 allowed_groups = lib.mkIf (cfg.oidc.allowed_groups != null) cfg.oidc.allowed_groups;
Skyler Grey61f0f852024-06-09 00:02:53 +0000121 };
Skyler Grey107cb5e2024-06-10 17:15:45 +0000122
Samuel Shuertc6f63032024-12-31 11:09:23 -0500123 settings.policy = lib.mkIf (cfg.acl != null) {
124 mode = "file";
125 path = (pkgs.writers.writeJSON "tailscale-acls.json" cfg.acl);
126 };
Skyler Grey61f0f852024-06-09 00:02:53 +0000127 };
Skyler Grey61f0f852024-06-09 00:02:53 +0000128 };
129}