{ options, config, lib, pkgs, ... }: with lib; let runDir = "/run/searx"; cfg = config.services.searx; settingsFile = pkgs.writeText "settings.yml" (builtins.toJSON cfg.settings); limiterSettingsFile = (pkgs.formats.toml { }).generate "limiter.toml" cfg.limiterSettings; generateConfig = '' cd ${runDir} # write NixOS settings as JSON ( umask 077 cp --no-preserve=mode ${settingsFile} settings.yml ) # substitute environment variables env -0 | while IFS='=' read -r -d ''' n v; do sed "s#@$n@#$v#g" -i settings.yml done ''; settingType = with types; (oneOf [ bool int float str (listOf settingType) (attrsOf settingType) ]) // { description = "JSON value"; }; in { imports = [ (mkRenamedOptionModule [ "services" "searx" "configFile" ] [ "services" "searx" "settingsFile" ]) ]; options = { services.searx = { enable = mkOption { type = types.bool; default = false; relatedPackages = [ "searx" ]; description = "Whether to enable Searx, the meta search engine."; }; environmentFile = mkOption { type = types.nullOr types.path; default = null; description = '' Environment file (see `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. ''; }; redisCreateLocally = mkOption { 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. ''; }; settings = mkOption { type = types.attrsOf settingType; default = { }; example = literalExpression '' { server.port = 8080; server.bind_address = "0.0.0.0"; server.secret_key = "@SEARX_SECRET_KEY@"; engines = lib.singleton { name = "wolframalpha"; shortcut = "wa"; api_key = "@WOLFRAM_API_KEY@"; engine = "wolframalpha_api"; }; } ''; 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@`. ::: {.note} For available settings, see the Searx [docs](https://searx.github.io/searx/admin/settings.html). ::: ''; }; settingsFile = mkOption { 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). ::: {.warning} This file, along with any secret key it contains, will be copied into the world-readable Nix store. ::: ''; }; 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/botdetection/limiter.toml). ::: ''; }; package = mkPackageOption pkgs "searxng" { }; runInUwsgi = mkOption { type = types.bool; default = false; description = '' Whether to run searx in uWSGI as a "vassal", instead of using its built-in HTTP server. This is the recommended mode for public or large instances, but is unnecessary for LAN or local-only use. ::: {.warning} The built-in HTTP server logs all queries by default. ::: ''; }; uwsgiConfig = mkOption { type = options.services.uwsgi.instance.type; default = { http = ":8080"; }; example = literalExpression '' { disable-logging = true; http = ":8080"; # serve via HTTP... socket = "/run/searx/searx.sock"; # ...or UNIX socket chmod-socket = "660"; # allow the searx group to read/write to the socket } ''; description = '' Additional configuration of the uWSGI vassal running searx. It should notably specify on which interfaces and ports the vassal should listen. ''; }; }; }; config = mkIf cfg.enable { environment.systemPackages = [ cfg.package ]; users.users.searx = { description = "Searx daemon user"; group = "searx"; isSystemUser = true; }; users.groups.searx = { }; systemd.services.searx-init = { description = "Initialise Searx settings"; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; User = "searx"; RuntimeDirectory = "searx"; RuntimeDirectoryMode = "750"; } // optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = builtins.toPath cfg.environmentFile; }; script = generateConfig; }; systemd.services.searx = mkIf (!cfg.runInUwsgi) { description = "Searx server, the meta search engine."; wantedBy = [ "network.target" "multi-user.target" ]; requires = [ "searx-init.service" ]; after = [ "searx-init.service" ]; serviceConfig = { User = "searx"; Group = "searx"; ExecStart = lib.getExe cfg.package; } // optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = builtins.toPath cfg.environmentFile; }; environment = { SEARX_SETTINGS_PATH = cfg.settingsFile; SEARXNG_SETTINGS_PATH = cfg.settingsFile; }; }; systemd.services.uwsgi = mkIf cfg.runInUwsgi { requires = [ "searx-init.service" ]; after = [ "searx-init.service" ]; }; 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; }; }; meta.maintainers = with maintainers; [ rnhmjoj _999eagle ]; }