8 cfg = config.services.dependency-track;
10 settingsFormat = pkgs.formats.javaProperties { };
12 frontendConfigFormat = pkgs.formats.json { };
13 frontendConfigFile = frontendConfigFormat.generate "config.json" {
14 API_BASE_URL = cfg.frontend.baseUrl;
15 OIDC_ISSUER = cfg.oidc.issuer;
16 OIDC_CLIENT_ID = cfg.oidc.clientId;
17 OIDC_SCOPE = cfg.oidc.scope;
18 OIDC_FLOW = cfg.oidc.flow;
19 OIDC_LOGIN_BUTTON_TEXT = cfg.oidc.loginButtonText;
23 config.services.nginx.virtualHosts.${cfg.nginx.domain}.addSSL
24 || config.services.nginx.virtualHosts.${cfg.nginx.domain}.forceSSL
25 || config.services.nginx.virtualHosts.${cfg.nginx.domain}.onlySSL
26 || config.services.nginx.virtualHosts.${cfg.nginx.domain}.enableACME;
30 if lib.isPath value then
32 services.dependency-track.${optionName}:
34 is a Nix path, but should be a string, since Nix
35 paths are copied into the world-readable Nix store.
40 filterNull = lib.filterAttrs (_: v: v != null);
46 lib.nameValuePair (lib.toUpper (lib.replaceStrings [ "." ] [ "_" ] n)) (
47 if lib.isBool v then lib.boolToString v else v
49 ) (filterNull settings);
52 options.services.dependency-track = {
53 enable = lib.mkEnableOption "dependency-track";
55 package = lib.mkPackageOption pkgs "dependency-track" { };
57 logLevel = lib.mkOption {
58 type = lib.types.enum [
66 description = "Log level for dependency-track";
70 type = lib.types.port;
73 On which port dependency-track should listen for new HTTP connections.
77 javaArgs = lib.mkOption {
78 type = lib.types.listOf lib.types.str;
79 default = [ "-Xmx4G" ];
80 description = "Java options passed to JVM";
85 type = lib.types.enum [
90 default = "postgresql";
92 `h2` database is not recommended for a production setup.
93 `postgresql` this settings it recommended for production setups.
94 `manual` the module doesn't handle database settings.
98 createLocally = lib.mkOption {
99 type = lib.types.bool;
102 Whether a database should be automatically created on the
103 local host. Set this to false if you plan on provisioning a
104 local database yourself.
108 databaseName = lib.mkOption {
109 type = lib.types.str;
110 default = "dependency-track";
112 Database name to use when connecting to an external or
113 manually provisioned database; has no effect when a local
114 database is automatically provisioned.
116 To use this with a local database, set {option}`services.dependency-track.database.createLocally`
117 to `false` and create the database and user.
121 username = lib.mkOption {
122 type = lib.types.str;
123 default = "dependency-track";
125 Username to use when connecting to an external or manually
126 provisioned database; has no effect when a local database is
127 automatically provisioned.
129 To use this with a local database, set {option}`services.dependency-track.database.createLocally`
130 to `false` and create the database and user.
134 passwordFile = lib.mkOption {
135 type = lib.types.path;
136 example = "/run/keys/db_password";
137 apply = assertStringPath "passwordFile";
139 The path to a file containing the database password.
144 ldap.bindPasswordFile = lib.mkOption {
145 type = lib.types.path;
146 example = "/run/keys/ldap_bind_password";
147 apply = assertStringPath "bindPasswordFile";
149 The path to a file containing the LDAP bind password.
154 baseUrl = lib.mkOption {
155 type = lib.types.str;
156 default = lib.optionalString cfg.nginx.enable "${
157 if sslEnabled then "https" else "http"
158 }://${cfg.nginx.domain}";
159 defaultText = lib.literalExpression ''
160 lib.optionalString config.services.dependency-track.nginx.enable "''${
161 if sslEnabled then "https" else "http"
162 }://''${config.services.dependency-track.nginx.domain}";
165 The base URL of the API server.
168 * This URL must be reachable by the browsers of your users.
169 * The frontend container itself does NOT communicate with the API server directly, it just serves static files.
170 * When deploying to dedicated servers, please use the external IP or domain of the API server.
176 enable = lib.mkEnableOption "oidc support";
177 issuer = lib.mkOption {
178 type = lib.types.str;
181 Defines the issuer URL to be used for OpenID Connect.
182 See alpine.oidc.issuer property of the API server.
185 clientId = lib.mkOption {
186 type = lib.types.str;
189 Defines the client ID for OpenID Connect.
192 scope = lib.mkOption {
193 type = lib.types.str;
194 default = "openid profile email";
196 Defines the scopes to request for OpenID Connect.
197 See also: https://openid.net/specs/openid-connect-basic-1_0.html#Scopes
200 flow = lib.mkOption {
201 type = lib.types.enum [
207 Specifies the OpenID Connect flow to use.
208 Values other than "implicit" will result in the Code+PKCE flow to be used.
209 Usage of the implicit flow is strongly discouraged, but may be necessary when
210 the IdP of choice does not support the Code+PKCE flow.
212 - https://oauth.net/2/grant-types/implicit/
213 - https://oauth.net/2/pkce/
216 loginButtonText = lib.mkOption {
217 type = lib.types.str;
218 default = "Login with OpenID Connect";
220 Defines the scopes to request for OpenID Connect.
221 See also: https://openid.net/specs/openid-connect-basic-1_0.html#Scopes
224 usernameClaim = lib.mkOption {
225 type = lib.types.str;
227 example = "preferred_username";
229 Defines the name of the claim that contains the username in the provider's userinfo endpoint.
230 Common claims are "name", "username", "preferred_username" or "nickname".
231 See also: https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
234 userProvisioning = lib.mkOption {
235 type = lib.types.bool;
239 Specifies if mapped OpenID Connect accounts are automatically created upon successful
240 authentication. When a user logs in with a valid access token but an account has
241 not been previously provisioned, an authentication failure will be returned.
242 This allows admins to control specifically which OpenID Connect users can access the
243 system and which users cannot. When this value is set to true, a local OpenID Connect
244 user will be created and mapped to the OpenID Connect account automatically. This
245 automatic provisioning only affects authentication, not authorization.
248 teamSynchronization = lib.mkOption {
249 type = lib.types.bool;
253 This option will ensure that team memberships for OpenID Connect users are dynamic and
254 synchronized with membership of OpenID Connect groups or assigned roles. When a team is
255 mapped to an OpenID Connect group, all local OpenID Connect users will automatically be
256 assigned to the team if they are a member of the group the team is mapped to. If the user
257 is later removed from the OpenID Connect group, they will also be removed from the team. This
258 option provides the ability to dynamically control user permissions via the identity provider.
259 Note that team synchronization is only performed during user provisioning and after successful
264 claim = lib.mkOption {
265 type = lib.types.str;
268 Defines the name of the claim that contains group memberships or role assignments in the provider's userinfo endpoint.
269 The claim must be an array of strings. Most public identity providers do not support group or role management.
270 When using a customizable / on-demand hosted identity provider, name, content, and inclusion in the userinfo endpoint
271 will most likely need to be configured.
274 default = lib.mkOption {
275 type = lib.types.nullOr lib.types.commas;
278 Defines one or more team names that auto-provisioned OIDC users shall be added to.
279 Multiple team names may be provided as comma-separated list.
281 Has no effect when {option}`services.dependency-track.oidc.userProvisioning`=false,
282 or {option}`services.dependency-track.oidc.teamSynchronization`=true.
289 enable = lib.mkOption {
290 type = lib.types.bool;
294 Whether to set up an nginx virtual host.
298 domain = lib.mkOption {
299 type = lib.types.str;
300 example = "dtrack.example.com";
302 The domain name under which to set up the virtual host.
307 settings = lib.mkOption {
308 type = lib.types.submodule {
309 freeformType = settingsFormat.type;
311 "alpine.data.directory" = lib.mkOption {
312 type = lib.types.path;
313 default = "/var/lib/dependency-track";
315 Defines the path to the data directory. This directory will hold logs, keys,
316 and any database or index files along with application-specific files or
320 "alpine.database.mode" = lib.mkOption {
321 type = lib.types.enum [
327 if cfg.database.type == "h2" then
329 else if cfg.database.type == "postgresql" then
333 defaultText = lib.literalExpression ''
334 if config.services.dependency-track.database.type == "h2" then "embedded"
335 else if config.services.dependency-track.database.type == "postgresql" then "external"
339 Defines the database mode of operation. Valid choices are:
340 'server', 'embedded', and 'external'.
341 In server mode, the database will listen for connections from remote hosts.
342 In embedded mode, the system will be more secure and slightly faster.
343 External mode should be used when utilizing an external database server
344 (i.e. mysql, postgresql, etc).
347 "alpine.database.url" = lib.mkOption {
348 type = lib.types.str;
350 if cfg.database.type == "h2" then
351 "jdbc:h2:/var/lib/dependency-track/db"
352 else if cfg.database.type == "postgresql" then
353 "jdbc:postgresql:${cfg.database.databaseName}?socketFactory=org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg&socketFactoryArg=/run/postgresql/.s.PGSQL.5432"
357 defaultText = lib.literalExpression ''
358 if config.services.dependency-track.database.type == "h2" then "jdbc:h2:/var/lib/dependency-track/db"
359 else if config.services.dependency-track.database.type == "postgresql" then "jdbc:postgresql:''${config.services.dependency-track.database.name}?socketFactory=org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg&socketFactoryArg=/run/postgresql/.s.PGSQL.5432"
362 description = "Specifies the JDBC URL to use when connecting to the database.";
364 "alpine.database.driver" = lib.mkOption {
365 type = lib.types.enum [
367 "org.postgresql.Driver"
368 "com.microsoft.sqlserver.jdbc.SQLServerDriver"
369 "com.mysql.cj.jdbc.Driver"
372 if cfg.database.type == "h2" then
374 else if cfg.database.type == "postgresql" then
375 "org.postgresql.Driver"
378 defaultText = lib.literalExpression ''
379 if config.services.dependency-track.database.type == "h2" then "org.h2.Driver"
380 else if config.services.dependency-track.database.type == "postgresql" then "org.postgresql.Driver"
383 description = "Specifies the JDBC driver class to use.";
385 "alpine.database.username" = lib.mkOption {
386 type = lib.types.str;
387 default = if cfg.database.createLocally then "dependency-track" else cfg.database.username;
388 defaultText = lib.literalExpression ''
389 if config.services.dependency-track.database.createLocally then "dependency-track"
390 else config.services.dependency-track.database.username
392 description = "Specifies the username to use when authenticating to the database.";
394 "alpine.ldap.enabled" = lib.mkOption {
395 type = lib.types.bool;
398 Defines if LDAP will be used for user authentication. If enabled,
399 alpine.ldap.* properties should be set accordingly.
402 "alpine.oidc.enabled" = lib.mkOption {
403 type = lib.types.bool;
404 default = cfg.oidc.enable;
405 defaultText = lib.literalExpression "config.services.dependency-track.oidc.enable";
407 Defines if OpenID Connect will be used for user authentication.
408 If enabled, alpine.oidc.* properties should be set accordingly.
411 "alpine.oidc.client.id" = lib.mkOption {
412 type = lib.types.str;
413 default = cfg.oidc.clientId;
414 defaultText = lib.literalExpression "config.services.dependency-track.oidc.clientId";
416 Defines the client ID to be used for OpenID Connect.
417 The client ID should be the same as the one configured for the frontend,
418 and will only be used to validate ID tokens.
421 "alpine.oidc.issuer" = lib.mkOption {
422 type = lib.types.str;
423 default = cfg.oidc.issuer;
424 defaultText = lib.literalExpression "config.services.dependency-track.oidc.issuer";
426 Defines the issuer URL to be used for OpenID Connect.
427 This issuer MUST support provider configuration via the /.well-known/openid-configuration endpoint.
429 - https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
430 - https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
433 "alpine.oidc.username.claim" = lib.mkOption {
434 type = lib.types.str;
435 default = cfg.oidc.usernameClaim;
436 defaultText = lib.literalExpression "config.services.dependency-track.oidc.usernameClaim";
438 Defines the name of the claim that contains the username in the provider's userinfo endpoint.
439 Common claims are "name", "username", "preferred_username" or "nickname".
440 See also: https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
443 "alpine.oidc.user.provisioning" = lib.mkOption {
444 type = lib.types.bool;
445 default = cfg.oidc.userProvisioning;
446 defaultText = lib.literalExpression "config.services.dependency-track.oidc.userProvisioning";
448 Specifies if mapped OpenID Connect accounts are automatically created upon successful
449 authentication. When a user logs in with a valid access token but an account has
450 not been previously provisioned, an authentication failure will be returned.
451 This allows admins to control specifically which OpenID Connect users can access the
452 system and which users cannot. When this value is set to true, a local OpenID Connect
453 user will be created and mapped to the OpenID Connect account automatically. This
454 automatic provisioning only affects authentication, not authorization.
457 "alpine.oidc.team.synchronization" = lib.mkOption {
458 type = lib.types.bool;
459 default = cfg.oidc.teamSynchronization;
460 defaultText = lib.literalExpression "config.services.dependency-track.oidc.teamSynchronization";
462 This option will ensure that team memberships for OpenID Connect users are dynamic and
463 synchronized with membership of OpenID Connect groups or assigned roles. When a team is
464 mapped to an OpenID Connect group, all local OpenID Connect users will automatically be
465 assigned to the team if they are a member of the group the team is mapped to. If the user
466 is later removed from the OpenID Connect group, they will also be removed from the team. This
467 option provides the ability to dynamically control user permissions via the identity provider.
468 Note that team synchronization is only performed during user provisioning and after successful
472 "alpine.oidc.teams.claim" = lib.mkOption {
473 type = lib.types.str;
474 default = cfg.oidc.teams.claim;
475 defaultText = lib.literalExpression "config.services.dependency-track.oidc.teams.claim";
477 Defines the name of the claim that contains group memberships or role assignments in the provider's userinfo endpoint.
478 The claim must be an array of strings. Most public identity providers do not support group or role management.
479 When using a customizable / on-demand hosted identity provider, name, content, and inclusion in the userinfo endpoint
480 will most likely need to be configured.
483 "alpine.oidc.teams.default" = lib.mkOption {
484 type = lib.types.nullOr lib.types.commas;
485 default = cfg.oidc.teams.default;
486 defaultText = lib.literalExpression "config.services.dependency-track.oidc.teams.default";
488 Defines one or more team names that auto-provisioned OIDC users shall be added to.
489 Multiple team names may be provided as comma-separated list.
491 Has no effect when {option}`services.dependency-track.oidc.userProvisioning`=false,
492 or {option}`services.dependency-track.oidc.teamSynchronization`=true.
498 description = "See https://docs.dependencytrack.org/getting-started/configuration/#default-configuration for possible options";
502 config = lib.mkIf cfg.enable {
503 services.nginx = lib.mkIf cfg.nginx.enable {
505 recommendedGzipSettings = lib.mkDefault true;
506 recommendedOptimisation = lib.mkDefault true;
507 recommendedProxySettings = lib.mkDefault true;
508 recommendedTlsSettings = lib.mkDefault true;
509 upstreams.dependency-track.servers."localhost:${toString cfg.port}" = { };
510 virtualHosts.${cfg.nginx.domain} = {
512 "/".proxyPass = "http://dependency-track";
513 "= /static/config.json".alias = frontendConfigFile;
518 systemd.services.dependency-track-postgresql-init = lib.mkIf cfg.database.createLocally {
519 after = [ "postgresql.service" ];
520 before = [ "dependency-track.service" ];
521 bindsTo = [ "postgresql.service" ];
522 path = [ config.services.postgresql.package ];
525 RemainAfterExit = true;
528 LoadCredential = [ "db_password:${cfg.database.passwordFile}" ];
533 shopt -s inherit_errexit
535 # Read the password from the credentials directory and
536 # escape any single quotes by adding additional single
537 # quotes after them, following the rules laid out here:
538 # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS
539 db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")"
540 db_password="''${db_password//\'/\'\'}"
542 echo "CREATE ROLE \"dependency-track\" WITH LOGIN PASSWORD '$db_password' CREATEDB" > /tmp/create_role.sql
543 psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='dependency-track'" | grep -q 1 || psql -tA --file="/tmp/create_role.sql"
544 psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'dependency-track'" | grep -q 1 || psql -tAc 'CREATE DATABASE "dependency-track" OWNER "dependency-track"'
548 services.postgresql.enable = lib.mkIf cfg.database.createLocally (lib.mkDefault true);
550 systemd.services."dependency-track" =
553 if cfg.database.createLocally then
555 "dependency-track-postgresql-init.service"
562 description = "Dependency Track";
563 wantedBy = [ "multi-user.target" ];
564 requires = databaseServices;
565 after = databaseServices;
566 # provide settings via env vars to allow overriding default settings.
568 HOME = "%S/dependency-track";
569 } // renderSettings cfg.settings;
571 User = "dependency-track";
572 Group = "dependency-track";
574 StateDirectory = "dependency-track";
576 [ "db_password:${cfg.database.passwordFile}" ]
577 ++ lib.optional cfg.settings."alpine.ldap.enabled"
578 "ldap_bind_password:${cfg.ldap.bindPasswordFile}";
582 shopt -s inherit_errexit
584 export ALPINE_DATABASE_PASSWORD_FILE="$CREDENTIALS_DIRECTORY/db_password"
585 ${lib.optionalString cfg.settings."alpine.ldap.enabled" ''
586 export ALPINE_LDAP_BIND_PASSWORD="$(<"$CREDENTIALS_DIRECTORY/ldap_bind_password")"
589 exec ${lib.getExe pkgs.jre_headless} ${
590 lib.escapeShellArgs (
593 "-DdependencyTrack.logging.level=${cfg.logLevel}"
595 "${cfg.package}/share/dependency-track/dependency-track.jar"
597 "${toString cfg.port}"
606 maintainers = lib.teams.cyberus.members;