Merge pull request #101849 from rnhmjoj/searx

nixos/searx: declarative configuration

authored by Michele Guerini Rocco and committed by GitHub b1fb65a7 ad4e3860

+331 -35
+12
nixos/doc/manual/release-notes/rl-2103.xml
··· 402 402 SDK licenses if your project requires it. See the androidenv documentation for more details. 403 403 </para> 404 404 </listitem> 405 + <listitem> 406 + <para> 407 + The Searx module has been updated with the ability to configure the 408 + service declaratively and uWSGI integration. 409 + The option <literal>services.searx.configFile</literal> has been renamed 410 + to <xref linkend="opt-services.searx.settingsFile"/> for consistency with 411 + the new <xref linkend="opt-services.searx.settings"/>. In addition, the 412 + <literal>searx</literal> uid and gid reservations have been removed 413 + since they were not necessary: the service is now running with a 414 + dynamically allocated uid. 415 + </para> 416 + </listitem> 405 417 </itemizedlist> 406 418 </section> 407 419
+2 -2
nixos/modules/misc/ids.nix
··· 143 143 nix-ssh = 104; 144 144 dictd = 105; 145 145 couchdb = 106; 146 - searx = 107; 146 + #searx = 107; # dynamically allocated as of 2020-10-27 147 147 kippo = 108; 148 148 jenkins = 109; 149 149 systemd-journal-gateway = 110; ··· 457 457 #nix-ssh = 104; # unused 458 458 dictd = 105; 459 459 couchdb = 106; 460 - searx = 107; 460 + #searx = 107; # dynamically allocated as of 2020-10-27 461 461 kippo = 108; 462 462 jenkins = 109; 463 463 systemd-journal-gateway = 110;
+196 -30
nixos/modules/services/networking/searx.nix
··· 3 3 with lib; 4 4 5 5 let 6 + runDir = "/run/searx"; 7 + cfg = config.services.searx; 6 8 7 - cfg = config.services.searx; 9 + hasEngines = 10 + builtins.hasAttr "engines" cfg.settings && 11 + cfg.settings.engines != { }; 12 + 13 + # Script to merge NixOS settings with 14 + # the default settings.yml bundled in searx. 15 + mergeConfig = '' 16 + cd ${runDir} 17 + # find the default settings.yml 18 + default=$(find '${cfg.package}/' -name settings.yml) 19 + 20 + # write NixOS settings as JSON 21 + cat <<'EOF' > settings.json 22 + ${builtins.toJSON cfg.settings} 23 + EOF 24 + 25 + ${optionalString hasEngines '' 26 + # extract and convert the default engines array to an object 27 + ${pkgs.yq-go}/bin/yq r "$default" engines -j | \ 28 + ${pkgs.jq}/bin/jq 'reduce .[] as $e ({}; .[$e.name] = $e)' \ 29 + > engines.json 30 + 31 + # merge and update the NixOS engines with the newly created object 32 + cp settings.json temp.json 33 + ${pkgs.jq}/bin/jq -s '. as [$s, $e] | $s | .engines |= 34 + ($e * . | to_entries | map (.value))' \ 35 + temp.json engines.json > settings.json 36 + 37 + # clean up temporary files 38 + rm {engines,temp}.json 39 + ''} 8 40 9 - configFile = cfg.configFile; 41 + # merge the default and NixOS settings 42 + ${pkgs.yq-go}/bin/yq m -P settings.json "$default" > settings.yml 43 + rm settings.json 44 + 45 + # substitute environment variables 46 + env -0 | while IFS='=' read -r -d ''' n v; do 47 + sed "s#@$n@#$v#g" -i settings.yml 48 + done 49 + 50 + # set strict permissions 51 + chmod 400 settings.yml 52 + ''; 10 53 11 54 in 12 55 13 56 { 14 57 58 + imports = [ 59 + (mkRenamedOptionModule 60 + [ "services" "searx" "configFile" ] 61 + [ "services" "searx" "settingsFile" ]) 62 + ]; 63 + 15 64 ###### interface 16 65 17 66 options = { 18 67 19 68 services.searx = { 20 69 21 - enable = mkEnableOption 22 - "the searx server. See https://github.com/asciimoo/searx"; 70 + enable = mkOption { 71 + type = types.bool; 72 + default = false; 73 + relatedPackages = [ "searx" ]; 74 + description = "Whether to enable Searx, the meta search engine."; 75 + }; 23 76 24 - configFile = mkOption { 77 + environmentFile = mkOption { 25 78 type = types.nullOr types.path; 26 79 default = null; 27 - description = " 28 - The path of the Searx server configuration file. If no file 29 - is specified, a default file is used (default config file has 30 - debug mode enabled). 31 - "; 80 + description = '' 81 + Environment file (see <literal>systemd.exec(5)</literal> 82 + "EnvironmentFile=" section for the syntax) to define variables for 83 + Searx. This option can be used to safely include secret keys into the 84 + Searx configuration. 85 + ''; 86 + }; 87 + 88 + settings = mkOption { 89 + type = types.attrs; 90 + default = { }; 91 + example = literalExample '' 92 + { server.port = 8080; 93 + server.bind_address = "0.0.0.0"; 94 + server.secret_key = "@SEARX_SECRET_KEY@"; 95 + 96 + engines.wolframalpha = 97 + { shortcut = "wa"; 98 + api_key = "@WOLFRAM_API_KEY@"; 99 + engine = "wolframalpha_api"; 100 + }; 101 + } 102 + ''; 103 + description = '' 104 + Searx settings. These will be merged with (taking precedence over) 105 + the default configuration. It's also possible to refer to 106 + environment variables 107 + (defined in <xref linkend="opt-services.searx.environmentFile"/>) 108 + using the syntax <literal>@VARIABLE_NAME@</literal>. 109 + <note> 110 + <para> 111 + For available settings, see the Searx 112 + <link xlink:href="https://searx.github.io/searx/admin/settings.html">docs</link>. 113 + </para> 114 + </note> 115 + ''; 116 + }; 117 + 118 + settingsFile = mkOption { 119 + type = types.path; 120 + default = "${runDir}/settings.yml"; 121 + description = '' 122 + The path of the Searx server settings.yml file. If no file is 123 + specified, a default file is used (default config file has debug mode 124 + enabled). Note: setting this options overrides 125 + <xref linkend="opt-services.searx.settings"/>. 126 + <warning> 127 + <para> 128 + This file, along with any secret key it contains, will be copied 129 + into the world-readable Nix store. 130 + </para> 131 + </warning> 132 + ''; 32 133 }; 33 134 34 135 package = mkOption { ··· 38 139 description = "searx package to use."; 39 140 }; 40 141 142 + runInUwsgi = mkOption { 143 + type = types.bool; 144 + default = false; 145 + description = '' 146 + Whether to run searx in uWSGI as a "vassal", instead of using its 147 + built-in HTTP server. This is the recommended mode for public or 148 + large instances, but is unecessary for LAN or local-only use. 149 + <warning> 150 + <para> 151 + The built-in HTTP server logs all queries by default. 152 + </para> 153 + </warning> 154 + ''; 155 + }; 156 + 157 + uwsgiConfig = mkOption { 158 + type = types.attrs; 159 + default = { http = ":8080"; }; 160 + example = lib.literalExample '' 161 + { 162 + disable-logging = true; 163 + http = ":8080"; # serve via HTTP... 164 + socket = "/run/searx/searx.sock"; # ...or UNIX socket 165 + } 166 + ''; 167 + description = '' 168 + Additional configuration of the uWSGI vassal running searx. It 169 + should notably specify on which interfaces and ports the vassal 170 + should listen. 171 + ''; 172 + }; 173 + 41 174 }; 42 175 43 176 }; ··· 45 178 46 179 ###### implementation 47 180 48 - config = mkIf config.services.searx.enable { 181 + config = mkIf cfg.enable { 182 + environment.systemPackages = [ cfg.package ]; 49 183 50 184 users.users.searx = 51 - { uid = config.ids.uids.searx; 52 - description = "Searx user"; 53 - createHome = true; 54 - home = "/var/lib/searx"; 185 + { description = "Searx daemon user"; 186 + group = "searx"; 187 + isSystemUser = true; 55 188 }; 56 189 57 - users.groups.searx = 58 - { gid = config.ids.gids.searx; 190 + users.groups.searx = { }; 191 + 192 + systemd.services.searx-init = { 193 + description = "Initialise Searx settings"; 194 + serviceConfig = { 195 + Type = "oneshot"; 196 + RemainAfterExit = true; 197 + User = "searx"; 198 + RuntimeDirectory = "searx"; 199 + RuntimeDirectoryMode = "750"; 200 + } // optionalAttrs (cfg.environmentFile != null) 201 + { EnvironmentFile = builtins.toPath cfg.environmentFile; }; 202 + script = mergeConfig; 203 + }; 204 + 205 + systemd.services.searx = mkIf (!cfg.runInUwsgi) { 206 + description = "Searx server, the meta search engine."; 207 + wantedBy = [ "network.target" "multi-user.target" ]; 208 + requires = [ "searx-init.service" ]; 209 + after = [ "searx-init.service" ]; 210 + serviceConfig = { 211 + User = "searx"; 212 + Group = "searx"; 213 + ExecStart = "${cfg.package}/bin/searx-run"; 214 + } // optionalAttrs (cfg.environmentFile != null) 215 + { EnvironmentFile = builtins.toPath cfg.environmentFile; }; 216 + environment.SEARX_SETTINGS_PATH = cfg.settingsFile; 217 + }; 218 + 219 + systemd.services.uwsgi = mkIf (cfg.runInUwsgi) 220 + { requires = [ "searx-init.service" ]; 221 + after = [ "searx-init.service" ]; 59 222 }; 60 223 61 - systemd.services.searx = 62 - { 63 - description = "Searx server, the meta search engine."; 64 - after = [ "network.target" ]; 65 - wantedBy = [ "multi-user.target" ]; 66 - serviceConfig = { 67 - User = "searx"; 68 - ExecStart = "${cfg.package}/bin/searx-run"; 69 - }; 70 - } // (optionalAttrs (configFile != null) { 71 - environment.SEARX_SETTINGS_PATH = configFile; 72 - }); 224 + services.uwsgi = mkIf (cfg.runInUwsgi) { 225 + enable = true; 226 + plugins = [ "python3" ]; 73 227 74 - environment.systemPackages = [ cfg.package ]; 228 + instance.type = "emperor"; 229 + instance.vassals.searx = { 230 + type = "normal"; 231 + strict = true; 232 + immediate-uid = "searx"; 233 + immediate-gid = "searx"; 234 + lazy-apps = true; 235 + enable-threads = true; 236 + module = "searx.webapp"; 237 + env = [ "SEARX_SETTINGS_PATH=${cfg.settingsFile}" ]; 238 + pythonPackages = self: [ cfg.package ]; 239 + } // cfg.uwsgiConfig; 240 + }; 75 241 76 242 }; 77 243
+1
nixos/tests/all-tests.nix
··· 342 342 sbt-extras = handleTest ./sbt-extras.nix {}; 343 343 scala = handleTest ./scala.nix {}; 344 344 sddm = handleTest ./sddm.nix {}; 345 + searx = handleTest ./searx.nix {}; 345 346 service-runner = handleTest ./service-runner.nix {}; 346 347 shadow = handleTest ./shadow.nix {}; 347 348 shadowsocks = handleTest ./shadowsocks {};
+109
nixos/tests/searx.nix
··· 1 + import ./make-test-python.nix ({ pkgs, ...} : 2 + 3 + { 4 + name = "searx"; 5 + meta = with pkgs.stdenv.lib.maintainers; { 6 + maintainers = [ rnhmjoj ]; 7 + }; 8 + 9 + # basic setup: searx running the built-in webserver 10 + nodes.base = { ... }: { 11 + imports = [ ../modules/profiles/minimal.nix ]; 12 + 13 + services.searx = { 14 + enable = true; 15 + environmentFile = pkgs.writeText "secrets" '' 16 + WOLFRAM_API_KEY = sometoken 17 + SEARX_SECRET_KEY = somesecret 18 + ''; 19 + 20 + settings.server = 21 + { port = "8080"; 22 + bind_address = "0.0.0.0"; 23 + secret_key = "@SEARX_SECRET_KEY@"; 24 + }; 25 + settings.engines = { 26 + wolframalpha = 27 + { api_key = "@WOLFRAM_API_KEY@"; 28 + engine = "wolframalpha_api"; 29 + }; 30 + startpage.shortcut = "start"; 31 + }; 32 + }; 33 + 34 + }; 35 + 36 + # fancy setup: run in uWSGI and use nginx as proxy 37 + nodes.fancy = { ... }: { 38 + imports = [ ../modules/profiles/minimal.nix ]; 39 + 40 + services.searx = { 41 + enable = true; 42 + runInUwsgi = true; 43 + uwsgiConfig = { 44 + # serve using the uwsgi protocol 45 + socket = "/run/searx/uwsgi.sock"; 46 + chmod-socket = "660"; 47 + 48 + # use /searx as url "mountpoint" 49 + mount = "/searx=searx.webapp:application"; 50 + module = ""; 51 + manage-script-name = true; 52 + }; 53 + }; 54 + 55 + # use nginx as reverse proxy 56 + services.nginx.enable = true; 57 + services.nginx.virtualHosts.localhost = { 58 + locations."/searx".extraConfig = 59 + '' 60 + include ${pkgs.nginx}/conf/uwsgi_params; 61 + uwsgi_pass unix:/run/searx/uwsgi.sock; 62 + ''; 63 + locations."/searx/static/".alias = "${pkgs.searx}/share/static/"; 64 + }; 65 + 66 + # allow nginx access to the searx socket 67 + users.users.nginx.extraGroups = [ "searx" ]; 68 + 69 + }; 70 + 71 + testScript = 72 + '' 73 + base.start() 74 + 75 + with subtest("Settings have been merged"): 76 + base.wait_for_unit("searx-init") 77 + base.wait_for_file("/run/searx/settings.yml") 78 + output = base.succeed( 79 + "${pkgs.yq-go}/bin/yq r /run/searx/settings.yml" 80 + " 'engines.(name==startpage).shortcut'" 81 + ).strip() 82 + assert output == "start", "Settings not merged" 83 + 84 + with subtest("Environment variables have been substituted"): 85 + base.succeed("grep -q somesecret /run/searx/settings.yml") 86 + base.succeed("grep -q sometoken /run/searx/settings.yml") 87 + base.copy_from_vm("/run/searx/settings.yml") 88 + 89 + with subtest("Basic setup is working"): 90 + base.wait_for_open_port(8080) 91 + base.wait_for_unit("searx") 92 + base.succeed( 93 + "${pkgs.curl}/bin/curl --fail http://localhost:8080" 94 + ) 95 + base.shutdown() 96 + 97 + with subtest("Nginx+uWSGI setup is working"): 98 + fancy.start() 99 + fancy.wait_for_open_port(80) 100 + fancy.wait_for_unit("uwsgi") 101 + fancy.succeed( 102 + "${pkgs.curl}/bin/curl --fail http://localhost/searx >&2" 103 + ) 104 + fancy.succeed( 105 + "${pkgs.curl}/bin/curl --fail http://localhost/searx/static/js/bootstrap.min.js >&2" 106 + ) 107 + ''; 108 + }) 109 +
+11 -3
pkgs/servers/web-apps/searx/default.nix
··· 1 - { lib, python3Packages, fetchFromGitHub, fetchpatch }: 1 + { lib, nixosTests, python3, python3Packages, fetchFromGitHub, fetchpatch }: 2 2 3 3 with python3Packages; 4 4 5 - buildPythonApplication rec { 5 + toPythonModule (buildPythonApplication rec { 6 6 pname = "searx"; 7 7 version = "0.17.0"; 8 8 ··· 34 34 rm tests/test_robot.py # A variable that is imported is commented out 35 35 ''; 36 36 37 + postInstall = '' 38 + # Create a symlink for easier access to static data 39 + mkdir -p $out/share 40 + ln -s ../${python3.sitePackages}/searx/static $out/share/ 41 + ''; 42 + 43 + passthru.tests = { inherit (nixosTests) searx; }; 44 + 37 45 meta = with lib; { 38 46 homepage = "https://github.com/asciimoo/searx"; 39 47 description = "A privacy-respecting, hackable metasearch engine"; 40 48 license = licenses.agpl3Plus; 41 49 maintainers = with maintainers; [ matejc fpletz globin danielfullmer ]; 42 50 }; 43 - } 51 + })