Unverified Commit e88108f4 authored by nixpkgs-ci[bot]'s avatar nixpkgs-ci[bot] Committed by GitHub
Browse files

Merge master into staging-next

parents 90abf686 9c47edbb
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -194,8 +194,6 @@
- `openrgb` was updated to 1.0rc2, which now uses Plugin API version 4.
  Some existing OpenRGB plugins may be incompatible or require updates.

- the `neovim` wrapper sets provider-related configuration in its generated config rather than as wrapper arguments. It should not affect configuration unless you set `wrapRc` to false.

- We now use the upstream wrapper script for Gradle, supporting both the `JAVA_HOME` and `GRADLE_OPTS` environment variables.

## Nixpkgs Library {#sec-nixpkgs-release-26.05-lib}
+259 −4
Original line number Diff line number Diff line
@@ -30,8 +30,35 @@ let
      path = "${pkg}/lib/node_modules/${pkg.pname}";
    }) cfg.customNodes
  );

  # Runners
  runnersCfg = cfg.taskRunners;
  runnersEnv = partitionEnv runnersCfg.environment;
  commonAllowedEnv = lib.attrNames runnersEnv.regular;
  enabledRunners = lib.filterAttrs (_name: runnerCfg: runnerCfg.enable) runnersCfg.runners;
  anyRunnerEnabled = runnersCfg.enable && (enabledRunners != { });
  runnerTypes = lib.attrNames enabledRunners;
  runnersStateDir = "n8n-task-runners";
  taskRunnerConfigs = lib.mapAttrsToList (runnerType: runnerCfg: {
    runner-type = runnerType;
    workdir = "/var/lib/${runnersStateDir}";
    command = runnerCfg.command;
    args = runnerCfg.args;
    allowed-env = commonAllowedEnv;
    env-overrides = runnerCfg.environment;
    health-check-server-port = toString runnerCfg.healthCheckPort;
  }) enabledRunners;

  launcherConfigFile = pkgs.writeText "n8n-task-runners.json" (
    builtins.toJSON { task-runners = taskRunnerConfigs; }
  );
