diff --git a/nixos/modules/services/networking/searx.nix b/nixos/modules/services/networking/searx.nix index 5b0836e67518..85696beeba4a 100644 --- a/nixos/modules/services/networking/searx.nix +++ b/nixos/modules/services/networking/searx.nix @@ -3,7 +3,7 @@ with lib; let - dataDir = "/var/lib/searx"; + runDir = "/run/searx"; cfg = config.services.searx; hasEngines = @@ -13,7 +13,7 @@ let # Script to merge NixOS settings with # the default settings.yml bundled in searx. mergeConfig = '' - cd ${dataDir} + cd ${runDir} # find the default settings.yml default=$(find '${cfg.package}/' -name settings.yml) @@ -46,6 +46,9 @@ let env -0 | while IFS='=' read -r -d ''' n v; do sed "s#@$n@#$v#g" -i settings.yml done + + # set strict permissions + chmod 400 settings.yml ''; in @@ -114,7 +117,7 @@ in settingsFile = mkOption { type = types.path; - default = "${dataDir}/settings.yml"; + 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 @@ -136,6 +139,38 @@ in description = "searx package to use."; }; + 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 unecessary for LAN or local-only use. + + + The built-in HTTP server logs all queries by default. + + + ''; + }; + + uwsgiConfig = mkOption { + type = types.attrs; + default = { http = ":8080"; }; + example = lib.literalExample '' + { + disable-logging = true; + http = ":8080"; # serve via HTTP... + socket = "/run/searx/searx.sock"; # ...or UNIX socket + } + ''; + description = '' + Additional configuration of the uWSGI vassal running searx. It + should notably specify on which interfaces and ports the vassal + should listen. + ''; + }; + }; }; @@ -143,23 +178,66 @@ in ###### implementation - config = mkIf config.services.searx.enable { - systemd.services.searx = { - description = "Searx server, the meta search engine."; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; + 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"; - DynamicUser = true; + RuntimeDirectory = "searx"; + RuntimeDirectoryMode = "750"; + } // optionalAttrs (cfg.environmentFile != null) + { EnvironmentFile = builtins.toPath cfg.environmentFile; }; + script = mergeConfig; + }; + + 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 = "${cfg.package}/bin/searx-run"; - StateDirectory = "searx"; } // optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = builtins.toPath cfg.environmentFile; }; environment.SEARX_SETTINGS_PATH = cfg.settingsFile; - preStart = mergeConfig; }; - environment.systemPackages = [ cfg.package ]; + systemd.services.uwsgi = mkIf (cfg.runInUwsgi) + { requires = [ "searx-init.service" ]; + after = [ "searx-init.service" ]; + }; + + 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 = [ "SEARX_SETTINGS_PATH=${cfg.settingsFile}" ]; + pythonPackages = self: [ cfg.package ]; + } // cfg.uwsgiConfig; + }; }; diff --git a/nixos/tests/searx.nix b/nixos/tests/searx.nix index 128c2a9381b2..e5fee3466bfa 100644 --- a/nixos/tests/searx.nix +++ b/nixos/tests/searx.nix @@ -6,7 +6,8 @@ import ./make-test-python.nix ({ pkgs, ...} : maintainers = [ rnhmjoj ]; }; - machine = { ... }: { + # basic setup: searx running the built-in webserver + nodes.base = { ... }: { imports = [ ../modules/profiles/minimal.nix ]; services.searx = { @@ -17,11 +18,10 @@ import ./make-test-python.nix ({ pkgs, ...} : ''; settings.server = - { port = 8080; + { port = "8080"; bind_address = "0.0.0.0"; secret_key = "@SEARX_SECRET_KEY@"; }; - settings.engines = { wolframalpha = { api_key = "@WOLFRAM_API_KEY@"; @@ -29,34 +29,81 @@ import ./make-test-python.nix ({ pkgs, ...} : }; startpage.shortcut = "start"; }; - }; + + }; + + # fancy setup: run in uWSGI and use nginx as proxy + nodes.fancy = { ... }: { + imports = [ ../modules/profiles/minimal.nix ]; + + services.searx = { + enable = true; + runInUwsgi = true; + 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 = ""; + manage-script-name = true; + }; + }; + + # 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 = "${pkgs.searx}/share/static/"; + }; + + # allow nginx access to the searx socket + users.users.nginx.extraGroups = [ "searx" ]; + }; testScript = '' - start_all() + base.start() with subtest("Settings have been merged"): - machine.wait_for_unit("searx") - output = machine.succeed( - "${pkgs.yq-go}/bin/yq r /var/lib/searx/settings.yml" + base.wait_for_unit("searx-init") + base.wait_for_file("/run/searx/settings.yml") + output = base.succeed( + "${pkgs.yq-go}/bin/yq r /run/searx/settings.yml" " 'engines.(name==startpage).shortcut'" ).strip() assert output == "start", "Settings not merged" with subtest("Environment variables have been substituted"): - machine.succeed("grep -q somesecret /var/lib/searx/settings.yml") - machine.succeed("grep -q sometoken /var/lib/searx/settings.yml") + 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("Searx service is running"): - machine.wait_for_open_port(8080) - machine.succeed( + with subtest("Basic setup is working"): + base.wait_for_open_port(8080) + base.wait_for_unit("searx") + base.succeed( "${pkgs.curl}/bin/curl --fail http://localhost:8080" ) + base.shutdown() - machine.copy_from_vm("/var/lib/searx/settings.yml") - machine.shutdown() + with subtest("Nginx+uWSGI setup is working"): + fancy.start() + fancy.wait_for_open_port(80) + fancy.wait_for_unit("uwsgi") + fancy.succeed( + "${pkgs.curl}/bin/curl --fail http://localhost/searx >&2" + ) + fancy.succeed( + "${pkgs.curl}/bin/curl --fail http://localhost/searx/static/js/bootstrap.min.js >&2" + ) ''; })