diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a3a6834dfe5a..15e253be5b80 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -359,3 +359,8 @@ nixos/tests/zfs.nix @raitobezarius nixos/modules/services/continuous-integration/buildbot @Mic92 @zowoq nixos/tests/buildbot.nix @Mic92 @zowoq pkgs/development/tools/continuous-integration/buildbot @Mic92 @zowoq + +# Pretix +pkgs/by-name/pr/pretix/ @mweinelt +nixos/modules/services/web-apps/pretix.nix @mweinelt +nixos/tests/web-apps/pretix.nix @mweinelt diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md index b649be5a9dc8..ba02a434e327 100644 --- a/nixos/doc/manual/release-notes/rl-2405.section.md +++ b/nixos/doc/manual/release-notes/rl-2405.section.md @@ -103,6 +103,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m - [Monado](https://monado.freedesktop.org/), an open source XR runtime. Available as [services.monado](#opt-services.monado.enable). +- [Pretix](https://pretix.eu/about/en/), an open source ticketing software for events. Available as [services.pretix]($opt-services-pretix.enable). + - [Clevis](https://github.com/latchset/clevis), a pluggable framework for automated decryption, used to unlock encrypted devices in initrd. Available as [boot.initrd.clevis.enable](#opt-boot.initrd.clevis.enable). - [armagetronad](https://wiki.armagetronad.org), a mid-2000s 3D lightcycle game widely played at iD Tech Camps. You can define multiple servers using `services.armagetronad..enable`. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 41e369ac1c65..2ccaea466c6a 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1358,6 +1358,7 @@ ./services/web-apps/plausible.nix ./services/web-apps/powerdns-admin.nix ./services/web-apps/pretalx.nix + ./services/web-apps/pretix.nix ./services/web-apps/prosody-filer.nix ./services/web-apps/rimgo.nix ./services/web-apps/sftpgo.nix diff --git a/nixos/modules/services/web-apps/pretix.nix b/nixos/modules/services/web-apps/pretix.nix new file mode 100644 index 000000000000..65e658d474eb --- /dev/null +++ b/nixos/modules/services/web-apps/pretix.nix @@ -0,0 +1,579 @@ +{ config +, lib +, pkgs +, utils +, ... +}: + +let + inherit (lib) + concatMapStringsSep + escapeShellArgs + filter + filterAttrs + getExe + getExe' + isAttrs + isList + literalExpression + mapAttrs + mkDefault + mkEnableOption + mkIf + mkOption + mkPackageOption + optionals + optionalString + recursiveUpdate + types + ; + + filterRecursiveNull = o: + if isAttrs o then + mapAttrs (_: v: filterRecursiveNull v) (filterAttrs (_: v: v != null) o) + else if isList o then + map filterRecursiveNull (filter (v: v != null) o) + else + o; + + cfg = config.services.pretix; + format = pkgs.formats.ini { }; + + configFile = format.generate "pretix.cfg" (filterRecursiveNull cfg.settings); + + finalPackage = cfg.package.override { + inherit (cfg) plugins; + }; + + pythonEnv = cfg.package.python.buildEnv.override { + extraLibs = with cfg.package.python.pkgs; [ + (toPythonModule finalPackage) + gunicorn + ] + ++ lib.optionals (cfg.settings.memcached.location != null) + cfg.package.optional-dependencies.memcached + ; + }; + + withRedis = cfg.settings.redis.location != null; +in +{ + meta = with lib; { + maintainers = with maintainers; [ hexa ]; + }; + + options.services.pretix = { + enable = mkEnableOption "pretix"; + + package = mkPackageOption pkgs "pretix" { }; + + group = mkOption { + type = types.str; + default = "pretix"; + description = '' + Group under which pretix should run. + ''; + }; + + user = mkOption { + type = types.str; + default = "pretix"; + description = '' + User under which pretix should run. + ''; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/keys/pretix-secrets.env"; + description = '' + Environment file to pass secret configuration values. + + Each line must follow the `PRETIX_SECTION_KEY=value` pattern. + ''; + }; + + plugins = mkOption { + type = types.listOf types.package; + default = []; + example = literalExpression '' + with config.services.pretix.package.plugins; [ + passbook + pages + ]; + ''; + description = '' + Pretix plugins to install into the Python environment. + ''; + }; + + gunicorn.extraArgs = mkOption { + type = with types; listOf str; + default = [ + "--name=pretix" + ]; + example = [ + "--name=pretix" + "--workers=4" + "--max-requests=1200" + "--max-requests-jitter=50" + "--log-level=info" + ]; + description = '' + Extra arguments to pass to gunicorn. + See for details. + ''; + apply = escapeShellArgs; + }; + + celery = { + extraArgs = mkOption { + type = with types; listOf str; + default = [ ]; + description = '' + Extra arguments to pass to celery. + + See for more info. + ''; + apply = utils.escapeSystemdExecArgs; + }; + }; + + nginx = { + enable = mkOption { + type = types.bool; + default = true; + example = false; + description = '' + Whether to set up an nginx virtual host. + ''; + }; + + domain = mkOption { + type = types.str; + example = "talks.example.com"; + description = '' + The domain name under which to set up the virtual host. + ''; + }; + }; + + database.createLocally = mkOption { + type = types.bool; + default = true; + example = false; + description = '' + Whether to automatically set up the database on the local DBMS instance. + + Only supported for PostgreSQL. Not required for sqlite. + ''; + }; + + settings = mkOption { + type = types.submodule { + freeformType = format.type; + options = { + pretix = { + instance_name = mkOption { + type = types.str; + example = "tickets.example.com"; + description = '' + The name of this installation. + ''; + }; + + url = mkOption { + type = types.str; + example = "https://tickets.example.com"; + description = '' + The installation’s full URL, without a trailing slash. + ''; + }; + + cachedir = mkOption { + type = types.path; + default = "/var/cache/pretix"; + description = '' + Directory for storing temporary files. + ''; + }; + + datadir = mkOption { + type = types.path; + default = "/var/lib/pretix"; + description = '' + Directory for storing user uploads and similar data. + ''; + }; + + logdir = mkOption { + type = types.path; + default = "/var/log/pretix"; + description = '' + Directory for storing log files. + ''; + }; + + currency = mkOption { + type = types.str; + default = "EUR"; + example = "USD"; + description = '' + Default currency for events in its ISO 4217 three-letter code. + ''; + }; + + registration = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Whether to allow registration of new admin users. + ''; + }; + }; + + database = { + backend = mkOption { + type = types.enum [ + "sqlite3" + "postgresql" + ]; + default = "postgresql"; + description = '' + Database backend to use. + + Only postgresql is recommended for production setups. + ''; + }; + + host = mkOption { + type = with types; nullOr types.path; + default = if cfg.settings.database.backend == "postgresql" then "/run/postgresql" else null; + defaultText = literalExpression '' + if config.services.pretix.settings..database.backend == "postgresql" then "/run/postgresql" + else null + ''; + description = '' + Database host or socket path. + ''; + }; + + name = mkOption { + type = types.str; + default = "pretix"; + description = '' + Database name. + ''; + }; + + user = mkOption { + type = types.str; + default = "pretix"; + description = '' + Database username. + ''; + }; + }; + + mail = { + from = mkOption { + type = types.str; + example = "tickets@example.com"; + description = '' + E-Mail address used in the `FROM` header of outgoing mails. + ''; + }; + + host = mkOption { + type = types.str; + default = "localhost"; + example = "mail.example.com"; + description = '' + Hostname of the SMTP server use for mail delivery. + ''; + }; + + port = mkOption { + type = types.port; + default = 25; + example = 587; + description = '' + Port of the SMTP server to use for mail delivery. + ''; + }; + }; + + celery = { + backend = mkOption { + type = types.str; + default = "redis+socket://${config.services.redis.servers.pretix.unixSocket}?virtual_host=1"; + defaultText = literalExpression '' + optionalString config.services.pretix.celery.enable "redis+socket://''${config.services.redis.servers.pretix.unixSocket}?virtual_host=1" + ''; + description = '' + URI to the celery backend used for the asynchronous job queue. + ''; + }; + + broker = mkOption { + type = types.str; + default = "redis+socket://${config.services.redis.servers.pretix.unixSocket}?virtual_host=2"; + defaultText = literalExpression '' + optionalString config.services.pretix.celery.enable "redis+socket://''${config.services.redis.servers.pretix.unixSocket}?virtual_host=2" + ''; + description = '' + URI to the celery broker used for the asynchronous job queue. + ''; + }; + }; + + redis = { + location = mkOption { + type = with types; nullOr str; + default = "unix://${config.services.redis.servers.pretix.unixSocket}?db=0"; + defaultText = literalExpression '' + "unix://''${config.services.redis.servers.pretix.unixSocket}?db=0" + ''; + description = '' + URI to the redis server, used to speed up locking, caching and session storage. + ''; + }; + + sessions = mkOption { + type = types.bool; + default = true; + example = false; + description = '' + Whether to use redis as the session storage. + ''; + }; + }; + + memcached = { + location = mkOption { + type = with types; nullOr str; + default = null; + example = "127.0.0.1:11211"; + description = '' + The `host:port` combination or the path to the UNIX socket of a memcached instance. + + Can be used instead of Redis for caching. + ''; + }; + }; + + tools = { + pdftk = mkOption { + type = types.path; + default = getExe pkgs.pdftk; + defaultText = literalExpression '' + lib.getExe pkgs.pdftk + ''; + description = '' + Path to the pdftk executable. + ''; + }; + }; + }; + }; + default = { }; + description = '' + pretix configuration as a Nix attribute set. All settings can also be passed + from the environment. + + See for possible options. + ''; + }; + }; + + config = mkIf cfg.enable { + # https://docs.pretix.eu/en/latest/admin/installation/index.html + + environment.systemPackages = [ + (pkgs.writeScriptBin "pretix-manage" '' + cd ${cfg.settings.pretix.datadir} + sudo=exec + if [[ "$USER" != ${cfg.user} ]]; then + sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} ${optionalString withRedis "-g redis-pretix"} --preserve-env=PRETIX_CONFIG_FILE' + fi + export PRETIX_CONFIG_FILE=${configFile} + $sudo ${getExe' pythonEnv "pretix-manage"} "$@" + '') + ]; + + services = { + nginx = mkIf cfg.nginx.enable { + enable = true; + recommendedGzipSettings = mkDefault true; + recommendedOptimisation = mkDefault true; + recommendedProxySettings = mkDefault true; + recommendedTlsSettings = mkDefault true; + upstreams.pretix.servers."unix:/run/pretix/pretix.sock" = { }; + virtualHosts.${cfg.nginx.domain} = { + # https://docs.pretix.eu/en/latest/admin/installation/manual_smallscale.html#ssl + extraConfig = '' + more_set_headers Referrer-Policy same-origin; + more_set_headers X-Content-Type-Options nosniff; + ''; + locations = { + "/".proxyPass = "http://pretix"; + "/media/" = { + alias = "${cfg.settings.pretix.datadir}/media/"; + extraConfig = '' + access_log off; + expires 7d; + ''; + }; + "^~ /media/(cachedfiles|invoices)" = { + extraConfig = '' + deny all; + return 404; + ''; + }; + "/static/" = { + alias = "${finalPackage}/${cfg.package.python.sitePackages}/pretix/static.dist/"; + extraConfig = '' + access_log off; + more_set_headers Cache-Control "public"; + expires 365d; + ''; + }; + }; + }; + }; + + postgresql = mkIf (cfg.database.createLocally && cfg.settings.database.backend == "postgresql") { + enable = true; + ensureUsers = [ { + name = cfg.settings.database.user; + ensureDBOwnership = true; + } ]; + ensureDatabases = [ cfg.settings.database.name ]; + }; + + redis.servers.pretix.enable = withRedis; + }; + + systemd.services = let + commonUnitConfig = { + environment.PRETIX_CONFIG_FILE = configFile; + serviceConfig = { + User = "pretix"; + Group = "pretix"; + EnvironmentFile = optionals (cfg.environmentFile != null) [ + cfg.environmentFile + ]; + StateDirectory = [ + "pretix" + ]; + CacheDirectory = "pretix"; + LogsDirectory = "pretix"; + WorkingDirectory = cfg.settings.pretix.datadir; + SupplementaryGroups = optionals withRedis [ + "redis-pretix" + ]; + AmbientCapabilities = ""; + CapabilityBoundingSet = [ "" ]; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + ProcSubset = "pid"; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + "@chown" + ]; + UMask = "0077"; + }; + }; + in { + pretix-web = recursiveUpdate commonUnitConfig { + description = "pretix web service"; + after = [ + "network.target" + "redis-pretix.service" + "postgresql.service" + ]; + wantedBy = [ "multi-user.target" ]; + preStart = '' + versionFile="${cfg.settings.pretix.datadir}/.version" + version=$(cat "$versionFile" 2>/dev/null || echo 0) + + pluginsFile="${cfg.settings.pretix.datadir}/.plugins" + plugins=$(cat "$pluginsFile" 2>/dev/null || echo "") + configuredPlugins="${concatMapStringsSep "|" (package: package.name) cfg.plugins}" + + if [[ $version != ${cfg.package.version} || $plugins != $configuredPlugins ]]; then + ${getExe' pythonEnv "pretix-manage"} migrate + + echo "${cfg.package.version}" > "$versionFile" + echo "$configuredPlugins" > "$pluginsFile" + fi + ''; + serviceConfig = { + ExecStart = "${getExe' pythonEnv "gunicorn"} --bind unix:/run/pretix/pretix.sock ${cfg.gunicorn.extraArgs} pretix.wsgi"; + RuntimeDirectory = "pretix"; + }; + }; + + pretix-periodic = recursiveUpdate commonUnitConfig { + description = "pretix periodic task runner"; + # every 15 minutes + startAt = [ "*:3,18,33,48" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${getExe' pythonEnv "pretix-manage"} runperiodic"; + }; + }; + + pretix-worker = recursiveUpdate commonUnitConfig { + description = "pretix asynchronous job runner"; + after = [ + "network.target" + "redis-pretix.service" + "postgresql.service" + ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig.ExecStart = "${getExe' pythonEnv "celery"} -A pretix.celery_app worker ${cfg.celery.extraArgs}"; + }; + }; + + systemd.sockets.pretix-web.socketConfig = { + ListenStream = "/run/pretix/pretix.sock"; + SocketUser = "nginx"; + }; + + users = { + groups."${cfg.group}" = {}; + users."${cfg.user}" = { + isSystemUser = true; + createHome = true; + home = cfg.settings.pretix.datadir; + inherit (cfg) group; + }; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 33921ae82198..89e92bc8a999 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -730,6 +730,7 @@ in { pppd = handleTest ./pppd.nix {}; predictable-interface-names = handleTest ./predictable-interface-names.nix {}; pretalx = runTest ./web-apps/pretalx.nix; + pretix = runTest ./web-apps/pretix.nix; printing-socket = handleTest ./printing.nix { socket = true; }; printing-service = handleTest ./printing.nix { socket = false; }; privoxy = handleTest ./privoxy.nix {}; diff --git a/nixos/tests/web-apps/pretix.nix b/nixos/tests/web-apps/pretix.nix new file mode 100644 index 000000000000..559316f9b85c --- /dev/null +++ b/nixos/tests/web-apps/pretix.nix @@ -0,0 +1,47 @@ +{ + lib, + pkgs, + ... +}: + +{ + name = "pretix"; + meta.maintainers = with lib.maintainers; [ hexa ]; + + nodes = { + pretix = { + networking.extraHosts = '' + 127.0.0.1 tickets.local + ''; + + services.pretix = { + enable = true; + nginx.domain = "tickets.local"; + plugins = with pkgs.pretix.plugins; [ + passbook + pages + ]; + settings = { + pretix = { + instance_name = "NixOS Test"; + url = "http://tickets.local"; + }; + mail.from = "hello@tickets.local"; + }; + }; + }; + }; + + testScript = '' + start_all() + + pretix.wait_for_unit("pretix-web.service") + pretix.wait_for_unit("pretix-worker.service") + + pretix.wait_until_succeeds("curl -q --fail http://tickets.local") + + pretix.succeed("pretix-manage --help") + + pretix.log(pretix.succeed("systemd-analyze security pretix-web.service")) + ''; +} diff --git a/pkgs/by-name/pr/pretix/package.nix b/pkgs/by-name/pr/pretix/package.nix new file mode 100644 index 000000000000..952da297900f --- /dev/null +++ b/pkgs/by-name/pr/pretix/package.nix @@ -0,0 +1,264 @@ +{ lib +, buildNpmPackage +, fetchFromGitHub +, fetchPypi +, fetchpatch2 +, nodejs +, python3 +, gettext +, nixosTests +, plugins ? [ ] +}: + +let + python = python3.override { + packageOverrides = self: super: { + django = super.django_4; + + stripe = super.stripe.overridePythonAttrs rec { + version = "7.9.0"; + + src = fetchPypi { + pname = "stripe"; + inherit version; + hash = "sha256-hOXkMINaSwzU/SpXzjhTJp0ds0OREc2mtu11LjSc9KE="; + }; + }; + + pretix-plugin-build = self.callPackage ./plugin-build.nix { }; + }; + }; + + pname = "pretix"; + version = "2024.2.0"; + + src = fetchFromGitHub { + owner = "pretix"; + repo = "pretix"; + rev = "refs/tags/v${version}"; + hash = "sha256-emtF5dDXEXN8GIucHbjF+m9Vkg1Jj6nmQdHhBOkXMAs="; + }; + + npmDeps = buildNpmPackage { + pname = "pretix-node-modules"; + inherit version src; + + sourceRoot = "${src.name}/src/pretix/static/npm_dir"; + npmDepsHash = "sha256-kE13dcTdWZZNHPMcHEiK0a2dEcu3Z3/q815YhaVkLbQ="; + + dontBuild = true; + + installPhase = '' + runHook preInstall + + mkdir $out + cp -R node_modules $out/ + + runHook postInstall + ''; + }; +in +python.pkgs.buildPythonApplication rec { + inherit pname version src; + pyproject = true; + + patches = [ + # Discover pretix.plugin entrypoints during build and add them into + # INSTALLED_APPS, so that their static files are collected. + ./plugin-build.patch + + (fetchpatch2 { + # Allow customization of cache and log directory + # https://github.com/pretix/pretix/pull/3997 + name = "pretix-directory-customization.patch"; + url = "https://github.com/pretix/pretix/commit/e151d1d1f08917e547df49da0779b36bb73b7294.patch"; + hash = "sha256-lO5eCKSqUaCwSm7rouMTFMwauWl9Tz/Yf0JE/IO+bnI="; + }) + ]; + + postPatch = '' + NODE_PREFIX=src/pretix/static.dist/node_prefix + mkdir -p $NODE_PREFIX + cp -R ${npmDeps}/node_modules $NODE_PREFIX/ + chmod -R u+w $NODE_PREFIX/ + + # unused + sed -i "/setuptools-rust/d" pyproject.toml + + substituteInPlace pyproject.toml \ + --replace-fail phonenumberslite phonenumbers \ + --replace-fail psycopg2-binary psycopg2 \ + --replace-fail vat_moss_forked==2020.3.20.0.11.0 vat-moss \ + --replace-fail "bleach==5.0.*" bleach \ + --replace-fail "dnspython==2.5.*" dnspython \ + --replace-fail "importlib_metadata==7.*" importlib_metadata \ + --replace-fail "protobuf==4.25.*" protobuf \ + --replace-fail "pycryptodome==3.20.*" pycryptodome \ + --replace-fail "pypdf==3.9.*" pypdf \ + --replace-fail "python-dateutil==2.8.*" python-dateutil \ + --replace-fail "sentry-sdk==1.40.*" sentry-sdk \ + --replace-fail "stripe==7.9.*" stripe + ''; + + build-system = with python.pkgs; [ + gettext + nodejs + pythonRelaxDepsHook + setuptools + tomli + ]; + + dependencies = with python.pkgs; [ + arabic-reshaper + babel + beautifulsoup4 + bleach + celery + chardet + cryptography + css-inline + defusedcsv + dj-static + django + django-bootstrap3 + django-compressor + django-countries + django-filter + django-formset-js-improved + django-formtools + django-hierarkey + django-hijack + django-i18nfield + django-libsass + django-localflavor + django-markup + django-oauth-toolkit + django-otp + django-phonenumber-field + django-redis + django-scopes + django-statici18n + djangorestframework + dnspython + drf-ujson2 + geoip2 + importlib-metadata + isoweek + jsonschema + kombu + libsass + lxml + markdown + mt-940 + oauthlib + openpyxl + packaging + paypalrestsdk + paypal-checkout-serversdk + pyjwt + phonenumbers + pillow + pretix-plugin-build + protobuf + psycopg2 + pycountry + pycparser + pycryptodome + pypdf + python-bidi + python-dateutil + pytz + pytz-deprecation-shim + pyuca + qrcode + redis + reportlab + requests + sentry-sdk + sepaxml + slimit + static3 + stripe + text-unidecode + tlds + tqdm + vat-moss + vobject + webauthn + zeep + ] ++ plugins; + + optional-dependencies = with python.pkgs; { + memcached = [ + pylibmc + ]; + }; + + postInstall = '' + mkdir -p $out/bin + cp ./src/manage.py $out/bin/pretix-manage + + # Trim packages size + rm -rfv $out/${python.sitePackages}/pretix/static.dist/node_prefix + ''; + + dontStrip = true; # no binaries + + nativeCheckInputs = with python.pkgs; [ + pytestCheckHook + pytest-xdist + pytest-mock + pytest-django + pytest-asyncio + pytest-rerunfailures + freezegun + fakeredis + responses + ] ++ lib.flatten (lib.attrValues optional-dependencies); + + pytestFlagsArray = [ + "--reruns" "3" + + # tests fail when run before 4:30am + # https://github.com/pretix/pretix/pull/3987 + "--deselect=src/tests/base/test_orders.py::PaymentReminderTests::test_sent_days" + "--deselect=src/tests/plugins/sendmail/test_rules.py::test_sendmail_rule_specified_subevent" + ]; + + preCheck = '' + export PYTHONPATH=$(pwd)/src:$PYTHONPATH + export DJANGO_SETTINGS_MODULE=tests.settings + ''; + + passthru = { + inherit + npmDeps + python + ; + plugins = lib.recurseIntoAttrs + (python.pkgs.callPackage ./plugins { + inherit (python.pkgs) callPackage; + } + ); + tests = { + inherit (nixosTests) pretix; + }; + }; + + meta = with lib; { + description = "Ticketing software that cares about your event—all the way"; + homepage = "https://github.com/pretix/pretix"; + license = with licenses; [ + agpl3Only + # 3rd party components below src/pretix/static + bsd2 + isc + mit + ofl # fontawesome + unlicense + # all other files below src/pretix/static and src/pretix/locale and aux scripts + asl20 + ]; + maintainers = with maintainers; [ hexa ]; + }; +} diff --git a/pkgs/by-name/pr/pretix/plugin-build.nix b/pkgs/by-name/pr/pretix/plugin-build.nix new file mode 100644 index 000000000000..307ff4114eb7 --- /dev/null +++ b/pkgs/by-name/pr/pretix/plugin-build.nix @@ -0,0 +1,37 @@ +{ + lib, + buildPythonPackage, + fetchPypi, + setuptools, + django, + gettext, +}: + +buildPythonPackage rec { + pname = "pretix-plugin-build"; + version = "1.0.1"; + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256-iLbqcCAbeK4PyLXiebpdE27rt6bOP7eXczIG2bdvvYo="; + }; + + build-system = [ + setuptools + ]; + + dependencies = [ + django + gettext + ]; + + doCheck = false; # no tests + + meta = with lib; { + description = ""; + homepage = "https://github.com/pretix/pretix-plugin-build"; + license = licenses.asl20; + maintainers = with maintainers; [ hexa ]; + }; +} diff --git a/pkgs/by-name/pr/pretix/plugin-build.patch b/pkgs/by-name/pr/pretix/plugin-build.patch new file mode 100644 index 000000000000..aa935ed28148 --- /dev/null +++ b/pkgs/by-name/pr/pretix/plugin-build.patch @@ -0,0 +1,20 @@ +diff --git a/src/pretix/_build_settings.py b/src/pretix/_build_settings.py +index c03f56a1a..d1ea73b84 100644 +--- a/src/pretix/_build_settings.py ++++ b/src/pretix/_build_settings.py +@@ -24,6 +24,8 @@ + This file contains settings that we need at wheel require time. All settings that we only need at runtime are set + in settings.py. + """ ++from importlib_metadata import entry_points ++ + from ._base_settings import * # NOQA + + ENTROPY = { +@@ -47,3 +49,6 @@ HAS_MEMCACHED = False + HAS_CELERY = False + HAS_GEOIP = False + SENTRY_ENABLED = False ++ ++for entry_point in entry_points(group='pretix.plugin'): ++ INSTALLED_APPS.append(entry_point.module) # noqa: F405 diff --git a/pkgs/by-name/pr/pretix/plugins/default.nix b/pkgs/by-name/pr/pretix/plugins/default.nix new file mode 100644 index 000000000000..70e643705c00 --- /dev/null +++ b/pkgs/by-name/pr/pretix/plugins/default.nix @@ -0,0 +1,13 @@ +{ callPackage +, ... +}: + +{ + pages = callPackage ./pages.nix { }; + + passbook = callPackage ./passbook.nix { }; + + reluctant-stripe = callPackage ./reluctant-stripe.nix { }; + + stretchgoals = callPackage ./stretchgoals.nix { }; +} diff --git a/pkgs/by-name/pr/pretix/plugins/pages.nix b/pkgs/by-name/pr/pretix/plugins/pages.nix new file mode 100644 index 000000000000..91f8ef032f77 --- /dev/null +++ b/pkgs/by-name/pr/pretix/plugins/pages.nix @@ -0,0 +1,37 @@ +{ lib +, buildPythonPackage +, fetchFromGitHub +, pretix-plugin-build +, setuptools +}: + +buildPythonPackage rec { + pname = "pretix-pages"; + version = "1.6.0"; + pyproject = true; + + src = fetchFromGitHub { + owner = "pretix"; + repo = "pretix-pages"; + rev = "v${version}"; + hash = "sha256-cO5tAiOifLpqFEQwYgrGoByUecpzvue8YmChpPwm+y0="; + }; + + build-system = [ + pretix-plugin-build + setuptools + ]; + + doCheck = false; # no tests + + pythonImportsCheck = [ + "pretix_pages" + ]; + + meta = with lib; { + description = "Plugin to add static pages to your pretix event"; + homepage = "https://github.com/pretix/pretix-pages"; + license = licenses.asl20; + maintainers = with maintainers; [ hexa ]; + }; +} diff --git a/pkgs/by-name/pr/pretix/plugins/passbook-openssl.patch b/pkgs/by-name/pr/pretix/plugins/passbook-openssl.patch new file mode 100644 index 000000000000..44c0d56886af --- /dev/null +++ b/pkgs/by-name/pr/pretix/plugins/passbook-openssl.patch @@ -0,0 +1,33 @@ +diff --git a/pretix_passbook/apps.py b/pretix_passbook/apps.py +index e34eee1..a7ad382 100644 +--- a/pretix_passbook/apps.py ++++ b/pretix_passbook/apps.py +@@ -22,15 +22,6 @@ class PassbookApp(AppConfig): + def ready(self): + from . import signals # NOQA + +- @cached_property +- def compatibility_errors(self): +- import shutil +- +- errs = [] +- if not shutil.which("openssl"): +- errs.append("The OpenSSL binary is not installed or not in the PATH.") +- return errs +- + @cached_property + def compatibility_warnings(self): + errs = [] +diff --git a/pretix_passbook/forms.py b/pretix_passbook/forms.py +index 2a38604..aec38de 100644 +--- a/pretix_passbook/forms.py ++++ b/pretix_passbook/forms.py +@@ -41,7 +41,7 @@ class CertificateFileField(forms.FileField): + return SimpleUploadedFile("cert.pem", content, "text/plain") + + openssl_cmd = [ +- "openssl", ++ "@openssl@", + "x509", + "-inform", + "DER", diff --git a/pkgs/by-name/pr/pretix/plugins/passbook.nix b/pkgs/by-name/pr/pretix/plugins/passbook.nix new file mode 100644 index 000000000000..b7d380aa681d --- /dev/null +++ b/pkgs/by-name/pr/pretix/plugins/passbook.nix @@ -0,0 +1,59 @@ +{ lib +, buildPythonPackage +, fetchFromGitHub +, substituteAll + +# build-system +, pretix-plugin-build +, setuptools + +# runtime +, openssl + +# dependencies +, googlemaps +, wallet-py3k +}: + +buildPythonPackage rec { + pname = "pretix-passbook"; + version = "1.13.1"; + pyproject = true; + + src = fetchFromGitHub { + owner = "pretix"; + repo = "pretix-passbook"; + rev = "v${version}"; + hash = "sha256-bp64wCEMon05JhOaDr/cVbqUxc+7ndcsSuSesxJt8GE="; + }; + + patches = [ + (substituteAll { + src = ./passbook-openssl.patch; + openssl = lib.getExe openssl; + }) + ]; + + build-system = [ + pretix-plugin-build + setuptools + ]; + + dependencies = [ + googlemaps + wallet-py3k + ]; + + doCheck = false; # no tests + + pythonImportsCheck = [ + "pretix_passbook" + ]; + + meta = with lib; { + description = "Support for Apple Wallet/Passbook files in pretix"; + homepage = "https://github.com/pretix/pretix-passbook"; + license = licenses.asl20; + maintainers = with maintainers; [ hexa ]; + }; +} diff --git a/pkgs/by-name/pr/pretix/plugins/reluctant-stripe.nix b/pkgs/by-name/pr/pretix/plugins/reluctant-stripe.nix new file mode 100644 index 000000000000..40ab96a36bca --- /dev/null +++ b/pkgs/by-name/pr/pretix/plugins/reluctant-stripe.nix @@ -0,0 +1,37 @@ +{ lib +, buildPythonPackage +, fetchFromGitHub +, pretix-plugin-build +, setuptools +}: + +buildPythonPackage { + pname = "pretix-reluctant-stripe"; + version = "unstable-2023-08-03"; + pyproject = true; + + src = fetchFromGitHub { + owner = "metarheinmain"; + repo = "pretix-reluctant-stripe"; + rev = "ae2d770442553e5fc00815ff4521a8fd2c113fd9"; + hash = "sha256-bw9aDMxl4/uar5KHjj+wwkYkaGMRxHWY/c1N75bxu0o="; + }; + + build-system = [ + pretix-plugin-build + setuptools + ]; + + doCheck = false; # no tests + + pythonImportsCheck = [ + "pretix_reluctant_stripe" + ]; + + meta = with lib; { + description = "Nudge users to not use Stripe as a payment provider"; + homepage = "https://github.com/metarheinmain/pretix-reluctant-stripe"; + license = licenses.asl20; + maintainers = with maintainers; [ hexa ]; + }; +} diff --git a/pkgs/by-name/pr/pretix/plugins/stretchgoals.nix b/pkgs/by-name/pr/pretix/plugins/stretchgoals.nix new file mode 100644 index 000000000000..4d1cb8d02e84 --- /dev/null +++ b/pkgs/by-name/pr/pretix/plugins/stretchgoals.nix @@ -0,0 +1,37 @@ +{ lib +, buildPythonPackage +, fetchFromGitHub +, pretix-plugin-build +, setuptools +}: + +buildPythonPackage { + pname = "pretix-avgchart"; + version = "unstable-2023-11-27"; + pyproject = true; + + src = fetchFromGitHub { + owner = "rixx"; + repo = "pretix-avgchart"; + rev = "219816c7ec523a5c23778523b2616ac0c835cb3a"; + hash = "sha256-1V/0PUvStgQeBQ0v6GoofAgyPmWs3RD+v5ekmAO9vFU="; + }; + + build-system = [ + pretix-plugin-build + setuptools + ]; + + doCheck = false; # no tests + + pythonImportsCheck = [ + "pretix_stretchgoals" + ]; + + meta = with lib; { + description = "Display the average ticket sales price over time"; + homepage = "https://github.com/rixx/pretix-avgchart"; + license = licenses.asl20; + maintainers = with maintainers; [ hexa ]; + }; +} diff --git a/pkgs/development/python-modules/css-inline/default.nix b/pkgs/development/python-modules/css-inline/default.nix index 44c142b89308..41caa34dedac 100644 --- a/pkgs/development/python-modules/css-inline/default.nix +++ b/pkgs/development/python-modules/css-inline/default.nix @@ -9,6 +9,7 @@ # native darwin dependencies , libiconv , Security +, SystemConfiguration # tests , pytestCheckHook @@ -52,6 +53,7 @@ buildPythonPackage rec { buildInputs = lib.optionals stdenv.isDarwin [ libiconv Security + SystemConfiguration ]; pythonImportsCheck = [ @@ -66,6 +68,9 @@ buildPythonPackage rec { disabledTests = [ # fails to connect to local server "test_remote_stylesheet" + ] ++ lib.optionals (stdenv.isDarwin) [ + # pyo3_runtime.PanicException: event loop thread panicked + "test_invalid_href" ]; meta = with lib; { diff --git a/pkgs/development/python-modules/django-compressor/default.nix b/pkgs/development/python-modules/django-compressor/default.nix index a8d367e854af..543c483023df 100644 --- a/pkgs/development/python-modules/django-compressor/default.nix +++ b/pkgs/development/python-modules/django-compressor/default.nix @@ -1,24 +1,32 @@ { lib , buildPythonPackage , fetchPypi +, pythonRelaxDepsHook + +# build-system +, setuptools + +# dependencies +, calmjs +, django-appconf +, jinja2 , rcssmin , rjsmin -, django-appconf + +# tests , beautifulsoup4 , brotli -, pytestCheckHook -, django-sekizai -, pytest-django , csscompressor -, calmjs -, jinja2 -, python +, django-sekizai +, pytestCheckHook +, pytest-django + }: buildPythonPackage rec { pname = "django-compressor"; version = "4.4"; - format = "setuptools"; + pyproject = true; src = fetchPypi { pname = "django_compressor"; @@ -26,7 +34,17 @@ buildPythonPackage rec { hash = "sha256-GwrMnPup9pvDjnxB2psNcKILyVWHtkP/75YJz0YGT2c="; }; - propagatedBuildInputs = [ + build-system = [ + setuptools + pythonRelaxDepsHook + ]; + + pythonRelaxDeps = [ + "rcssmin" + "rjsmin" + ]; + + dependencies = [ beautifulsoup4 calmjs django-appconf @@ -35,7 +53,9 @@ buildPythonPackage rec { rjsmin ]; - checkInputs = [ + env.DJANGO_SETTINGS_MODULE = "compressor.test_settings"; + + nativeCheckInputs = [ beautifulsoup4 brotli csscompressor @@ -53,8 +73,6 @@ buildPythonPackage rec { pythonImportsCheck = [ "compressor" ]; - DJANGO_SETTINGS_MODULE = "compressor.test_settings"; - meta = with lib; { description = "Compresses linked and inline JavaScript or CSS into single cached files"; homepage = "https://django-compressor.readthedocs.org/"; diff --git a/pkgs/development/python-modules/django-otp/default.nix b/pkgs/development/python-modules/django-otp/default.nix index 216b24e3c0c0..c1078160b6eb 100644 --- a/pkgs/development/python-modules/django-otp/default.nix +++ b/pkgs/development/python-modules/django-otp/default.nix @@ -1,46 +1,63 @@ { lib , buildPythonPackage , fetchFromGitHub +, hatchling , django , freezegun -, pythonOlder , qrcode +, pytest +, python }: buildPythonPackage rec { pname = "django-otp"; - version = "1.1.3"; - format = "setuptools"; - disabled = pythonOlder "3"; + version = "1.3.0post1"; + pyproject = true; src = fetchFromGitHub { owner = "django-otp"; repo = "django-otp"; rev = "v${version}"; - hash = "sha256-Ac9p7q9yaUr3WTTGxCY16Yo/Z8i1RtnD2g0Aj2pqSXY="; + hash = "sha256-Q8YTCYERyoAXenSiDabxuxaWiD6ZeJKKKgaR/Rg3y20="; }; - postPatch = '' - patchShebangs manage.py - ''; + build-system = [ + hatchling + ]; - propagatedBuildInputs = [ + dependencies = [ django qrcode ]; + env.DJANGO_SETTINGS_MODUOLE = "test.test_project.settings"; + nativeCheckInputs = [ freezegun + pytest ]; checkPhase = '' - ./manage.py test django_otp + runHook preCheck + + export PYTHONPATH=$PYTHONPATH:test + export DJANGO_SETTINGS_MODULE=test_project.settings + ${python.interpreter} -m django test django_otp + + runHook postCheck ''; - pythonImportsCheck = [ "django_otp" ]; + pytestFlagsArray = [ + "src/django_otp/test.py" + ]; + + pythonImportsCheck = [ + "django_otp" + ]; meta = with lib; { - homepage = "https://github.com/jazzband/django-model-utils"; + homepage = "https://github.com/django-otp/django-otp"; + changelog = "https://github.com/django-otp/django-otp/blob/${src.rev}/CHANGES.rst"; description = "Pluggable framework for adding two-factor authentication to Django using one-time passwords"; license = licenses.bsd2; maintainers = with maintainers; [ ]; diff --git a/pkgs/development/python-modules/django-statici18n/default.nix b/pkgs/development/python-modules/django-statici18n/default.nix index 9fc04339c226..8ce7bc0340a9 100644 --- a/pkgs/development/python-modules/django-statici18n/default.nix +++ b/pkgs/development/python-modules/django-statici18n/default.nix @@ -1,25 +1,37 @@ { lib , buildPythonPackage , fetchFromGitHub + +# build-system +, setuptools + +# dependencies , django , django-appconf + +# tests , pytest-django , pytestCheckHook }: buildPythonPackage rec { pname = "django-statici18n"; - version = "2.3.1"; - format = "setuptools"; + version = "2.4.0"; + pyproject = true; src = fetchFromGitHub { owner = "zyegfryed"; - repo = pname; - rev = "refs/tags/v${version}"; - hash = "sha256-2fFJJNdF0jspS7djDL8sToPTetzNR6pfNp5ohCNa30I="; + repo = "django-statici18n"; + # https://github.com/zyegfryed/django-statici18n/issues/59 + rev = "9b83a8f0f2e625dd5f56d53cfe4e07aca9479ab6"; + hash = "sha256-KrIlWmN7um9ad2avfANOza579bjYkxTo9F0UFpvLu3A="; }; - propagatedBuildInputs = [ + build-system = [ + setuptools + ]; + + dependencies = [ django django-appconf ]; @@ -28,7 +40,7 @@ buildPythonPackage rec { "statici18n" ]; - DJANGO_SETTINGS_MODULE = "tests.test_project.project.settings"; + env.DJANGO_SETTINGS_MODULE = "tests.test_project.project.settings"; nativeCheckInputs = [ pytest-django diff --git a/pkgs/development/python-modules/paypalrestsdk/default.nix b/pkgs/development/python-modules/paypalrestsdk/default.nix new file mode 100644 index 000000000000..43061b4eeab2 --- /dev/null +++ b/pkgs/development/python-modules/paypalrestsdk/default.nix @@ -0,0 +1,44 @@ +{ buildPythonPackage +, fetchPypi + +# build-system +, setuptools + +# dependencies +, pyopenssl +, requests +, six +}: + +buildPythonPackage rec { + pname = "paypalrestsdk"; + version = "1.13.2"; + pyproject = true; + + src = fetchPypi { + inherit pname version; + sha256 = "sha256-kZUfNtsw1oW5ceFASYSRo1bPHfjv9xZWYDrKTtcs81o="; + }; + + build-system = [ + setuptools + ]; + + dependencies = [ + pyopenssl + requests + six + ]; + + doCheck = false; # no tests + + pythonImportsCheck = [ + "paypalrestsdk" + ]; + + meta = { + homepage = "https://developer.paypal.com/"; + description = "Python APIs to create, process and manage payment"; + license = "PayPal SDK License"; + }; +} diff --git a/pkgs/development/python-modules/wallet-py3k/default.nix b/pkgs/development/python-modules/wallet-py3k/default.nix new file mode 100644 index 000000000000..f78463e6748a --- /dev/null +++ b/pkgs/development/python-modules/wallet-py3k/default.nix @@ -0,0 +1,47 @@ +{ lib +, buildPythonPackage +, fetchPypi +, substituteAll +, openssl +, setuptools +, six +}: + +buildPythonPackage rec { + pname = "wallet-py3k"; + version = "0.0.4"; + pyproject = true; + + src = fetchPypi { + inherit pname version; + hash = "sha256-kyHSh8qHbzK6gFLGnL6dUJ/GLJHTNC86jjXa/APqIzI="; + }; + + patches = [ + (substituteAll { + src = ./openssl-path.patch; + openssl = lib.getExe openssl; + }) + ]; + + build-system = [ + setuptools + ]; + + dependencies = [ + six + ]; + + doCheck = false; # no tests + + pythonImportsCheck = [ + "wallet" + ]; + + meta = with lib; { + description = "Passbook file generator"; + homepage = "https://pypi.org/project/wallet-py3k"; + license = licenses.mit; + maintainers = with maintainers; [ hexa ]; + }; +} diff --git a/pkgs/development/python-modules/wallet-py3k/openssl-path.patch b/pkgs/development/python-modules/wallet-py3k/openssl-path.patch new file mode 100644 index 000000000000..8864818b13f7 --- /dev/null +++ b/pkgs/development/python-modules/wallet-py3k/openssl-path.patch @@ -0,0 +1,13 @@ +diff --git a/wallet/models.py b/wallet/models.py +index 1b75402..ad115de 100644 +--- a/wallet/models.py ++++ b/wallet/models.py +@@ -320,7 +320,7 @@ class Pass(object): + def _createSignature(self, manifest, certificate, key, + wwdr_certificate, password): + openssl_cmd = [ +- 'openssl', ++ '@openssl@', + 'smime', + '-binary', + '-sign', diff --git a/pkgs/top-level/python-aliases.nix b/pkgs/top-level/python-aliases.nix index c10b48c8421a..96fccb3f3ecc 100644 --- a/pkgs/top-level/python-aliases.nix +++ b/pkgs/top-level/python-aliases.nix @@ -302,7 +302,6 @@ mapAliases ({ pam = python-pam; # added 2020-09-07. PasteDeploy = pastedeploy; # added 2021-10-07 pathpy = path; # added 2022-04-12 - paypalrestsdk = throw "paypalrestsdk was removed, the upstream repo was archived back in 2020"; # Added 2023-11-25 pdfposter = throw "pdfposter was promoted to a top-level attribute"; # Added 2023-06-29 pdfminer = pdfminer-six; # added 2022-05-25 pep257 = pydocstyle; # added 2022-04-12 diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index 4b2fc2b9df41..0d2c33cfaf94 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -2527,7 +2527,7 @@ self: super: with self; { css-inline = callPackage ../development/python-modules/css-inline { inherit (pkgs.darwin) libiconv; - inherit (pkgs.darwin.apple_sdk.frameworks) Security; + inherit (pkgs.darwin.apple_sdk.frameworks) Security SystemConfiguration; }; css-parser = callPackage ../development/python-modules/css-parser { }; @@ -9290,6 +9290,8 @@ self: super: with self; { paypalhttp = callPackage ../development/python-modules/paypalhttp { }; + paypalrestsdk = callPackage ../development/python-modules/paypalrestsdk { }; + pbkdf2 = callPackage ../development/python-modules/pbkdf2 { }; pbr = callPackage ../development/python-modules/pbr { }; @@ -16347,6 +16349,8 @@ self: super: with self; { wallbox = callPackage ../development/python-modules/wallbox { }; + wallet-py3k = callPackage ../development/python-modules/wallet-py3k { }; + walrus = callPackage ../development/python-modules/walrus { }; wand = callPackage ../development/python-modules/wand { };