lol
1{ config, pkgs, lib, ... }:
2
3let
4 cfg = config.services.zitadel;
5
6 settingsFormat = pkgs.formats.yaml { };
7in
8{
9 options.services.zitadel =
10 let inherit (lib) mkEnableOption mkOption mkPackageOption types;
11 in {
12 enable = mkEnableOption "ZITADEL, a user and identity access management platform";
13
14 package = mkPackageOption pkgs "ZITADEL" { default = [ "zitadel" ]; };
15
16 user = mkOption {
17 type = types.str;
18 default = "zitadel";
19 description = "The user to run ZITADEL under.";
20 };
21
22 group = mkOption {
23 type = types.str;
24 default = "zitadel";
25 description = "The group to run ZITADEL under.";
26 };
27
28 openFirewall = mkOption {
29 type = types.bool;
30 default = false;
31 description = ''
32 Whether to open the port specified in `listenPort` in the firewall.
33 '';
34 };
35
36 masterKeyFile = mkOption {
37 type = types.path;
38 description = ''
39 Path to a file containing a master encryption key for ZITADEL. The
40 key must be 32 bytes.
41 '';
42 };
43
44 tlsMode = mkOption {
45 type = types.enum [ "external" "enabled" "disabled" ];
46 default = "external";
47 example = "enabled";
48 description = ''
49 The TLS mode to use. Options are:
50
51 - enabled: ZITADEL accepts HTTPS connections directly. You must
52 configure TLS if this option is selected.
53 - external: ZITADEL forces HTTPS connections, with TLS terminated at a
54 reverse proxy.
55 - disabled: ZITADEL accepts HTTP connections only. Should only be used
56 for testing.
57 '';
58 };
59
60 settings = mkOption {
61 type = lib.types.submodule {
62 freeformType = settingsFormat.type;
63
64 options = {
65 Port = mkOption {
66 type = types.port;
67 default = 8080;
68 description = "The port that ZITADEL listens on.";
69 };
70
71 TLS = {
72 KeyPath = mkOption {
73 type = types.nullOr types.path;
74 default = null;
75 description = "Path to the TLS certificate private key.";
76 };
77 Key = mkOption {
78 type = types.nullOr types.str;
79 default = null;
80 description = ''
81 The TLS certificate private key, as a base64-encoded string.
82
83 Note that the contents of this option will be added to the Nix
84 store as world-readable plain text. Set
85 [KeyPath](#opt-services.zitadel.settings.TLS.KeyPath) instead
86 if this is undesired.
87 '';
88 };
89 CertPath = mkOption {
90 type = types.nullOr types.path;
91 default = null;
92 description = "Path to the TLS certificate.";
93 };
94 Cert = mkOption {
95 type = types.nullOr types.str;
96 default = null;
97 description = ''
98 The TLS certificate, as a base64-encoded string.
99
100 Note that the contents of this option will be added to the Nix
101 store as world-readable plain text. Set
102 [CertPath](#opt-services.zitadel.settings.TLS.CertPath) instead
103 if this is undesired.
104 '';
105 };
106 };
107 };
108 };
109 default = { };
110 example = lib.literalExpression ''
111 {
112 Port = 8123;
113 ExternalDomain = "example.com";
114 TLS = {
115 CertPath = "/path/to/cert.pem";
116 KeyPath = "/path/to/cert.key";
117 };
118 Database.cockroach.Host = "db.example.com";
119 };
120 '';
121 description = ''
122 Contents of the runtime configuration file. See
123 https://zitadel.com/docs/self-hosting/manage/configure for more
124 details.
125 '';
126 };
127
128 extraSettingsPaths = mkOption {
129 type = types.listOf types.path;
130 default = [ ];
131 description = ''
132 A list of paths to extra settings files. These will override the
133 values set in [settings](#opt-services.zitadel.settings). Useful if
134 you want to keep sensitive secrets out of the Nix store.
135 '';
136 };
137
138 steps = mkOption {
139 type = settingsFormat.type;
140 default = { };
141 example = lib.literalExpression ''
142 {
143 FirstInstance = {
144 InstanceName = "Example";
145 Org.Human = {
146 UserName = "foobar";
147 FirstName = "Foo";
148 LastName = "Bar";
149 };
150 };
151 }
152 '';
153 description = ''
154 Contents of the database initialization config file. See
155 https://zitadel.com/docs/self-hosting/manage/configure for more
156 details.
157 '';
158 };
159
160 extraStepsPaths = mkOption {
161 type = types.listOf types.path;
162 default = [ ];
163 description = ''
164 A list of paths to extra steps files. These will override the values
165 set in [steps](#opt-services.zitadel.steps). Useful if you want to
166 keep sensitive secrets out of the Nix store.
167 '';
168 };
169 };
170
171 config = lib.mkIf cfg.enable {
172 assertions = [{
173 assertion = cfg.tlsMode == "enabled"
174 -> ((cfg.settings.TLS.Key != null || cfg.settings.TLS.KeyPath != null)
175 && (cfg.settings.TLS.Cert != null || cfg.settings.TLS.CertPath
176 != null));
177 message = ''
178 A TLS certificate and key must be configured in
179 services.zitadel.settings.TLS if services.zitadel.tlsMode is enabled.
180 '';
181 }];
182
183 networking.firewall.allowedTCPPorts =
184 lib.mkIf cfg.openFirewall [ cfg.settings.Port ];
185
186 systemd.services.zitadel =
187 let
188 configFile = settingsFormat.generate "config.yaml" cfg.settings;
189 stepsFile = settingsFormat.generate "steps.yaml" cfg.steps;
190
191 args = lib.cli.toGNUCommandLineShell { } {
192 config = cfg.extraSettingsPaths ++ [ configFile ];
193 steps = cfg.extraStepsPaths ++ [ stepsFile ];
194 masterkeyFile = cfg.masterKeyFile;
195 inherit (cfg) tlsMode;
196 };
197 in
198 {
199 description = "ZITADEL identity access management";
200 path = [ cfg.package ];
201 wantedBy = [ "multi-user.target" ];
202
203 script = ''
204 zitadel start-from-init ${args}
205 '';
206
207 serviceConfig = {
208 Type = "simple";
209 User = cfg.user;
210 Group = cfg.group;
211 Restart = "on-failure";
212 };
213 };
214
215 users.users.zitadel = lib.mkIf (cfg.user == "zitadel") {
216 isSystemUser = true;
217 group = cfg.group;
218 };
219 users.groups.zitadel = lib.mkIf (cfg.group == "zitadel") { };
220 };
221
222 meta.maintainers = with lib.maintainers; [ Sorixelle ];
223}