From 2e1ee510ad0d50452055909880263f279567c83d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 6 Mar 2026 03:05:28 +0100 Subject: [PATCH 1/9] python3Packages.django-vcache: init at 1.0.0 --- .../python-modules/django-vcache/default.nix | 60 +++++++++++++++++++ pkgs/top-level/python-packages.nix | 2 + 2 files changed, 62 insertions(+) create mode 100644 pkgs/development/python-modules/django-vcache/default.nix diff --git a/pkgs/development/python-modules/django-vcache/default.nix b/pkgs/development/python-modules/django-vcache/default.nix new file mode 100644 index 000000000000..a2cad50a356f --- /dev/null +++ b/pkgs/development/python-modules/django-vcache/default.nix @@ -0,0 +1,60 @@ +{ + lib, + buildPythonPackage, + croniter, + django, + fetchFromGitLab, + hatchling, + ormsgpack, + prometheus-client, + pythonOlder, + valkey, + zstd, +}: + +buildPythonPackage rec { + pname = "django-vcache"; + version = "1.0.0"; + pyproject = true; + + src = fetchFromGitLab { + owner = "glitchtip"; + repo = "django-vcache"; + tag = "v${version}"; + hash = "sha256-bOHEw4nl82tFjHiJdmyW0LleKMpjUh8uu4crGp6IsWY="; + }; + + build-system = [ hatchling ]; + + dependencies = [ + django + ormsgpack + croniter + valkey + ] + ++ valkey.optional-dependencies.libvalkey + ++ lib.optional (pythonOlder "3.14") zstd; + + optional-dependencies = { + metrics = [ prometheus-client ]; + valkey = [ valkey ] ++ valkey.optional-dependencies.libvalkey; + }; + + pythonImportsCheck = [ "django_vcache" ]; + + # requires valkey sentinel cluster + doCheck = false; + + meta = { + description = "Specialized, lightweight Django cache backend for Valkey"; + homepage = "https://gitlab.com/glitchtip/django-vcache/"; + changelog = "https://gitlab.com/glitchtip/django-vcache/-/blob/main/CHANGELOG.md#${ + lib.replaceString "." "" version + }"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [ + defelo + felbinger + ]; + }; +} diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index 8a21ae262dab..3ce449e87b64 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -4426,6 +4426,8 @@ self: super: with self; { django-valkey = callPackage ../development/python-modules/django-valkey { }; + django-vcache = callPackage ../development/python-modules/django-vcache { }; + django-versatileimagefield = callPackage ../development/python-modules/django-versatileimagefield { }; From c17f09a1ce38179a554d8512d098c2ed6f339e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 6 Mar 2026 01:50:12 +0100 Subject: [PATCH 2/9] python314Packages.django-vtasks: init at 1.0.3 --- .../python-modules/django-vtasks/default.nix | 90 +++++++++++++++++++ pkgs/top-level/python-packages.nix | 2 + 2 files changed, 92 insertions(+) create mode 100644 pkgs/development/python-modules/django-vtasks/default.nix diff --git a/pkgs/development/python-modules/django-vtasks/default.nix b/pkgs/development/python-modules/django-vtasks/default.nix new file mode 100644 index 000000000000..ca0c1ec7e30e --- /dev/null +++ b/pkgs/development/python-modules/django-vtasks/default.nix @@ -0,0 +1,90 @@ +{ + lib, + buildPythonPackage, + croniter, + dj-database-url, + django-valkey, + django, + fetchFromGitLab, + hatchling, + orjson, + postgresql, + postgresqlTestHook, + prometheus-client, + psycopg, + pytest-asyncio, + pytest-django, + pytestCheckHook, + pythonOlder, + redisTestHook, + valkey, + zstandard, +}: + +buildPythonPackage rec { + pname = "django-vtasks"; + version = "1.0.3"; + pyproject = true; + + src = fetchFromGitLab { + owner = "glitchtip"; + repo = "django-vtasks"; + tag = "v${version}"; + hash = "sha256-75W63HsLBT4EPQCiAXjd9qr6n07/2e5GCUNWeDzXUq0="; + }; + + postPatch = '' + # upstream does not use pytest to run the tests, but we just need to patch one async test to do so + substituteInPlace scripts/test_stale_conn.py \ + --replace-fail "import asyncio" "import asyncio; import pytest" \ + --replace-fail "async def test_async_stale_connection():" "@pytest.mark.asyncio + async def test_async_stale_connection():" + ''; + + build-system = [ hatchling ]; + + dependencies = [ + django + orjson + croniter + ] + ++ lib.optional (pythonOlder "3.14") zstandard; + + optional-dependencies = { + metrics = [ prometheus-client ]; + valkey = [ valkey ] ++ valkey.optional-dependencies.libvalkey; + }; + + pythonImportsCheck = [ "django_vtasks" ]; + + env = { + DATABASE_URL = "postgresql://postgres@%2Fbuild%2Frun%2Fpostgresql"; + VALKEY_URL = "redis://127.0.0.1:6379"; + }; + + nativeCheckInputs = [ + django # must come first as vtasks only works with django 6 + + dj-database-url + django-valkey + postgresql + postgresqlTestHook + psycopg + pytest-asyncio + pytest-django + pytestCheckHook + redisTestHook # contains valkey + ]; + + meta = { + description = "Very fast valkey/postgres django tasks backend"; + homepage = "https://gitlab.com/glitchtip/django-vtasks"; + changelog = "https://gitlab.com/glitchtip/django-vtasks/-/releases/${src.tag}"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [ + defelo + felbinger + ]; + broken = lib.versionOlder (lib.versions.major django.version) "6"; + }; +} diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index 3ce449e87b64..a6c74019826e 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -4434,6 +4434,8 @@ self: super: with self; { django-vite = callPackage ../development/python-modules/django-vite { }; + django-vtasks = callPackage ../development/python-modules/django-vtasks { }; + django-waffle = callPackage ../development/python-modules/django-waffle { }; django-weasyprint = callPackage ../development/python-modules/django-weasyprint { }; From 32a285ba8cb931b886fafae86f2e2f32e3ce20ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 6 Mar 2026 16:58:42 +0100 Subject: [PATCH 3/9] python313Packages.django-autoslug: fix tests with django_6 --- .../python-modules/django-autoslug/default.nix | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkgs/development/python-modules/django-autoslug/default.nix b/pkgs/development/python-modules/django-autoslug/default.nix index c9749a1c90da..119851f3b34a 100644 --- a/pkgs/development/python-modules/django-autoslug/default.nix +++ b/pkgs/development/python-modules/django-autoslug/default.nix @@ -3,7 +3,6 @@ buildPythonPackage, fetchFromGitHub, setuptools, - wheel, django, django-modeltranslation, }: @@ -20,9 +19,15 @@ buildPythonPackage rec { hash = "sha256-IRLY4VaKYXVkSgU/zdY+PSmGrcFB2FlE5L7j0FqisRM="; }; + postPatch = lib.optionalString (lib.versionAtLeast django.version "6.0") '' + # sadly upstream does not use pytest, so we must patch the one failing test or we could not run any tests at all + # see https://github.com/justinmayer/django-autoslug/issues/87 + substituteInPlace autoslug/tests/tests.py \ + --replace-fail "assert b.slug == 'hello_world-2'" "assert b.slug == 'hello_world_2'" + ''; + build-system = [ setuptools - wheel ]; dependencies = [ django ]; From 36ea75a27788fa3dece40dacbadc17590104af33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 6 Mar 2026 03:58:39 +0100 Subject: [PATCH 4/9] python313Packages.django-extensions: fix tests with django_6 --- .../django-extensions/default.nix | 6 + .../django-extensions/django_6-compat.diff | 111 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 pkgs/development/python-modules/django-extensions/django_6-compat.diff diff --git a/pkgs/development/python-modules/django-extensions/default.nix b/pkgs/development/python-modules/django-extensions/default.nix index b2295131331e..257f500afd4f 100644 --- a/pkgs/development/python-modules/django-extensions/default.nix +++ b/pkgs/development/python-modules/django-extensions/default.nix @@ -36,6 +36,12 @@ buildPythonPackage rec { hash = "sha256-WgO/bDe4anQCc1q2Gdq3W70yDqDgmsvn39Qf9ZNVXuE="; }; + patches = lib.optionals (lib.versionAtLeast django.version "6.0") [ + # Fix some tests when run with Django 6 + # see https://github.com/django-extensions/django-extensions/pull/1979 + ./django_6-compat.diff + ]; + build-system = [ setuptools ]; dependencies = [ diff --git a/pkgs/development/python-modules/django-extensions/django_6-compat.diff b/pkgs/development/python-modules/django-extensions/django_6-compat.diff new file mode 100644 index 000000000000..6996a24a8a46 --- /dev/null +++ b/pkgs/development/python-modules/django-extensions/django_6-compat.diff @@ -0,0 +1,111 @@ +From 5c096ad7a59a3e9edee66e27fbae46c30e4abd6b Mon Sep 17 00:00:00 2001 +From: asherzod1 +Date: Wed, 11 Mar 2026 00:53:19 +0500 +Subject: [PATCH] fix: add Django 6 compatibility for signals, constraints, and + random char field + +--- + django_extensions/db/fields/__init__.py | 18 +++++++++++++----- + .../management/commands/list_signals.py | 4 +++- + tests/test_management_command.py | 6 +++++- + tests/testapp/models.py | 7 ++++++- + 4 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/django_extensions/db/fields/__init__.py b/django_extensions/db/fields/__init__.py +index 938254e1f..c233cee84 100644 +--- a/django_extensions/db/fields/__init__.py ++++ b/django_extensions/db/fields/__init__.py +@@ -86,7 +86,11 @@ def find_unique(self, model_instance, field, iterator, *args): + + new = next(iterator) + kwargs[self.attname] = new +- while not new or queryset.filter(query, **kwargs): ++ while True: ++ matching = queryset.filter(query, **kwargs) ++ has_match = matching.exists() if hasattr(matching, "exists") else bool(matching) ++ if new and not has_match: ++ break + new = next(iterator) + kwargs[self.attname] = new + setattr(model_instance, self.attname, new) +@@ -388,10 +392,14 @@ def in_unique_together(self, model_instance): + return False + + def pre_save(self, model_instance, add): +- if (not add or self.keep_default) and getattr( +- model_instance, self.attname +- ) != "": +- return getattr(model_instance, self.attname) ++ current_value = getattr(model_instance, self.attname) ++ # Django 6 may call pre_save multiple times for inserts; if we've already ++ # populated the field value, reuse it instead of regenerating. ++ if current_value not in ("", None): ++ return current_value ++ ++ if (not add or self.keep_default) and current_value != "": ++ return current_value + + population = "" + if self.include_alpha: +diff --git a/django_extensions/management/commands/list_signals.py b/django_extensions/management/commands/list_signals.py +index 9cadc4f85..e7bc9d7f6 100644 +--- a/django_extensions/management/commands/list_signals.py ++++ b/django_extensions/management/commands/list_signals.py +@@ -52,7 +52,9 @@ def handle(self, *args, **options): + for signal in signals: + signal_name = SIGNAL_NAMES.get(signal, "unknown") + for receiver in signal.receivers: +- if django.VERSION >= (5, 0): ++ if django.VERSION >= (6, 0): ++ lookup, receiver, _sender, is_async = receiver ++ elif django.VERSION >= (5, 0): + lookup, receiver, is_async = receiver + else: + lookup, receiver = receiver +diff --git a/tests/test_management_command.py b/tests/test_management_command.py +index ea205ed72..0fb9dc27b 100644 +--- a/tests/test_management_command.py ++++ b/tests/test_management_command.py +@@ -3,6 +3,7 @@ + import logging + import importlib + ++import django + from django.core.management import ( + call_command, + find_commands, +@@ -421,7 +422,10 @@ def test_field_class(self): + stdout=out, + ) + self.output = out.getvalue() +- self.assertIn("id - AutoField", self.output) ++ if django.VERSION >= (6, 0): ++ self.assertIn("id - BigAutoField", self.output) ++ else: ++ self.assertIn("id - AutoField", self.output) + self.assertIn("char_field - CharField", self.output) + self.assertIn("integer_field - IntegerField", self.output) + self.assertIn("foreign_key_field - ForeignKey", self.output) +diff --git a/tests/testapp/models.py b/tests/testapp/models.py +index 278402f51..3f31efcda 100644 +--- a/tests/testapp/models.py ++++ b/tests/testapp/models.py +@@ -1,4 +1,5 @@ + # -*- coding: utf-8 -*- ++import django + from django.db import models + from django.contrib.auth import get_user_model + from django.db.models import UniqueConstraint +@@ -178,7 +179,11 @@ class Meta: + fields=("common_field", "uniq_field"), name="unique_common_uniq_pair" + ), + models.CheckConstraint( +- check=~models.Q(common_field=models.F("another_common_field")), ++ **( ++ {"condition": ~models.Q(common_field=models.F("another_common_field"))} ++ if django.VERSION >= (5, 2) ++ else {"check": ~models.Q(common_field=models.F("another_common_field"))} ++ ), + name="common_and_another_common_differ", + ), + ] From 0303aabc1a138501aee7add36c67bb0d55ae5509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 6 Mar 2026 16:59:26 +0100 Subject: [PATCH 5/9] python313Packages.django-prometheus: allow newer django versions --- pkgs/development/python-modules/django-prometheus/default.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/development/python-modules/django-prometheus/default.nix b/pkgs/development/python-modules/django-prometheus/default.nix index ff97896101d6..b2526d41c4ad 100644 --- a/pkgs/development/python-modules/django-prometheus/default.nix +++ b/pkgs/development/python-modules/django-prometheus/default.nix @@ -28,6 +28,10 @@ buildPythonPackage rec { --replace-fail '"pytest-runner"' "" ''; + pythonRelaxDeps = [ + "django" + ]; + build-system = [ setuptools ]; dependencies = [ prometheus-client ]; From bfaa1478a8cf2e905db18e3d265ceac433f2d449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 6 Mar 2026 21:12:24 +0100 Subject: [PATCH 6/9] python313Packages.django-guardian: add pytest-xdist to decrease test time from 120s to ~30s --- pkgs/development/python-modules/django-guardian/default.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/development/python-modules/django-guardian/default.nix b/pkgs/development/python-modules/django-guardian/default.nix index 820bd1077652..b6e00c831edf 100644 --- a/pkgs/development/python-modules/django-guardian/default.nix +++ b/pkgs/development/python-modules/django-guardian/default.nix @@ -6,6 +6,7 @@ django, pytestCheckHook, pytest-django, + pytest-xdist, setuptools, }: @@ -29,6 +30,7 @@ buildPythonPackage rec { django-environ pytestCheckHook pytest-django + pytest-xdist ]; pythonImportsCheck = [ "guardian" ]; From b28a56e55f771006c00879d0224ef3454cbe3b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 6 Mar 2026 00:50:57 +0100 Subject: [PATCH 7/9] glitchtip: 5.2.1 -> 6.0.9 Diff: https://gitlab.com/glitchtip/glitchtip-backend/-/compare/v5.2.1...v6.0.9 Changelog: https://gitlab.com/glitchtip/glitchtip-backend/-/blob/v6.0.9/CHANGELOG --- nixos/modules/services/web-apps/glitchtip.nix | 134 ++++++++++-------- nixos/tests/glitchtip.nix | 4 +- pkgs/by-name/gl/glitchtip/frontend.nix | 11 +- pkgs/by-name/gl/glitchtip/package.nix | 35 ++--- 4 files changed, 99 insertions(+), 85 deletions(-) diff --git a/nixos/modules/services/web-apps/glitchtip.nix b/nixos/modules/services/web-apps/glitchtip.nix index 6a74cda886a1..e32af666bc06 100644 --- a/nixos/modules/services/web-apps/glitchtip.nix +++ b/nixos/modules/services/web-apps/glitchtip.nix @@ -45,19 +45,6 @@ in default = "glitchtip"; }; - listenAddress = lib.mkOption { - type = lib.types.str; - description = "The address to listen on."; - default = "127.0.0.1"; - example = "0.0.0.0"; - }; - - port = lib.mkOption { - type = lib.types.port; - description = "The port to listen on."; - default = 8000; - }; - stateDir = lib.mkOption { type = lib.types.path; description = "State directory of glitchtip."; @@ -74,12 +61,16 @@ in DEBUG = 0; DEBUG_TOOLBAR = 0; DATABASE_URL = lib.mkIf config.services.glitchtip.database.createLocally "postgresql://@/glitchtip"; + GLITCHTIP_VERSION = config.services.glitchtip.package.version; + GRANIAN_HOST = "127.0.0.1"; + GRANIAN_PORT = 8000; + GRANIAN_STATIC_PATH_MOUNT = "''${config.services.glitchtip.package}/lib/glitchtip/static"; + GRANIAN_WORKERS = 1; + PYTHONUNBUFFERED = 1; REDIS_URL = lib.mkIf config.services.glitchtip.redis.createLocally "unix://''${config.services.redis.servers.glitchtip.unixSocket}"; - CELERY_BROKER_URL = lib.mkIf config.services.glitchtip.redis.createLocally "redis+socket://''${config.services.redis.servers.glitchtip.unixSocket}"; } ''; example = { - GLITCHTIP_DOMAIN = "https://glitchtip.example.com"; DATABASE_URL = "postgres://postgres:postgres@postgres/postgres"; }; @@ -93,10 +84,22 @@ in ]); options = { - GLITCHTIP_DOMAIN = lib.mkOption { - type = lib.types.str; - description = "The URL under which GlitchTip is externally reachable."; - example = "https://glitchtip.example.com"; + GLITCHTIP_ENABLE_MCP = lib.mkOption { + type = lib.types.bool; + description = "Whether to enable the MCP api."; + default = false; + }; + + GRANIAN_WORKERS = lib.mkOption { + type = lib.types.ints.positive; + description = "Number of granian workers to start"; + default = 1; + }; + + ENABLE_OBSERVABILITY_API = lib.mkOption { + type = lib.types.bool; + description = "Whether to enable the Prometheus metrics endpoint."; + default = false; }; ENABLE_USER_REGISTRATION = lib.mkOption { @@ -144,37 +147,47 @@ in Whether to enable and configure a local Redis instance. ''; }; - - gunicorn.extraArgs = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - description = "Extra arguments for gunicorn."; - }; - - celery.extraArgs = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - description = "Extra arguments for celery."; - }; }; }; + imports = [ + (lib.mkRenamedOptionModule + [ "services" "glitchtip" "listenAddress" ] + [ "services" "glitchtip" "settings" "GRANIAN_HOST" ] + ) + (lib.mkRenamedOptionModule + [ "services" "glitchtip" "port" ] + [ "services" "glitchtip" "settings" "GRANIAN_PORT" ] + ) + (lib.mkRemovedOptionModule [ "services" "glitchtip" "celery" "extraArgs" ] + "GlitchTip 6 migrated away from celery. Please check the upstream docs how to handle your usecase now." + ) + (lib.mkRemovedOptionModule [ "services" "glitchtip" "gunicorn" "extraArgs" ] + "GlitchTip 6 migrated away from gunicorn. Please check the upstream docs how to handle your usecase now." + ) + ]; + config = lib.mkIf cfg.enable { services.glitchtip.settings = { DEBUG = lib.mkDefault 0; DEBUG_TOOLBAR = lib.mkDefault 0; - PYTHONPATH = "${python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/glitchtip"; - DATABASE_URL = lib.mkIf cfg.database.createLocally "postgresql://@/glitchtip"; - REDIS_URL = lib.mkIf cfg.redis.createLocally "unix://${config.services.redis.servers.glitchtip.unixSocket}"; - CELERY_BROKER_URL = lib.mkIf cfg.redis.createLocally "redis+socket://${config.services.redis.servers.glitchtip.unixSocket}"; GLITCHTIP_VERSION = pkg.version; + GRANIAN_HOST = lib.mkDefault "127.0.0.1"; + GRANIAN_PORT = lib.mkDefault 8000; + GRANIAN_STATIC_PATH_MOUNT = "${pkg}/lib/glitchtip/static"; + GRANIAN_WORKERS = lib.mkDefault 1; + PYTHONPATH = "${python.pkgs.makePythonPath pkg.propagatedBuildInputs}:${pkg}/lib/glitchtip"; + PYTHONUNBUFFERED = lib.mkDefault 1; + } + // lib.optionalAttrs cfg.database.createLocally { DATABASE_URL = "postgresql://@/glitchtip"; } + // lib.optionalAttrs cfg.redis.createLocally { + REDIS_URL = "unix://${config.services.redis.servers.glitchtip.unixSocket}"; }; systemd.services = let commonService = { wantedBy = [ "multi-user.target" ]; - wants = [ "network-online.target" ]; requires = lib.optional cfg.database.createLocally "postgresql.target" @@ -235,31 +248,40 @@ in { glitchtip = commonService // { description = "GlitchTip"; + environment = + environment + // lib.optionalAttrs (cfg.settings.ENABLE_OBSERVABILITY_API && cfg.settings.WORKERS > 1) { + PROMETHEUS_MULTIPROC_DIR = "/tmp/prometheus_multiproc"; + }; + bindsTo = [ "glitchtip-worker.service" ]; + before = [ "glitchtip-worker.service" ]; preStart = '' ${lib.getExe pkg} migrate + ${lib.getExe pkg} createcachetable + ${lib.getExe pkg} maintain_partitions ''; serviceConfig = commonServiceConfig // { ExecStart = '' - ${lib.getExe python.pkgs.gunicorn} \ - --bind=${cfg.listenAddress}:${toString cfg.port} \ - ${lib.concatStringsSep " " cfg.gunicorn.extraArgs} \ - glitchtip.wsgi + ${lib.getExe python.pkgs.granian} \ + --interface ${if cfg.settings.GLITCHTIP_ENABLE_MCP then "asgi" else "asginl"} \ + glitchtip.asgi:application \ + --host ${cfg.settings.GRANIAN_HOST} \ + --port ${toString cfg.settings.GRANIAN_PORT} \ + --workers ${toString cfg.settings.GRANIAN_WORKERS} \ + --no-ws ''; }; }; glitchtip-worker = commonService // { description = "GlitchTip Job Runner"; - + environment = environment // { + IS_WORKER = "1"; + }; serviceConfig = commonServiceConfig // { - ExecStart = '' - ${lib.getExe python.pkgs.celery} \ - -A glitchtip worker \ - -B -s /run/glitchtip/celerybeat-schedule \ - ${lib.concatStringsSep " " cfg.celery.extraArgs} - ''; + ExecStart = "${lib.getExe pkg} runworker --scheduler"; }; }; }; @@ -289,15 +311,13 @@ in systemd.tmpfiles.settings.glitchtip."${cfg.stateDir}/uploads".d = { inherit (cfg) user group; }; - environment.systemPackages = - let - glitchtip-manage = pkgs.writeShellScriptBin "glitchtip-manage" '' - set -o allexport - ${lib.toShellVars environment} - ${lib.concatMapStringsSep "\n" (f: "source ${f}") cfg.environmentFiles} - ${config.security.wrapperDir}/sudo -E -u ${cfg.user} ${lib.getExe pkg} "$@" - ''; - in - [ glitchtip-manage ]; + environment.systemPackages = [ + (pkgs.writeShellScriptBin "glitchtip-manage" '' + set -o allexport + ${lib.toShellVars environment} + ${lib.concatMapStringsSep "\n" (f: "source ${f}") cfg.environmentFiles} + ${config.security.wrapperDir}/sudo -E -u ${cfg.user} ${lib.getExe pkg} "$@" + '') + ]; }; } diff --git a/nixos/tests/glitchtip.nix b/nixos/tests/glitchtip.nix index 21b3a0197c48..d409562c904a 100644 --- a/nixos/tests/glitchtip.nix +++ b/nixos/tests/glitchtip.nix @@ -16,7 +16,7 @@ in { services.glitchtip = { enable = true; - port = 8000; + settings.GRANIAN_PORT = 8000; settings.GLITCHTIP_DOMAIN = domain; environmentFiles = [ (builtins.toFile "glitchtip.env" '' @@ -31,7 +31,7 @@ in }; interactive.nodes.machine = { - services.glitchtip.listenAddress = "0.0.0.0"; + services.glitchtip.settings.GRANIAN_HOST = "0.0.0.0"; networking.firewall.allowedTCPPorts = [ 8000 ]; virtualisation.forwardPorts = [ { diff --git a/pkgs/by-name/gl/glitchtip/frontend.nix b/pkgs/by-name/gl/glitchtip/frontend.nix index 07f14a72c636..2642e460075c 100644 --- a/pkgs/by-name/gl/glitchtip/frontend.nix +++ b/pkgs/by-name/gl/glitchtip/frontend.nix @@ -5,22 +5,27 @@ fetchNpmDeps, jq, moreutils, + nodejs_22, }: buildNpmPackage (finalAttrs: { pname = "glitchtip-frontend"; - version = "5.2.1"; + version = "6.0.9"; src = fetchFromGitLab { owner = "glitchtip"; repo = "glitchtip-frontend"; tag = "v${finalAttrs.version}"; - hash = "sha256-aqGgaVjJogG3mDooQVpR59SR0HDuMPfKuB1i0Z/AMs8="; + hash = "sha256-NbAsiyX1nJLqhqEmkSHc2IlVdgKDqximhAQSJPEM2QM="; }; + nodejs = nodejs_22; + npmDeps = fetchNpmDeps { + name = "${finalAttrs.pname}-${finalAttrs.version}-npm-deps"; inherit (finalAttrs) src; - hash = "sha256-urho5XwUJL7m8/xxv9EvH0MxQIW5TG7nOBSIa77RhJc="; + npmDepsFetcherVersion = 3; + hash = "sha256-CH8fGLppFp+uivhx3gpo8vNAYHtCv0EBTLKGUAhrvSw="; }; postPatch = '' diff --git a/pkgs/by-name/gl/glitchtip/package.nix b/pkgs/by-name/gl/glitchtip/package.nix index 1c420539e06b..a1e6925ef369 100644 --- a/pkgs/by-name/gl/glitchtip/package.nix +++ b/pkgs/by-name/gl/glitchtip/package.nix @@ -2,7 +2,6 @@ lib, python313, fetchFromGitLab, - fetchPypi, callPackage, stdenv, makeWrapper, @@ -13,19 +12,7 @@ let python = python313.override { self = python; packageOverrides = final: prev: { - django = final.django_5; - django-csp = prev.django-csp.overridePythonAttrs rec { - version = "4.0"; - src = fetchPypi { - inherit version; - pname = "django_csp"; - hash = "sha256-snAQu3Ausgo9rTKReN8rYaK4LTOLcPvcE8OjvShxKDM="; - }; - }; - django-ninja-cursor-pagination = prev.django-ninja-cursor-pagination.overridePythonAttrs { - # checks are failing with django 5 - doCheck = false; - }; + django = final.django_6; }; }; @@ -36,14 +23,11 @@ let anonymizeip boto3 brotli - celery - celery-batches cxxfilt django django-allauth django-anymail django-cors-headers - django-csp django-environ django-extensions django-import-export @@ -55,21 +39,24 @@ let django-prometheus django-storages django-valkey + django-vcache + django-vtasks + duckdb google-cloud-logging granian - gunicorn + mcp + minidump orjson psycopg pydantic sentry-sdk symbolic user-agents - uvicorn + uuid6 uwsgi-chunked whitenoise ] - ++ celery.optional-dependencies.redis - ++ celery.optional-dependencies.sqlalchemy + ++ lib.optional (pythonOlder "3.14") zstandard ++ django-allauth.optional-dependencies.headless-spec ++ django-allauth.optional-dependencies.mfa ++ django-allauth.optional-dependencies.socialaccount @@ -78,8 +65,10 @@ let ++ django-storages.optional-dependencies.google ++ django-valkey.optional-dependencies.libvalkey ++ django-valkey.optional-dependencies.lz4 + ++ django-vtasks.optional-dependencies.valkey ++ granian.optional-dependencies.reload ++ granian.optional-dependencies.uvloop + ++ mcp.optional-dependencies.cli ++ psycopg.optional-dependencies.c ++ psycopg.optional-dependencies.pool ++ pydantic.optional-dependencies.email; @@ -89,14 +78,14 @@ in stdenv.mkDerivation (finalAttrs: { pname = "glitchtip"; - version = "5.2.1"; + version = "6.0.9"; pyproject = true; src = fetchFromGitLab { owner = "glitchtip"; repo = "glitchtip-backend"; tag = "v${finalAttrs.version}"; - hash = "sha256-FxkIbSrIyvLnD9n/Cb2udOhsnoC/bwGAfCRB1L/fhwo="; + hash = "sha256-cv83nqoJob8rvBOFDUIr8kVSZQesG/ml+6n7yuZqP90="; }; propagatedBuildInputs = pythonPackages; From 7bfe8919ea506c42ba0cc0c9513a7e523eb92eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Wed, 5 Mar 2025 01:21:49 +0100 Subject: [PATCH 8/9] nixos/glitchtip: add nginx.createLocally option --- nixos/modules/services/web-apps/glitchtip.nix | 49 ++++++++++++++++--- nixos/tests/glitchtip.nix | 25 ++++++---- pkgs/by-name/gl/glitchtip/package.nix | 8 +++ 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/nixos/modules/services/web-apps/glitchtip.nix b/nixos/modules/services/web-apps/glitchtip.nix index e32af666bc06..3660b83807bd 100644 --- a/nixos/modules/services/web-apps/glitchtip.nix +++ b/nixos/modules/services/web-apps/glitchtip.nix @@ -53,7 +53,7 @@ in settings = lib.mkOption { description = '' - Configuration of GlitchTip. See for more information. + Configuration of GlitchTip. See for more information and required settings. ''; default = { }; defaultText = lib.literalExpression '' @@ -61,6 +61,7 @@ in DEBUG = 0; DEBUG_TOOLBAR = 0; DATABASE_URL = lib.mkIf config.services.glitchtip.database.createLocally "postgresql://@/glitchtip"; + GLITCHTIP_DOMAIN = lib.mkIf config.services.glitchtip.nginx.createLocally "https://''${config.services.glitchtip.nginx.domain}"; GLITCHTIP_VERSION = config.services.glitchtip.package.version; GRANIAN_HOST = "127.0.0.1"; GRANIAN_PORT = 8000; @@ -71,6 +72,7 @@ in } ''; example = { + GLITCHTIP_DOMAIN = "https://glitchtip.example.com"; DATABASE_URL = "postgres://postgres:postgres@postgres/postgres"; }; @@ -84,6 +86,13 @@ in ]); options = { + GLITCHTIP_DOMAIN = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "The URL under which GlitchTip is externally reachable."; + example = "https://glitchtip.example.com"; + default = null; + }; + GLITCHTIP_ENABLE_MCP = lib.mkOption { type = lib.types.bool; description = "Whether to enable the MCP api."; @@ -135,17 +144,31 @@ in database.createLocally = lib.mkOption { type = lib.types.bool; default = true; - description = '' - Whether to enable and configure a local PostgreSQL database server. - ''; + description = "Whether to enable and configure a local PostgreSQL database server."; + }; + + nginx = { + createLocally = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to enable and configure a local Nginx server."; + }; + + domain = lib.mkOption { + type = lib.types.str; + example = "glitchtip.example.com"; + description = '' + Domain under which GlitchTip will be reachable. + In contrast to `settings.GLITCHTIP_DOMAIN` this option has no protocol. + It will also set `settings.GLITCHTIP_DOMAIN` with the `https://` protocol. + ''; + }; }; redis.createLocally = lib.mkOption { type = lib.types.bool; default = true; - description = '' - Whether to enable and configure a local Redis instance. - ''; + description = "Whether to enable and configure a local Redis instance."; }; }; }; @@ -180,6 +203,7 @@ in PYTHONUNBUFFERED = lib.mkDefault 1; } // lib.optionalAttrs cfg.database.createLocally { DATABASE_URL = "postgresql://@/glitchtip"; } + // lib.optionalAttrs cfg.nginx.createLocally { GLITCHTIP_DOMAIN = "https://${cfg.nginx.domain}"; } // lib.optionalAttrs cfg.redis.createLocally { REDIS_URL = "unix://${config.services.redis.servers.glitchtip.unixSocket}"; }; @@ -286,6 +310,17 @@ in }; }; + services.nginx = lib.mkIf cfg.nginx.createLocally { + enable = true; + virtualHosts.${cfg.nginx.domain} = { + forceSSL = lib.mkDefault true; + locations = { + "/".proxyPass = "http://${cfg.settings.GRANIAN_HOST}:${toString cfg.settings.GRANIAN_PORT}"; + "/static/".root = "${pkg}/lib/glitchtip"; + }; + }; + }; + services.postgresql = lib.mkIf cfg.database.createLocally { enable = true; ensureDatabases = [ "glitchtip" ]; diff --git a/nixos/tests/glitchtip.nix b/nixos/tests/glitchtip.nix index d409562c904a..149873726bef 100644 --- a/nixos/tests/glitchtip.nix +++ b/nixos/tests/glitchtip.nix @@ -1,7 +1,8 @@ { lib, ... }: let - domain = "http://glitchtip.local:8000"; + domain = "glitchtip.local"; + url = "http://${domain}"; in { @@ -16,8 +17,10 @@ in { services.glitchtip = { enable = true; - settings.GRANIAN_PORT = 8000; - settings.GLITCHTIP_DOMAIN = domain; + nginx.createLocally = true; + nginx.domain = domain; + settings.GLITCHTIP_DOMAIN = lib.mkForce url; + settings.CSRF_TRUSTED_ORIGINS = "${url},http://localhost:8000"; environmentFiles = [ (builtins.toFile "glitchtip.env" '' SECRET_KEY=8Hz7YCGzo7fiicHb8Qr22ZqwoIB7lSRx @@ -25,19 +28,23 @@ in ]; }; + services.nginx.virtualHosts.${domain}.forceSSL = false; + environment.systemPackages = [ pkgs.sentry-cli ]; - networking.hosts."127.0.0.1" = [ "glitchtip.local" ]; + networking.hosts."127.0.0.1" = [ domain ]; }; + interactive.sshBackdoor.enable = true; + interactive.defaults.virtualisation.graphics = false; + interactive.nodes.machine = { - services.glitchtip.settings.GRANIAN_HOST = "0.0.0.0"; - networking.firewall.allowedTCPPorts = [ 8000 ]; + networking.firewall.allowedTCPPorts = [ 80 ]; virtualisation.forwardPorts = [ { from = "host"; host.port = 8000; - guest.port = 8000; + guest.port = 80; } ]; }; @@ -53,7 +60,7 @@ in machine.wait_for_unit("glitchtip-worker.service") machine.wait_for_open_port(8000) - origin_url = "${domain}" + origin_url = "${url}" cookie_jar_path = "/tmp/cookies.txt" curl = f"curl -b {cookie_jar_path} -c {cookie_jar_path} -fS -H 'Origin: {origin_url}'" @@ -89,7 +96,7 @@ in # fetch dsn resp = json.loads(machine.succeed(f"{curl} {origin_url}/api/0/projects/main/test/keys/")) assert len(resp) == 1 - assert re.match(r"^http://[\da-f]+@glitchtip\.local:8000/\d+$", dsn := resp[0]["dsn"]["public"]) + assert re.match(r"^http://[\da-f]+@glitchtip\.local/\d+$", dsn := resp[0]["dsn"]["public"]) # send event machine.succeed(f"SENTRY_DSN={dsn} sentry-cli send-event -m 'hello world'") diff --git a/pkgs/by-name/gl/glitchtip/package.nix b/pkgs/by-name/gl/glitchtip/package.nix index a1e6925ef369..56ba28f73bc0 100644 --- a/pkgs/by-name/gl/glitchtip/package.nix +++ b/pkgs/by-name/gl/glitchtip/package.nix @@ -88,6 +88,14 @@ stdenv.mkDerivation (finalAttrs: { hash = "sha256-cv83nqoJob8rvBOFDUIr8kVSZQesG/ml+6n7yuZqP90="; }; + postPatch = '' + echo 'import os + ALLAUTH_TRUSTED_CLIENT_IP_HEADER = os.getenv( + "ALLAUTH_TRUSTED_CLIENT_IP_HEADER", + None + )' >> glitchtip/settings.py + ''; + propagatedBuildInputs = pythonPackages; nativeBuildInputs = [ From c56016bab787ca577550d1cd8df42728a2d4c667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Tue, 7 Apr 2026 17:16:24 +0200 Subject: [PATCH 9/9] glitchtip: 6.0.9 -> 6.1.4; glitchtip.frontend: 6.0.9 -> 6.1.5 Diff: https://gitlab.com/glitchtip/glitchtip-backend/-/compare/v6.0.9...v6.1.4 Changelog: https://gitlab.com/glitchtip/glitchtip-backend/-/blob/v6.1.4/CHANGELOG Diff: https://gitlab.com/glitchtip/glitchtip-frontend/-/compare/v6.0.9...6.1.5 Changelog: https://gitlab.com/glitchtip/glitchtip-frontend/-/releases/v6.1.5 --- pkgs/by-name/gl/glitchtip/frontend.nix | 6 +++--- pkgs/by-name/gl/glitchtip/package.nix | 14 ++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/pkgs/by-name/gl/glitchtip/frontend.nix b/pkgs/by-name/gl/glitchtip/frontend.nix index 2642e460075c..23e6504dd076 100644 --- a/pkgs/by-name/gl/glitchtip/frontend.nix +++ b/pkgs/by-name/gl/glitchtip/frontend.nix @@ -10,13 +10,13 @@ buildNpmPackage (finalAttrs: { pname = "glitchtip-frontend"; - version = "6.0.9"; + version = "6.1.5"; src = fetchFromGitLab { owner = "glitchtip"; repo = "glitchtip-frontend"; tag = "v${finalAttrs.version}"; - hash = "sha256-NbAsiyX1nJLqhqEmkSHc2IlVdgKDqximhAQSJPEM2QM="; + hash = "sha256-YngWs12wJLi3NgRT3RhGYy38dhOqBATYPMsXEbeoycU="; }; nodejs = nodejs_22; @@ -25,7 +25,7 @@ buildNpmPackage (finalAttrs: { name = "${finalAttrs.pname}-${finalAttrs.version}-npm-deps"; inherit (finalAttrs) src; npmDepsFetcherVersion = 3; - hash = "sha256-CH8fGLppFp+uivhx3gpo8vNAYHtCv0EBTLKGUAhrvSw="; + hash = "sha256-8T2Ci8S0YHyzY1jjgcNXt5mxUy/4toJrD2edUxtJz3M="; }; postPatch = '' diff --git a/pkgs/by-name/gl/glitchtip/package.nix b/pkgs/by-name/gl/glitchtip/package.nix index 56ba28f73bc0..0f02c9d83c0f 100644 --- a/pkgs/by-name/gl/glitchtip/package.nix +++ b/pkgs/by-name/gl/glitchtip/package.nix @@ -1,6 +1,6 @@ { lib, - python313, + python314, fetchFromGitLab, callPackage, stdenv, @@ -9,7 +9,7 @@ }: let - python = python313.override { + python = python314.override { self = python; packageOverrides = final: prev: { django = final.django_6; @@ -21,6 +21,8 @@ let [ aiohttp anonymizeip + arro3-core + arro3-io boto3 brotli cxxfilt @@ -38,7 +40,6 @@ let django-postgres-partition django-prometheus django-storages - django-valkey django-vcache django-vtasks duckdb @@ -56,15 +57,12 @@ let uwsgi-chunked whitenoise ] - ++ lib.optional (pythonOlder "3.14") zstandard ++ django-allauth.optional-dependencies.headless-spec ++ django-allauth.optional-dependencies.mfa ++ django-allauth.optional-dependencies.socialaccount ++ django-storages.optional-dependencies.boto3 ++ django-storages.optional-dependencies.azure ++ django-storages.optional-dependencies.google - ++ django-valkey.optional-dependencies.libvalkey - ++ django-valkey.optional-dependencies.lz4 ++ django-vtasks.optional-dependencies.valkey ++ granian.optional-dependencies.reload ++ granian.optional-dependencies.uvloop @@ -78,14 +76,14 @@ in stdenv.mkDerivation (finalAttrs: { pname = "glitchtip"; - version = "6.0.9"; + version = "6.1.4"; pyproject = true; src = fetchFromGitLab { owner = "glitchtip"; repo = "glitchtip-backend"; tag = "v${finalAttrs.version}"; - hash = "sha256-cv83nqoJob8rvBOFDUIr8kVSZQesG/ml+6n7yuZqP90="; + hash = "sha256-wju/QbIwdtNYQmRppCfjoaqb++spFZbqAsvBwwZyeUM="; }; postPatch = ''