diff --git a/actions/infinoted b/actions/infinoted new file mode 100755 index 000000000..9976f0789 --- /dev/null +++ b/actions/infinoted @@ -0,0 +1,179 @@ +#!/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 infinoted. +""" + +import argparse +import grp +import os +import pwd +import shutil +import subprocess + +from plinth import action_utils + + +DATA_DIR = '/var/lib/infinoted' +KEY_DIR = '/etc/infinoted' + +CONF_PATH = '/etc/xdg/infinoted.conf' +CONF = ''' +[infinoted] + +# Possible values : no-tls, allow-tls, require-tls +security-policy=require-tls + +# Absolute path of the certificate file. +certificate-file=/etc/infinoted/infinoted-cert.pem + +# Absolute path of the private key file. +key-file=/etc/infinoted/infinoted-key.pem + +# Setting this to 0 disables autosave. +autosave-interval=60 + +# Specify a path to use a root certificate instead of a certificate-key pair. +#certificate-chain= + +#password= + +# If you want to regularly synchronize the saved documents. +#sync-directory + +#sync-interval= +''' + +DEFAULT_PATH = '/etc/default/infinoted' +DEFAULT = ''' +# defaults file for infinoted + +# Should infinoted be started by the init script? (true/false) +INFINOTED_ENABLED=true + +# The configuration file to be used. +INFINOTED_CONFIG=/etc/xdg/infinoted.conf + +# The session autosave file to be updated periodically and loaded +# upon startup. +INFINOTED_SESSION_FILE=/var/lib/infinoted +''' + +SYSTEMD_SERVICE_PATH = '/etc/systemd/system/infinoted.service' +SYSTEMD_SERVICE = ''' +# +# This file is managed and overwritten by Plinth. If you wish to edit +# it, disable infinoted in Plinth, remove this file and manage it manually. +# + +[Unit] +Description=collaborative text editor service +Documentation=man:infinoted(1) +After=network.target + +[Service] +User=infinoted +EnvironmentFile=-/etc/default/infinoted +ExecStart=/usr/bin/infinoted ${OPTIONS} + +[Install] +WantedBy=multi-user.target +''' + + +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('setup', help='Configure infinoted after install') + + return parser.parse_args() + + +def subcommand_setup(_): + """Configure infinoted after install.""" + + if not os.path.isfile(CONF_PATH): + with open(CONF_PATH, 'w') as file_handle: + file_handle.write(CONF) + + if not os.path.isfile(DEFAULT_PATH): + with open(DEFAULT_PATH, 'w') as file_handle: + file_handle.write(DEFAULT) + + if not os.path.isfile(SYSTEMD_SERVICE_PATH): + with open(SYSTEMD_SERVICE_PATH, 'w') as file_handle: + file_handle.write(SYSTEMD_SERVICE) + subprocess.check_call(['systemctl', 'daemon-reload']) + + # Create infinoted group if needed. + try: + grp.getgrnam('infinoted') + except KeyError: + subprocess.run(['addgroup', '--system', 'infinoted'], check=True) + + # Create infinoted user is needed. + try: + pwd.getpwnam('infinoted') + except KeyError: + subprocess.run(['adduser', '--system', '--ingroup', 'infinoted', + '--home', DATA_DIR, + '--gecos', '"Infinoted collaborative editing server"', + 'infinoted'], check=True) + + if not os.path.exists(DATA_DIR): + os.makedirs(DATA_DIR, mode=0o750) + shutil.chown(DATA_DIR, user='infinoted', group='infinoted') + + if not os.path.exists(KEY_DIR): + os.makedirs(KEY_DIR, mode=0o750) + shutil.chown(KEY_DIR, user='infinoted', group='infinoted') + + if not os.path.exists(KEY_DIR + '/infinoted-cert.pem'): + old_umask = os.umask(0o027) + try: + # infinoted doesn't have a "create key and exit" mode. Run as + # daemon so we can stop after. + subprocess.run(['infinoted', '--create-key', + '--create-certificate', '--daemonize'], check=True) + subprocess.run(['infinoted', '--kill-daemon'], check=True) + finally: + os.umask(old_umask) + + shutil.chown(KEY_DIR + '/infinoted-cert.pem', + user='infinoted', group='infinoted') + shutil.chown(KEY_DIR + '/infinoted-key.pem', + user='infinoted', group='infinoted') + + action_utils.service_enable('infinoted') + + +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/plinth/modules-enabled/infinoted b/data/etc/plinth/modules-enabled/infinoted new file mode 100644 index 000000000..0f25f2eb0 --- /dev/null +++ b/data/etc/plinth/modules-enabled/infinoted @@ -0,0 +1 @@ +plinth.modules.infinoted diff --git a/data/usr/lib/firewalld/services/infinoted-plinth.xml b/data/usr/lib/firewalld/services/infinoted-plinth.xml new file mode 100644 index 000000000..0a9ed778c --- /dev/null +++ b/data/usr/lib/firewalld/services/infinoted-plinth.xml @@ -0,0 +1,6 @@ + + + infinoted + infinoted is a dedicated server which allows clients to edit plain text documents and source files collaboratively over a network. + + diff --git a/plinth/modules/infinoted/__init__.py b/plinth/modules/infinoted/__init__.py new file mode 100644 index 000000000..f46de06b2 --- /dev/null +++ b/plinth/modules/infinoted/__init__.py @@ -0,0 +1,107 @@ +# +# 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 for infinoted. +""" + +from django.utils.translation import ugettext_lazy as _ + +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.utils import format_lazy +from plinth.views import ServiceView + +version = 1 + +depends = ['apps'] + +service = None + +managed_services = ['infinoted'] + +managed_packages = ['infinoted'] + +title = _('Gobby Server (infinoted)') + +description = [ + _('infinoted is a server for Gobby, a collaborative text editor.'), + + format_lazy( + _('To use it, download Gobby ' + 'and install it. Then start Gobby and select "Connect to Server" ' + 'and enter your {box_name}\'s domain name.'), + box_name=_(cfg.box_name)), +] + + +def init(): + """Initialize the infinoted module.""" + menu = cfg.main_menu.get('apps:index') + menu.add_urlname(title, 'glyphicon-pencil', 'infinoted:index') + + global service + service = service_module.Service( + managed_services[0], title, ports=['infinoted-plinth'], + is_external=True, enable=enable, disable=disable) + + if service.is_enabled(): + add_shortcut() + + +class InfinotedServiceView(ServiceView): + service_id = managed_services[0] + diagnostics_module_name = "infinoted" + description = description + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(managed_packages) + helper.call('post', actions.superuser_run, 'infinoted', ['setup']) + helper.call('post', service.notify_enabled, None, True) + helper.call('post', add_shortcut) + + +def add_shortcut(): + frontpage.add_shortcut('infinoted', title, None, 'glyphicon-pencil', + description) + + +def enable(): + """Enable the module.""" + actions.superuser_run('service', ['enable', managed_services[0]]) + add_shortcut() + + +def disable(): + """Disable the module.""" + actions.superuser_run('service', ['disable', managed_services[0]]) + frontpage.remove_shortcut('infinoted') + + +def diagnose(): + """Run diagnostics and return the results.""" + results = [] + + results.append(action_utils.diagnose_port_listening(6523, 'tcp4')) + results.append(action_utils.diagnose_port_listening(6523, 'tcp6')) + + return results diff --git a/plinth/modules/infinoted/tests/__init__.py b/plinth/modules/infinoted/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plinth/modules/infinoted/urls.py b/plinth/modules/infinoted/urls.py new file mode 100644 index 000000000..ac903e564 --- /dev/null +++ b/plinth/modules/infinoted/urls.py @@ -0,0 +1,29 @@ +# +# 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 infinoted module. +""" + +from django.conf.urls import url + +from plinth.modules.infinoted import InfinotedServiceView + + +urlpatterns = [ + url(r'^apps/infinoted/$', InfinotedServiceView.as_view(), name='index'), +]