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