diff --git a/actions/mumble b/actions/mumble
new file mode 100755
index 000000000..6f0993a0c
--- /dev/null
+++ b/actions/mumble
@@ -0,0 +1,79 @@
+#!/usr/bin/python3
+
+#
+# This file is part of FreedomBox.
+#
+# 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 .
+#
+
+"""
+Configure Mumble server.
+"""
+
+import argparse
+import sys
+from subprocess import Popen, PIPE
+
+
+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('create-password',
+ help='Setup mumble superuser password')
+
+ return parser.parse_args()
+
+
+def read_from_stdin():
+ """Read password from stdin"""
+
+ return (''.join(sys.stdin)).strip()
+
+
+def subcommand_create_password(arguments):
+ """Save superuser password with murmurd command"""
+
+ password = read_from_stdin()
+
+ cmd = ['murmurd', '-ini', '/etc/mumble-server.ini', '-readsupw']
+ proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=False)
+
+ # The exit code of the command above seems to be 1 when successful!
+ # checking if the 'phrase' is included in the error message which
+ # shows that the password is successfully set.
+ out, err = proc.communicate(input=password.encode())
+ out, err = out.decode(), err.decode()
+
+ phrase = "Superuser password set on server"
+ if phrase not in err:
+ print(
+ "Error occured while saving password: %s" % err
+ )
+ sys.exit(1)
+
+
+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/plinth/modules/mumble/__init__.py b/plinth/modules/mumble/__init__.py
index 384c07236..565e6a478 100644
--- a/plinth/modules/mumble/__init__.py
+++ b/plinth/modules/mumble/__init__.py
@@ -26,7 +26,6 @@ from plinth import app as app_module
from plinth import frontpage, menu
from plinth.daemon import Daemon
from plinth.modules.firewall.components import Firewall
-from plinth.views import AppView
from .manifest import backup, clients # noqa, pylint: disable=unused-import
@@ -100,17 +99,6 @@ def init():
app.set_enabled(True)
-class MumbleAppView(AppView):
- app_id = 'mumble'
- diagnostics_module_name = 'mumble'
- name = name
- description = description
- clients = clients
- manual_page = manual_page
- port_forwarding_info = port_forwarding_info
- icon_filename = icon_filename
-
-
def setup(helper, old_version=None):
"""Install and configure the module."""
helper.install(managed_packages)
diff --git a/plinth/modules/mumble/forms.py b/plinth/modules/mumble/forms.py
new file mode 100644
index 000000000..14f387b89
--- /dev/null
+++ b/plinth/modules/mumble/forms.py
@@ -0,0 +1,38 @@
+#
+# This file is part of FreedomBox.
+#
+# 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 .
+#
+"""
+Mumble server configuration form
+"""
+
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from plinth.forms import AppForm
+
+
+class MumbleForm(AppForm):
+ """Mumble server configuration"""
+ super_user_password = forms.CharField(
+ max_length=20,
+ label=_('Set SuperUser Password'),
+ widget=forms.PasswordInput,
+ help_text=_(
+ 'Optional. Leave this field blank to keep the current password. '
+ 'SuperUser password can be used to manage permissions in Mumble.'
+ ),
+ required=False,
+ )
diff --git a/plinth/modules/mumble/urls.py b/plinth/modules/mumble/urls.py
index 78580d0b7..50cbde182 100644
--- a/plinth/modules/mumble/urls.py
+++ b/plinth/modules/mumble/urls.py
@@ -20,7 +20,7 @@ URLs for the Mumble module
from django.conf.urls import url
-from plinth.modules.mumble import MumbleAppView
+from plinth.modules.mumble.views import MumbleAppView
urlpatterns = [
url(r'^apps/mumble/$', MumbleAppView.as_view(), name='index'),
diff --git a/plinth/modules/mumble/views.py b/plinth/modules/mumble/views.py
new file mode 100644
index 000000000..65714a674
--- /dev/null
+++ b/plinth/modules/mumble/views.py
@@ -0,0 +1,52 @@
+#
+# This file is part of FreedomBox.
+#
+# 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.contrib import messages
+from django.utils.translation import ugettext_lazy as _
+
+from plinth import actions
+from plinth.modules.mumble import (
+ name, description, clients, manual_page, port_forwarding_info,
+)
+from plinth.modules.mumble.forms import MumbleForm
+from plinth.views import AppView
+
+
+class MumbleAppView(AppView):
+ app_id = 'mumble'
+ diagnostics_module_name = 'mumble'
+ name = name
+ description = description
+ clients = clients
+ manual_page = manual_page
+ port_forwarding_info = port_forwarding_info
+ form_class = MumbleForm
+
+ def form_valid(self, form):
+ """Apply new superuser password if it exists"""
+ new_config = form.cleaned_data
+
+ password = new_config.get('super_user_password')
+ if password:
+ actions.run_as_user(
+ 'mumble', ['create-password'],
+ input=password.encode(),
+ become_user="mumble-server",
+ )
+ messages.success(self.request,
+ _('SuperUser password successfully updated.'))
+
+ return super().form_valid(form)