diff --git a/actions/mumble b/actions/mumble new file mode 100755 index 000000000..588132985 --- /dev/null +++ b/actions/mumble @@ -0,0 +1,125 @@ +#!/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 Mumble server +""" + +import argparse +import subprocess + + +SERVICE_CONFIG = '/etc/default/mumble-server' + + +def parse_arguments(): + """Return parsed command line arguments as dictionary.""" + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') + + # Get whether service is enabled + subparsers.add_parser('get-enabled', + help='Get whether Mumble service is enabled') + + # Enable service + subparsers.add_parser('enable', help='Enable Mumble service') + + # Disable service + subparsers.add_parser('disable', help='Disable Mumble service') + + # Get whether daemon is running + subparsers.add_parser('is-running', + help='Get whether Mumble daemon is running') + + return parser.parse_args() + + +def subcommand_get_enabled(_): + """Get whether service is enabled.""" + try: + with open(SERVICE_CONFIG, 'r') as file: + for line in file: + if line.startswith('MURMUR_DAEMON_START'): + value = line.split('=')[1].strip() + print('yes' if int(value) else 'no') + return + except FileNotFoundError: + pass + + print('no') + + +def subcommand_enable(_): + """Start service.""" + set_service_enable(enable=True) + subprocess.call(['service', 'mumble-server', 'start']) + + +def subcommand_disable(_): + """Stop service.""" + subprocess.call(['service', 'mumble-server', 'stop']) + set_service_enable(enable=False) + + +def set_service_enable(enable): + """Enable/disable daemon; enable: boolean.""" + newline = 'MURMUR_DAEMON_START=1\n' if enable \ + else 'MURMUR_DAEMON_START=0\n' + + with open(SERVICE_CONFIG, 'r') as file: + lines = file.readlines() + for index, line in enumerate(lines): + if line.startswith('MURMUR_DAEMON_START'): + lines[index] = newline + break + + with open(SERVICE_CONFIG, 'w') as file: + file.writelines(lines) + + +def subcommand_is_running(_): + """Get whether server is running.""" + try: + output = subprocess.check_output(['service', 'mumble-server', + 'status']) + except subprocess.CalledProcessError: + # If daemon is not running we get a status code != 0 and a + # CalledProcessError + print('no') + else: + running = False + for line in output.decode().split('\n'): + if 'Active' in line and 'running' in line: + running = True + break + + print('yes' if running else 'no') + + +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/mumble b/data/etc/plinth/modules-enabled/mumble new file mode 100644 index 000000000..ca0cbcda0 --- /dev/null +++ b/data/etc/plinth/modules-enabled/mumble @@ -0,0 +1 @@ +plinth.modules.mumble diff --git a/data/usr/lib/firewalld/services/mumble-plinth.xml b/data/usr/lib/firewalld/services/mumble-plinth.xml new file mode 100644 index 000000000..e6a6b5af6 --- /dev/null +++ b/data/usr/lib/firewalld/services/mumble-plinth.xml @@ -0,0 +1,7 @@ + + + Mumble Voice Chat Server + Mumble is an open source, low-latency, encrypted, high quality voice chat software primarily intended for use while gaming. Mumble uses a client-server architecture which allows users to talk to each other via the same server. Enable this if you are running a Mumble server and if you wish to connect external clients such as Mumble desktop client and Plumble Android app to your Mumble server. + + + diff --git a/plinth/modules/mumble/__init__.py b/plinth/modules/mumble/__init__.py new file mode 100644 index 000000000..44735f092 --- /dev/null +++ b/plinth/modules/mumble/__init__.py @@ -0,0 +1,46 @@ +# +# 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 Mumble server +""" + +from gettext import gettext as _ + +from plinth import actions +from plinth import cfg +from plinth import service as service_module + + +depends = ['plinth.modules.apps'] + +service = None + + +def init(): + """Intialize the Mumble module.""" + menu = cfg.main_menu.get('apps:index') + menu.add_urlname(_('Voice Chat (Mumble)'), 'glyphicon-headphones', + 'mumble:index', 50) + + output = actions.run('mumble', ['get-enabled']) + enabled = (output.strip() == 'yes') + + global service + service = service_module.Service( + 'mumble-plinth', _('Mumble Voice Chat Server'), + is_external=True, enabled=enabled) diff --git a/plinth/modules/mumble/forms.py b/plinth/modules/mumble/forms.py new file mode 100644 index 000000000..db51fc097 --- /dev/null +++ b/plinth/modules/mumble/forms.py @@ -0,0 +1,30 @@ +# +# 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 . +# + +""" +Forms for configuring Mumble +""" + +from django import forms +from gettext import gettext as _ + + +class MumbleForm(forms.Form): + """Mumble configuration form.""" + enabled = forms.BooleanField( + label=_('Enable Mumble daemon'), + required=False) diff --git a/plinth/modules/mumble/templates/mumble.html b/plinth/modules/mumble/templates/mumble.html new file mode 100644 index 000000000..a36f0e26d --- /dev/null +++ b/plinth/modules/mumble/templates/mumble.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} +{% comment %} +# +# 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 . +# +{% endcomment %} + +{% load bootstrap %} + +{% block content %} + +

