Merge remote-tracking branch 'sunil/polish'

This commit is contained in:
James Valleroy 2015-07-30 18:53:23 -04:00
commit 565703270e
24 changed files with 155 additions and 478 deletions

View File

@ -1,85 +0,0 @@
#!/bin/sh
#
# 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 <http://www.gnu.org/licenses/>.
#
# Usage:
# module-manager list-available
# module-manager list-enabled <python_root>
# module-manager enable <python_root> <module_name>
# module-manager disable <python_root> <module_name>
# list of modules that may be enabled/disabled
modules="owncloud"
case "$1" in
"list-available")
# TODO: Replace this with something like "aptitude search -F %p plinth-"
echo "$modules"
;;
"list-enabled")
# TODO: Replace this with something like 'aptitude search -F %p | grep "plinth-"'
for module in "$modules"
do
if [ -e "$2"/modules/enabled/"$module" ] ; then
echo "$module"
fi
done
;;
"enable")
# TODO: Replace this with "aptitude install plinth-<module>"
for module in "$modules"
do
if [ "$3" = "$module" ] ; then
if [ ! -e "$2"/modules/enabled/"$3" ] ; then
touch "$2"/modules/enabled/"$3"
RETVAL=$?
if [ $RETVAL -eq 0 ] ; then
echo "enabled" "$3"
else
echo "failed to enable" "$3"
fi
exit $RETVAL
fi
fi
done
echo "failed to enable invalid module" "$3"
exit 1
;;
"disable")
# TODO: Replace this with "aptitude purge plinth-<module>"
for module in "$modules"
do
if [ "$3" = "$module" ] ; then
if [ -e "$2"/modules/enabled/"$3" ] ; then
rm -f "$2"/modules/enabled/"$3"
RETVAL=$?
if [ $RETVAL -eq 0 ] ; then
echo "disabled" "$3"
else
echo "failed to disable" "$3"
fi
exit $RETVAL
fi
fi
done
echo "failed to disable invalid module" "$3"
exit 1
;;
esac

View File

@ -25,7 +25,6 @@ import argparse
import augeas
import json
import os
import subprocess
from plinth import action_utils
from plinth.modules.pagekite import utils

View File

