Loading nixos/modules/services/networking/searx.nix +211 −147 Original line number Diff line number Diff line Loading @@ -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} Loading Loading @@ -46,15 +46,8 @@ let // { description = "JSON value"; }; in { imports = [ (mkRenamedOptionModule [ "services" "searx" "configFile" ] [ "services" "searx" "settingsFile" ]) ]; options = { services.searx = { enable = mkOption { Loading @@ -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. ''; }; Loading @@ -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"; Loading @@ -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). ::: ''; }; Loading @@ -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. ::: ''; }; Loading @@ -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; Loading @@ -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 = '' Loading @@ -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"; }; Loading @@ -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 = { Loading @@ -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" ]; Loading @@ -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 ]; } nixos/tests/searx.nix +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 = ""; Loading @@ -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 = '' Loading @@ -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"): Loading pkgs/by-name/se/searxng/package.nix +10 −34 Original line number Diff line number Diff line Loading @@ -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 = '' Loading Loading @@ -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; Loading Loading
nixos/modules/services/networking/searx.nix +211 −147 Original line number Diff line number Diff line Loading @@ -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} Loading Loading @@ -46,15 +46,8 @@ let // { description = "JSON value"; }; in { imports = [ (mkRenamedOptionModule [ "services" "searx" "configFile" ] [ "services" "searx" "settingsFile" ]) ]; options = { services.searx = { enable = mkOption { Loading @@ -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. ''; }; Loading @@ -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"; Loading @@ -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). ::: ''; }; Loading @@ -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. ::: ''; }; Loading @@ -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; Loading @@ -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 = '' Loading @@ -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"; }; Loading @@ -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 = { Loading @@ -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" ]; Loading @@ -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 ]; }
nixos/tests/searx.nix +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 = ""; Loading @@ -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 = '' Loading @@ -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"): Loading
pkgs/by-name/se/searxng/package.nix +10 −34 Original line number Diff line number Diff line Loading @@ -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 = '' Loading Loading @@ -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; Loading