Voice Chat (Mumble)

+ +

Mumble is an open source, low-latency, encrypted, high quality voice chat + software.

+ +

You can connect to your Mumble server on the regular Mumble port 64738. + Clients to connect to Mumble from your + desktop and Android devices are available.

+ + +

Status

+ +

+ {% if status.is_running %} + Mumble server is running + {% else %} + Mumble server is not running + {% endif %} +

+ +

Configuration

+ +
+ {% csrf_token %} + + {{ form|bootstrap }} + + +
+ +{% endblock %} diff --git a/plinth/modules/mumble/urls.py b/plinth/modules/mumble/urls.py new file mode 100644 index 000000000..1ae30062c --- /dev/null +++ b/plinth/modules/mumble/urls.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 . +# + +""" +URLs for the Mumble module +""" + +from django.conf.urls import patterns, url + + +urlpatterns = patterns( + 'plinth.modules.mumble.views', + url(r'^apps/mumble/$', 'index', name='index'), + ) diff --git a/plinth/modules/mumble/views.py b/plinth/modules/mumble/views.py new file mode 100644 index 000000000..767b9cd4e --- /dev/null +++ b/plinth/modules/mumble/views.py @@ -0,0 +1,87 @@ +# +# 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 configuring Mumble Server +""" + +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.template.response import TemplateResponse +from gettext import gettext as _ +import logging + +from .forms import MumbleForm +from plinth import actions +from plinth import package +from plinth.modules import mumble + +logger = logging.getLogger(__name__) + + +@login_required +@package.required('mumble-server') +def index(request): + """Serve configuration page.""" + status = get_status() + + form = None + + if request.method == 'POST': + form = MumbleForm(request.POST, prefix='mumble') + # pylint: disable=E1101 + if form.is_valid(): + _apply_changes(request, status, form.cleaned_data) + status = get_status() + form = MumbleForm(initial=status, prefix='mumble') + else: + form = MumbleForm(initial=status, prefix='mumble') + + return TemplateResponse(request, 'mumble.html', + {'title': _('Voice Chat (Mumble)'), + 'status': status, + 'form': form}) + + +def get_status(): + """Get the current settings from server.""" + output = actions.run('mumble', ['get-enabled']) + enabled = (output.strip() == 'yes') + + output = actions.superuser_run('mumble', ['is-running']) + is_running = (output.strip() == 'yes') + + status = {'enabled': enabled, + 'is_running': is_running} + + return status + + +def _apply_changes(request, old_status, new_status): + """Apply the changes.""" + modified = False + + if old_status['enabled'] != new_status['enabled']: + sub_command = 'enable' if new_status['enabled'] else 'disable' + actions.superuser_run('mumble', [sub_command]) + mumble.service.notify_enabled(None, new_status['enabled']) + modified = True + + if modified: + messages.success(request, _('Configuration updated')) + else: + messages.info(request, _('Setting unchanged')) diff --git a/setup.py b/setup.py index 1e0d85247..6556c55f6 100755 --- a/setup.py +++ b/setup.py @@ -122,6 +122,8 @@ setuptools.setup( package_data={'plinth': ['templates/*', 'modules/*/templates/*']}, data_files=[('/etc/init.d', ['data/etc/init.d/plinth']), + ('/usr/lib/firewalld/services/', + glob.glob('data/usr/lib/firewalld/services/*.xml')), ('/usr/lib/freedombox/setup.d/', ['data/usr/lib/freedombox/setup.d/86_plinth']), ('/usr/lib/freedombox/first-run.d',