Unverified Commit 09612cb5 authored by dotlambda's avatar dotlambda Committed by GitHub
Browse files

inventree: drop (#500291)

parents 7ea94bb5 3603c991
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -880,7 +880,6 @@
  ./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
+0 −446
Original line number Diff line number Diff line
{
  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.buildDocsInSandbox = false;
  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";
      defaultText = lib.literalExpression ''"''${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";
        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
          <option>database.dbuser</option>.
        '';
      };
      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 = ''
        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 = { };
      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 ];
        };
      }
    ]
  );
}
+0 −1
Original line number Diff line number Diff line
@@ -797,7 +797,6 @@ 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;

nixos/tests/inventree.nix

deleted100644 → 0
+0 −33
Original line number Diff line number Diff line
{ 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")
  '';
}
+0 −291
Original line number Diff line number Diff line
{
  fetchFromGitHub,
  fetchYarnDeps,
  lib,
  nodejs,
  python312,
  stdenv,
  yarnBuildHook,
  yarnConfigHook,
  yarnInstallHook,
}:
let
  python3 = python312.override {
    self = python3;
    packageOverrides = self: super: {
      django = super.django_4;
    };
  };

  version = "1.1.10";
  src = fetchFromGitHub {
    owner = "inventree";
    repo = "inventree";
    tag = "${version}";
    hash = "sha256-TPB/3pFIU+ui4c+CbqIKTyAfJ/Xepm/RIhZeYhTrgI4=";
  };

  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-Ijbkx+INZgsvMhkzo8h/FUY75W3UHnKAdUjQRD8kJZw=";
      };

      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-storages
      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
      requests-mock

      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"
        "test_import_part"
        "test_model_names"
      ];
      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
    ];
  };
}
Loading