@ -22,11 +22,16 @@ Configuration helper for the Tor service
"""
import argparse
import codecs
import os
import re
import socket
from plinth import action_utils
TOR_CONFIG = '/etc/tor/torrc'
TOR_STATE_FILE = '/var/lib/tor/state'
TOR_AUTH_COOKIE = '/var/run/tor/control.authcookie'
def parse_arguments():
@ -34,20 +39,12 @@ def parse_arguments():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
# Enable and start the service
subparsers.add_parser('enable', help='Enable and start Tor service')
# Disable and stop the service
subparsers.add_parser('disable', help='Disable and stop Tor service')
# Get currently configured Tor hidden service information
subparsers.add_parser('get-hs', help='Get hidden service')
# Enable Tor hidden service
subparsers.add_parser('enable-hs', help='Enable hidden service')
# Disable Tor hidden service
subparsers.add_parser('disable-hs', help='Disable hidden service')
subparsers.add_parser('get-ports', help='Get list of Tor ports')
return parser.parse_args()
@ -146,6 +143,42 @@ def get_hidden_service():
return hs_hostname + ' ' + ','.join(hs_ports)
def subcommand_get_ports(_):
"""Return a list of running Tor ports."""
try:
print('orport', _get_orport())
except Exception:
pass
with open(TOR_STATE_FILE, 'r') as state_file:
for line in state_file:
matches = re.match(r'^\s*TransportProxy\s+(\S*)\s+\S+:(\d+)\s*$',
line)
if matches:
print('{0} {1}'.format(matches.group(1), matches.group(2)))
def _get_orport():
"""Return the ORPort by querying running instance."""
cookie = open(TOR_AUTH_COOKIE, 'rb').read()
cookie = codecs.encode(cookie, 'hex').decode()
commands = '''AUTHENTICATE {cookie}
GETINFO net/listeners/or
QUIT
'''.format(cookie=cookie)
tor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tor_socket.connect(('localhost', 9051))
tor_socket.send(commands.encode())
response = tor_socket.recv(1024)
tor_socket.close()
line = response.split(b'\r\n')[1].decode()
matches = re.match(r'.*="[^:]+:(\d+)"', line)
return matches.group(1)
def main():
"""Parse arguments and perform all duties"""
arguments = parse_arguments()

View File

@ -1,28 +0,0 @@
#!/bin/sh
#
# 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 <http://www.gnu.org/licenses/>.
#
# Action to get ports used by Tor.
echo "ORPort" `tor-get-orport`
transports="obfs3 scramblesuit"
for transport in $transports
do
echo $transport `grep $transport /var/lib/tor/state | awk -F'[: ]*' '{print $4}'`
done

View File

@ -1 +0,0 @@
plinth.modules.packages

View File

@ -36,7 +36,7 @@ def init():
"""Initialize the Deluge module."""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname(_('BitTorrent (Deluge)'), 'glyphicon-magnet',
'deluge:index', 60)
'deluge:index', 200)
global service
service = service_module.Service(

View File

@ -43,7 +43,7 @@ def init():
"""Initialize the dynamicdns module"""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname('Dynamic DNS', 'glyphicon-refresh',
'dynamicdns:index', 40)
'dynamicdns:index', 500)
@package.required(['ez-ipupdate'])

View File

@ -38,8 +38,15 @@ runs on must be cheap. The software it runs on must be easy to
install and administrate by anybody. It must be easy to
transition from existing services.</p>
<p>There are a number of projects working to realize a future of
distributed services; we aim to bring them all together in a
convenient package.</p>
<p>For more information about the FreedomBox project, see the
<a href="https://wiki.debian.org/FreedomBox">Debian Wiki</a>.</p>
<p><a class="btn btn-primary btn-lg"
href="http://wiki.debian.org/FreedomBox" target="_blank">Learn more
href="https://wiki.debian.org/FreedomBox" target="_blank">Learn more
&raquo;</a></p>
<p style='margin-top:30px'>
@ -47,20 +54,3 @@ href="http://wiki.debian.org/FreedomBox" target="_blank">Learn more
</p>
{% endblock %}
{% block sidebar %}
<div class="sidebar">
<h3>Our Goal</h3>
<p>There are a number of projects working to realize a future of
distributed services; we aim to bring them all together in a
convenient package.</p>
<p>For more information about the FreedomBox project, see the
<a href="http://wiki.debian.org/FreedomBox">Debian Wiki</a>.</p>
</div>
{% endblock %}

View File

@ -36,7 +36,7 @@ def init():
"""Initialize the ikiwiki module."""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname(_('Wiki & Blog (Ikiwiki)'), 'glyphicon-edit',
'ikiwiki:index', 38)
'ikiwiki:index', 1100)
global service
service = service_module.Service(

View File

@ -36,7 +36,7 @@ def init():
"""Intialize the Mumble module."""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname(_('Voice Chat (Mumble)'), 'glyphicon-headphones',
'mumble:index', 50)
'mumble:index', 900)
global service
service = service_module.Service(

View File

@ -15,6 +15,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Plinth module for configuring ownCloud.
"""
from django import forms
from django.contrib import messages
from django.template.response import TemplateResponse
@ -37,7 +41,8 @@ class OwnCloudForm(forms.Form): # pylint: disable-msg=W0232
def init():
"""Initialize the ownCloud module"""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname('ownCloud', 'glyphicon-picture', 'owncloud:index', 35)
menu.add_urlname(_('File Hosting (ownCloud)'), 'glyphicon-picture',
'owncloud:index', 700)
status = get_status()

View File

