about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeder Bergebakken Sundt <pbsds@hotmail.com>2024-01-31 17:11:38 +0100
committerGitHub <noreply@github.com>2024-01-31 17:11:38 +0100
commitd5d752af43287dc8cf8d316c6b580ab0754e760c (patch)
tree4d93518f78d9d17bab943d7d42d41b8289a1cea0
parentcdd4f2ac1b76a3944c7345cf2d2473ef1c2bd3b1 (diff)
parent5283fe407c9eb996df44a3bc13a06af836790e2e (diff)
downloadnixlib-d5d752af43287dc8cf8d316c6b580ab0754e760c.tar
nixlib-d5d752af43287dc8cf8d316c6b580ab0754e760c.tar.gz
nixlib-d5d752af43287dc8cf8d316c6b580ab0754e760c.tar.bz2
nixlib-d5d752af43287dc8cf8d316c6b580ab0754e760c.tar.lz
nixlib-d5d752af43287dc8cf8d316c6b580ab0754e760c.tar.xz
nixlib-d5d752af43287dc8cf8d316c6b580ab0754e760c.tar.zst
nixlib-d5d752af43287dc8cf8d316c6b580ab0754e760c.zip
Merge pull request #280821 from SuperSandro2000/pretalx
 pretalx: init 2023.1.3 
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/web-apps/pretalx.nix415
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/web-apps/pretalx.nix31
-rw-r--r--pkgs/by-name/pr/pretalx/package.nix201
5 files changed, 649 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index d16553f57642..0eb88e7874f6 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1343,6 +1343,7 @@
   ./services/web-apps/plantuml-server.nix
   ./services/web-apps/plausible.nix
   ./services/web-apps/powerdns-admin.nix
+  ./services/web-apps/pretalx.nix
   ./services/web-apps/prosody-filer.nix
   ./services/web-apps/restya-board.nix
   ./services/web-apps/rimgo.nix
