diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md
index 7c6b539e5c11..d83c31a9fce8 100644
--- a/nixos/doc/manual/release-notes/rl-2411.section.md
+++ b/nixos/doc/manual/release-notes/rl-2411.section.md
@@ -149,6 +149,8 @@
- [ToDesk](https://www.todesk.com/linux.html), a remote desktop applicaton. Available as [services.todesk.enable](#opt-services.todesk.enable).
+- [Dependency Track](https://dependencytrack.org/), an intelligent Component Analysis platform that allows organizations to identify and reduce risk in the software supply chain. Available as [services.dependency-track](option.html#opt-services.dependency-track).
+
## Backward Incompatibilities {#sec-release-24.11-incompatibilities}
- `transmission` package has been aliased with a `trace` warning to `transmission_3`. Since [Transmission 4 has been released last year](https://github.com/transmission/transmission/releases/tag/4.0.0), and Transmission 3 will eventually go away, it was decided perform this warning alias to make people aware of the new version. The `services.transmission.package` defaults to `transmission_3` as well because the upgrade can cause data loss in certain specific usage patterns (examples: [#5153](https://github.com/transmission/transmission/issues/5153), [#6796](https://github.com/transmission/transmission/issues/6796)). Please make sure to back up to your data directory per your usage:
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 7793528d5818..4a97be04fe7f 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1396,6 +1396,7 @@
./services/web-apps/crabfit.nix
./services/web-apps/davis.nix
./services/web-apps/cryptpad.nix
+ ./services/web-apps/dependency-track.nix
./services/web-apps/dex.nix
./services/web-apps/discourse.nix
./services/web-apps/documize.nix
diff --git a/nixos/modules/services/web-apps/dependency-track.nix b/nixos/modules/services/web-apps/dependency-track.nix
new file mode 100644
index 000000000000..ba3dbee97288
--- /dev/null
+++ b/nixos/modules/services/web-apps/dependency-track.nix
@@ -0,0 +1,608 @@
+{
+ config,
+ pkgs,
+ lib,
+ ...
+}:
+let
+ cfg = config.services.dependency-track;
+
+ settingsFormat = pkgs.formats.javaProperties { };
+
+ frontendConfigFormat = pkgs.formats.json { };
+ frontendConfigFile = frontendConfigFormat.generate "config.json" {
+ API_BASE_URL = cfg.frontend.baseUrl;
+ OIDC_ISSUER = cfg.oidc.issuer;
+ OIDC_CLIENT_ID = cfg.oidc.clientId;
+ OIDC_SCOPE = cfg.oidc.scope;
+ OIDC_FLOW = cfg.oidc.flow;
+ OIDC_LOGIN_BUTTON_TEXT = cfg.oidc.loginButtonText;
+ };
+
+ sslEnabled =
+ config.services.nginx.virtualHosts.${cfg.nginx.domain}.addSSL
+ || config.services.nginx.virtualHosts.${cfg.nginx.domain}.forceSSL
+ || config.services.nginx.virtualHosts.${cfg.nginx.domain}.onlySSL
+ || config.services.nginx.virtualHosts.${cfg.nginx.domain}.enableACME;
+
+ assertStringPath =
+ optionName: value:
+ if lib.isPath value then
+ throw ''
+ services.dependency-track.${optionName}:
+ ${toString value}
+ is a Nix path, but should be a string, since Nix
+ paths are copied into the world-readable Nix store.
+ ''
+ else
+ value;
+
+ filterNull = lib.filterAttrs (_: v: v != null);
+
+ renderSettings =
+ settings:
+ lib.mapAttrs' (
+ n: v:
+ lib.nameValuePair (lib.toUpper (lib.replaceStrings [ "." ] [ "_" ] n)) (
+ if lib.isBool v then lib.boolToString v else v
+ )
+ ) (filterNull settings);
+in
+{
+ options.services.dependency-track = {
+ enable = lib.mkEnableOption "dependency-track";
+
+ package = lib.mkPackageOption pkgs "dependency-track" { };
+
+ logLevel = lib.mkOption {
+ type = lib.types.enum [
+ "INFO"
+ "WARN"
+ "ERROR"
+ "DEBUG"
+ "TRACE"
+ ];
+ default = "INFO";
+ description = "Log level for dependency-track";
+ };
+
+ port = lib.mkOption {
+ type = lib.types.port;
+ default = 8080;
+ description = ''
+ On which port dependency-track should listen for new HTTP connections.
+ '';
+ };
+
+ javaArgs = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ default = [ "-Xmx4G" ];
+ description = "Java options passed to JVM";
+ };
+
+ database = {
+ type = lib.mkOption {
+ type = lib.types.enum [
+ "h2"
+ "postgresql"
+ "manual"
+ ];
+ default = "postgresql";
+ description = ''
+ `h2` database is not recommended for a production setup.
+ `postgresql` this settings it recommended for production setups.
+ `manual` the module doesn't handle database settings.
+ '';
+ };
+
+ createLocally = lib.mkOption {
+ type = lib.types.bool;
+ default = true;
+ description = ''
+ Whether a database should be automatically created on the
+ local host. Set this to false if you plan on provisioning a
+ local database yourself.
+ '';
+ };
+
+ databaseName = lib.mkOption {
+ type = lib.types.str;
+ default = "dependency-track";
+ description = ''
+ Database name to use when connecting to an external or
+ manually provisioned database; has no effect when a local
+ database is automatically provisioned.
+
+ To use this with a local database, set {option}`services.dependency-track.database.createLocally`
+ to `false` and create the database and user.
+ '';
+ };
+
+ username = lib.mkOption {
+ type = lib.types.str;
+ default = "dependency-track";
+ description = ''
+ Username to use when connecting to an external or manually
+ provisioned database; has no effect when a local database is
+ automatically provisioned.
+
+ To use this with a local database, set {option}`services.dependency-track.database.createLocally`
+ to `false` and create the database and user.
+ '';
+ };
+
+ passwordFile = lib.mkOption {
+ type = lib.types.path;
+ example = "/run/keys/db_password";
+ apply = assertStringPath "passwordFile";
+ description = ''
+ The path to a file containing the database password.
+ '';
+ };
+ };
+
+ ldap.bindPasswordFile = lib.mkOption {
+ type = lib.types.path;
+ example = "/run/keys/ldap_bind_password";
+ apply = assertStringPath "bindPasswordFile";
+ description = ''
+ The path to a file containing the LDAP bind password.
+ '';
+ };
+
+ frontend = {
+ baseUrl = lib.mkOption {
+ type = lib.types.str;
+ default = lib.optionalString cfg.nginx.enable "${
+ if sslEnabled then "https" else "http"
+ }://${cfg.nginx.domain}";
+ defaultText = lib.literalExpression ''
+ lib.optionalString config.services.dependency-track.nginx.enable "''${
+ if sslEnabled then "https" else "http"
+ }://''${config.services.dependency-track.nginx.domain}";
+ '';
+ description = ''
+ The base URL of the API server.
+
+ NOTE:
+ * This URL must be reachable by the browsers of your users.
+ * The frontend container itself does NOT communicate with the API server directly, it just serves static files.
+ * When deploying to dedicated servers, please use the external IP or domain of the API server.
+ '';
+ };
+ };
+
+ oidc = {
+ enable = lib.mkEnableOption "oidc support";
+ issuer = lib.mkOption {
+ type = lib.types.str;
+ default = "";
+ description = ''
+ Defines the issuer URL to be used for OpenID Connect.
+ See alpine.oidc.issuer property of the API server.
+ '';
+ };
+ clientId = lib.mkOption {
+ type = lib.types.str;
+ default = "";
+ description = ''
+ Defines the client ID for OpenID Connect.
+ '';
+ };
+ scope = lib.mkOption {
+ type = lib.types.str;
+ default = "openid profile email";
+ description = ''
+ Defines the scopes to request for OpenID Connect.
+ See also: https://openid.net/specs/openid-connect-basic-1_0.html#Scopes
+ '';
+ };
+ flow = lib.mkOption {
+ type = lib.types.enum [
+ "code"
+ "implicit"
+ ];
+ default = "code";
+ description = ''
+ Specifies the OpenID Connect flow to use.
+ Values other than "implicit" will result in the Code+PKCE flow to be used.
+ Usage of the implicit flow is strongly discouraged, but may be necessary when
+ the IdP of choice does not support the Code+PKCE flow.
+ See also:
+ - https://oauth.net/2/grant-types/implicit/
+ - https://oauth.net/2/pkce/
+ '';
+ };
+ loginButtonText = lib.mkOption {
+ type = lib.types.str;
+ default = "Login with OpenID Connect";
+ description = ''
+ Defines the scopes to request for OpenID Connect.
+ See also: https://openid.net/specs/openid-connect-basic-1_0.html#Scopes
+ '';
+ };
+ usernameClaim = lib.mkOption {
+ type = lib.types.str;
+ default = "name";
+ example = "preferred_username";
+ description = ''
+ Defines the name of the claim that contains the username in the provider's userinfo endpoint.
+ Common claims are "name", "username", "preferred_username" or "nickname".
+ See also: https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
+ '';
+ };
+ userProvisioning = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Specifies if mapped OpenID Connect accounts are automatically created upon successful
+ authentication. When a user logs in with a valid access token but an account has
+ not been previously provisioned, an authentication failure will be returned.
+ This allows admins to control specifically which OpenID Connect users can access the
+ system and which users cannot. When this value is set to true, a local OpenID Connect
+ user will be created and mapped to the OpenID Connect account automatically. This
+ automatic provisioning only affects authentication, not authorization.
+ '';
+ };
+ teamSynchronization = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ example = true;
+ description = ''
+ This option will ensure that team memberships for OpenID Connect users are dynamic and
+ synchronized with membership of OpenID Connect groups or assigned roles. When a team is
+ mapped to an OpenID Connect group, all local OpenID Connect users will automatically be
+ assigned to the team if they are a member of the group the team is mapped to. If the user
+ is later removed from the OpenID Connect group, they will also be removed from the team. This
+ option provides the ability to dynamically control user permissions via the identity provider.
+ Note that team synchronization is only performed during user provisioning and after successful
+ authentication.
+ '';
+ };
+ teams = {
+ claim = lib.mkOption {
+ type = lib.types.str;
+ default = "groups";
+ description = ''
+ Defines the name of the claim that contains group memberships or role assignments in the provider's userinfo endpoint.
+ The claim must be an array of strings. Most public identity providers do not support group or role management.
+ When using a customizable / on-demand hosted identity provider, name, content, and inclusion in the userinfo endpoint
+ will most likely need to be configured.
+ '';
+ };
+ default = lib.mkOption {
+ type = lib.types.nullOr lib.types.commas;
+ default = null;
+ description = ''
+ Defines one or more team names that auto-provisioned OIDC users shall be added to.
+ Multiple team names may be provided as comma-separated list.
+
+ Has no effect when {option}`services.dependency-track.oidc.userProvisioning`=false,
+ or {option}`services.dependency-track.oidc.teamSynchronization`=true.
+ '';
+ };
+ };
+ };
+
+ nginx = {
+ enable = lib.mkOption {
+ type = lib.types.bool;
+ default = true;
+ example = false;
+ description = ''
+ Whether to set up an nginx virtual host.
+ '';
+ };
+
+ domain = lib.mkOption {
+ type = lib.types.str;
+ example = "dtrack.example.com";
+ description = ''
+ The domain name under which to set up the virtual host.
+ '';
+ };
+ };
+
+ settings = lib.mkOption {
+ type = lib.types.submodule {
+ freeformType = settingsFormat.type;
+ options = {
+ "alpine.data.directory" = lib.mkOption {
+ type = lib.types.path;
+ default = "/var/lib/dependency-track";
+ description = ''
+ Defines the path to the data directory. This directory will hold logs, keys,
+ and any database or index files along with application-specific files or
+ directories.
+ '';
+ };
+ "alpine.database.mode" = lib.mkOption {
+ type = lib.types.enum [
+ "server"
+ "embedded"
+ "external"
+ ];
+ default =
+ if cfg.database.type == "h2" then
+ "embedded"
+ else if cfg.database.type == "postgresql" then
+ "external"
+ else
+ null;
+ defaultText = lib.literalExpression ''
+ if config.services.dependency-track.database.type == "h2" then "embedded"
+ else if config.services.dependency-track.database.type == "postgresql" then "external"
+ else null
+ '';
+ description = ''
+ Defines the database mode of operation. Valid choices are:
+ 'server', 'embedded', and 'external'.
+ In server mode, the database will listen for connections from remote hosts.
+ In embedded mode, the system will be more secure and slightly faster.
+ External mode should be used when utilizing an external database server
+ (i.e. mysql, postgresql, etc).
+ '';
+ };
+ "alpine.database.url" = lib.mkOption {
+ type = lib.types.str;
+ default =
+ if cfg.database.type == "h2" then
+ "jdbc:h2:/var/lib/dependency-track/db"
+ else if cfg.database.type == "postgresql" then
+ "jdbc:postgresql:${cfg.database.databaseName}?socketFactory=org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg&socketFactoryArg=/run/postgresql/.s.PGSQL.5432"
+ else
+ null;
+
+ defaultText = lib.literalExpression ''
+ if config.services.dependency-track.database.type == "h2" then "jdbc:h2:/var/lib/dependency-track/db"
+ 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"
+ else null
+ '';
+ description = "Specifies the JDBC URL to use when connecting to the database.";
+ };
+ "alpine.database.driver" = lib.mkOption {
+ type = lib.types.enum [
+ "org.h2.Driver"
+ "org.postgresql.Driver"
+ "com.microsoft.sqlserver.jdbc.SQLServerDriver"
+ "com.mysql.cj.jdbc.Driver"
+ ];
+ default =
+ if cfg.database.type == "h2" then
+ "org.h2.Driver"
+ else if cfg.database.type == "postgresql" then
+ "org.postgresql.Driver"
+ else
+ null;
+ defaultText = lib.literalExpression ''
+ if config.services.dependency-track.database.type == "h2" then "org.h2.Driver"
+ else if config.services.dependency-track.database.type == "postgresql" then "org.postgresql.Driver"
+ else null;
+ '';
+ description = "Specifies the JDBC driver class to use.";
+ };
+ "alpine.database.username" = lib.mkOption {
+ type = lib.types.str;
+ default = if cfg.database.createLocally then "dependency-track" else cfg.database.username;
+ defaultText = lib.literalExpression ''
+ if config.services.dependency-track.database.createLocally then "dependency-track"
+ else config.services.dependency-track.database.username
+ '';
+ description = "Specifies the username to use when authenticating to the database.";
+ };
+ "alpine.ldap.enabled" = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = ''
+ Defines if LDAP will be used for user authentication. If enabled,
+ alpine.ldap.* properties should be set accordingly.
+ '';
+ };
+ "alpine.oidc.enabled" = lib.mkOption {
+ type = lib.types.bool;
+ default = cfg.oidc.enable;
+ defaultText = lib.literalExpression "config.services.dependency-track.oidc.enable";
+ description = ''
+ Defines if OpenID Connect will be used for user authentication.
+ If enabled, alpine.oidc.* properties should be set accordingly.
+ '';
+ };
+ "alpine.oidc.client.id" = lib.mkOption {
+ type = lib.types.str;
+ default = cfg.oidc.clientId;
+ defaultText = lib.literalExpression "config.services.dependency-track.oidc.clientId";
+ description = ''
+ Defines the client ID to be used for OpenID Connect.
+ The client ID should be the same as the one configured for the frontend,
+ and will only be used to validate ID tokens.
+ '';
+ };
+ "alpine.oidc.issuer" = lib.mkOption {
+ type = lib.types.str;
+ default = cfg.oidc.issuer;
+ defaultText = lib.literalExpression "config.services.dependency-track.oidc.issuer";
+ description = ''
+ Defines the issuer URL to be used for OpenID Connect.
+ This issuer MUST support provider configuration via the /.well-known/openid-configuration endpoint.
+ See also:
+ - https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
+ - https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
+ '';
+ };
+ "alpine.oidc.username.claim" = lib.mkOption {
+ type = lib.types.str;
+ default = cfg.oidc.usernameClaim;
+ defaultText = lib.literalExpression "config.services.dependency-track.oidc.usernameClaim";
+ description = ''
+ Defines the name of the claim that contains the username in the provider's userinfo endpoint.
+ Common claims are "name", "username", "preferred_username" or "nickname".
+ See also: https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
+ '';
+ };
+ "alpine.oidc.user.provisioning" = lib.mkOption {
+ type = lib.types.bool;
+ default = cfg.oidc.userProvisioning;
+ defaultText = lib.literalExpression "config.services.dependency-track.oidc.userProvisioning";
+ description = ''
+ Specifies if mapped OpenID Connect accounts are automatically created upon successful
+ authentication. When a user logs in with a valid access token but an account has
+ not been previously provisioned, an authentication failure will be returned.
+ This allows admins to control specifically which OpenID Connect users can access the
+ system and which users cannot. When this value is set to true, a local OpenID Connect
+ user will be created and mapped to the OpenID Connect account automatically. This
+ automatic provisioning only affects authentication, not authorization.
+ '';
+ };
+ "alpine.oidc.team.synchronization" = lib.mkOption {
+ type = lib.types.bool;
+ default = cfg.oidc.teamSynchronization;
+ defaultText = lib.literalExpression "config.services.dependency-track.oidc.teamSynchronization";
+ description = ''
+ This option will ensure that team memberships for OpenID Connect users are dynamic and
+ synchronized with membership of OpenID Connect groups or assigned roles. When a team is
+ mapped to an OpenID Connect group, all local OpenID Connect users will automatically be
+ assigned to the team if they are a member of the group the team is mapped to. If the user
+ is later removed from the OpenID Connect group, they will also be removed from the team. This
+ option provides the ability to dynamically control user permissions via the identity provider.
+ Note that team synchronization is only performed during user provisioning and after successful
+ authentication.
+ '';
+ };
+ "alpine.oidc.teams.claim" = lib.mkOption {
+ type = lib.types.str;
+ default = cfg.oidc.teams.claim;
+ defaultText = lib.literalExpression "config.services.dependency-track.oidc.teams.claim";
+ description = ''
+ Defines the name of the claim that contains group memberships or role assignments in the provider's userinfo endpoint.
+ The claim must be an array of strings. Most public identity providers do not support group or role management.
+ When using a customizable / on-demand hosted identity provider, name, content, and inclusion in the userinfo endpoint
+ will most likely need to be configured.
+ '';
+ };
+ "alpine.oidc.teams.default" = lib.mkOption {
+ type = lib.types.nullOr lib.types.commas;
+ default = cfg.oidc.teams.default;
+ defaultText = lib.literalExpression "config.services.dependency-track.oidc.teams.default";
+ description = ''
+ Defines one or more team names that auto-provisioned OIDC users shall be added to.
+ Multiple team names may be provided as comma-separated list.
+
+ Has no effect when {option}`services.dependency-track.oidc.userProvisioning`=false,
+ or {option}`services.dependency-track.oidc.teamSynchronization`=true.
+ '';
+ };
+ };
+ };
+ default = { };
+ description = "See https://docs.dependencytrack.org/getting-started/configuration/#default-configuration for possible options";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.nginx = lib.mkIf cfg.nginx.enable {
+ enable = true;
+ recommendedGzipSettings = lib.mkDefault true;
+ recommendedOptimisation = lib.mkDefault true;
+ recommendedProxySettings = lib.mkDefault true;
+ recommendedTlsSettings = lib.mkDefault true;
+ upstreams.dependency-track.servers."localhost:${toString cfg.port}" = { };
+ virtualHosts.${cfg.nginx.domain} = {
+ locations = {
+ "/".proxyPass = "http://dependency-track";
+ "= /static/config.json".alias = frontendConfigFile;
+ };
+ };
+ };
+
+ systemd.services.dependency-track-postgresql-init = lib.mkIf cfg.database.createLocally {
+ after = [ "postgresql.service" ];
+ before = [ "dependency-track.service" ];
+ bindsTo = [ "postgresql.service" ];
+ path = [ config.services.postgresql.package ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ User = "postgres";
+ Group = "postgres";
+ LoadCredential = [ "db_password:${cfg.database.passwordFile}" ];
+ PrivateTmp = true;
+ };
+ script = ''
+ set -eou pipefail
+ shopt -s inherit_errexit
+
+ # Read the password from the credentials directory and
+ # escape any single quotes by adding additional single
+ # quotes after them, following the rules laid out here:
+ # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS
+ db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")"
+ db_password="''${db_password//\'/\'\'}"
+
+ echo "CREATE ROLE \"dependency-track\" WITH LOGIN PASSWORD '$db_password' CREATEDB" > /tmp/create_role.sql
+ psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='dependency-track'" | grep -q 1 || psql -tA --file="/tmp/create_role.sql"
+ psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'dependency-track'" | grep -q 1 || psql -tAc 'CREATE DATABASE "dependency-track" OWNER "dependency-track"'
+ '';
+ };
+
+ services.postgresql.enable = lib.mkIf cfg.database.createLocally (lib.mkDefault true);
+
+ systemd.services."dependency-track" =
+ let
+ databaseServices =
+ if cfg.database.createLocally then
+ [
+ "dependency-track-postgresql-init.service"
+ "postgresql.service"
+ ]
+ else
+ [ ];
+ in
+ {
+ description = "Dependency Track";
+ wantedBy = [ "multi-user.target" ];
+ requires = databaseServices;
+ after = databaseServices;
+ # provide settings via env vars to allow overriding default settings.
+ environment = {
+ HOME = "%S/dependency-track";
+ } // renderSettings cfg.settings;
+ serviceConfig = {
+ User = "dependency-track";
+ Group = "dependency-track";
+ DynamicUser = true;
+ StateDirectory = "dependency-track";
+ LoadCredential =
+ [ "db_password:${cfg.database.passwordFile}" ]
+ ++ lib.optional cfg.settings."alpine.ldap.enabled"
+ "ldap_bind_password:${cfg.ldap.bindPasswordFile}";
+ };
+ script = ''
+ set -eou pipefail
+ shopt -s inherit_errexit
+
+ export ALPINE_DATABASE_PASSWORD_FILE="$CREDENTIALS_DIRECTORY/db_password"
+ ${lib.optionalString cfg.settings."alpine.ldap.enabled" ''
+ export ALPINE_LDAP_BIND_PASSWORD="$(<"$CREDENTIALS_DIRECTORY/ldap_bind_password")"
+ ''}
+
+ exec ${lib.getExe pkgs.jre_headless} ${
+ lib.escapeShellArgs (
+ cfg.javaArgs
+ ++ [
+ "-DdependencyTrack.logging.level=${cfg.logLevel}"
+ "-jar"
+ "${cfg.package}/share/dependency-track/dependency-track.jar"
+ "-port"
+ "${toString cfg.port}"
+ ]
+ )
+ }
+ '';
+ };
+ };
+
+ meta = {
+ maintainers = lib.teams.cyberus.members;
+ };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 0e7330247575..a625cd92e236 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -255,6 +255,7 @@ in {
deepin = handleTest ./deepin.nix {};
deluge = handleTest ./deluge.nix {};
dendrite = handleTest ./matrix/dendrite.nix {};
+ dependency-track = handleTest ./dependency-track.nix {};
devpi-server = handleTest ./devpi-server.nix {};
dex-oidc = handleTest ./dex-oidc.nix {};
dhparams = handleTest ./dhparams.nix {};
diff --git a/nixos/tests/dependency-track.nix b/nixos/tests/dependency-track.nix
new file mode 100644
index 000000000000..ab0d78827286
--- /dev/null
+++ b/nixos/tests/dependency-track.nix
@@ -0,0 +1,65 @@
+import ./make-test-python.nix (
+ { pkgs, ... }:
+ let
+ dependencyTrackPort = 8081;
+ in
+ {
+ name = "dependency-track";
+ meta = {
+ maintainers = pkgs.lib.teams.cyberus.members;
+ };
+
+ nodes = {
+ server =
+ { pkgs, ... }:
+ {
+ virtualisation = {
+ cores = 2;
+ diskSize = 4096;
+ };
+
+ environment.systemPackages = with pkgs; [ curl ];
+ systemd.services.dependency-track = {
+ # source: https://github.com/DependencyTrack/dependency-track/blob/37e0ba59e8057c18a87a7a76e247a8f75677a56c/dev/scripts/data-nist-generate-dummy.sh
+ preStart = ''
+ set -euo pipefail
+
+ NIST_DIR="$HOME/.dependency-track/nist"
+
+ rm -rf "$NIST_DIR"
+ mkdir -p "$NIST_DIR"
+
+ for feed in $(seq "2024" "2002"); do
+ touch "$NIST_DIR/nvdcve-1.1-$feed.json.gz"
+ echo "9999999999999" > "$NIST_DIR/nvdcve-1.1-$feed.json.gz.ts"
+ done
+ '';
+ };
+ services.dependency-track = {
+ enable = true;
+ port = dependencyTrackPort;
+ nginx.domain = "localhost";
+ database.passwordFile = "${pkgs.writeText "dbPassword" ''hunter2'THE'''H''''E''}";
+ };
+ };
+ };
+
+ testScript = ''
+ import json
+
+ start_all()
+
+ server.wait_for_unit("dependency-track.service")
+ server.wait_until_succeeds(
+ "journalctl -o cat -u dependency-track.service | grep 'Dependency-Track is ready'"
+ )
+ server.wait_for_open_port(${toString dependencyTrackPort})
+
+ with subtest("version api returns correct version"):
+ version = json.loads(
+ server.succeed("curl http://localhost/api/version")
+ )
+ assert version["version"] == "${pkgs.dependency-track.version}"
+ '';
+ }
+)
diff --git a/pkgs/by-name/de/dependency-track/0000-remove-frontend-download.patch b/pkgs/by-name/de/dependency-track/0000-remove-frontend-download.patch
new file mode 100644
index 000000000000..6e68f1813c25
--- /dev/null
+++ b/pkgs/by-name/de/dependency-track/0000-remove-frontend-download.patch
@@ -0,0 +1,44 @@
+diff --git a/pom.xml b/pom.xml
+index 9988e765..906917c0 100644
+--- a/pom.xml
++++ b/pom.xml
+@@ -660,38 +660,13 @@
+ maven-antrun-plugin
+ 3.1.0
+
+-
+- frontend-download
+- prepare-package
+-
+-
+-
+-
+-
+-
+- run
+-
+-
+-
+- frontend-extract
+- prepare-package
+-
+-
+-
+-
+-
+-
+-
+- run
+-
+-
+
+ frontend-resource-deploy
+ prepare-package
+
+
+
+-
++
+
+
+
diff --git a/pkgs/by-name/de/dependency-track/0001-add-junixsocket.patch b/pkgs/by-name/de/dependency-track/0001-add-junixsocket.patch
new file mode 100644
index 000000000000..c6b0f9e76c65
--- /dev/null
+++ b/pkgs/by-name/de/dependency-track/0001-add-junixsocket.patch
@@ -0,0 +1,17 @@
+diff --git a/pom.xml b/pom.xml
+index 9988e765..f69576b4 100644
+--- a/pom.xml
++++ b/pom.xml
+@@ -457,6 +457,12 @@
+ ${lib.testcontainers.version}
+ test
+
++
++ com.kohlschutter.junixsocket
++ junixsocket-core
++ 2.10.0
++ pom
++
+
+
+
diff --git a/pkgs/by-name/de/dependency-track/package.nix b/pkgs/by-name/de/dependency-track/package.nix
new file mode 100644
index 000000000000..64f45f5c0619
--- /dev/null
+++ b/pkgs/by-name/de/dependency-track/package.nix
@@ -0,0 +1,113 @@
+{
+ lib,
+ buildNpmPackage,
+ fetchFromGitHub,
+ jre_headless,
+ cyclonedx-cli,
+ makeWrapper,
+ maven,
+ nix-update-script,
+ nixosTests,
+}:
+let
+ version = "4.11.7";
+
+ frontend = buildNpmPackage {
+ pname = "dependency-track-frontend";
+ inherit version;
+
+ src = fetchFromGitHub {
+ owner = "DependencyTrack";
+ repo = "frontend";
+ rev = version;
+ hash = "sha256-hgBDzzG90gunnlZeektzdBIdatNjbkDVmNLbxjyxAXE=";
+ };
+
+ npmDepsHash = "sha256-veyt7fn4g/eh/+2CapQxlEssZP8cQXONpI6sSW299tk=";
+ forceGitDeps = true;
+ makeCacheWritable = true;
+
+ # The prepack script runs the build script, which we'd rather do in the build phase.
+ npmPackFlags = [ "--ignore-scripts" ];
+ };
+in
+
+maven.buildMavenPackage rec {
+ inherit version;
+ pname = "dependency-track";
+
+ src = fetchFromGitHub {
+ owner = "DependencyTrack";
+ repo = "dependency-track";
+ rev = version;
+ hash = "sha256-BMkn9WnUGs4RxH5I1QQ2UDrlo32JcbfjfFcOG5YogLI=";
+ };
+
+ patches = [
+ ./0000-remove-frontend-download.patch
+ ./0001-add-junixsocket.patch
+ ];
+
+ mvnJdk = jre_headless;
+ mvnHash = "sha256-c/JwBiKsXuWbCm1dTCrVc+V/1G7Eii1mUW8xDyewyLs=";
+ manualMvnArtifacts = [ "com.coderplus.maven.plugins:copy-rename-maven-plugin:1.0.1" ];
+ buildOffline = true;
+
+ mvnDepsParameters = lib.escapeShellArgs [
+ "-Dmaven.test.skip=true"
+ "-P enhance"
+ "-P embedded-jetty"
+ ];
+
+ mvnParameters = lib.escapeShellArgs [
+ "-Dmaven.test.skip=true"
+ "-P enhance"
+ "-P embedded-jetty"
+ "-P bundle-ui"
+ "-Dservices.bom.merge.skip=false"
+ "-Dlogback.configuration.file=${src}/src/main/docker/logback.xml"
+ "-Dcyclonedx-cli.path=${lib.getExe cyclonedx-cli}"
+ ];
+
+ preBuild = ''
+ mkdir -p frontend
+ cp -r ${frontend}/lib/node_modules/@dependencytrack/frontend/dist frontend/
+ '';
+
+ afterDepsSetup = ''
+ mvn cyclonedx:makeBom -Dmaven.repo.local=$mvnDeps/.m2 \
+ org.codehaus.mojo:exec-maven-plugin:exec@merge-services-bom
+ '';
+
+ doCheck = false;
+
+ nativeBuildInputs = [ makeWrapper ];
+
+ installPhase = ''
+ runHook preInstall
+
+ install -Dm644 target/dependency-track-*.jar $out/share/dependency-track/dependency-track.jar
+ makeWrapper ${jre_headless}/bin/java $out/bin/dependency-track \
+ --add-flags "-jar $out/share/dependency-track/dependency-track.jar"
+
+ runHook postInstall
+ '';
+
+ passthru = {
+ # passthru for nix-update
+ inherit (frontend) npmDeps;
+ tests = {
+ inherit (nixosTests) dependency-track;
+ };
+ updateScript = nix-update-script { };
+ };
+
+ meta = {
+ description = "Intelligent Component Analysis platform that allows organizations to identify and reduce risk in the software supply chain";
+ homepage = "https://github.com/DependencyTrack/dependency-track";
+ license = lib.licenses.asl20;
+ maintainers = lib.teams.cyberus.members;
+ mainProgram = "dependency-track";
+ inherit (jre_headless.meta) platforms;
+ };
+}
diff --git a/pkgs/by-name/ma/maven/build-maven-package.nix b/pkgs/by-name/ma/maven/build-maven-package.nix
index 3808d40c6f9f..a84cd64ee43c 100644
--- a/pkgs/by-name/ma/maven/build-maven-package.nix
+++ b/pkgs/by-name/ma/maven/build-maven-package.nix
@@ -93,6 +93,7 @@ stdenv.mkDerivation (builtins.removeAttrs args [ "mvnFetchExtraArgs" ] // {
runHook preBuild
mvnDeps=$(cp -dpR ${fetchedMavenDeps}/.m2 ./ && chmod +w -R .m2 && pwd)
+ runHook afterDepsSetup
mvn package -o -nsu "-Dmaven.repo.local=$mvnDeps/.m2" ${mvnSkipTests} ${mvnParameters}
runHook postBuild