@ -22,37 +22,27 @@
{% block content %}
<h2>File Hosting (ownCloud)</h2>
<p>ownCloud gives you universal access to your files through a web
interface or WebDAV. It also provides a platform to easily view &
sync your contacts, calendars and bookmarks across all your devices
and enables basic editing right on the web. Installation has minimal
server requirements, doesn't need special permissions and is
quick. ownCloud is extendable via a simple but powerful API for
applications and plugins.</p>
<p>When enabled, the ownCloud installation will be available
from <a href="/owncloud">/owncloud</a> path on the web server.
Visit this URL to set up the initial administration account for
ownCloud.</p>
<form class="form" method="post">
{% csrf_token %}
<h2>ownCloud</h2>
<p>When enabled, the ownCloud installation will be available
from <a href="/owncloud">/owncloud</a> path on the web server.
Visit this URL to set up the initial administration account for
ownCloud.</p>
{{ form|bootstrap }}
<input type="submit" class="btn btn-primary btn-md" value="Apply changes"/>
</form>
{% endblock %}
{% block sidebar %}
<div class="sidebar">
<h3>ownCloud</h3>
<p>ownCloud gives you universal access to your files through a web
interface or WebDAV. It also provides a platform to easily view
& sync your contacts, calendars and bookmarks across all your
devices and enables basic editing right on the web. Installation
has minimal server requirements, doesn't need special
permissions and is quick. ownCloud is extendable via a simple
but powerful API for applications and plugins.</p>
</div>
{% endblock %}

View File

@ -1,27 +0,0 @@
#
# 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 <http://www.gnu.org/licenses/>.
#
"""
Plinth module to manage packages
"""
from . import packages
from .packages import init
__all__ = ['packages', 'init']
depends = ['plinth.modules.system']

View File

@ -1,130 +0,0 @@
#
# 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 <http://www.gnu.org/licenses/>.
#
from django import forms
from django.contrib import messages
from django.template.response import TemplateResponse
from gettext import gettext as _
import os
from plinth import actions
from plinth import cfg
def get_modules_available():
"""Return list of all modules"""
output = actions.run('module-manager', ['list-available'])
return output.split()
def get_modules_enabled():
"""Return list of all modules"""
root = os.path.join(os.path.dirname(__file__), '..', '..')
output = actions.run('module-manager',
['list-enabled', root])
return output.split()
class PackagesForm(forms.Form):
"""Packages form"""
def __init__(self, *args, **kwargs):
# pylint: disable-msg=E1002, E1101
super(forms.Form, self).__init__(*args, **kwargs)
modules_available = get_modules_available()
for module in modules_available:
label = _('Enable {module}').format(module=module)
self.fields[module + '_enabled'] = forms.BooleanField(
label=label, required=False)
def init():
"""Initialize the Packages module"""
menu = cfg.main_menu.get('system:index')
menu.add_urlname('Package Manager', 'glyphicon-gift', 'packages:index', 20)
def index(request):
"""Serve the form"""
status = get_status()
form = None
if request.method == 'POST':
form = PackagesForm(request.POST, prefix='packages')
# pylint: disable-msg=E1101
if form.is_valid():
_apply_changes(request, status, form.cleaned_data)
status = get_status()
form = PackagesForm(initial=status, prefix='packages')
else:
form = PackagesForm(initial=status, prefix='packages')
return TemplateResponse(request, 'packages.html',
{'title': _('Add/Remove Plugins'),
'form': form})
def get_status():
"""Return the current status"""
modules_available = get_modules_available()
modules_enabled = get_modules_enabled()
return {module + '_enabled': module in modules_enabled
for module in modules_available}
def _apply_changes(request, old_status, new_status):
"""Apply form changes"""
root = os.path.join(os.path.dirname(__file__), '..', '..')
for field, enabled in new_status.items():
if not field.endswith('_enabled'):
continue
if old_status[field] == new_status[field]:
continue
module = field.split('_enabled')[0]
if enabled:
try:
actions.superuser_run('module-manager',
['enable', root, module])
except Exception:
# TODO: need to get plinth to load the module we just
# enabled
messages.error(
request, _('Error enabling module - {module}').format(
module=module))
else:
messages.success(
request, _('Module enabled - {module}').format(
module=module))
else:
try:
actions.superuser_run('module-manager',
['disable', root, module])
except Exception:
# TODO: need a smoother way for plinth to unload the
# module
messages.error(
request, _('Error disabling module - {module}').format(
module=module))
else:
messages.success(
request, _('Module disabled - {module}').format(
module=module))

View File

