From 1d300413e84d4e67a3f0530b1ecff7f69c1760f0 Mon Sep 17 00:00:00 2001 From: kurogeek Date: Thu, 8 Jan 2026 16:30:35 +0700 Subject: [PATCH] inventree: init Co-authored-by: DavHau --- nixos/modules/module-list.nix | 1 + nixos/modules/services/misc/inventree.nix | 457 ++++++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/inventree.nix | 33 ++ pkgs/by-name/in/inventree/package.nix | 287 ++++++++++++++ pkgs/top-level/all-packages.nix | 2 + 6 files changed, 781 insertions(+) create mode 100644 nixos/modules/services/misc/inventree.nix create mode 100644 nixos/tests/inventree.nix create mode 100644 pkgs/by-name/in/inventree/package.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 0e98f50ab6e7..f0a6a8a9bc72 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -876,6 +876,7 @@ ./services/misc/ihaskell.nix ./services/misc/iio-niri.nix ./services/misc/input-remapper.nix + ./services/misc/inventree.nix ./services/misc/invidious-router.nix ./services/misc/irkerd.nix ./services/misc/jackett.nix diff --git a/nixos/modules/services/misc/inventree.nix b/nixos/modules/services/misc/inventree.nix new file mode 100644 index 000000000000..b5e5ecec9210 --- /dev/null +++ b/nixos/modules/services/misc/inventree.nix @@ -0,0 +1,457 @@ +{ + config, + pkgs, + lib, + ... +}: +let + cfg = config.services.inventree; + pkg = cfg.package; + + mysqlLocal = cfg.database.createLocally && cfg.database.dbtype == "mysql"; + pgsqlLocal = cfg.database.createLocally && cfg.database.dbtype == "postgresql"; + + manage = pkgs.writeShellScriptBin "inventree-manage" '' + set -a + ${lib.toShellVars cfg.settings} + ${lib.optionalString ( + cfg.database.passwordFile != null + ) ''INVENTREE_DB_PASSWORD="$(<"${cfg.database.passwordFile}")"''} + set +a + + pushd "${cfg.dataDir}" + sudo=exec + if [[ "$USER" != ${cfg.user} ]]; then + ${ + if config.security.sudo.enable then + "sudo='exec ${config.security.wrapperDir}/sudo -u ${cfg.user} -E'" + else + ">&2 echo 'Aborting, inventree-manage must be run as user `${cfg.user}`!'; exit 2" + } + fi + $sudo ${cfg.package}/bin/inventree "$@" + popd + ''; + +in +{ + meta.maintainers = with lib.maintainers; [ + kurogeek + ]; + + options.services.inventree = with lib; { + enable = lib.mkEnableOption "inventree"; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/inventree"; + description = "Inventree's data storage path. Will be `/var/lib/inventree` by default."; + }; + + package = mkOption { + type = types.package; + description = "Which package to use for the InvenTree instance."; + default = pkgs.inventree; + defaultText = literalExpression "pkgs.inventree"; + }; + + adminPasswordFile = mkOption { + type = types.nullOr lib.types.path; + default = null; + example = "/run/keys/inventree-password"; + description = "Path to a file containing admin password"; + }; + + secretKeyFile = mkOption { + type = types.path; + default = "${cfg.dataDir}/secret_key.txt"; + example = "/run/keys/inventree-secret-key"; + description = '' + Path to a file containing the secret key + ''; + }; + + database = { + dbtype = mkOption { + type = lib.types.nullOr ( + lib.types.enum [ + "postgresql" + "mysql" + ] + ); + default = "postgresql"; + description = "Database type."; + }; + dbhost = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = + if pgsqlLocal then + "/run/postgresql" + else if mysqlLocal then + "/run/mysqld/mysqld.sock" + else + "localhost"; + defaultText = "localhost"; + example = "localhost"; + description = '' + Database host or socket path. + If [](#opt-services.inventree.database.createLocally) is true and + [](#opt-services.inventree.database.dbtype) is either `postgresql` or `mysql`, + defaults to the correct Unix socket instead. + ''; + }; + dbport = mkOption { + type = types.port; + default = 5432; + description = "Database host port."; + }; + dbname = mkOption { + type = types.str; + default = "inventree"; + description = "Database name."; + }; + dbuser = mkOption { + type = types.str; + default = "inventree"; + defaultText = lib.literalExpression "inventree"; + description = "Database username."; + }; + passwordFile = mkOption { + type = with types; nullOr path; + default = null; + example = "/run/keys/inventree-dbpassword"; + description = '' + A file containing the password corresponding to + . + ''; + }; + createLocally = mkOption { + type = types.bool; + default = true; + description = "Create the database and database user locally."; + }; + }; + + domain = mkOption { + type = types.str; + default = "localhost"; + example = "inventree.example.com"; + description = mdDoc '' + The INVENTREE_SITE_URL option defines the base URL for the + InvenTree server. This is a critical setting, and it is required + for correct operation of the server. If not specified, the + server will attempt to determine the site URL automatically - + but this may not always be correct! + + The site URL is the URL that users will use to access the + InvenTree server. For example, if the server is accessible at + `https://inventree.example.com`, the site URL should be set to + `https://inventree.example.com`. Note that this is not + necessarily the same as the internal URL that the server is + running on - the internal URL will depend entirely on your + server configuration and may be obscured by a reverse proxy or + other such setup. + ''; + }; + + user = mkOption { + type = types.str; + default = "inventree"; + description = "User under which InvenTree runs."; + }; + + group = mkOption { + type = types.str; + default = "inventree"; + description = "Group under which InvenTree runs."; + }; + + settings = mkOption { + type = + with lib.types; + attrsOf ( + nullOr (oneOf [ + path + str + ]) + ); + default = { + INVENTREE_CONFIG_FILE = lib.mkDefault "${cfg.dataDir}/config/config.yaml"; + INVENTREE_OIDC_PRIVATE_KEY_FILE = lib.mkDefault "${cfg.dataDir}/config/oidc_private_key.txt"; + INVENTREE_STATIC_ROOT = lib.mkDefault "${cfg.dataDir}/data/static_root"; + INVENTREE_MEDIA_ROOT = lib.mkDefault "${cfg.dataDir}/data/media"; + INVENTREE_BACKUP_DIR = lib.mkDefault "${cfg.dataDir}/data/backups"; + INVENTREE_SITE_URL = lib.mkDefault "http://${cfg.domain}"; + INVENTREE_PLUGIN_FILE = lib.mkDefault "${cfg.dataDir}/data/plugins/plugins.txt"; + INVENTREE_PLUGIN_DIR = lib.mkDefault "${cfg.dataDir}/data/plugins"; + INVENTREE_ADMIN_PASSWORD_FILE = lib.mkDefault cfg.adminPasswordFile; + INVENTREE_SECRET_KEY_FILE = lib.mkDefault cfg.secretKeyFile; + INVENTREE_AUTO_UPDATE = lib.mkDefault "false"; + }; + description = '' + InvenTree config options. + + See [the documentation](https://docs.inventree.org/en/stable/start/config/) for available options. + ''; + example = { + INVENTREE_CACHE_ENABLED = true; + INVENTREE_CACHE_HOST = "localhost"; + + INVENTREE_EMAIL_HOST = "smtp.example.com"; + INVENTREE_EMAIL_PORT = 25; + }; + }; + + }; + + config = lib.mkIf cfg.enable ( + lib.mkMerge [ + { + services.inventree.settings = { + INVENTREE_DB_ENGINE = cfg.database.dbtype; + INVENTREE_DB_NAME = cfg.database.dbname; + INVENTREE_DB_HOST = cfg.database.dbhost; + INVENTREE_DB_USER = cfg.database.dbuser; + INVENTREE_DB_PORT = toString cfg.database.dbport; + + INVENTREE_CONFIG_FILE = lib.mkDefault "${cfg.dataDir}/config/config.yaml"; + INVENTREE_OIDC_PRIVATE_KEY_FILE = lib.mkDefault "${cfg.dataDir}/config/oidc_private_key.txt"; + INVENTREE_STATIC_ROOT = lib.mkDefault "${cfg.dataDir}/data/static_root"; + INVENTREE_MEDIA_ROOT = lib.mkDefault "${cfg.dataDir}/data/media"; + INVENTREE_BACKUP_DIR = lib.mkDefault "${cfg.dataDir}/data/backups"; + INVENTREE_SITE_URL = lib.mkDefault "http://${cfg.domain}"; + INVENTREE_PLUGIN_FILE = lib.mkDefault "${cfg.dataDir}/data/plugins/plugins.txt"; + INVENTREE_PLUGIN_DIR = lib.mkDefault "${cfg.dataDir}/data/plugins"; + INVENTREE_ADMIN_PASSWORD_FILE = lib.mkDefault cfg.adminPasswordFile; + INVENTREE_SECRET_KEY_FILE = lib.mkDefault cfg.secretKeyFile; + INVENTREE_AUTO_UPDATE = lib.mkDefault "false"; + }; + environment.systemPackages = [ manage ]; + systemd.tmpfiles.rules = ( + map (dir: "d ${dir} 0755 inventree inventree") [ + "${cfg.dataDir}" + "${cfg.dataDir}/config" + "${cfg.dataDir}/data" + "${cfg.dataDir}/data/static_root" + "${cfg.dataDir}/data/media" + "${cfg.dataDir}/data/backups" + "${cfg.dataDir}/data/plugins" + ] + ); + + services.postgresql = lib.mkIf pgsqlLocal { + enable = true; + ensureDatabases = [ cfg.database.dbname ]; + ensureUsers = [ + { + name = cfg.database.dbuser; + ensureDBOwnership = true; + } + ]; + }; + + services.mysql = lib.mkIf mysqlLocal { + enable = true; + package = lib.mkDefault pkgs.mariadb; + ensureDatabases = [ cfg.database.dbname ]; + ensureUsers = [ + { + name = cfg.database.dbuser; + ensurePermissions = { + "${cfg.database.dbname}.*" = "ALL PRIVILEGES"; + }; + } + ]; + }; + + services.nginx.enable = true; + services.nginx.virtualHosts.${cfg.domain} = { + locations = + let + unixPath = config.systemd.sockets.inventree-server.socketConfig.ListenStream; + in + { + "/" = { + extraConfig = '' + proxy_set_header Host $host; + proxy_set_header X-Forwarded-By $server_addr:$server_port; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header CLIENT_IP $remote_addr; + + proxy_pass_request_headers on; + + proxy_redirect off; + + client_max_body_size 100M; + + proxy_buffering off; + proxy_request_buffering off; + ''; + proxyPass = "http://unix:${unixPath}"; + }; + "/auth" = { + extraConfig = '' + internal; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header X-Original-URI $request_uri; + ''; + proxyPass = "http://unix:${unixPath}:/auth/"; + }; + "/static/" = { + alias = "${cfg.settings.INVENTREE_STATIC_ROOT}/"; + extraConfig = '' + autoindex on; + + # Caching settings + expires 30d; + add_header Pragma public; + add_header Cache-Control "public"; + ''; + }; + "/media/" = { + alias = "${cfg.settings.INVENTREE_MEDIA_ROOT}/"; + extraConfig = '' + auth_request /auth; + add_header Content-disposition "attachment"; + ''; + }; + }; + }; + + systemd.services.inventree-setup = { + description = "Inventree setup"; + wantedBy = [ "inventree.target" ]; + partOf = [ "inventree.target" ]; + after = lib.optional mysqlLocal "mysql.service" ++ lib.optional pgsqlLocal "postgresql.target"; + requires = lib.optional mysqlLocal "mysql.service" ++ lib.optional pgsqlLocal "postgresql.target"; + before = [ + "inventree-static.service" + "inventree-server.service" + "inventree-qcluster.service" + ]; + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + Group = cfg.group; + RemainAfterExit = true; + PrivateTmp = true; + } + // lib.optionalAttrs (cfg.database.passwordFile != null) { + LoadCredential = "db_password:${cfg.database.passwordFile}"; + }; + environment = cfg.settings; + script = '' + set -euo pipefail + umask u=rwx,g=,o= + + ${ + lib.optionalString (cfg.database.passwordFile != null) '' + INVENTREE_DB_PASSWORD=$(<"$CREDENTIALS_DIRECTORY/db_password") + '' + } \ + exec ${pkg}/bin/inventree migrate + ''; + }; + + systemd.services.inventree-static = { + description = "Inventree static migration"; + wantedBy = [ "inventree.target" ]; + partOf = [ "inventree.target" ]; + before = [ "inventree-server.service" ]; + environment = cfg.settings; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + StateDirectory = "inventree"; + PrivateTmp = true; + } + // lib.optionalAttrs (cfg.database.passwordFile != null) { + LoadCredential = "db_password:${cfg.database.passwordFile}"; + }; + script = '' + ${ + lib.optionalString (cfg.database.passwordFile != null) '' + INVENTREE_DB_PASSWORD=$(<"$CREDENTIALS_DIRECTORY/db_password") + '' + } \ + exec ${pkg}/bin/inventree collectstatic --no-input + ''; + }; + + systemd.services.inventree-server = { + description = "Inventree Gunicorn service"; + requiredBy = [ "inventree.target" ]; + partOf = [ "inventree.target" ]; + environment = cfg.settings; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + StateDirectory = "inventree"; + PrivateTmp = true; + } + // lib.optionalAttrs (cfg.database.passwordFile != null) { + LoadCredential = "db_password:${cfg.database.passwordFile}"; + }; + script = '' + ${ + lib.optionalString (cfg.database.passwordFile != null) '' + INVENTREE_DB_PASSWORD=$(<"$CREDENTIALS_DIRECTORY/db_password") + '' + } \ + exec ${pkg}/bin/gunicorn InvenTree.wsgi + ''; + }; + + systemd.sockets.inventree-server = { + wantedBy = [ "sockets.target" ]; + partOf = [ "inventree.target" ]; + socketConfig.ListenStream = "/run/inventree/gunicorn.socket"; + }; + + systemd.services.inventree-qcluster = { + description = "InvenTree qcluster server"; + requiredBy = [ "inventree.target" ]; + wantedBy = [ "inventree.target" ]; + partOf = [ "inventree.target" ]; + environment = cfg.settings; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + StateDirectory = "inventree"; + PrivateTmp = true; + } + // lib.optionalAttrs (cfg.database.passwordFile != null) { + LoadCredential = "db_password:${cfg.database.passwordFile}"; + }; + script = '' + ${ + lib.optionalString (cfg.database.passwordFile != null) '' + INVENTREE_DB_PASSWORD=$(<"$CREDENTIALS_DIRECTORY/db_password") + '' + } \ + exec ${pkg}/bin/inventree qcluster + ''; + }; + + systemd.targets.inventree = { + description = "Target for all InvenTree services"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + }; + + users = lib.optionalAttrs (cfg.user == cfg.user) { + users.${cfg.user} = { + group = cfg.group; + isSystemUser = true; + home = cfg.dataDir; + }; + groups.${cfg.group}.members = [ cfg.user ]; + }; + } + ] + ); +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 8985130a6f8d..59a836b5ea89 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -790,6 +790,7 @@ in installer = handleTest ./installer.nix { }; installer-systemd-stage-1 = handleTest ./installer-systemd-stage-1.nix { }; intune = runTest ./intune.nix; + inventree = runTest ./inventree.nix; invidious = runTest ./invidious.nix; invoiceplane = runTest ./invoiceplane.nix; iodine = runTest ./iodine.nix; diff --git a/nixos/tests/inventree.nix b/nixos/tests/inventree.nix new file mode 100644 index 000000000000..34fe8730ae67 --- /dev/null +++ b/nixos/tests/inventree.nix @@ -0,0 +1,33 @@ +{ lib, ... }: +{ + name = "inventree"; + meta.maintainers = with lib.maintainers; [ + kurogeek + ]; + + nodes = { + psqlTest = { + services.inventree = { + enable = true; + }; + }; + mysqlTest = { + services.inventree = { + enable = true; + database.dbtype = "mysql"; + }; + }; + }; + testScript = '' + start_all() + psqlTest.wait_for_unit("inventree.target") + psqlTest.wait_for_unit("inventree-server.service") + psqlTest.wait_for_open_unix_socket("/run/inventree/gunicorn.socket") + psqlTest.wait_until_succeeds("curl -sf http://localhost/web") + + mysqlTest.wait_for_unit("inventree.target") + mysqlTest.wait_for_unit("inventree-server.service") + mysqlTest.wait_for_open_unix_socket("/run/inventree/gunicorn.socket") + mysqlTest.wait_until_succeeds("curl -sf http://localhost/web") + ''; +} diff --git a/pkgs/by-name/in/inventree/package.nix b/pkgs/by-name/in/inventree/package.nix new file mode 100644 index 000000000000..109b4ad7550b --- /dev/null +++ b/pkgs/by-name/in/inventree/package.nix @@ -0,0 +1,287 @@ +{ + fetchFromGitHub, + fetchYarnDeps, + lib, + nodejs, + python312, + stdenv, + yarnBuildHook, + yarnConfigHook, + yarnInstallHook, +}: +let + python3 = python312.override { + self = python3; + packageOverrides = self: super: { + django = super.django_4; + }; + }; + + version = "0.18.0"; + src = fetchFromGitHub { + owner = "inventree"; + repo = "inventree"; + rev = "d6c722041987d13a4e596711aafa0f30cde3f9a0"; + hash = "sha256-BnRE6BM6/LHUtP3H0L8qh3LAxFphNlpll+r1oH4sGuQ="; + }; + + frontend = + let + frontendSource = src + "/src/frontend"; + in + stdenv.mkDerivation (finalAttrs: { + + pname = "inventree-frontend"; + inherit version; + + src = frontendSource; + + yarnOfflineCache = fetchYarnDeps { + yarnLock = finalAttrs.src + "/yarn.lock"; + hash = "sha256-wXIVtW1dMxhyuZ1LmuYZ2IwUbRZwbYmdRkiribMbK20="; + }; + + nativeBuildInputs = [ + nodejs + yarnConfigHook + yarnBuildHook + yarnInstallHook + ]; + + buildPhase = '' + mkdir -p $out + + export PATH=$PATH:$TMP/frontend/node_modules/.bin + substituteInPlace $TMP/frontend/vite.config.ts --replace-warn "../../src/backend/InvenTree/web/static/web" "$out/static/web" + + npm run extract + npm run compile + npm run build + ''; + }); +in +python3.pkgs.buildPythonApplication rec { + pname = "inventree"; + pyproject = true; + inherit version src; + + dependencies = + with python3.pkgs; + [ + django + dj-rest-auth + django-allauth + django-cleanup + django-cors-headers + django-crispy-forms + django-dbbackup + django-error-report-2 + django-filter + django-flags + django-formtools + django-ical + django-import-export + django-maintenance-mode + django-markdownify + django-money + django-mptt + django-mailbox + django-anymail + django-q2 + django-redis + django-sesame + django-sql-utils + django-sslserver + django-stdimage + django-structlog + django-taggit + django-oauth-toolkit + django-otp + django-user-sessions + django-weasyprint + standard-imghdr + django-xforwardedfor-middleware + djangorestframework-simplejwt + djangorestframework + drf-spectacular + + bleach + cryptography + distutils + dulwich + feedparser + gunicorn + jinja2 + pdf2image + pillow + pint + python-barcode + python-dotenv + qrcode + pytz + pyyaml + rapidfuzz + sentry-sdk + structlog + tablib + tinycss2 + weasyprint + whitenoise + pypdf + ppf-datamatrix + psycopg2 + mysqlclient + + opentelemetry-api + opentelemetry-sdk + opentelemetry-exporter-otlp + opentelemetry-instrumentation-django + opentelemetry-instrumentation-requests + opentelemetry-instrumentation-redis + opentelemetry-instrumentation-sqlite3 + opentelemetry-instrumentation-system-metrics + opentelemetry-instrumentation-wsgi + ] + ++ tablib.optional-dependencies.all + ++ tablib.optional-dependencies.xls + ++ tablib.optional-dependencies.xlsx + ++ djangorestframework-simplejwt.optional-dependencies.crypto + ++ django-anymail.optional-dependencies.amazon-ses + ++ django-allauth.optional-dependencies.socialaccount + ++ django-allauth.optional-dependencies.saml + ++ django-allauth.optional-dependencies.openid + ++ django-allauth.optional-dependencies.mfa; + + build-system = [ python3.pkgs.setuptools ]; + + prePatch = + let + skippedCheckFunctions = [ + "test_task_check_for_updates" + "test_download_image" + "test_commit_info" + "test_rates" + "test_download_build_orders" + "test_valid_url" + "test_refresh_endpoint" + "test_download_csv" + "test_download_line_items" + "test_export" + "test_download_xlsx" + "test_download_csv" + "test_export" + "test_part_label_translation" + "test_part_download" + "test_date_filters" + "test_bom_export" + "test_hash" + "test_date" + "test_api_call" + "test_function_errors" + "test_stocktake_exporter" + "test_return" + "test_plugin_install" + "test_full_process" + "test_package_loading" + "test_export" + "test_users_exist" + ]; + skippedFuncScripts = builtins.map (funcName: '' + grep -rlZ ${funcName} . | while IFS= read -r -d "" file; do + substituteInPlace "$file" --replace-fail "${funcName}" "skip_${funcName}" + done + '') skippedCheckFunctions; + in + '' + ${lib.concatStringsSep "\n" skippedFuncScripts} + ''; + + installPhase = + let + pythonPath = python3.pkgs.makePythonPath dependencies; + in + '' + runHook preInstall + + # Don't need to bother with a non-maintained library from ages ago + substituteInPlace src/backend/InvenTree/InvenTree/settings.py --replace-fail "django_slowtests.testrunner.DiscoverSlowestTestsRunner" "django.test.runner.DiscoverRunner" + + mkdir -p $out/lib/${pname}/src/backend/InvenTree/web/ + cp -r src $out/lib/${pname} + ln -s ${frontend}/static $out/lib/${pname}/src/backend/InvenTree/web + # cp -r ${frontend}/static $out/lib/${pname}/src/backend/InvenTree/web + + chmod +x $out/lib/${pname}/src/backend/InvenTree/manage.py + + makeWrapper $out/lib/${pname}/src/backend/InvenTree/manage.py $out/bin/${pname} \ + --prefix PYTHONPATH : "${pythonPath}:$out/lib/${pname}/src/backend/InvenTree" + + makeWrapper ${lib.getExe python3.pkgs.gunicorn} $out/bin/gunicorn \ + --prefix PYTHONPATH : "${pythonPath}:$out/${python3.sitePackages}":"${pythonPath}:$out/lib/${pname}/src/backend/InvenTree" + + runHook postInstall + ''; + doCheck = true; + env = { + DJANGO_SETTINGS_MODULE = "InvenTree.settings"; + }; + + checkPhase = '' + runHook preCheck + + tmpDir=$(mktemp -d) + mkdir -p $tmpDir/media + mkdir -p $tmpDir/.cache/fontconfig + export HOME=$tmpDir + export INVENTREE_STATIC_ROOT=$tmpDir + export INVENTREE_MEDIA_ROOT=$tmpDir/media + export INVENTREE_BACKUP_DIR=$tmpDir + export INVENTREE_DB_ENGINE=django.db.backends.sqlite3 + export INVENTREE_DB_NAME=inventree.db + export INVENTREE_SITE_URL="http://localhost:8000" + + export INVENTREE_PLUGINS_ENABLED=true + export INVENTREE_PLUGIN_TESTING=true + export INVENTREE_PLUGIN_TESTING_SETUP=true + + pushd src/backend/InvenTree + ${python3.interpreter} ./manage.py check + ${python3.interpreter} ./manage.py migrate + + ${python3.interpreter} ./manage.py test --failfast + popd + + runHook postCheck + ''; + + nativeCheckInputs = with python3.pkgs; [ + django-test-migrations + pytest-django + pytest-env + pytestCheckHook + invoke + coverage + pytest-cov + pdfminer-six + ]; + passthru = + let + pythonPath = python3.pkgs.makePythonPath dependencies; + in + { + inherit frontend; + pythonPath = pythonPath; + }; + + meta = { + description = "Open Source Inventory Management System"; + homepage = "https://inventree.org/"; + changelog = "https://github.com/paperless-ngx/paperless-ngx/releases/tag/${src.tag}"; + license = lib.licenses.mit; + platforms = lib.platforms.linux; + mainProgram = "inventree"; + maintainers = with lib.maintainers; [ + kurogeek + ]; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index a74ca9d7fd5c..dcab0bf56907 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -12517,4 +12517,6 @@ with pkgs; gpac-unstable = callPackage ../by-name/gp/gpac/package.nix { releaseChannel = "unstable"; }; + + inventree = callPackage ../by-name/in/inventree/package.nix { }; }