From c4bfb85d486068677bf63f84fcab477fd526f8d3 Mon Sep 17 00:00:00 2001 From: kurogeek Date: Wed, 8 Oct 2025 15:50:00 +0700 Subject: [PATCH] mob next [ci-skip] [ci skip] [skip ci] lastFile:modules/nixos/inventree/default.nix --- modules/nixos/inventree/default.nix | 308 ++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 modules/nixos/inventree/default.nix diff --git a/modules/nixos/inventree/default.nix b/modules/nixos/inventree/default.nix new file mode 100644 index 0000000..6e4346a --- /dev/null +++ b/modules/nixos/inventree/default.nix @@ -0,0 +1,308 @@ +{ + lib, + config, + pkgs, + ... +}: +let + inherit (lib) + mkEnableOption + mkOption + types + mkIf + ; + + configFormat = pkgs.formats.json { }; + configFile = "/run/inventree/config.json"; + cfg = config.services.inventree; + pkg = cfg.package; + + inventree-invoke = pkgs.writeShellApplication { + name = "inventree-invoke"; + text = '' + export INVENTREE_CONFIG_FILE=${configFile} + export INVENTREE_SECRET_KEY_FILE=${cfg.secretKeyFile} + export PYTHONPATH=${pkg.pythonPath} + + exec -a "$0" ${pkgs.python3Packages.invoke}/bin/invoke -r ${cfg.package}/opt/inventree "$@" + ''; + }; +in +{ + options.services.inventree = { + enable = mkEnableOption "InvenTree parts manager"; + + package = lib.mkOption { + type = types.package; + default = pkgs.inventree; + description = '' + InvenTree package to use + ''; + }; + + hostName = mkOption { + type = types.str; + description = "FQDN for the InvenTree instance."; + }; + + dataDir = mkOption { + type = types.path; + default = "/var/lib/inventree"; + example = "/var/lib/inventree"; + description = '' + The default path for all inventree data. + ''; + }; + + secretKeyFile = mkOption { + type = types.path; + default = "${cfg.dataDir}/secret_key.txt"; + description = '' + Path to a file containing the secret key + ''; + }; + + config = mkOption { + type = types.submodule ({ + freeformType = configFormat.type; + options = { + site_url = mkOption { + type = types.str; + default = "https://${cfg.hostName}"; + }; + static_root = mkOption { + type = types.path; + default = "${cfg.dataDir}/static"; + description = '' + Static file storage + ''; + }; + media_root = mkOption { + type = types.path; + default = "${cfg.dataDir}/media_root"; + description = "Media root directory"; + }; + backup_dir = mkOption { + type = types.path; + default = "${cfg.dataDir}/backups"; + description = "Backup directory"; + }; + oidc_private_key_file = mkOption { + type = types.path; + default = "${cfg.dataDir}/oidc.key"; + }; + }; + }); + default = { }; + description = '' + Config options, see https://docs.inventree.org/en/stable/start/config/ + for details + ''; + }; + + serverStartTimeout = mkOption { + type = types.str; + default = "10min"; + description = '' + TimeoutStartSec for the server systemd service. + See https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#TimeoutStartSec= + for more details + ''; + }; + + serverStopTimeout = mkOption { + type = types.str; + default = "5min"; + description = '' + TimeoutStopSec for the server systemd service. + See https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#TimeoutStopSec= + for more details + ''; + }; + + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ inventree-invoke ]; + + services.inventree.config = { + plugins_enabled = false; + plugin_file = "${cfg.dataDir}/plugins.txt"; + plugin_dir = "${cfg.dataDir}/plugins"; + database = { + ENGINE = "postgresql"; + NAME = "inventree"; + HOST = "/run/postgresql"; + }; + }; + + services.postgresql = { + enable = true; + ensureDatabases = [ "inventree" ]; + ensureUsers = [ + { + name = "inventree"; + ensureDBOwnership = true; + } + ]; + }; + + users.users.inventree = { + group = "inventree"; + isSystemUser = true; + description = "InvenTree daemon user"; + }; + + users.groups.inventree = { }; + + services.nginx.virtualHosts.${cfg.hostName} = { + locations = + let + unixPath = config.systemd.sockets.inventree-gunicorn.socketConfig.ListenStream; + in + { + "/" = { + extraConfig = '' + client_max_body_size 100M; + ''; + proxyPass = "http://unix:${unixPath}"; + }; + "/static/" = { + alias = "${cfg.config.static_root}/"; + extraConfig = '' + expires 30d; + ''; + }; + "/media/" = { + alias = "${cfg.config.media_root}/"; + extraConfig = '' + auth_request /auth; + ''; + }; + "/auth" = { + extraConfig = '' + internal; + ''; + proxyPass = "http://unix:${unixPath}:/auth/"; + }; + }; + }; + + systemd.targets.inventree = { + description = "Target for all InvenTree services"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + }; + + systemd.services.inventree-config = { + description = "Inventree config generation"; + wantedBy = [ "inventree.target" ]; + partOf = [ "inventree.target" ]; + before = [ + "inventree-static.service" + "inventree-gunicorn.service" + "inventree-qcluster.service" + ]; + serviceConfig = { + User = "root"; + Group = "root"; + Type = "oneshot"; + RemainAfterExit = true; + PrivateTmp = true; + }; + script = '' + set -euo pipefail + + umask u=rwx,g=,o= + + chown inventree:inventree ${configFile} + ''; + }; + + systemd.services.inventree-static = { + description = "InvenTree static migration"; + wantedBy = [ "inventree.target" ]; + partOf = [ "inventree.target" ]; + before = [ "inventree-gunicorn.service" ]; + environment = { + INVENTREE_CONFIG_FILE = configFile; + INVENTREE_SECRET_KEY_FILE = cfg.secretKeyFile; + INVENTREE_AUTO_UPDATE = "1"; + INVENTREE_PLUGINS_ENABLED = "1"; + INVENTREE_PLUGIN_NOINSTALL = "1"; + + PYTHONPATH = pkg.pythonPath; + }; + serviceConfig = { + User = "inventree"; + Group = "inventree"; + StateDirectory = "inventree"; + #RuntimeDirectory = "inventree"; + PrivateTmp = true; + ExecStart = '' + ${pkg}/opt/inventree/src/backend/InvenTree/manage.py collectstatic --no-input + ''; + }; + }; + + systemd.services.inventree-gunicorn = { + description = "InvenTree Gunicorn server"; + requiredBy = [ "inventree.target" ]; + partOf = [ "inventree.target" ]; + #wantedBy = [ "inventree.target" ]; + environment = { + INVENTREE_CONFIG_FILE = configFile; + INVENTREE_SECRET_KEY_FILE = cfg.secretKeyFile; + INVENTREE_AUTO_UPDATE = "1"; + INVENTREE_PLUGINS_ENABLED = "1"; + INVENTREE_PLUGIN_NOINSTALL = "1"; + + PYTHONPATH = pkg.pythonPath; + }; + serviceConfig = { + User = "inventree"; + Group = "inventree"; + StateDirectory = "inventree"; + #RuntimeDirectory = "inventree"; + PrivateTmp = true; + ExecStart = '' + ${pkg.gunicorn}/bin/gunicorn InvenTree.wsgi \ + --pythonpath ${pkg}/opt/inventree/src/backend/InvenTree + ''; + }; + }; + + systemd.sockets.inventree-gunicorn = { + 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 = { + INVENTREE_CONFIG_FILE = configFile; + INVENTREE_SECRET_KEY_FILE = cfg.secretKeyFile; + INVENTREE_AUTO_UPDATE = "1"; + INVENTREE_PLUGINS_ENABLED = "0"; + INVENTREE_PLUGIN_NOINSTALL = "1"; + + PYTHONPATH = pkg.pythonPath; + }; + serviceConfig = { + User = "inventree"; + Group = "inventree"; + StateDirectory = "inventree"; + #RuntimeDirectory = "inventree"; + PrivateTmp = true; + ExecStart = '' + ${pkg}/opt/inventree/src/backend/InvenTree/manage.py qcluster + ''; + }; + }; + }; +}