diff --git a/LICENSES b/LICENSES
index 84f71d4f9..94fdf6903 100644
--- a/LICENSES
+++ b/LICENSES
@@ -70,3 +70,4 @@ otherwise.
- static/themes/default/icons/windows.png :: [[https://thenounproject.com/icon/1206946/download/color/000000/png][CC BY 3.0 US]]
- static/themes/default/icons/gnu-linux.png :: [[https://upload.wikimedia.org/wikipedia/commons/9/95/Tux-icon-mono.svg][Public Domain]]
- static/themes/default/icons/mediawiki.svg :: [[http://tango.freedesktop.org/][Public Domain]]
+- static/themes/default/icons/searx.png :: [[https://github.com/asciimoo/searx/blob/master/searx/static/themes/simple/img/logo_searx_a.png][GPLv3+]]
diff --git a/actions/searx b/actions/searx
new file mode 100755
index 000000000..25fc2f07d
--- /dev/null
+++ b/actions/searx
@@ -0,0 +1,99 @@
+#!/usr/bin/python3
+# -*- mode: python -*-
+#
+# This file is part of Plinth.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+"""
+Configuration helper for searx.
+"""
+
+import argparse
+import os
+
+from plinth import action_utils
+from plinth.utils import YAMLFile, gunzip
+
+
+def parse_arguments():
+ """Return parsed command line arguments as dictionary."""
+ parser = argparse.ArgumentParser()
+ subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
+
+ subparsers.add_parser('enable', help='Enable searx')
+ subparsers.add_parser('disable', help='Disable searx')
+ subparsers.add_parser(
+ 'setup', help="Perform post-installation operations for Searx")
+
+ subparsers.required = True
+ return parser.parse_args()
+
+
+def _copy_uwsgi_configuration():
+ """Copy the example uwsgi configuration shipped with Searx to the
+ appropriate uwsgi directory."""
+ example_config = ('/usr/share/doc/searx/examples/'
+ 'uwsgi/apps-available/searx.ini')
+ destination = '/etc/uwsgi/apps-enabled/searx.ini'
+
+ if not os.path.exists(destination):
+ os.symlink(example_config, destination)
+ action_utils.webserver_enable('uwsgi', kind='module')
+
+
+def _generate_secret_key():
+ """ Generate a secret key for the Searx installation."""
+ settings_file = '/etc/searx/settings.yml'
+
+ # Create settings file if not exists
+ if not os.path.exists(settings_file):
+ example_settings_file = '/usr/share/doc/searx/examples/settings.yml.gz'
+ gunzip(example_settings_file, settings_file)
+
+ # Generate and set a secret key
+ with YAMLFile(settings_file) as settings:
+ secret_key = os.urandom(16).hex()
+ settings['server']['secret_key'] = secret_key
+
+ action_utils.service_restart('uwsgi')
+
+
+def subcommand_setup(_):
+ """Post installation actions for Searx"""
+ _copy_uwsgi_configuration()
+ _generate_secret_key()
+
+
+def subcommand_enable(_):
+ """Enable web configuration and reload."""
+ action_utils.webserver_enable('searx-plinth')
+
+
+def subcommand_disable(_):
+ """Disable web configuration and reload."""
+ action_utils.webserver_disable('searx-plinth')
+
+
+def main():
+ """Parse arguments and perform all duties."""
+ arguments = parse_arguments()
+
+ subcommand = arguments.subcommand.replace('-', '_')
+ subcommand_method = globals()['subcommand_' + subcommand]
+ subcommand_method(arguments)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/data/etc/apache2/conf-available/searx-plinth.conf b/data/etc/apache2/conf-available/searx-plinth.conf
new file mode 100644
index 000000000..69d92392f
--- /dev/null
+++ b/data/etc/apache2/conf-available/searx-plinth.conf
@@ -0,0 +1,5 @@
+
+ Options FollowSymLinks Indexes
+ SetHandler uwsgi-handler
+ uWSGISocket /run/uwsgi/app/searx/socket
+
\ No newline at end of file
diff --git a/data/etc/plinth/modules-enabled/searx b/data/etc/plinth/modules-enabled/searx
new file mode 100644
index 000000000..d4cc7b9d2
--- /dev/null
+++ b/data/etc/plinth/modules-enabled/searx
@@ -0,0 +1 @@
+plinth.modules.searx
diff --git a/plinth/modules/searx/__init__.py b/plinth/modules/searx/__init__.py
new file mode 100644
index 000000000..6577a7bf9
--- /dev/null
+++ b/plinth/modules/searx/__init__.py
@@ -0,0 +1,119 @@
+#
+# This file is part of Plinth.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+"""
+Plinth module to configure Searx
+"""
+
+from django.utils.translation import ugettext_lazy as _
+
+from plinth import service as service_module
+from plinth import action_utils, actions, frontpage
+from plinth.menu import main_menu
+
+from .manifest import clients
+
+clients = clients
+
+version = 1
+
+managed_services = ['searx']
+
+managed_packages = [
+ 'searx', 'uwsgi', 'uwsgi-plugin-python3', 'libapache2-mod-uwsgi'
+]
+
+name = _('Searx')
+
+short_description = _('Web Search')
+
+description = [
+ _('Searx is a privacy-respecting internet metasearch engine. '
+ 'It aggregrates and displays results from multiple search engines.'),
+ _('Searx can be used to avoid tracking and profiling by search engines. '
+ 'It stores no cookies by default. Additionally, Searx can be used over '
+ 'Tor for online anonymity.'),
+ _('When enabled, Searx\'s web interface will be available from '
+ '/searx.'),
+]
+
+service = None
+
+
+def init():
+ """Intialize the module."""
+ menu = main_menu.get('apps')
+ menu.add_urlname(name, 'glyphicon-search', 'searx:index',
+ short_description)
+
+ global service
+ setup_helper = globals()['setup_helper']
+ if setup_helper.get_state() != 'needs-setup':
+ service = service_module.Service(managed_services[0], name, ports=[
+ 'http', 'https'
+ ], is_external=True, is_enabled=is_enabled, enable=enable,
+ disable=disable)
+
+ if is_enabled():
+ add_shortcut()
+
+
+def setup(helper, old_version=None):
+ """Install and configure the module."""
+ helper.install(managed_packages)
+ helper.call('setup', actions.superuser_run, 'searx', ['setup'])
+ helper.call('post', actions.superuser_run, 'searx', ['enable'])
+ global service
+ if service is None:
+ service = service_module.Service(managed_services[0], name, ports=[
+ 'http', 'https'
+ ], is_external=True, is_enabled=is_enabled, enable=enable,
+ disable=disable)
+ helper.call('post', add_shortcut)
+
+
+def add_shortcut():
+ """Helper method to add a shortcut to the frontpage."""
+ frontpage.add_shortcut('searx', name, short_description=short_description,
+ url='/searx', login_required=True)
+
+
+def is_enabled():
+ """Return whether the module is enabled."""
+ return action_utils.webserver_is_enabled('searx-plinth')
+
+
+def enable():
+ """Enable the module."""
+ actions.superuser_run('searx', ['enable'])
+ add_shortcut()
+
+
+def disable():
+ """Disable the module."""
+ actions.superuser_run('searx', ['disable'])
+ frontpage.remove_shortcut('searx')
+
+
+def diagnose():
+ """Run diagnostics and return the results."""
+ results = []
+
+ results.extend(
+ action_utils.diagnose_url_on_all('https://{host}/searx/',
+ check_certificate=False))
+
+ return results
diff --git a/plinth/modules/searx/manifest.py b/plinth/modules/searx/manifest.py
new file mode 100644
index 000000000..8cffa5df2
--- /dev/null
+++ b/plinth/modules/searx/manifest.py
@@ -0,0 +1,28 @@
+#
+# This file is part of Plinth.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+from django.utils.translation import ugettext_lazy as _
+
+from plinth.clients import validate
+
+clients = validate([{
+ 'name': _('Searx'),
+ 'platforms': [{
+ 'type': 'web',
+ 'url': '/searx'
+ }]
+}])
diff --git a/plinth/modules/searx/urls.py b/plinth/modules/searx/urls.py
new file mode 100644
index 000000000..a8bcc4f9b
--- /dev/null
+++ b/plinth/modules/searx/urls.py
@@ -0,0 +1,32 @@
+#
+# This file is part of Plinth.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+"""
+URLs for the Searx module.
+"""
+
+from django.conf.urls import url
+
+from plinth.modules import searx
+from plinth.views import ServiceView
+
+urlpatterns = [
+ url(r'^apps/searx/$',
+ ServiceView.as_view(
+ service_id='searx', diagnostics_module_name='searx',
+ description=searx.description, clients=searx.clients,
+ show_status_block=False), name='index'),
+]
diff --git a/plinth/utils.py b/plinth/utils.py
index 1f971ab43..918548457 100644
--- a/plinth/utils.py
+++ b/plinth/utils.py
@@ -18,6 +18,7 @@
Miscellaneous utility methods.
"""
+import gzip
import importlib
import os
import random
@@ -136,3 +137,21 @@ def grep(pattern, file_name):
return [
line.rstrip() for line in open(file_name) if re.search(pattern, line)
]
+
+
+def gunzip(gzip_file, output_file):
+ """Utility to unzip a given gzip file and write it to an output file
+
+ gzip_file: string path to a gzip file
+ output_file: string path to the output file
+ mode: an octal number to specify file permissions
+ """
+ output_dir = os.path.dirname(output_file)
+ if not os.path.exists(output_dir):
+ os.makedirs(output_dir, mode=0o755)
+
+ with gzip.open(gzip_file, 'rb') as f:
+ contents = f.read()
+ with open(output_file, 'wb') as f:
+ f.write(contents)
+ os.chmod(output_file, 0o644)
diff --git a/static/themes/default/icons/searx.png b/static/themes/default/icons/searx.png
new file mode 100644
index 000000000..2db081710
Binary files /dev/null and b/static/themes/default/icons/searx.png differ