in
{
  meta.maintainers = with lib.maintainers; [
    sweenu
    gepbird
  ];

  imports = [
    (lib.mkRemovedOptionModule [ "services" "n8n" "settings" ] "Use services.n8n.environment instead.")
    (lib.mkRemovedOptionModule [
@@ -128,13 +155,188 @@ in
              When enabled, n8n sends notifications of new versions and security updates.
            '';
          };
          N8N_CUSTOM_EXTENSIONS = lib.mkOption {
            internal = true;
            type = with lib.types; nullOr path;
            default = if cfg.customNodes != [ ] then toString customNodesDir else null;
            description = ''
              Specify the path to directories containing your custom nodes.
            '';
          };
          N8N_RUNNERS_MODE = lib.mkOption {
            internal = true;
            type =
              with lib.types;
              enum [
                "internal"
                "external"
              ];
            default = if runnersCfg.enable then "external" else "internal";
            description = ''
              How to launch and run the task runner.
              `internal` means n8n will launch a task runner as child process.
              `external` means an external orchestrator will launch the task runner.
            '';
          };
          N8N_RUNNERS_BROKER_PORT = lib.mkOption {
            type = with lib.types; coercedTo port toString str;
            default = 5679;
            description = ''
              Port the task broker listens on for task runner connections.
            '';
          };
          N8N_RUNNERS_BROKER_LISTEN_ADDRESS = lib.mkOption {
            type = lib.types.str;
            default = "127.0.0.1";
            description = ''
              Address the task broker listens on.
            '';
          };
          N8N_RUNNERS_AUTH_TOKEN_FILE = lib.mkOption {
            type = with lib.types; nullOr path;
            default = null;
            description = ''
              Path to a file containing the shared authentication token
              used between the n8n server (task broker) and the task runners.

              This option is required when {option}`services.n8n.taskRunners.enable` is true.
              The file should be readable by the service and not stored in the Nix store.
              Use tools like `agenix` or `sops-nix` to manage this secret.
            '';
          };
        };
      };
      default = { };
    };

    taskRunners = {
      enable = lib.mkEnableOption "n8n task runners for sandboxed Code node execution";

      launcherPackage = lib.mkPackageOption pkgs "n8n-task-runner-launcher" { };

      environment = lib.mkOption {
        description = ''
          Environment variables for the task runner launcher and runners.
          These are common to all runners and passed via `allowed-env` in the launcher config.
          See <https://docs.n8n.io/hosting/configuration/environment-variables/task-runners/> for available options.

          Environment variables ending with `_FILE` are automatically handled as secrets:
          they are loaded via systemd credentials for secure access with `DynamicUser=true`.

          Note: The authentication token should be set via {option}`services.n8n.environment.N8N_RUNNERS_AUTH_TOKEN_FILE`.
        '';
        example = lib.literalExpression ''
          {
            N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT = 15;
            N8N_RUNNERS_MAX_CONCURRENCY = 10;
          }
        '';
        type = lib.types.submodule {
          freeformType =
            with lib.types;
            attrsOf (oneOf [
              str
              (coercedTo int toString str)
              (coercedTo bool builtins.toJSON str)
            ]);
          options = {
            N8N_RUNNERS_CONFIG_PATH = lib.mkOption {
              internal = true;
              type = with lib.types; nullOr path;
              default = launcherConfigFile;
              description = ''
                Path to the configuration file for the task runner launcher.
              '';
            };
            N8N_RUNNERS_AUTH_TOKEN_FILE = lib.mkOption {
              type = with lib.types; nullOr path;
              default = cfg.environment.N8N_RUNNERS_AUTH_TOKEN_FILE;
              defaultText = lib.literalExpression "config.services.n8n.environment.N8N_RUNNERS_AUTH_TOKEN_FILE";
              description = ''
                Path to the authentication token file for the task runner.
              '';
            };
            N8N_RUNNERS_TASK_BROKER_URI = lib.mkOption {
              type = lib.types.str;
              default = "http://${cfg.environment.N8N_RUNNERS_BROKER_LISTEN_ADDRESS}:${cfg.environment.N8N_RUNNERS_BROKER_PORT}";
              defaultText = lib.literalExpression ''"http://''${config.services.n8n.environment.N8N_RUNNERS_BROKER_LISTEN_ADDRESS}:''${config.services.n8n.environment.N8N_RUNNERS_BROKER_PORT}"'';
              description = ''
                URI of the n8n task broker that the runner connects to.
              '';
            };
          };
        };
        default = { };
      };

      runners = lib.mkOption {
        type = lib.types.attrsOf (
          lib.types.submodule (
            { name, ... }:
            {
              options = {
                enable = lib.mkOption {
                  type = lib.types.bool;
                  default = true;
                  description = ''
                    Whether to enable the ${name} task runner.
                    Only takes effect when {option}`services.n8n.taskRunners.enable` is true.
                  '';
                };

                command = lib.mkOption {
                  type = lib.types.str;
                  description = "Command to execute for this runner.";
                };

                healthCheckPort = lib.mkOption {
                  type = lib.types.port;
                  description = "Port for the runner's health check server.";
                };

                args = lib.mkOption {
                  type = with lib.types; listOf str;
                  default = [ ];
                  description = "Additional command-line arguments to pass to the task runner.";
                };

                environment = lib.mkOption {
                  type = with lib.types; attrsOf str;
                  default = { };
                  description = "Environment variables specific to this task runner.";
                };
              };
            }
          )
        );
        default = { };
        defaultText = lib.literalExpression ''
          {
            javascript = {
              enable = true;
              command = lib.getExe' config.services.n8n.package "n8n-task-runner";
              healthCheckPort = 5681;
            };
            python = {
              enable = true;
              command = lib.getExe' config.services.n8n.package "n8n-task-runner-python";
              healthCheckPort = 5682;
            };
          }
        '';
        description = "Configuration for individual task runners.";
      };
    };
  };

  config = lib.mkIf cfg.enable {
    assertions = [
      {
        assertion = anyRunnerEnabled -> cfg.environment.N8N_RUNNERS_AUTH_TOKEN_FILE != null;
        message = "services.n8n.environment.N8N_RUNNERS_AUTH_TOKEN_FILE must be set when task runners are enabled.";
      }
    ];

    systemd.services.n8n = {
      description = "n8n service";
      after = [ "network.target" ];
@@ -144,15 +346,12 @@ in
        // {
          HOME = cfg.environment.N8N_USER_FOLDER;
        }
        // lib.optionalAttrs (cfg.customNodes != [ ]) {
          N8N_CUSTOM_EXTENSIONS = toString customNodesDir;
        }
        // n8nEnv.fileBasedTransformed;
      serviceConfig = {
        Type = "simple";
        ExecStart = lib.getExe cfg.package;
        Restart = "on-failure";
        StateDirectory = "n8n";
        StateDirectory = baseNameOf cfg.environment.N8N_USER_FOLDER;

        LoadCredential = lib.mapAttrsToList (
          varName: secretPath: "${envVarToCredName varName}:${secretPath}"
@@ -178,6 +377,62 @@ in
      };
    };

    warnings = lib.optional (runnersCfg.enable && !anyRunnerEnabled) ''
      services.n8n.taskRunners.enable is true, but both JavaScript and Python runners are disabled.
      Enable at least one runner or disable taskRunners.
    '';

    # We set the defaults here to ease adding attributes
    services.n8n.taskRunners.runners = {
      javascript = lib.mapAttrs (_: lib.mkDefault) {
        enable = true;
        command = lib.getExe' cfg.package "n8n-task-runner";
        healthCheckPort = 5681;
      };
      python = lib.mapAttrs (_: lib.mkDefault) {
        enable = true;
        command = lib.getExe' cfg.package "n8n-task-runner-python";
        healthCheckPort = 5682;
      };
    };

    systemd.services.n8n-task-runner = lib.mkIf anyRunnerEnabled {
      description = "n8n task runner";
      after = [ "n8n.service" ];
      requires = [ "n8n.service" ];
      wantedBy = [ "multi-user.target" ];
      environment = runnersEnv.regular // runnersEnv.fileBasedTransformed;
      serviceConfig = {
        Type = "simple";
        ExecStart = "${lib.getExe runnersCfg.launcherPackage} ${lib.concatStringsSep " " runnerTypes}";
        Restart = "on-failure";

        StateDirectory = runnersStateDir;

        LoadCredential = lib.mapAttrsToList (
          varName: secretPath: "${envVarToCredName varName}:${secretPath}"
        ) runnersEnv.fileBased;

        # Hardening
        DynamicUser = "true";
        NoNewPrivileges = "yes";
        PrivateTmp = "yes";
        PrivateDevices = "yes";
        DevicePolicy = "closed";
        ProtectSystem = "strict";
        ProtectHome = "read-only";
        ProtectControlGroups = "yes";
        ProtectKernelModules = "yes";
        ProtectKernelTunables = "yes";
        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
        RestrictNamespaces = "yes";
        RestrictRealtime = "yes";
        RestrictSUIDSGID = "yes";
        MemoryDenyWriteExecute = "no"; # v8 JIT requires memory segments to be Writable-Executable.
        LockPersonality = "yes";
      };
    };

    networking.firewall = lib.mkIf cfg.openFirewall {
      allowedTCPPorts = [ (lib.toInt cfg.environment.N8N_PORT) ];
    };