diff --git a/nixos/modules/services/web-apps/pretalx.nix b/nixos/modules/services/web-apps/pretalx.nix
new file mode 100644
index 000000000000..ff6218112d2f
--- /dev/null
+++ b/nixos/modules/services/web-apps/pretalx.nix
@@ -0,0 +1,415 @@
+{ config
+, lib
+, pkgs
+, utils
+, ...
+}:
+
+let
+  cfg = config.services.pretalx;
+  format = pkgs.formats.ini { };
+
+  configFile = format.generate "pretalx.cfg" cfg.settings;
+
+  extras = cfg.package.optional-dependencies.redis
+    ++ lib.optionals (cfg.settings.database.backend == "mysql") cfg.package.optional-dependencies.mysql
+    ++ lib.optionals (cfg.settings.database.backend == "postgresql") cfg.package.optional-dependencies.postgres;
+
+  pythonEnv = cfg.package.python.buildEnv.override {
+    extraLibs = [ (cfg.package.python.pkgs.toPythonModule cfg.package) ]
+      ++ (with cfg.package.python.pkgs; [ gunicorn ]
+      ++ lib.optional cfg.celery.enable celery) ++ extras;
+  };
+in
+
+{
+  meta = with lib; {
+    maintainers = teams.c3d2.members;
+  };
+
+  options.services.pretalx = {
+    enable = lib.mkEnableOption (lib.mdDoc "pretalx");
+
+    package = lib.mkPackageOptionMD pkgs "pretalx" {};
+
+    group = lib.mkOption {
+      type = lib.types.str;
+      default = "pretalx";
+      description = "Group under which pretalx should run.";
+    };
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = "pretalx";
+      description = "User under which pretalx should run.";
+    };
+
+    gunicorn.extraArgs = lib.mkOption {
+      type = with lib.types; listOf str;
+      default = [
+        "--name=pretalx"
+      ];
+      example = [
+        "--name=pretalx"
+        "--workers=4"
+        "--max-requests=1200"
+        "--max-requests-jitter=50"
+        "--log-level=info"
+      ];
+      description = lib.mdDoc ''
+        Extra arguments to pass to gunicorn.
+        See <https://docs.pretalx.org/administrator/installation.html#step-6-starting-pretalx-as-a-service> for details.
+      '';
+      apply = lib.escapeShellArgs;
+    };
+
+    celery = {
+      enable = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        example = false;
+        description = lib.mdDoc ''
+          Whether to set up celery as an asynchronous task runner.
+        '';
+      };
+
+      extraArgs = lib.mkOption {
+        type = with lib.types; listOf str;
+        default = [ ];
+        description = lib.mdDoc ''
+          Extra arguments to pass to celery.
+
+          See <https://docs.celeryq.dev/en/stable/reference/cli.html#celery-worker> for more info.
+        '';
+        apply = utils.escapeSystemdExecArgs;
+      };
+    };
+
+    nginx = {
+      enable = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        example = false;
+        description = lib.mdDoc ''
+          Whether to set up an nginx virtual host.
+        '';
+      };
+
+      domain = lib.mkOption {
+        type = lib.types.str;
+        example = "talks.example.com";
+        description = lib.mdDoc ''
+          The domain name under which to set up the virtual host.
+        '';
+      };
+    };
+
+    database.createLocally = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      example = false;
+      description = lib.mdDoc ''
+        Whether to automatically set up the database on the local DBMS instance.
+
+        Currently only supported for PostgreSQL. Not required for sqlite.
+      '';
+    };
+
+    settings = lib.mkOption {
+      type = lib.types.submodule {
+        freeformType = format.type;
+        options = {
+          database = {
+            backend = lib.mkOption {
+              type = lib.types.enum [
+                "postgresql"
+              ];
+              default = "postgresql";
+              description = lib.mdDoc ''
+                Database backend to use.
+
+                Currently only PostgreSQL gets tested, and as such we don't support any other DBMS.
+              '';
+              readOnly = true; # only postgres supported right now
+            };
+
+            host = lib.mkOption {
+              type = with lib.types; nullOr types.path;
+              default = if cfg.settings.database.backend == "postgresql" then "/run/postgresql"
+                else if cfg.settings.database.backend == "mysql" then "/run/mysqld/mysqld.sock"
+                else null;
+              defaultText = lib.literalExpression ''
+                if config.services.pretalx.settings..database.backend == "postgresql" then "/run/postgresql"
+                else if config.services.pretalx.settings.database.backend == "mysql" then "/run/mysqld/mysqld.sock"
+                else null
+              '';
+              description = lib.mdDoc ''
+                Database host or socket path.
+              '';
+            };
+
+            name = lib.mkOption {
+              type = lib.types.str;
+              default = "pretalx";
+              description = lib.mdDoc ''
+                Database name.
+              '';
+            };
+
+            user = lib.mkOption {
+              type = lib.types.str;
+              default = "pretalx";
+              description = lib.mdDoc ''
+                Database username.
+              '';
+            };
+          };
+
+          filesystem = {
+            data = lib.mkOption {
+              type = lib.types.path;
+              default = "/var/lib/pretalx";
+              description = lib.mdDoc ''
+                Base path for all other storage paths.
+              '';
+            };
+            logs = lib.mkOption {
+              type = lib.types.path;
+              default = "/var/log/pretalx";
+              description = lib.mdDoc ''
+                Path to the log directory, that pretalx logs message to.
+              '';
+            };
+            static = lib.mkOption {
+              type = lib.types.path;
+              default = "${cfg.package.static}/";
+              defaultText = lib.literalExpression "\${config.services.pretalx.package}.static}/";
+              readOnly = true;
+              description = lib.mdDoc ''
+                Path to the directory that contains static files.
+              '';
+            };
+          };
+
+          celery = {
+            backend = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = lib.optionalString cfg.celery.enable "redis+socket://${config.services.redis.servers.pretalx.unixSocket}?virtual_host=1";
+              defaultText = lib.literalExpression ''
+                optionalString config.services.pretalx.celery.enable "redis+socket://''${config.services.redis.servers.pretalx.unixSocket}?virtual_host=1"
+              '';
+              description = lib.mdDoc ''
+                URI to the celery backend used for the asynchronous job queue.
+              '';
+            };
+
+            broker = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = lib.optionalString cfg.celery.enable "redis+socket://${config.services.redis.servers.pretalx.unixSocket}?virtual_host=2";
+              defaultText = lib.literalExpression ''
+                optionalString config.services.pretalx.celery.enable "redis+socket://''${config.services.redis.servers.pretalx.unixSocket}?virtual_host=2"
+              '';
+              description = lib.mdDoc ''
+                URI to the celery broker used for the asynchronous job queue.
+              '';
+            };
+          };
+
+          redis = {
+            location = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = "unix://${config.services.redis.servers.pretalx.unixSocket}?db=0";
+              defaultText = lib.literalExpression ''
+                "unix://''${config.services.redis.servers.pretalx.unixSocket}?db=0"
+              '';
+              description = lib.mdDoc ''
+                URI to the redis server, used to speed up locking, caching and session storage.
+              '';
+            };
+
+            session = lib.mkOption {
+              type = lib.types.bool;
+              default = true;
+              example = false;
+              description = lib.mdDoc ''
+                Whether to use redis as the session storage.
+              '';
+            };
+          };
+
+          site = {
+            url = lib.mkOption {
+              type = lib.types.str;
+              default = "https://${cfg.nginx.domain}";
+              defaultText = lib.literalExpression "https://\${config.services.pretalx.nginx.domain}";
+              example = "https://talks.example.com";
+              description = lib.mdDoc ''
+                The base URI below which your pretalx instance will be reachable.
+              '';
+            };
+          };
+        };
+      };
+      default = { };
+      description = lib.mdDoc ''
+        pretalx configuration as a Nix attribute set. All settings can also be passed
+        from the environment.
+
+        See <https://docs.pretalx.org/administrator/configure.html> for possible options.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    # https://docs.pretalx.org/administrator/installation.html
+
+    environment.systemPackages = [
+      (pkgs.writeScriptBin "pretalx-manage" ''
+        cd ${cfg.settings.filesystem.data}
+        sudo=exec
+        if [[ "$USER" != ${cfg.user} ]]; then
+          sudo='exec /run/wrappers/bin/sudo -u ${cfg.user} --preserve-env=PRETALX_CONFIG_FILE'
+        fi
+        export PRETALX_CONFIG_FILE=${configFile}
+        $sudo ${lib.getExe' pythonEnv "pretalx-manage"} "$@"
+      '')
+    ];
+
+    services = {
+      nginx = lib.mkIf cfg.nginx.enable {
+        enable = true;
+        recommendedGzipSettings = lib.mkDefault true;
+        recommendedOptimisation = lib.mkDefault true;
+        recommendedProxySettings = lib.mkDefault true;
+        recommendedTlsSettings = lib.mkDefault true;
+        upstreams.pretalx.servers."unix:/run/pretalx/pretalx.sock" = { };
+        virtualHosts.${cfg.nginx.domain} = {
+          # https://docs.pretalx.org/administrator/installation.html#step-7-ssl
+          extraConfig = ''
+            more_set_headers Referrer-Policy same-origin;
+            more_set_headers X-Content-Type-Options nosniff;
+          '';
+          locations = {
+            "/".proxyPass = "http://pretalx";
+            "/media/" = {
+              alias = "${cfg.settings.filesystem.data}/data/media/";
+              extraConfig = ''
+                access_log off;
+                more_set_headers Content-Disposition 'attachment; filename="$1"';
+                expires 7d;
+              '';
+            };
+            "/static/" = {
+              alias = cfg.settings.filesystem.static;
+              extraConfig = ''
+                access_log off;
+                more_set_headers Cache-Control "public";
+                expires 365d;
+              '';
+            };
+          };
+        };
+      };
+
+      postgresql = lib.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.pretalx.enable = true;
+    };
+
+    systemd.services = let
+      commonUnitConfig = {
+        environment.PRETALX_CONFIG_FILE = configFile;
+        serviceConfig = {
+          User = "pretalx";
+          Group = "pretalx";
+          StateDirectory = [ "pretalx" "pretalx/media" ];
+          LogsDirectory = "pretalx";
+          WorkingDirectory = cfg.settings.filesystem.data;
+          SupplementaryGroups = [ "redis-pretalx" ];
+        };
+      };
+    in {
+      pretalx-web = lib.recursiveUpdate commonUnitConfig {
+        description = "pretalx web service";
+        after = [
+          "network.target"
+          "redis-pretalx.service"
+        ] ++ lib.optionals (cfg.settings.database.backend == "postgresql") [
+          "postgresql.service"
+        ] ++ lib.optionals (cfg.settings.database.backend == "mysql") [
+          "mysql.service"
+        ];
+        wantedBy = [ "multi-user.target" ];
+        preStart = ''
+          versionFile="${cfg.settings.filesystem.data}/.version"
+          version=$(cat "$versionFile" 2>/dev/null || echo 0)
+
+          if [[ $version != ${cfg.package.version} ]]; then
+            ${lib.getExe' pythonEnv "pretalx-manage"} migrate
+
+            echo "${cfg.package.version}" > "$versionFile"
+          fi
+        '';
+        serviceConfig = {
+          ExecStart = "${lib.getExe' pythonEnv "gunicorn"} --bind unix:/run/pretalx/pretalx.sock ${cfg.gunicorn.extraArgs} pretalx.wsgi";
+          RuntimeDirectory = "pretalx";
+        };
+      };
+
+      pretalx-periodic = lib.recursiveUpdate commonUnitConfig {
+        description = "pretalx periodic task runner";
+        # every 15 minutes
+        startAt = [ "*:3,18,33,48" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${lib.getExe' pythonEnv "pretalx-manage"} runperiodic";
+        };
+      };
+
+      pretalx-clear-sessions = lib.recursiveUpdate commonUnitConfig {
+        description = "pretalx session pruning";
+        startAt = [ "monthly" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${lib.getExe' pythonEnv "pretalx-manage"} clearsessions";
+        };
+      };
+
+      pretalx-worker = lib.mkIf cfg.celery.enable (lib.recursiveUpdate commonUnitConfig {
+        description = "pretalx asynchronous job runner";
+        after = [
+          "network.target"
+          "redis-pretalx.service"
+        ] ++ lib.optionals (cfg.settings.database.backend == "postgresql") [
+          "postgresql.service"
+        ] ++ lib.optionals (cfg.settings.database.backend == "mysql") [
+          "mysql.service"
+        ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig.ExecStart = "${lib.getExe' pythonEnv "celery"} -A pretalx.celery_app worker ${cfg.celery.extraArgs}";
+      });
+    };
+
+    systemd.sockets.pretalx-web.socketConfig = {
+      ListenStream = "/run/pretalx/pretalx.sock";
+      SocketUser = "nginx";
+    };
+
+    users = {
+      groups."${cfg.group}" = {};
+      users."${cfg.user}" = {
+        isSystemUser = true;
+        createHome = true;
+        home = cfg.settings.filesystem.data;
+        inherit (cfg) group;
+      };
+    };
+  };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 0bff7bf0d705..fbb4573d8135 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -713,6 +713,7 @@ in {
   power-profiles-daemon = handleTest ./power-profiles-daemon.nix {};
   pppd = handleTest ./pppd.nix {};
   predictable-interface-names = handleTest ./predictable-interface-names.nix {};
+  pretalx = runTest ./web-apps/pretalx.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/pretalx.nix b/nixos/tests/web-apps/pretalx.nix
new file mode 100644
index 000000000000..a226639b076b
--- /dev/null
+++ b/nixos/tests/web-apps/pretalx.nix
@@ -0,0 +1,31 @@
+{ lib, ... }:
+
+{
+  name = "pretalx";
+  meta.maintainers = lib.teams.c3d2.members;
+
+  nodes = {
+    pretalx = {
+      networking.extraHosts = ''
+        127.0.0.1 talks.local
+      '';
+
+      services.pretalx = {
+        enable = true;
+        nginx.domain = "talks.local";
+        settings = {
+          site.url = "http://talks.local";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    pretalx.wait_for_unit("pretalx-web.service")
+    pretalx.wait_for_unit("pretalx-worker.service")
+
+    pretalx.wait_until_succeeds("curl -q --fail http://talks.local/orga/")
+  '';
+}
diff --git a/pkgs/by-name/pr/pretalx/package.nix b/pkgs/by-name/pr/pretalx/package.nix
new file mode 100644
index 000000000000..a4bf9fc9b5ef
--- /dev/null
+++ b/pkgs/by-name/pr/pretalx/package.nix
@@ -0,0 +1,201 @@
+{ lib
+, buildNpmPackage
+, gettext
+, python3
+, fetchFromGitHub
+, nixosTests
+}:
+
+let
+  python = python3.override {
+    packageOverrides = final: prev: {
+      django-bootstrap4 = prev.django-bootstrap4.overridePythonAttrs (oldAttrs: rec {
+        version = "3.0.0";
+        src = oldAttrs.src.override {
+          rev = "v${version}";
+          hash = "sha256-a8BopUwZjmvxOzBVqs4fTo0SY8sEEloGUw90daYWfz8=";
+        };
+
+        propagatedBuildInputs = with final; [
+          beautifulsoup4
+          django
+        ];
+
+        # fails with some assertions
+        doCheck = false;
+      });
+    };
+  };
+
+  version = "2023.1.3";
+
+  src = fetchFromGitHub {
+    owner = "pretalx";
+    repo = "pretalx";
+    rev = "v${version}";
+    hash = "sha256-YxmkjfftNrInIcSkK21wJXiEU6hbdDa1Od8p+HiLprs=";
+  };
+
+  meta = with lib; {
+    description = "Conference planning tool: CfP, scheduling, speaker management";
+    homepage = "https://github.com/pretalx/pretalx";
+    license = licenses.asl20;
+    maintainers = teams.c3d2.members;
+    platforms = platforms.linux;
+  };
+
+  frontend = buildNpmPackage {
+    pname = "pretalx-frontend";
+    inherit version src;
+
+    sourceRoot = "${src.name}/src/pretalx/frontend/schedule-editor";
+
+    npmDepsHash = "sha256-4cnBHZ8WpHgp/bbsYYbdtrhuD6ffUAZq9ZjoLpWGfRg=";
+
+    npmBuildScript = "build";
+
+    inherit meta;
+  };
+in
+python.pkgs.buildPythonApplication rec {
+  pname = "pretalx";
+  inherit version src;
+  pyproject = true;
+
+  outputs = [
+    "out"
+    "static"
+  ];
+
+  postPatch = ''
+    substituteInPlace src/pretalx/common/management/commands/rebuild.py \
+      --replace 'subprocess.check_call(["npm", "run", "build"], cwd=frontend_dir, env=env)' ""
+
+    substituteInPlace src/setup.cfg \
+      --replace "--cov=./" ""
+  '';
+
+  nativeBuildInputs = [
+    gettext
+    python.pkgs.pythonRelaxDepsHook
+  ];
+
+  pythonRelaxDeps = [
+    "bleach"
+    "cssutils"
+    "django-filter"
+    "django-formtools"
+    "libsass"
+    "markdown"
+    "pillow"
+  ];
+
+  propagatedBuildInputs = with python.pkgs; [
+    beautifulsoup4
+    bleach
+    celery
+    css-inline
+    csscompressor
+    cssutils
+    defusedcsv
+    django
+    django-bootstrap4
+    django-compressor
+    django-context-decorator
+    django-countries
+    django-csp
+    django-filter
+    django-formset-js-improved
+    django-formtools
+    django-hierarkey
+    django-i18nfield
+    django-libsass
+    django-scopes
+    djangorestframework
+    libsass
+    markdown
+    pillow
+    publicsuffixlist
+    python-dateutil
+    qrcode
+    reportlab
+    requests
+    rules
+    urlman
+    vobject
+    whitenoise
+    zxcvbn
+  ] ++ beautifulsoup4.optional-dependencies.lxml;
+
+  passthru.optional-dependencies = {
+    mysql = with python.pkgs; [
+      mysqlclient
+    ];
+    postgres = with python.pkgs; [
+      psycopg2
+    ];
+    redis = with python.pkgs; [
+      redis
+    ];
+  };
+
+  postBuild = ''
+    rm -r ./src/pretalx/frontend/schedule-editor
+    ln -s ${frontend}/lib/node_modules/@pretalx/schedule-editor ./src/pretalx/frontend/schedule-editor
+
+    # Generate all static files, see https://docs.pretalx.org/administrator/commands.html#python-m-pretalx-rebuild
+    PYTHONPATH=$PYTHONPATH:./src python -m pretalx rebuild
+  '';
+
+  postInstall = ''
+    mkdir -p $out/bin
+    cp ./src/manage.py $out/bin/pretalx-manage
+
+    # The processed source files are in the static output, except for fonts, which are duplicated.
+    # See <https://github.com/pretalx/pretalx/issues/1585> for more details.
+    find $out/${python.sitePackages}/pretalx/static \
+      -mindepth 1 \
+      -not -path "$out/${python.sitePackages}/pretalx/static/fonts*" \
+      -delete
+
+    # Copy generated static files into dedicated output
+    mkdir -p $static
+    cp -r ./src/static.dist/** $static/
+
+    # Copy frontend files
+    ln -s ${frontend}/lib/node_modules/@pretalx/schedule-editor/dist/* $static
+  '';
+
+  preCheck = ''
+    export PRETALX_CONFIG_FILE="$src/src/tests/ci_sqlite.cfg"
+    cd src
+  '';
+
+  nativeCheckInputs = with python.pkgs; [
+    faker
+    freezegun
+    pytest-django
+    pytest-mock
+    pytest-xdist
+    pytestCheckHook
+    responses
+  ] ++ lib.flatten (builtins.attrValues passthru.optional-dependencies);
+
+  disabledTests = [
+    # tries to run npm run i18n:extract
+    "test_common_custom_makemessages_does_not_blow_up"
+    # Expected to perform X queries or less but Y were done
+    "test_schedule_export_public"
+    "test_schedule_frab_json_export"
+    "test_schedule_frab_xml_export"
+  ];
+
+  passthru = {
+    inherit python;
+    tests = {
+      inherit (nixosTests) pretalx;
+    };
+  };
+
+  inherit meta;
+}