Unverified Commit 8c34fc8c authored by Sandro Jäckel's avatar Sandro Jäckel Committed by GitHub
Browse files

Searxng rework, searxng: 0-unstable-2025-07-08 -> 0-unstable-2025-07-11 (#346777)

parents cc593b0e a4c2474f
Loading
Loading
Loading
Loading
+211 −147
Original line number Diff line number Diff line
@@ -15,8 +15,8 @@ let

  settingsFile = pkgs.writeText "settings.yml" (builtins.toJSON cfg.settings);

  limiterSettingsFile = (pkgs.formats.toml { }).generate "limiter.toml" cfg.limiterSettings;
  faviconsSettingsFile = (pkgs.formats.toml { }).generate "favicons.toml" cfg.faviconsSettings;
  limiterSettingsFile = (pkgs.formats.toml { }).generate "limiter.toml" cfg.limiterSettings;

  generateConfig = ''
    cd ${runDir}
@@ -46,15 +46,8 @@ let
    // {
      description = "JSON value";
    };

in

{

  imports = [
    (mkRenamedOptionModule [ "services" "searx" "configFile" ] [ "services" "searx" "settingsFile" ])
  ];

  options = {
    services.searx = {
      enable = mkOption {
@@ -64,14 +57,20 @@ in
        description = "Whether to enable Searx, the meta search engine.";
      };

      domain = mkOption {
        type = types.str;
        description = ''
          The domain under which searxng will be served.
          Right now this is only used with the configureNginx option.
        '';
      };

      environmentFile = mkOption {
        type = types.nullOr types.path;
        default = null;
        description = ''
          Environment file (see {manpage}`systemd.exec(5)`
          "EnvironmentFile=" section for the syntax) to define variables for
          Searx. This option can be used to safely include secret keys into the
          Searx configuration.
          Environment file (see {manpage}`systemd.exec(5)` "EnvironmentFile=" section for the syntax) to define variables for Searx.
          This option can be used to safely include secret keys into the Searx configuration.
        '';
      };

@@ -79,21 +78,27 @@ in
        type = types.bool;
        default = false;
        description = ''
          Configure a local Redis server for SearXNG. This is required if you
          want to enable the rate limiter and bot protection of SearXNG.
          Configure a local Redis server for SearXNG.
          This is required if you want to enable the rate limiter and bot protection of SearXNG.
        '';
      };

      settings = mkOption {
        type = types.attrsOf settingType;
        type = types.submodule {
          freeformType = settingType;
          imports = [
            (mkRenamedOptionModule [ "redis" ] [ "valkey" ])
          ];
        };
        default = { };
        example = literalExpression ''
          { server.port = 8080;
          {
            server.port = 8080;
            server.bind_address = "0.0.0.0";
            server.secret_key = "@SEARX_SECRET_KEY@";

            engines = lib.singleton
              { name = "wolframalpha";
            engines = lib.singleton {
              name = "wolframalpha";
              shortcut = "wa";
              api_key = "@WOLFRAM_API_KEY@";
              engine = "wolframalpha_api";
@@ -101,15 +106,12 @@ in
          }
        '';
        description = ''
          Searx settings. These will be merged with (taking precedence over)
          the default configuration. It's also possible to refer to
          environment variables
          (defined in [](#opt-services.searx.environmentFile))
          using the syntax `@VARIABLE_NAME@`.
          Searx settings.
          These will be merged with (taking precedence over) the default configuration.
          It's also possible to refer to environment variables (defined in [](#opt-services.searx.environmentFile)) using the syntax `@VARIABLE_NAME@`.

          ::: {.note}
          For available settings, see the Searx
          [docs](https://searx.github.io/searx/admin/settings.html).
          For available settings, see the Searx [docs](https://docs.searxng.org/admin/settings/index.html).
          :::
        '';
      };
@@ -118,39 +120,15 @@ in
        type = types.path;
        default = "${runDir}/settings.yml";
        description = ''
          The path of the Searx server settings.yml file. If no file is
          specified, a default file is used (default config file has debug mode
          enabled). Note: setting this options overrides
          [](#opt-services.searx.settings).
          The path of the Searx server settings.yml file.
          If no file is specified, a default file is used (default config file has debug mode enabled).

          ::: {.warning}
          This file, along with any secret key it contains, will be copied
          into the world-readable Nix store.
          ::: {.note}
          Setting this options overrides [](#opt-services.searx.settings).
          :::
        '';
      };

      limiterSettings = mkOption {
        type = types.attrsOf settingType;
        default = { };
        example = literalExpression ''
          {
            real_ip = {
              x_for = 1;
              ipv4_prefix = 32;
              ipv6_prefix = 56;
            }
            botdetection.ip_lists.block_ip = [
              # "93.184.216.34" # example.org
            ];
          }
        '';
        description = ''
          Limiter settings for SearXNG.

          ::: {.note}
          For available settings, see the SearXNG
          [schema file](https://github.com/searxng/searxng/blob/master/searx/limiter.toml).
          ::: {.warning}
          This file, along with any secret key it contains, will be copied into the world-readable Nix store.
          :::
        '';
      };
@@ -163,7 +141,7 @@ in
            favicons = {
              cfg_schema = 1;
              cache = {
                db_url = "/run/searx/faviconcache.db";
                db_url = "/var/cache/searx/faviconcache.db";
                HOLD_TIME = 5184000;
                LIMIT_TOTAL_BYTES = 2147483648;
                BLOB_MAX_BYTES = 40960;
@@ -183,9 +161,33 @@ in
        '';
      };

      limiterSettings = mkOption {
        type = types.attrsOf settingType;
        default = { };
        example = literalExpression ''
          {
            real_ip = {
              x_for = 1;
              ipv4_prefix = 32;
              ipv6_prefix = 56;
            }
            botdetection.ip_lists.block_ip = [
              # "93.184.216.34" # example.org
            ];
          }
        '';
        description = ''
          Limiter settings for SearXNG.

          ::: {.note}
          For available settings, see the SearXNG [schema file](https://github.com/searxng/searxng/blob/master/searx/limiter.toml).
          :::
        '';
      };

      package = mkPackageOption pkgs "searxng" { };

      runInUwsgi = mkOption {
      configureUwsgi = mkOption {
        type = types.bool;
        default = false;
        description = ''
@@ -199,8 +201,16 @@ in
        '';
      };

      configureNginx = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether to configure nginx as an frontend to uwsgi.
        '';
      };

      uwsgiConfig = mkOption {
        type = options.services.uwsgi.instance.type;
        inherit (options.services.uwsgi.instance) type;
        default = {
          http = ":8080";
        };
@@ -218,23 +228,108 @@ in
          should listen.
        '';
      };
    };
  };

  imports = [
    (mkRenamedOptionModule [ "services" "searx" "configFile" ] [ "services" "searx" "settingsFile" ])
    (mkRenamedOptionModule [ "services" "searx" "runInUwsgi" ] [ "services" "searx" "configureUwsgi" ])
  ];

  config = mkIf cfg.enable {
    environment = {
      etc = {
        "searxng/favicons.toml" = lib.mkIf (cfg.faviconsSettings != { }) {
          source = faviconsSettingsFile;
        };
        "searxng/limiter.toml" = lib.mkIf (cfg.limiterSettings != { }) {
          source = limiterSettingsFile;
        };
      };
      systemPackages = [ cfg.package ];
    };

    services = {
      nginx = lib.mkIf cfg.configureNginx {
        enable = true;
        virtualHosts."${cfg.domain}".locations = {
          "/" = {
            recommendedProxySettings = true;
            recommendedUwsgiSettings = true;
            uwsgiPass = "unix:${config.services.uwsgi.instance.vassals.searx.socket}";
            extraConfig = # nginx
              ''
                uwsgi_param  HTTP_HOST             $host;
                uwsgi_param  HTTP_CONNECTION       $http_connection;
                uwsgi_param  HTTP_X_SCHEME         $scheme;
                uwsgi_param  HTTP_X_SCRIPT_NAME    ""; # NOTE: When we ever make the path configurable, this must be set to anything not "/"!
                uwsgi_param  HTTP_X_REAL_IP        $remote_addr;
                uwsgi_param  HTTP_X_FORWARDED_FOR  $proxy_add_x_forwarded_for;
              '';
          };
          "/static/".alias = lib.mkDefault "${cfg.package}/share/static/";
        };
      };

      redis.servers.searx = lib.mkIf cfg.redisCreateLocally {
        enable = true;
        user = "searx";
        port = 0;
      };

  config = mkIf cfg.enable {
    environment.systemPackages = [ cfg.package ];
      searx = {
        configureUwsgi = lib.mkIf cfg.configureNginx true;
        settings = {
          # merge NixOS settings with defaults settings.yml
          use_default_settings = mkDefault true;
          server.base_url = lib.mkIf cfg.configureNginx "http${
            lib.optionalString (lib.any lib.id (
              with config.services.nginx.virtualHosts."${cfg.domain}";
              [
                onlySSL
                addSSL
                forceSSL
              ]
            )) "s"
          }://${cfg.domain}/";
          ui.static_use_hash = true;
          valkey.url = lib.mkIf cfg.redisCreateLocally "unix://${config.services.redis.servers.searx.unixSocket}";
        };
      };

    users.users.searx = {
      description = "Searx daemon user";
      group = "searx";
      isSystemUser = true;
      uwsgi = mkIf cfg.configureUwsgi {
        enable = true;
        plugins = [ "python3" ];
        instance.type = "emperor";
        instance.vassals.searx =
          {
            type = "normal";
            strict = true;
            immediate-uid = "searx";
            immediate-gid = "searx";
            lazy-apps = true;
            enable-threads = true;
            module = "searx.webapp";
            env = [
              "SEARXNG_SETTINGS_PATH=${cfg.settingsFile}"
            ];
            buffer-size = 32768;
            pythonPackages = _: [ cfg.package ];
          }
          // lib.optionalAttrs cfg.configureNginx {
            socket = "/run/searx/uwsgi.sock";
            chmod-socket = "660";
          }
          // cfg.uwsgiConfig;
      };
    };

    users.groups.searx = { };
    systemd.services = {
      nginx = lib.mkIf cfg.configureNginx {
        serviceConfig.SupplementaryGroups = [ "searx" ];
      };

    systemd.services.searx-init = {
      searx-init = {
        description = "Initialise Searx settings";
        serviceConfig =
          {
@@ -243,14 +338,15 @@ in
            User = "searx";
            RuntimeDirectory = "searx";
            RuntimeDirectoryMode = "750";
            RuntimeDirectoryPreserve = "yes";
          }
          // optionalAttrs (cfg.environmentFile != null) {
          EnvironmentFile = builtins.toPath cfg.environmentFile;
            EnvironmentFile = cfg.environmentFile;
          };
        script = generateConfig;
      };

    systemd.services.searx = mkIf (!cfg.runInUwsgi) {
      searx = mkIf (!cfg.configureUwsgi) {
        description = "Searx server, the meta search engine.";
        wantedBy = [ "multi-user.target" ];
        requires = [ "searx-init.service" ];
@@ -265,67 +361,35 @@ in
            ExecStart = lib.getExe cfg.package;
          }
          // optionalAttrs (cfg.environmentFile != null) {
          EnvironmentFile = builtins.toPath cfg.environmentFile;
            EnvironmentFile = cfg.environmentFile;
          };
        environment = {
        SEARX_SETTINGS_PATH = cfg.settingsFile;
          SEARXNG_SETTINGS_PATH = cfg.settingsFile;
        };
      };

    systemd.services.uwsgi = mkIf cfg.runInUwsgi {
      uwsgi = mkIf cfg.configureUwsgi {
        requires = [ "searx-init.service" ];
        after = [ "searx-init.service" ];
        restartTriggers = [
          cfg.package
          cfg.settingsFile
        ] ++ lib.optional (cfg.environmentFile != null) cfg.environmentFile;
      };

    services.searx.settings = {
      # merge NixOS settings with defaults settings.yml
      use_default_settings = mkDefault true;
      redis.url = lib.mkIf cfg.redisCreateLocally "unix://${config.services.redis.servers.searx.unixSocket}";
    };

    services.uwsgi = mkIf cfg.runInUwsgi {
      enable = true;
      plugins = [ "python3" ];

      instance.type = "emperor";
      instance.vassals.searx = {
        type = "normal";
        strict = true;
        immediate-uid = "searx";
        immediate-gid = "searx";
        lazy-apps = true;
        enable-threads = true;
        module = "searx.webapp";
        env = [
          # TODO: drop this as it is only required for searx
          "SEARX_SETTINGS_PATH=${cfg.settingsFile}"
          # searxng compatibility https://github.com/searxng/searxng/issues/1519
          "SEARXNG_SETTINGS_PATH=${cfg.settingsFile}"
        ];
        buffer-size = 32768;
        pythonPackages = self: [ cfg.package ];
      } // cfg.uwsgiConfig;
    };

    services.redis.servers.searx = lib.mkIf cfg.redisCreateLocally {
      enable = true;
      user = "searx";
      port = 0;
    };

    environment.etc = {
      "searxng/limiter.toml" = lib.mkIf (cfg.limiterSettings != { }) {
        source = limiterSettingsFile;
      };
      "searxng/favicons.toml" = lib.mkIf (cfg.faviconsSettings != { }) {
        source = faviconsSettingsFile;
    users = {
      groups.searx = { };
      users.searx = {
        description = "Searx daemon user";
        group = "searx";
        isSystemUser = true;
      };
    };
  };

  meta.maintainers = with maintainers; [
    rnhmjoj
    SuperSandro2000
    _999eagle
  ];
}
+34 −45
Original line number Diff line number Diff line
{ pkgs, ... }:
{ lib, pkgs, ... }:

{
  name = "searx";
  meta = with pkgs.lib.maintainers; {
    maintainers = [ rnhmjoj ];
  meta = with lib.maintainers; {
    maintainers = [
      SuperSandro2000
      _999eagle
    ];
  };

  # basic setup: searx running the built-in webserver
  nodes.base =
    { ... }:
    {
  nodes.base = {
    services.searx = {
      enable = true;
      environmentFile = pkgs.writeText "secrets" ''
          WOLFRAM_API_KEY  = sometoken
        SEARX_SECRET_KEY = somesecret
      '';

        settings.server = {
          port = "8080";
          bind_address = "0.0.0.0";
          secret_key = "@SEARX_SECRET_KEY@";
        };
        settings.engines = [
          {
            name = "wolframalpha";
            api_key = "@WOLFRAM_API_KEY@";
            engine = "wolframalpha_api";
          }
      settings = {
        engines = [
          {
            name = "startpage";
            shortcut = "start";
          }
        ];
        plugins = { };
        server = {
          port = "8080";
          bind_address = "0.0.0.0";
          secret_key = "@SEARX_SECRET_KEY@";
        };
      };
    };

  };

  # fancy setup: run in uWSGI and use nginx as proxy
  nodes.fancy =
    { config, ... }:
    { config, lib, ... }:
    {
      services.searx = {
        enable = true;
        # searx refuses to run if unchanged
        settings.server.secret_key = "somesecret";
        settings = {
          plugins = { };
          server.secret_key = "somesecret";
        };

        runInUwsgi = true;
        configureNginx = true;
        domain = "localhost";
        uwsgiConfig = {
          # serve using the uwsgi protocol
          socket = "/run/searx/uwsgi.sock";
          chmod-socket = "660";

          # use /searx as url "mountpoint"
          mount = "/searx=searx.webapp:application";
          module = "";
@@ -59,19 +56,12 @@
        };
      };

      # use nginx as reverse proxy
      services.nginx.enable = true;
      services.nginx.virtualHosts.localhost = {
        locations."/searx".extraConfig = ''
          include ${pkgs.nginx}/conf/uwsgi_params;
          uwsgi_pass unix:/run/searx/uwsgi.sock;
        '';
        locations."/searx/static/".alias = "${config.services.searx.package}/share/static/";
      services.nginx.virtualHosts.${config.services.searx.domain} = {
        locations = {
          "/static/" = lib.mkForce { };
          "/searx/static/".alias = "${config.services.searx.package}/share/static/";
        };
      };

      # allow nginx access to the searx socket
      users.users.nginx.extraGroups = [ "searx" ];

    };

  testScript = ''
@@ -89,7 +79,6 @@

    with subtest("Environment variables have been substituted"):
        base.succeed("grep -q somesecret /run/searx/settings.yml")
        base.succeed("grep -q sometoken /run/searx/settings.yml")
        base.copy_from_vm("/run/searx/settings.yml")

    with subtest("Basic setup is working"):
+10 −34
Original line number Diff line number Diff line
@@ -7,45 +7,20 @@
}:
let
  python = python3.override {
    packageOverrides = final: prev: {
      httpx = prev.httpx.overridePythonAttrs (old: rec {
        version = "0.27.2";
        src = old.src.override {
          tag = version;
          hash = "sha256-N0ztVA/KMui9kKIovmOfNTwwrdvSimmNkSvvC+3gpck=";
        };
      });

      httpx-socks = prev.httpx-socks.overridePythonAttrs (old: rec {
        version = "0.9.2";
        src = old.src.override {
          tag = "v${version}";
          hash = "sha256-PUiciSuDCO4r49st6ye5xPLCyvYMKfZY+yHAkp5j3ZI=";
        };
      });

      starlette = prev.starlette.overridePythonAttrs (old: {
        disabledTests = old.disabledTests or [ ] ++ [
          # fails in assertion with spacing issue
          "test_request_body"
          "test_request_stream"
          "test_wsgi_post"
        ];
      });
    };
    packageOverrides = final: prev: { };
  };
in
python.pkgs.toPythonModule (
  python.pkgs.buildPythonApplication rec {
    pname = "searxng";
    version = "0-unstable-2025-07-08";
    version = "0-unstable-2025-07-16";
    format = "setuptools";

    src = fetchFromGitHub {
      owner = "searxng";
      repo = "searxng";
      rev = "bd593d0bad2189f57657bbcfa2c5e86f795c680e";
      hash = "sha256-vNI66OKA8LPXqc2mt8lm4iKS6njRLQhjzcykCQyPJsk=";
      rev = "62fac1c6a9db94682f8ef686f0424a482663b288";
      hash = "sha256-3Ma16EdQdqnXyz+ipH5qq9TF0+DwpNU2kq2RTgK5b/A=";
    };

    postPatch = ''
@@ -77,23 +52,24 @@ python.pkgs.toPythonModule (
        babel
        brotli
        certifi
        cryptography
        fasttext-predict
        flask
        flask-babel
        httpx
        httpx-socks
        isodate
        jinja2
        lxml
        markdown-it-py
        msgspec
        pygments
        python-dateutil
        pyyaml
        redis
        setproctitle
        typer
        uvloop
        setproctitle
        httpx
        httpx-socks
        markdown-it-py
        valkey
      ]
      ++ httpx.optional-dependencies.http2
      ++ httpx-socks.optional-dependencies.asyncio;