+56 −2
Original line number Diff line number Diff line
{ lib, pkgs, ... }:
let
  port = 5678;
  brokerPort = 5679;
  webhookUrl = "http://example.com";
  secretFile = toString (pkgs.writeText "n8n-encryption-key" "test-encryption-key-12345");
  authTokenFile = toString (pkgs.writeText "n8n-runner-auth-token" "test-runner-auth-token-12345");
in
{
  name = "n8n";
  meta.maintainers = with lib.maintainers; [ k900 ];
  meta.maintainers = with lib.maintainers; [
    k900
    sweenu
    gepbird
  ];

  node.pkgsReadOnly = false;

@@ -20,8 +26,26 @@ in
          WEBHOOK_URL = webhookUrl;
          N8N_TEMPLATES_ENABLED = false;
          DB_PING_INTERVAL_SECONDS = 2;
          # !!! Don't do this with real keys. The /nix store is world-readable!
          # !!! Don't do this with real keys/tokens. The /nix store is world-readable!
          N8N_ENCRYPTION_KEY_FILE = secretFile;
          N8N_RUNNERS_AUTH_TOKEN_FILE = authTokenFile;
        };
        taskRunners = {
          enable = true;
          environment = {
            # Common env var for all runners
            N8N_RUNNERS_MAX_CONCURRENCY = 10;
          };
          runners = {
            javascript = {
              args = [ "--disallow-code-generation-from-strings" ];
              environment.NODE_FUNCTION_ALLOW_BUILTIN = "*";
            };
            python = {
              args = [ "--test-arg" ];
              environment.N8N_RUNNERS_STDLIB_ALLOW = "*";
            };
          };
        };
      };
    };
