diff --git a/actions/cockpit.socket b/actions/cockpit.socket
new file mode 100755
index 000000000..7fa660d6d
--- /dev/null
+++ b/actions/cockpit.socket
@@ -0,0 +1,110 @@
+#!/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 Cockpit.
+"""
+
+import argparse
+import augeas
+import subprocess
+
+from plinth import action_utils
+
+CONFIG_FILE = '/etc/cockpit/cockpit.conf'
+
+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('pre-setup', help='Perform pre-setup operations')
+ subparsers.add_parser('setup', help='Setup Cockpit configuration')
+ subparsers.add_parser('enable', help='Enable Cockpit site')
+ subparsers.add_parser('disable', help='Disable Cockpit site')
+
+ subparsers.required = True
+ return parser.parse_args()
+
+
+def subcommand_pre_setup(_):
+ """Preseed debconf values before packages are installed."""
+ """subprocess.check_output(
+ ['debconf-set-selections'],
+ input=b'cockpit tt-rss/database-type string pgsql')"""
+ pass
+
+def subcommand_setup(_):
+ """Setup Cockpit configuration."""
+
+ open(CONFIG_FILE, 'a').close()
+
+ aug = load_augeas()
+ SECTION = CONFIG_FILE+'/WebService'
+ aug.set('/files'+SECTION, '')
+ origins = "https://localhost:9090 https://localhost:4430"
+ aug.set('/files'+SECTION+'/Origins', origins)
+ aug.save()
+
+ action_utils.webserver_enable('proxy_wstunnel', kind='module')
+ action_utils.service_restart('cockpit.socket')
+ action_utils.webserver_enable('cockpit-plinth')
+ action_utils.service_restart('apache2')
+
+def subcommand_enable(_):
+ """Enable web configuration and reload."""
+ action_utils.service_enable('cockpit.socket')
+# action_utils.webserver_enable('cockpit-plinth')
+
+
+def subcommand_disable(_):
+ """Disable web configuration and reload."""
+# action_utils.webserver_disable('cockpit-plinth')
+ action_utils.service_disable('cockpit.socket')
+
+def subcommand_add_domain(_):
+ aug = load_augeas()
+
+def subcommand_remove_domain(_):
+ aug = load_augeas()
+
+def subcommand_change_domain(_):
+ aug = load_augeas()
+
+def load_augeas():
+ """Initialize Augeas."""
+ aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
+ augeas.Augeas.NO_MODL_AUTOLOAD)
+ aug.set('/augeas/load/inifile/lens', 'Puppet.lns')
+ aug.set('/augeas/load/inifile/incl[last() + 1]', CONFIG_FILE)
+ aug.load()
+ return aug
+
+
+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/cockpit-plinth.conf b/data/etc/apache2/conf-available/cockpit-plinth.conf
new file mode 100644
index 000000000..9509a0133
--- /dev/null
+++ b/data/etc/apache2/conf-available/cockpit-plinth.conf
@@ -0,0 +1,21 @@
+##
+## On all sites, provide cockpit on a defaut path: /cockpit
+##
+## Requires the following Apache modules to be enabled:
+## mod_headers
+## mod_proxy
+## mod_proxy_http
+## mod_proxy_wstunnel
+##
+
+
+ proxypass http://localhost:9090/
+
+
+
+ proxypass http://localhost:9090/cockpit/
+
+
+
+ proxypass ws://localhost:9090/cockpit/socket
+
diff --git a/data/etc/plinth/modules-enabled/cockpit b/data/etc/plinth/modules-enabled/cockpit
new file mode 100644
index 000000000..b4e16954d
--- /dev/null
+++ b/data/etc/plinth/modules-enabled/cockpit
@@ -0,0 +1 @@
+plinth.modules.cockpit
diff --git a/plinth/modules/cockpit/__init__.py b/plinth/modules/cockpit/__init__.py
new file mode 100644
index 000000000..dd44b8760
--- /dev/null
+++ b/plinth/modules/cockpit/__init__.py
@@ -0,0 +1,133 @@
+#
+# 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 Cockpit.
+"""
+
+from django.utils.translation import ugettext_lazy as _
+from plinth.utils import format_lazy
+
+from plinth import actions
+from plinth import action_utils
+from plinth import cfg
+from plinth import frontpage
+from plinth import service as service_module
+from plinth.menu import main_menu
+from plinth.signals import domain_added, domain_removed, domainname_change
+
+
+version = 1
+
+managed_services = ['cockpit.socket']
+
+managed_packages = ['cockpit']
+
+title = _('Dashboard of Servers \n (Cockpit)')
+
+description = [
+ _('Cockpit is an interactive server admin interface. It is easy to use '
+ 'and very light weight. Cockpit interacts directly with the operating '
+ 'system from a real linux session in a browser'),
+
+ format_lazy(
+ _('When enabled, Cockpit will be available from '
+ '/cockpit path on the web server. It can be accessed by '
+ 'any user with a {box_name} login.'),
+ box_name=_(cfg.box_name))
+]
+
+service = None
+
+
+def init():
+ """Intialize the module."""
+ menu = main_menu.get('apps')
+ menu.add_urlname(title, 'glyphicon-dashboard', 'cockpit:index')
+
+ global service
+ setup_helper = globals()['setup_helper']
+ if setup_helper.get_state() != 'needs-setup':
+ service = service_module.Service(
+ managed_services[0], title, 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.call('pre', actions.superuser_run, 'cockpit', ['pre-setup'])
+ helper.install(managed_packages)
+ helper.call('post', actions.superuser_run, 'cockpit', ['setup'])
+ global service
+ if service is None:
+ service = service_module.Service(
+ managed_services[0], title, ports=['http', 'https'],
+ is_external=True,
+ is_enabled=is_enabled, enable=enable, disable=disable)
+ helper.call('post', service.notify_enabled, None, True)
+ helper.call('post', add_shortcut)
+
+
+def add_shortcut():
+ frontpage.add_shortcut('cockpit', title, url='/cockpit',
+ login_required=True)
+
+
+def is_enabled():
+ """Return whether the module is enabled."""
+ return (action_utils.webserver_is_enabled('cockpit-plinth') and
+ action_utils.service_is_running('cockpit.socket'))
+
+
+def enable():
+ """Enable the module."""
+ actions.superuser_run('cockpit.socket', ['enable'])
+ add_shortcut()
+
+
+def disable():
+ """Disable the module."""
+ actions.superuser_run('cockpit.socket', ['disable'])
+ frontpage.remove_shortcut('cockpit')
+
+
+def diagnose():
+ """Run diagnostics and return the results."""
+ results = []
+
+ results.extend(action_utils.diagnose_url_on_all(
+ 'https://{host}/cockpit', check_certificate=False))
+
+ return results
+
+def on_domain_added():
+ actions.superuser_run('cockpit.socket', ['add_domain'])
+
+def on_domain_removed():
+ actions.superuser_run('cockpit.socket', ['remove_domain'])
+
+def on_domainname_change(sender, old_domainname, new_domainname, **kwargs):
+ del sender
+ del kwargs
+ actions.superuser_run('cockpit.socket',
+ ['change_domain',
+ '--domainname', new_domainname],
+ async=True)
diff --git a/plinth/modules/cockpit/tests/__init__.py b/plinth/modules/cockpit/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/plinth/modules/cockpit/urls.py b/plinth/modules/cockpit/urls.py
new file mode 100644
index 000000000..72a9e8096
--- /dev/null
+++ b/plinth/modules/cockpit/urls.py
@@ -0,0 +1,35 @@
+#
+# 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 Cockpit module.
+"""
+
+from django.conf.urls import url
+
+from plinth.views import ServiceView
+from plinth.modules import cockpit
+
+
+urlpatterns = [
+ url(r'^apps/cockpit/$', ServiceView.as_view(
+ service_id=cockpit.managed_services[0],
+ diagnostics_module_name="cockpit",
+ description=cockpit.description,
+ show_status_block=True
+ ), name='index'),
+]
diff --git a/static/themes/default/icons/cockpit.png b/static/themes/default/icons/cockpit.png
new file mode 100644
index 000000000..86195c4aa
Binary files /dev/null and b/static/themes/default/icons/cockpit.png differ