@ -1,63 +0,0 @@
{% 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 <http://www.gnu.org/licenses/>.
#
{% endcomment %}
{% load bootstrap %}
{% block content %}
<h2>{{ title }}</h2>
<p><code>aptitude purge modules</code></p>
<p><code>aptitude install modules</code></p>
<p>The modules should depend on the appropriate Debian packages.</p>
<h2>Manage Plugins</h2>
<form class="form" method="post">
{% csrf_token %}
{{ form|bootstrap }}
<p>Enabling a plugin will cause a corresponding page to appear in
Plinth.</p>
<input type="submit" class="btn btn-primary" value="Update setup"/>
</form>
{% endblock %}
{% block sidebar %}
<div class="sidebar">
<h3>Help</h3>
<p>On this page, you can add or remove {{ cfg.product_name }}
plugins to your {{ cfg.box_name }}.</p>
<p>Plugins are just Debian packages, so Debian's usual package
management features should make this job fairly easy.</p>
</div>
{% endblock %}

View File

@ -1,28 +0,0 @@
#
# 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 <http://www.gnu.org/licenses/>.
#
"""
URLs for the Packages module
"""
from django.conf.urls import patterns, url
urlpatterns = patterns( # pylint: disable-msg=C0103
'plinth.modules.packages.packages',
url(r'^sys/packages/$', 'index', name='index')
)

View File

@ -31,4 +31,4 @@ def init():
"""Intialize the PageKite module"""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname(_('Public Visibility (PageKite)'),
'glyphicon-flag', 'pagekite:index', 50)
'glyphicon-flag', 'pagekite:index', 800)

View File

@ -36,7 +36,7 @@ def init():
"""Intialize the module."""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname(_('Web Proxy (Privoxy)'), 'glyphicon-cloud-upload',
'privoxy:index', 50)
'privoxy:index', 1000)
global service
service = service_module.Service(

View File

@ -34,7 +34,7 @@ def init():
"""Intialize the module."""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname(_('Email Client (Roundcube)'), 'glyphicon-envelope',
'roundcube:index', 50)
'roundcube:index', 600)
def is_enabled():
"""Return whether the module is enabled."""

View File

@ -22,35 +22,54 @@
{% block content %}
<h2>Tor</h2>
<h2>Anonymity Network (Tor)</h2>
<p>Tor is an anonymous communication system. You can learn more about
it from the <a href="https://www.torproject.org/">Tor Project</a>
website. For best protection when web surfing, the Tor Project
recommends that you use
the <a href="https://www.torproject.org/download/download-easy.html.en">
Tor Browser</a>.</p>
<h3>Status</h3>
<p>
{% if is_running %}
{% if status.is_running %}
<div class='running-status active'></div> Tor is running
{% else %}
<div class='running-status inactive'></div> Tor is not running
{% endif %}
</p>
<h3>Hidden Service</h3>
{% if status.hs_enabled %}
<div class="row">
<div class="col-sm-3">
<table class="table table-bordered table-condensed table-striped">
<thead>
<tr>
<th>Hidden Service</th>
<th>Port</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ status.hs_hostname }}</td>
<td>{{ status.hs_ports }}</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endif %}
<p>A hidden service will allow your {{ cfg.box_name }} to provide selected
services (such as OwnCloud or Chat) without revealing its location.
Here is the current configuration:</p>
<h3>Configuration</h3>
<ul>
<li>Hostname: {{ tor_hs_hostname }}</li>
<li>Ports: {{ tor_hs_ports }}</li>
</ul>
<form class="form form-inline" method="post">
<form class="form" method="post">
{% csrf_token %}
{{ form|bootstrap }}
<input type="submit" class="btn btn-primary btn-sm" value="Update setup"/>
<input type="submit" class="btn btn-primary btn-md" value="Update setup"/>
</form>
<h3>Bridge</h3>
@ -62,13 +81,21 @@ port-forwarded, if necessary:</p>
<div class="row">
<div class="col-sm-3">
<table class="table table-bordered table-condensed">
{% for name, port in tor_ports.items %}
<table class="table table-bordered table-condensed table-striped">
<thead>
<tr>
<th>Service</th>
<th>Port</th>
</tr>
</thead>
<tbody>
{% for name, port in status.ports.items %}
<tr>
<td>{{ name }}</td>
<td>{{ port }}</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
</div>
@ -79,20 +106,3 @@ port-forwarded, if necessary:</p>
9050.</p>
{% endblock %}
{% block sidebar %}
<div class="sidebar">
<h3>Tor</h3>
<p>Tor is an anonymous communication system. You can learn more about
it from the <a href="https://www.torproject.org/">Tor Project</a>
website. For best protection when web surfing, the Tor Project
recommends that you use the
<a href="https://www.torproject.org/download/download-easy.html.en">
Tor Browser</a>.</p>
</div>
{% endblock %}