@@ -48,5 +72,35 @@ in
    custom_extensions_dir = machine.succeed("grep -oP 'N8N_CUSTOM_EXTENSIONS=\\K[^\"]+' /etc/systemd/system/n8n.service").strip()
    machine.succeed(f"test -L {custom_extensions_dir}/n8n-nodes-carbonejs")
    machine.succeed(f"test -f {custom_extensions_dir}/n8n-nodes-carbonejs/package.json")

    # Test task runner integration on n8n service
    machine.succeed("grep -qF 'N8N_RUNNERS_MODE=external' /etc/systemd/system/n8n.service")
    machine.succeed("grep -qF 'N8N_RUNNERS_BROKER_PORT=${toString brokerPort}' /etc/systemd/system/n8n.service")
    machine.succeed("grep -qF 'LoadCredential=n8n_runners_auth_token_file:${authTokenFile}' /etc/systemd/system/n8n.service")

    # Test task runner service
    machine.wait_for_unit("n8n-task-runner.service")
    machine.succeed("systemctl is-active n8n-task-runner.service")

    # Test that both runner types are enabled
    machine.succeed("grep -qF 'javascript python' /etc/systemd/system/n8n-task-runner.service")

    # Test common environment variables are passed to launcher
    machine.succeed("grep -qF 'N8N_RUNNERS_MAX_CONCURRENCY=10' /etc/systemd/system/n8n-task-runner.service")
    machine.succeed("grep -qF 'N8N_RUNNERS_TASK_BROKER_URI=http://127.0.0.1:${toString brokerPort}' /etc/systemd/system/n8n-task-runner.service")

    # Test auth token is loaded via credentials
    machine.succeed("grep -qF 'LoadCredential=n8n_runners_auth_token_file:${authTokenFile}' /etc/systemd/system/n8n-task-runner.service")

    # Test launcher config file
    config_path = machine.succeed("grep -oP 'N8N_RUNNERS_CONFIG_PATH=\\K[^[:space:]\"]+' /etc/systemd/system/n8n-task-runner.service").strip()
    config = machine.succeed(f"cat {config_path}")
    assert "NODE_FUNCTION_ALLOW_BUILTIN" in config, "JavaScript env-override not in config"
    assert "N8N_RUNNERS_STDLIB_ALLOW" in config, "Python env-override not in config"
    assert "N8N_RUNNERS_MAX_CONCURRENCY" in config, "Common allowed-env not in config"
    assert "--disallow-code-generation-from-strings" in config, "JavaScript args not in config"
    assert "--test-arg" in config, "Python args not in config"
    assert '"health-check-server-port":"5681"' in config, "JavaScript health check port not in config"
    assert '"health-check-server-port":"5682"' in config, "Python health check port not in config"
  '';
}
+26 −24
Original line number Diff line number Diff line
@@ -140,6 +140,15 @@ python3.pkgs.buildPythonApplication rec {
    pytest-xdist
  ]);

  env.LC_ALL = "en_US.UTF-8";

  preCheck = ''
    export GDK_PIXBUF_MODULE_FILE=${librsvg}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache
    export XDG_DATA_DIRS="$out/share:${gtk3}/share/gsettings-schemas/${gtk3.name}:$XDG_ICON_DIRS:$XDG_DATA_DIRS"
  '';

  checkPhase =
    let
      pytestFlags = [
        # missing translation strings in potfiles
        "--deselect=tests/test_po.py::TPOTFILESIN::test_missing"
@@ -154,20 +163,13 @@ python3.pkgs.buildPythonApplication rec {
      ++ lib.optionals (withXineBackend || !withGstPlugins) [
        "--ignore=tests/plugin/test_replaygain.py"
      ];

  env.LC_ALL = "en_US.UTF-8";

  preCheck = ''
    export GDK_PIXBUF_MODULE_FILE=${librsvg}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache
    export XDG_DATA_DIRS="$out/share:${gtk3}/share/gsettings-schemas/${gtk3.name}:$XDG_ICON_DIRS:$XDG_DATA_DIRS"
  '';

  checkPhase = ''
    in
    ''
      runHook preCheck

      xvfb-run -s '-screen 0 1920x1080x24' \
        dbus-run-session --config-file=${dbus}/share/dbus-1/session.conf \
      pytest $pytestFlags
        pytest ${lib.concatStringsSep " " pytestFlags}

      runHook postCheck
    '';
+1 −1
Original line number Diff line number Diff line
@@ -139,7 +139,7 @@ stdenv.mkDerivation rec {
  # Find FLTK without requiring an OpenGL library in buildInputs
  ++ lib.optional (guiModule == "fltk") "-DFLTK_SKIP_OPENGL=ON";

  CXXFLAGS = [
  env.CXXFLAGS = toString [
    # GCC 13: error: 'uint8_t' does not name a type
    "-include cstdint"
  ];
Loading