1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 boolToString
11 concatStringsSep
12 hasAttr
13 isBool
14 mapAttrs
15 mkDefault
16 mkEnableOption
17 mkIf
18 mkOption
19 mkPackageOption
20 ;
21
22 inherit (lib.types)
23 attrsOf
24 bool
25 either
26 package
27 str
28 submodule
29 ;
30
31 toStringEnv = value: if isBool value then boolToString value else toString value;
32
33 cfg = config.services.netbird.server.dashboard;
34in
35
36{
37 options.services.netbird.server.dashboard = {
38 enable = mkEnableOption "the static netbird dashboard frontend";
39
40 package = mkPackageOption pkgs "netbird-dashboard" { };
41
42 enableNginx = mkEnableOption "Nginx reverse-proxy to serve the dashboard";
43
44 domain = mkOption {
45 type = str;
46 default = "localhost";
47 description = "The domain under which the dashboard runs.";
48 };
49
50 managementServer = mkOption {
51 type = str;
52 description = "The address of the management server, used for the API endpoints.";
53 };
54
55 settings = mkOption {
56 type = submodule { freeformType = attrsOf (either str bool); };
57
58 defaultText = ''
59 {
60 AUTH_AUDIENCE = "netbird";
61 AUTH_CLIENT_ID = "netbird";
62 AUTH_SUPPORTED_SCOPES = "openid profile email";
63 NETBIRD_TOKEN_SOURCE = "idToken";
64 USE_AUTH0 = false;
65 }
66 '';
67
68 description = ''
69 An attribute set that will be used to substitute variables when building the dashboard.
70 Any values set here will be templated into the frontend and be public for anyone that can reach your website.
71 The exact values sadly aren't documented anywhere.
72 A starting point when searching for valid values is this [script](https://github.com/netbirdio/dashboard/blob/main/docker/init_react_envs.sh)
73 The only mandatory value is 'AUTH_AUTHORITY' as we cannot set a default value here.
74 '';
75 };
76
77 finalDrv = mkOption {
78 readOnly = true;
79 type = package;
80 description = ''
81 The derivation containing the final templated dashboard.
82 '';
83 };
84 };
85
86 config = mkIf cfg.enable {
87 assertions = [
88 {
89 assertion = hasAttr "AUTH_AUTHORITY" cfg.settings;
90 message = "The setting AUTH_AUTHORITY is required for the dashboard to function.";
91 }
92 ];
93
94 services.netbird.server.dashboard = {
95 settings = {
96 # Due to how the backend and frontend work this secret will be templated into the backend
97 # and then served statically from your website
98 # This enables you to login without the normally needed indirection through the backend
99 # but this also means anyone that can reach your website can
100 # fetch this secret, which is why there is no real need to put it into
101 # special options as its public anyway
102 # As far as I know leaking this secret is just
103 # an information leak as one can fetch some basic app
104 # information from the IDP
105 # To actually do something one still needs to have login
106 # data and this secret so this being public will not
107 # suffice for anything just decreasing security
108 AUTH_CLIENT_SECRET = "";
109
110 NETBIRD_MGMT_API_ENDPOINT = cfg.managementServer;
111 NETBIRD_MGMT_GRPC_API_ENDPOINT = cfg.managementServer;
112 }
113 // (mapAttrs (_: mkDefault) {
114 # Those values have to be easily overridable
115 AUTH_AUDIENCE = "netbird"; # must be set for your devices to be able to log in
116 AUTH_CLIENT_ID = "netbird";
117 AUTH_SUPPORTED_SCOPES = "openid profile email";
118 NETBIRD_TOKEN_SOURCE = "idToken";
119 USE_AUTH0 = false;
120 });
121
122 # The derivation containing the templated dashboard
123 finalDrv =
124 pkgs.runCommand "netbird-dashboard"
125 {
126 nativeBuildInputs = [ pkgs.gettext ];
127 env = {
128 ENV_STR = concatStringsSep " " [
129 "$AUTH_AUDIENCE"
130 "$AUTH_AUTHORITY"
131 "$AUTH_CLIENT_ID"
132 "$AUTH_CLIENT_SECRET"
133 "$AUTH_REDIRECT_URI"
134 "$AUTH_SILENT_REDIRECT_URI"
135 "$AUTH_SUPPORTED_SCOPES"
136 "$NETBIRD_DRAG_QUERY_PARAMS"
137 "$NETBIRD_GOOGLE_ANALYTICS_ID"
138 "$NETBIRD_HOTJAR_TRACK_ID"
139 "$NETBIRD_MGMT_API_ENDPOINT"
140 "$NETBIRD_MGMT_GRPC_API_ENDPOINT"
141 "$NETBIRD_TOKEN_SOURCE"
142 "$USE_AUTH0"
143 ];
144 }
145 // (mapAttrs (_: toStringEnv) cfg.settings);
146 }
147 ''
148 cp -R ${cfg.package} build
149
150 find build -type d -exec chmod 755 {} \;
151 OIDC_TRUSTED_DOMAINS="build/OidcTrustedDomains.js"
152
153 envsubst "$ENV_STR" < "$OIDC_TRUSTED_DOMAINS.tmpl" > "$OIDC_TRUSTED_DOMAINS"
154
155 for f in $(grep -R -l AUTH_SUPPORTED_SCOPES build/); do
156 mv "$f" "$f.copy"
157 envsubst "$ENV_STR" < "$f.copy" > "$f"
158 rm "$f.copy"
159 done
160
161 cp -R build $out
162 '';
163 };
164
165 services.nginx = mkIf cfg.enableNginx {
166 enable = true;
167
168 virtualHosts.${cfg.domain} = {
169 locations = {
170 "/" = {
171 root = cfg.finalDrv;
172 tryFiles = "$uri $uri.html $uri/ =404";
173 };
174
175 "/404.html".extraConfig = ''
176 internal;
177 '';
178 };
179
180 extraConfig = ''
181 error_page 404 /404.html;
182 '';
183 };
184 };
185 };
186}