View File

@ -32,15 +32,22 @@ from plinth import package
class TorForm(forms.Form): # pylint: disable=W0232
"""Tor configuration form"""
hs_enabled = forms.BooleanField(
label=_('Enable Hidden Service'),
enabled = forms.BooleanField(
label=_('Enable Tor'),
required=False)
hs_enabled = forms.BooleanField(
label=_('Enable Tor Hidden Service'),
required=False,
help_text=_('A hidden service will allow FreedomBox to provide '
'selected services (such as ownCloud or Chat) without '
'revealing its location.'))
def init():
"""Initialize the Tor module"""
"""Initialize the Tor module."""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname('Tor', 'glyphicon-eye-close', 'tor:index', 30)
menu.add_urlname(_('Anonymity Network (Tor)'), 'glyphicon-eye-close',
'tor:index', 100)
@package.required(['tor'])
@ -62,17 +69,13 @@ def index(request):
return TemplateResponse(request, 'tor.html',
{'title': _('Tor Control Panel'),
'is_running': status['is_running'],
'tor_ports': status['ports'],
'tor_hs_enabled': status['hs_enabled'],
'tor_hs_hostname': status['hs_hostname'],
'tor_hs_ports': status['hs_ports'],
'status': status,
'form': form})
def get_status():
"""Return the current status"""
output = actions.superuser_run('tor-get-ports')
output = actions.superuser_run('tor', ['get-ports'])
port_info = output.split('\n')
ports = {}
for line in port_info:
@ -98,7 +101,8 @@ def get_status():
hs_hostname = hs_info[0]
hs_ports = hs_info[1]
return {'is_running': action_utils.service_is_running('tor'),
return {'enabled': action_utils.service_is_enabled('tor'),
'is_running': action_utils.service_is_running('tor'),
'ports': ports,
'hs_enabled': hs_enabled,
'hs_hostname': hs_hostname,
@ -106,16 +110,24 @@ def get_status():
def _apply_changes(request, old_status, new_status):
"""Apply the changes"""
if old_status['hs_enabled'] == new_status['hs_enabled']:
"""Apply the changes."""
if old_status['enabled'] == new_status['enabled'] and \
old_status['hs_enabled'] == new_status['hs_enabled']:
messages.info(request, _('Setting unchanged'))
return
if new_status['hs_enabled']:
messages.success(request, _('Tor hidden service enabled'))
command = 'enable-hs'
else:
messages.success(request, _('Tor hidden service disabled'))
command = 'disable-hs'
if old_status['enabled'] != new_status['enabled']:
if new_status['enabled']:
messages.success(request, _('Tor enabled'))
actions.superuser_run('tor', ['enable'])
else:
messages.success(request, _('Tor disabled'))
actions.superuser_run('tor', ['disable'])
actions.superuser_run('tor', [command])
if old_status['hs_enabled'] != new_status['hs_enabled']:
if new_status['hs_enabled']:
messages.success(request, _('Tor hidden service enabled'))
actions.superuser_run('tor', ['enable-hs'])
else:
messages.success(request, _('Tor hidden service disabled'))
actions.superuser_run('tor', ['disable-hs'])

View File

@ -36,7 +36,7 @@ def init():
"""Intialize the Transmission module."""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname(_('BitTorrent (Transmission)'), 'glyphicon-save',
'transmission:index', 100)
'transmission:index', 300)
global service
service = service_module.Service(

View File

@ -38,7 +38,7 @@ def init():
"""Initialize the XMPP module"""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname(_('Chat Server (XMPP)'), 'glyphicon-comment',
'xmpp:index', 40)
'xmpp:index', 400)
global service
service = service_module.Service(