mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-09-19 04:59:01 +00:00
Merge remote-tracking branch 'upstream/master' into dynamicdns
This commit is contained in:
commit
6c214c915c
2
INSTALL
2
INSTALL
@ -7,7 +7,7 @@
|
|||||||
$ sudo apt-get install libjs-jquery libjs-modernizr \
|
$ sudo apt-get install libjs-jquery libjs-modernizr \
|
||||||
libjs-bootstrap make pandoc python3 python3-cherrypy3 \
|
libjs-bootstrap make pandoc python3 python3-cherrypy3 \
|
||||||
python3-coverage python3-django python3-bootstrapform \
|
python3-coverage python3-django python3-bootstrapform \
|
||||||
python3-setuptools
|
python3-gi python3-setuptools gir1.2-packagekitglib-1.0
|
||||||
|
|
||||||
2. Install Plinth:
|
2. Install Plinth:
|
||||||
|
|
||||||
|
|||||||
@ -9,19 +9,19 @@
|
|||||||
#
|
#
|
||||||
# This program is distributed in the hope that it will be useful,
|
# This program is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU Affero General Public License for more details.
|
# GNU Affero General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
# Action to backup ejabberd database before changing hostname.
|
domainname="$1"
|
||||||
|
hostname=$(hostname)
|
||||||
|
|
||||||
BACKUP=/tmp/ejabberd.dump
|
if grep -q 127.0.1.1 /etc/hosts ; then
|
||||||
ejabberdctl dump $BACKUP
|
sed -i "s/127.0.1.1.*/127.0.1.1 $hostname.$domainname $hostname/" /etc/hosts
|
||||||
ejabberdctl stop
|
else
|
||||||
|
sed -i "/127.0.0.1.*/a \
|
||||||
# Make sure there aren't files in the Mnesia spool dir
|
127.0.1.1 $hostname.$domainname $hostname" /etc/hosts
|
||||||
mkdir -p /var/lib/ejabberd/oldfiles
|
fi
|
||||||
mv /var/lib/ejabberd/*.* /var/lib/ejabberd/oldfiles/
|
|
||||||
@ -30,10 +30,6 @@ def parse_arguments():
|
|||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||||
|
|
||||||
# Get installed status
|
|
||||||
subparsers.add_parser('get-installed',
|
|
||||||
help='Get whether firewalld is installed')
|
|
||||||
|
|
||||||
# Get status
|
# Get status
|
||||||
subparsers.add_parser('get-status',
|
subparsers.add_parser('get-status',
|
||||||
help='Get whether firewalld is running')
|
help='Get whether firewalld is running')
|
||||||
@ -64,14 +60,6 @@ def parse_arguments():
|
|||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def subcommand_get_installed(_):
|
|
||||||
"""Print whether firewalld is installed"""
|
|
||||||
with open('/dev/null', 'w') as file_handle:
|
|
||||||
status = subprocess.call(['which', 'firewalld'], stdout=file_handle)
|
|
||||||
|
|
||||||
print('installed' if not status else 'not installed')
|
|
||||||
|
|
||||||
|
|
||||||
def subcommand_get_status(_):
|
def subcommand_get_status(_):
|
||||||
"""Print status of the firewalld service"""
|
"""Print status of the firewalld service"""
|
||||||
subprocess.call(['firewall-cmd', '--state'])
|
subprocess.call(['firewall-cmd', '--state'])
|
||||||
|
|||||||
@ -18,11 +18,15 @@
|
|||||||
|
|
||||||
hostname="$1"
|
hostname="$1"
|
||||||
|
|
||||||
echo "$hostname" > /etc/hostname
|
if [ -d /run/systemd/system ] ; then
|
||||||
if [ -x /etc/init.d/hostname.sh ] ; then
|
hostnamectl set-hostname --transient --static "$hostname"
|
||||||
service hostname.sh start
|
|
||||||
else
|
else
|
||||||
service hostname start
|
echo "$hostname" > /etc/hostname
|
||||||
|
if [ -x /etc/init.d/hostname.sh ] ; then
|
||||||
|
invoke-rc.d hostname.sh start
|
||||||
|
else
|
||||||
|
service hostname start
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
service avahi-daemon restart
|
service avahi-daemon restart
|
||||||
|
|||||||
@ -58,14 +58,6 @@ done
|
|||||||
|
|
||||||
if [ "$owncloud_enable" != "$owncloud_enable_cur" ] ; then
|
if [ "$owncloud_enable" != "$owncloud_enable_cur" ] ; then
|
||||||
if $owncloud_enable ; then
|
if $owncloud_enable ; then
|
||||||
# Select postgresql as the backend database for OwnCloud, and
|
|
||||||
# make sure its php support is enabled when owncloud is
|
|
||||||
# installed.
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends \
|
|
||||||
install -y postgresql php5-pgsql 2>&1 | logger -t owncloud-setup
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends \
|
|
||||||
install -y owncloud 2>&1 | logger -t owncloud-setup
|
|
||||||
|
|
||||||
# Keep existing configuration if it exist
|
# Keep existing configuration if it exist
|
||||||
if [ ! -e /etc/owncloud/config.php ] ; then
|
if [ ! -e /etc/owncloud/config.php ] ; then
|
||||||
# Set up postgresql database and user
|
# Set up postgresql database and user
|
||||||
|
|||||||
@ -48,10 +48,6 @@ def parse_arguments():
|
|||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||||
|
|
||||||
# Get installed status
|
|
||||||
subparsers.add_parser('get-installed',
|
|
||||||
help='Get whether PakeKite is installed')
|
|
||||||
|
|
||||||
# Start PageKite
|
# Start PageKite
|
||||||
subparsers.add_parser('start', help='Start PageKite service')
|
subparsers.add_parser('start', help='Start PageKite service')
|
||||||
|
|
||||||
@ -91,14 +87,6 @@ def parse_arguments():
|
|||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def subcommand_get_installed(_):
|
|
||||||
"""Print whether PageKite is installed"""
|
|
||||||
with open('/dev/null', 'w') as file_handle:
|
|
||||||
status = subprocess.call(['which', 'pagekite'], stdout=file_handle)
|
|
||||||
|
|
||||||
print('installed' if not status else 'not installed')
|
|
||||||
|
|
||||||
|
|
||||||
def subcommand_start(_):
|
def subcommand_start(_):
|
||||||
"""Start PageKite service"""
|
"""Start PageKite service"""
|
||||||
status = subprocess.call(['service', 'pagekite', 'start'])
|
status = subprocess.call(['service', 'pagekite', 'start'])
|
||||||
|
|||||||
27
actions/tor
27
actions/tor
@ -34,10 +34,6 @@ def parse_arguments():
|
|||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||||
|
|
||||||
# Get whether Tor is installed
|
|
||||||
subparsers.add_parser('get-installed',
|
|
||||||
help='Get whether Tor is installed')
|
|
||||||
|
|
||||||
# Get whether Tor is running
|
# Get whether Tor is running
|
||||||
subparsers.add_parser('is-running', help='Get whether Tor is running')
|
subparsers.add_parser('is-running', help='Get whether Tor is running')
|
||||||
|
|
||||||
@ -59,11 +55,6 @@ def parse_arguments():
|
|||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def subcommand_get_installed(_):
|
|
||||||
"""Get whether Tor is installed"""
|
|
||||||
print('installed' if get_installed() else 'not installed')
|
|
||||||
|
|
||||||
|
|
||||||
def subcommand_is_running(_):
|
def subcommand_is_running(_):
|
||||||
"""Get whether Tor is running"""
|
"""Get whether Tor is running"""
|
||||||
try:
|
try:
|
||||||
@ -101,7 +92,7 @@ def subcommand_get_hs(_):
|
|||||||
|
|
||||||
def subcommand_enable_hs(_):
|
def subcommand_enable_hs(_):
|
||||||
"""Enable Tor hidden service"""
|
"""Enable Tor hidden service"""
|
||||||
if not get_installed() or get_hidden_service():
|
if get_hidden_service():
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(TOR_CONFIG, 'r') as conffile:
|
with open(TOR_CONFIG, 'r') as conffile:
|
||||||
@ -121,7 +112,7 @@ def subcommand_enable_hs(_):
|
|||||||
|
|
||||||
def subcommand_disable_hs(_):
|
def subcommand_disable_hs(_):
|
||||||
"""Disable Tor hidden service"""
|
"""Disable Tor hidden service"""
|
||||||
if not get_installed() or not get_hidden_service():
|
if not get_hidden_service():
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(TOR_CONFIG, 'r') as conffile:
|
with open(TOR_CONFIG, 'r') as conffile:
|
||||||
@ -154,19 +145,8 @@ def subcommand_disable_hs(_):
|
|||||||
subprocess.call(['service', 'tor', 'restart'])
|
subprocess.call(['service', 'tor', 'restart'])
|
||||||
|
|
||||||
|
|
||||||
def get_installed():
|
|
||||||
"""Get whether Tor is installed"""
|
|
||||||
with open('/dev/null', 'w') as file_handle:
|
|
||||||
status = subprocess.call(['which', 'tor'], stdout=file_handle)
|
|
||||||
|
|
||||||
return not status
|
|
||||||
|
|
||||||
|
|
||||||
def set_tor_service(enable):
|
def set_tor_service(enable):
|
||||||
"""Enable/disable Tor service; enable: boolean"""
|
"""Enable/disable Tor service; enable: boolean"""
|
||||||
if not get_installed():
|
|
||||||
return
|
|
||||||
|
|
||||||
newline = 'RUN_DAEMON="yes"\n' if enable else 'RUN_DAEMON="no"\n'
|
newline = 'RUN_DAEMON="yes"\n' if enable else 'RUN_DAEMON="no"\n'
|
||||||
|
|
||||||
with open(SERVICE_CONFIG, 'r') as file:
|
with open(SERVICE_CONFIG, 'r') as file:
|
||||||
@ -182,9 +162,6 @@ def set_tor_service(enable):
|
|||||||
|
|
||||||
def get_hidden_service():
|
def get_hidden_service():
|
||||||
"""Return a string with configured Tor hidden service information"""
|
"""Return a string with configured Tor hidden service information"""
|
||||||
if not get_installed():
|
|
||||||
return ''
|
|
||||||
|
|
||||||
hs_dir = None
|
hs_dir = None
|
||||||
hs_ports = []
|
hs_ports = []
|
||||||
|
|
||||||
|
|||||||
127
actions/xmpp
127
actions/xmpp
@ -23,6 +23,14 @@ Configuration helper for the ejabberd service
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import re
|
||||||
|
|
||||||
|
JWCHAT_CONFIG = '/etc/jwchat/config.js'
|
||||||
|
EJABBERD_CONFIG = '/etc/ejabberd/ejabberd.yml'
|
||||||
|
EJABBERD_BACKUP = '/var/log/ejabberd/ejabberd.dump'
|
||||||
|
EJABBERD_BACKUP_NEW = '/var/log/ejabberd/ejabberd_new.dump'
|
||||||
|
|
||||||
|
|
||||||
def parse_arguments():
|
def parse_arguments():
|
||||||
@ -30,9 +38,29 @@ def parse_arguments():
|
|||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
|
||||||
|
|
||||||
# Get whether ejabberd is installed
|
# Prepare ejabberd for hostname change
|
||||||
subparsers.add_parser('get-installed',
|
pre_hostname_change = subparsers.add_parser(
|
||||||
help='Get whether ejabberd is installed')
|
'pre-change-hostname',
|
||||||
|
help='Prepare ejabberd for hostname change')
|
||||||
|
pre_hostname_change.add_argument('--old-hostname',
|
||||||
|
help='Previous hostname')
|
||||||
|
pre_hostname_change.add_argument('--new-hostname',
|
||||||
|
help='New hostname')
|
||||||
|
|
||||||
|
# Update ejabberd and jwchat with new hostname
|
||||||
|
hostname_change = subparsers.add_parser(
|
||||||
|
'change-hostname',
|
||||||
|
help='Update ejabberd and jwchat with new hostname')
|
||||||
|
hostname_change.add_argument('--old-hostname',
|
||||||
|
help='Previous hostname')
|
||||||
|
hostname_change.add_argument('--new-hostname',
|
||||||
|
help='New hostname')
|
||||||
|
|
||||||
|
# Update ejabberd and jwchat with new domainname
|
||||||
|
domainname_change = subparsers.add_parser(
|
||||||
|
'change-domainname',
|
||||||
|
help='Update ejabberd and jwchat with new domainname')
|
||||||
|
domainname_change.add_argument('--domainname', help='New domainname')
|
||||||
|
|
||||||
# Register a new user account
|
# Register a new user account
|
||||||
register = subparsers.add_parser('register',
|
register = subparsers.add_parser('register',
|
||||||
@ -45,37 +73,96 @@ def parse_arguments():
|
|||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def subcommand_get_installed(_):
|
def subcommand_pre_change_hostname(arguments):
|
||||||
"""Get whether ejabberd is installed"""
|
"""Prepare ejabberd for hostname change"""
|
||||||
print('installed' if get_installed() else 'not installed')
|
old_hostname = arguments.old_hostname
|
||||||
|
new_hostname = arguments.new_hostname
|
||||||
|
|
||||||
|
subprocess.call(['ejabberdctl', 'backup', EJABBERD_BACKUP])
|
||||||
|
try:
|
||||||
|
subprocess.check_output(['ejabberdctl', 'mnesia-change-nodename',
|
||||||
|
'ejabberd@' + old_hostname,
|
||||||
|
'ejabberd@' + new_hostname,
|
||||||
|
EJABBERD_BACKUP, EJABBERD_BACKUP_NEW])
|
||||||
|
os.remove(EJABBERD_BACKUP)
|
||||||
|
except subprocess.CalledProcessError as err:
|
||||||
|
print('Failed to change hostname in ejabberd backup database: %s', err)
|
||||||
|
|
||||||
|
|
||||||
|
def subcommand_change_hostname(arguments):
|
||||||
|
"""Update ejabberd and jwchat with new hostname"""
|
||||||
|
subprocess.call(['service', 'ejabberd', 'stop'])
|
||||||
|
subprocess.call(['pkill', '-u', 'ejabberd'])
|
||||||
|
|
||||||
|
# Make sure there aren't files in the Mnesia spool dir
|
||||||
|
os.makedirs('/var/lib/ejabberd/oldfiles', exist_ok=True)
|
||||||
|
subprocess.call('mv /var/lib/ejabberd/*.* /var/lib/ejabberd/oldfiles/',
|
||||||
|
shell=True)
|
||||||
|
|
||||||
|
subprocess.call(['service', 'ejabberd', 'start'])
|
||||||
|
|
||||||
|
# restore backup database
|
||||||
|
if os.path.exists(EJABBERD_BACKUP_NEW):
|
||||||
|
try:
|
||||||
|
subprocess.check_output(['ejabberdctl',
|
||||||
|
'restore',
|
||||||
|
EJABBERD_BACKUP_NEW])
|
||||||
|
os.remove(EJABBERD_BACKUP_NEW)
|
||||||
|
except subprocess.CalledProcessError as err:
|
||||||
|
print('Failed to restore ejabberd backup database: %s', err)
|
||||||
|
else:
|
||||||
|
print('Could not load ejabberd backup database: %s not found'
|
||||||
|
% EJABBERD_BACKUP_NEW)
|
||||||
|
|
||||||
|
|
||||||
|
def subcommand_change_domainname(arguments):
|
||||||
|
"""Update ejabberd and jwchat with new domainname"""
|
||||||
|
domainname = arguments.domainname
|
||||||
|
fqdn = socket.gethostname() + '.' + domainname
|
||||||
|
|
||||||
|
# update jwchat's sitename, if it's installed
|
||||||
|
if os.path.exists(JWCHAT_CONFIG):
|
||||||
|
with open(JWCHAT_CONFIG, 'r') as conffile:
|
||||||
|
lines = conffile.readlines()
|
||||||
|
with open(JWCHAT_CONFIG, 'w') as conffile:
|
||||||
|
for line in lines:
|
||||||
|
if re.match(r'\s*var\s+SITENAME', line):
|
||||||
|
conffile.write('var SITENAME = "' + fqdn + '";\n')
|
||||||
|
else:
|
||||||
|
conffile.write(line)
|
||||||
|
else:
|
||||||
|
print('Skipping configuring jwchat sitename: %s not found',
|
||||||
|
JWCHAT_CONFIG)
|
||||||
|
|
||||||
|
subprocess.call(['service', 'ejabberd', 'stop'])
|
||||||
|
subprocess.call(['pkill', '-u', 'ejabberd'])
|
||||||
|
|
||||||
|
# add updated FQDN to top of ejabberd hosts list
|
||||||
|
with open(EJABBERD_CONFIG, 'r') as conffile:
|
||||||
|
lines = conffile.readlines()
|
||||||
|
with open(EJABBERD_CONFIG, 'w') as conffile:
|
||||||
|
for line in lines:
|
||||||
|
conffile.write(line)
|
||||||
|
if re.match(r'\s*hosts:', line):
|
||||||
|
conffile.write(' - "' + fqdn + '"\n')
|
||||||
|
|
||||||
|
subprocess.call(['service', 'ejabberd', 'start'])
|
||||||
|
|
||||||
|
|
||||||
def subcommand_register(arguments):
|
def subcommand_register(arguments):
|
||||||
"""Register a new user account"""
|
"""Register a new user account"""
|
||||||
if not get_installed():
|
|
||||||
print('Failed to register XMPP account: ejabberd is not installed.')
|
|
||||||
return
|
|
||||||
|
|
||||||
username = arguments.username
|
username = arguments.username
|
||||||
password = arguments.password
|
password = arguments.password
|
||||||
hostname = subprocess.check_output(['hostname'])
|
fqdn = socket.getfqdn()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output(['ejabberdctl', 'register',
|
output = subprocess.check_output(['ejabberdctl', 'register',
|
||||||
username, hostname, password])
|
username, fqdn, password])
|
||||||
print(output.decode())
|
print(output.decode())
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print('Failed to register XMPP account:', e.output.decode())
|
print('Failed to register XMPP account:', e.output.decode())
|
||||||
|
|
||||||
|
|
||||||
def get_installed():
|
|
||||||
"""Check if ejabberd is installed"""
|
|
||||||
with open('/dev/null', 'w') as file_handle:
|
|
||||||
status = subprocess.call(['which', 'ejabberdctl'], stdout=file_handle)
|
|
||||||
|
|
||||||
return not status
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Parse arguments and perform all duties"""
|
"""Parse arguments and perform all duties"""
|
||||||
arguments = parse_arguments()
|
arguments = parse_arguments()
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# 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 set up new hostname for ejabberd and jwchat.
|
|
||||||
|
|
||||||
hostname="$1"
|
|
||||||
old_hostname=`debconf-show ejabberd | awk '/hostname/ { print $3 }'`
|
|
||||||
|
|
||||||
# Based on http://www.process-one.net/docs/ejabberd/guide_en.html#htoc77
|
|
||||||
|
|
||||||
BACKUP=/tmp/ejabberd.dump
|
|
||||||
|
|
||||||
# Note: dpkg-reconfigure will fail if there have been manual changes made to the
|
|
||||||
# configuration file for a package. Since this is the case for ejabberd,
|
|
||||||
# manually update the hostname in the configuration file.
|
|
||||||
echo "ejabberd ejabberd/hostname string $hostname" | debconf-set-selections
|
|
||||||
echo "jwchat jwchat/ApacheServerName string $hostname" | debconf-set-selections
|
|
||||||
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure jwchat
|
|
||||||
|
|
||||||
sed -i "s/$old_hostname/$hostname/g" /etc/ejabberd/ejabberd.yml
|
|
||||||
sed -i "s/$old_hostname/$hostname/g" $BACKUP
|
|
||||||
|
|
||||||
service ejabberd restart
|
|
||||||
|
|
||||||
# Load backup database
|
|
||||||
sleep 10
|
|
||||||
ejabberdctl load $BACKUP
|
|
||||||
rm $BACKUP
|
|
||||||
@ -19,4 +19,4 @@
|
|||||||
Plinth package init file
|
Plinth package init file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '0.4.1'
|
__version__ = '0.4.2'
|
||||||
|
|||||||
@ -31,6 +31,8 @@ import socket
|
|||||||
|
|
||||||
from plinth import actions
|
from plinth import actions
|
||||||
from plinth import cfg
|
from plinth import cfg
|
||||||
|
from plinth.signals import pre_hostname_change, post_hostname_change
|
||||||
|
from plinth.signals import domainname_change
|
||||||
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
@ -41,6 +43,12 @@ def get_hostname():
|
|||||||
return socket.gethostname()
|
return socket.gethostname()
|
||||||
|
|
||||||
|
|
||||||
|
def get_domainname():
|
||||||
|
"""Return the domainname"""
|
||||||
|
fqdn = socket.getfqdn()
|
||||||
|
return '.'.join(fqdn.split('.')[1:])
|
||||||
|
|
||||||
|
|
||||||
class TrimmedCharField(forms.CharField):
|
class TrimmedCharField(forms.CharField):
|
||||||
"""Trim the contents of a CharField"""
|
"""Trim the contents of a CharField"""
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
@ -68,6 +76,15 @@ and must not be greater than 63 characters in length.'),
|
|||||||
validators.RegexValidator(r'^[a-zA-Z][a-zA-Z0-9]{,62}$',
|
validators.RegexValidator(r'^[a-zA-Z][a-zA-Z0-9]{,62}$',
|
||||||
_('Invalid hostname'))])
|
_('Invalid hostname'))])
|
||||||
|
|
||||||
|
domainname = TrimmedCharField(
|
||||||
|
label=_('Domain Name'),
|
||||||
|
help_text=_('Your domain name is the global name by which other \
|
||||||
|
machines on the Internet can reach you. It must consist of alphanumeric words \
|
||||||
|
separated by dots.'),
|
||||||
|
validators=[
|
||||||
|
validators.RegexValidator(r'^[a-zA-Z][a-zA-Z0-9.]*$',
|
||||||
|
_('Invalid domain name'))])
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# pylint: disable-msg=E1101, W0233
|
# pylint: disable-msg=E1101, W0233
|
||||||
forms.Form.__init__(self, *args, **kwargs)
|
forms.Form.__init__(self, *args, **kwargs)
|
||||||
@ -124,6 +141,7 @@ def index(request):
|
|||||||
def get_status():
|
def get_status():
|
||||||
"""Return the current status"""
|
"""Return the current status"""
|
||||||
return {'hostname': get_hostname(),
|
return {'hostname': get_hostname(),
|
||||||
|
'domainname': get_domainname(),
|
||||||
'time_zone': open('/etc/timezone').read().rstrip()}
|
'time_zone': open('/etc/timezone').read().rstrip()}
|
||||||
|
|
||||||
|
|
||||||
@ -140,6 +158,17 @@ def _apply_changes(request, old_status, new_status):
|
|||||||
else:
|
else:
|
||||||
messages.info(request, _('Hostname is unchanged'))
|
messages.info(request, _('Hostname is unchanged'))
|
||||||
|
|
||||||
|
if old_status['domainname'] != new_status['domainname']:
|
||||||
|
try:
|
||||||
|
set_domainname(new_status['domainname'])
|
||||||
|
except Exception as exception:
|
||||||
|
messages.error(request, _('Error setting domain name: %s') %
|
||||||
|
exception)
|
||||||
|
else:
|
||||||
|
messages.success(request, _('Domain name set'))
|
||||||
|
else:
|
||||||
|
messages.info(request, _('Domain name is unchanged'))
|
||||||
|
|
||||||
if old_status['time_zone'] != new_status['time_zone']:
|
if old_status['time_zone'] != new_status['time_zone']:
|
||||||
try:
|
try:
|
||||||
actions.superuser_run('timezone-change', [new_status['time_zone']])
|
actions.superuser_run('timezone-change', [new_status['time_zone']])
|
||||||
@ -154,11 +183,34 @@ def _apply_changes(request, old_status, new_status):
|
|||||||
|
|
||||||
def set_hostname(hostname):
|
def set_hostname(hostname):
|
||||||
"""Sets machine hostname to hostname"""
|
"""Sets machine hostname to hostname"""
|
||||||
|
old_hostname = get_hostname()
|
||||||
|
|
||||||
# Hostname should be ASCII. If it's unicode but passed our
|
# Hostname should be ASCII. If it's unicode but passed our
|
||||||
# valid_hostname check, convert to ASCII.
|
# valid_hostname check, convert to ASCII.
|
||||||
hostname = str(hostname)
|
hostname = str(hostname)
|
||||||
|
|
||||||
|
pre_hostname_change.send_robust(sender='config',
|
||||||
|
old_hostname=old_hostname,
|
||||||
|
new_hostname=hostname)
|
||||||
|
|
||||||
LOGGER.info('Changing hostname to - %s', hostname)
|
LOGGER.info('Changing hostname to - %s', hostname)
|
||||||
actions.superuser_run('xmpp-pre-hostname-change')
|
|
||||||
actions.superuser_run('hostname-change', hostname)
|
actions.superuser_run('hostname-change', hostname)
|
||||||
actions.superuser_run('xmpp-hostname-change', hostname, async=True)
|
|
||||||
|
post_hostname_change.send_robust(sender='config',
|
||||||
|
old_hostname=old_hostname,
|
||||||
|
new_hostname=hostname)
|
||||||
|
|
||||||
|
|
||||||
|
def set_domainname(domainname):
|
||||||
|
"""Sets machine domain name to domainname"""
|
||||||
|
old_domainname = get_domainname()
|
||||||
|
|
||||||
|
# Domain name should be ASCII. If it's unicode, convert to ASCII.
|
||||||
|
domainname = str(domainname)
|
||||||
|
|
||||||
|
LOGGER.info('Changing domain name to - %s', domainname)
|
||||||
|
actions.superuser_run('domainname-change', domainname)
|
||||||
|
|
||||||
|
domainname_change.send_robust(sender='config',
|
||||||
|
old_domainname=old_domainname,
|
||||||
|
new_domainname=domainname)
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import logging
|
|||||||
|
|
||||||
from plinth import actions
|
from plinth import actions
|
||||||
from plinth import cfg
|
from plinth import cfg
|
||||||
|
from plinth import package
|
||||||
from plinth.signals import service_enabled
|
from plinth.signals import service_enabled
|
||||||
import plinth.service as service_module
|
import plinth.service as service_module
|
||||||
|
|
||||||
@ -42,13 +43,9 @@ def init():
|
|||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@package.required('firewalld')
|
||||||
def index(request):
|
def index(request):
|
||||||
"""Serve introcution page"""
|
"""Serve introcution page"""
|
||||||
if not get_installed_status():
|
|
||||||
return TemplateResponse(request, 'firewall.html',
|
|
||||||
{'title': _('Firewall'),
|
|
||||||
'firewall_status': 'not_installed'})
|
|
||||||
|
|
||||||
if not get_enabled_status():
|
if not get_enabled_status():
|
||||||
return TemplateResponse(request, 'firewall.html',
|
return TemplateResponse(request, 'firewall.html',
|
||||||
{'title': _('Firewall'),
|
{'title': _('Firewall'),
|
||||||
@ -65,14 +62,8 @@ def index(request):
|
|||||||
'external_enabled_services': external_enabled_services})
|
'external_enabled_services': external_enabled_services})
|
||||||
|
|
||||||
|
|
||||||
def get_installed_status():
|
|
||||||
"""Return whether firewall is installed"""
|
|
||||||
output = _run(['get-installed'], superuser=True)
|
|
||||||
return output.split()[0] == 'installed'
|
|
||||||
|
|
||||||
|
|
||||||
def get_enabled_status():
|
def get_enabled_status():
|
||||||
"""Return whether firewall is installed"""
|
"""Return whether firewall is enabled"""
|
||||||
output = _run(['get-status'], superuser=True)
|
output = _run(['get-status'], superuser=True)
|
||||||
return output.split()[0] == 'running'
|
return output.split()[0] == 'running'
|
||||||
|
|
||||||
|
|||||||
@ -29,13 +29,7 @@ threat from the Internet.</p>
|
|||||||
|
|
||||||
<p>The following is the current status:</p>
|
<p>The following is the current status:</p>
|
||||||
|
|
||||||
{% if firewall_status = 'not_installed' %}
|
{% if firewall_status = 'not_running' %}
|
||||||
<p>Firewall is not installed. Please install it. Firewall comes
|
|
||||||
pre-installed with {{ cfg.box_name }}. On any Debian based system (such
|
|
||||||
as {{ cfg.box_name }}) you may install it using the
|
|
||||||
command <code>aptitude install firewalld</code></p>
|
|
||||||
|
|
||||||
{% elif firewall_status = 'not_running' %}
|
|
||||||
|
|
||||||
<p>Firewall daemon is not running. Please run it. Firewall comes
|
<p>Firewall daemon is not running. Please run it. Firewall comes
|
||||||
enabled by default on {{ cfg.box_name }}. On any Debian based system
|
enabled by default on {{ cfg.box_name }}. On any Debian based system
|
||||||
|
|||||||
@ -23,6 +23,7 @@ from gettext import gettext as _
|
|||||||
|
|
||||||
from plinth import actions
|
from plinth import actions
|
||||||
from plinth import cfg
|
from plinth import cfg
|
||||||
|
from plinth import package
|
||||||
from plinth import service
|
from plinth import service
|
||||||
|
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ def init():
|
|||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@package.required('postgresql', 'php5-pgsql', 'owncloud')
|
||||||
def index(request):
|
def index(request):
|
||||||
"""Serve the ownCloud configuration page"""
|
"""Serve the ownCloud configuration page"""
|
||||||
status = get_status()
|
status = get_status()
|
||||||
|
|||||||
@ -27,14 +27,10 @@
|
|||||||
|
|
||||||
<h2>ownCloud</h2>
|
<h2>ownCloud</h2>
|
||||||
|
|
||||||
<p>When enabled, the owncloud installation will be available
|
<p>When enabled, the ownCloud installation will be available
|
||||||
from <a href="/owncloud">/owncloud</a> path on the web server.
|
from <a href="/owncloud">/owncloud</a> path on the web server.
|
||||||
Visit this URL to set up the initial administration account for
|
Visit this URL to set up the initial administration account for
|
||||||
owncloud.</p>
|
ownCloud.</p>
|
||||||
|
|
||||||
<p><strong>Note: Setting up owncloud for the first time might take
|
|
||||||
5 minutes or more, depending on download bandwidth from the
|
|
||||||
Debian APT sources.</strong></p>
|
|
||||||
|
|
||||||
{{ form|bootstrap }}
|
{{ form|bootstrap }}
|
||||||
<input type="submit" class="btn btn-primary btn-md" value="Update setup"/>
|
<input type="submit" class="btn btn-primary btn-md" value="Update setup"/>
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import logging
|
|||||||
|
|
||||||
from plinth import actions
|
from plinth import actions
|
||||||
from plinth import cfg
|
from plinth import cfg
|
||||||
|
from plinth import package
|
||||||
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
@ -101,6 +102,7 @@ https://pagekite.net/wiki/Howto/SshOverPageKite/">instructions</a>'))
|
|||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@package.required('pagekite')
|
||||||
def configure(request):
|
def configure(request):
|
||||||
"""Serve the configuration form"""
|
"""Serve the configuration form"""
|
||||||
status = get_status()
|
status = get_status()
|
||||||
@ -131,11 +133,6 @@ def get_status():
|
|||||||
"""
|
"""
|
||||||
status = {}
|
status = {}
|
||||||
|
|
||||||
# Check if PageKite is installed
|
|
||||||
output = _run(['get-installed'])
|
|
||||||
if output.split()[0] != 'installed':
|
|
||||||
return None
|
|
||||||
|
|
||||||
# PageKite service enabled/disabled
|
# PageKite service enabled/disabled
|
||||||
output = _run(['get-status'])
|
output = _run(['get-status'])
|
||||||
status['enabled'] = (output.split()[0] == 'enabled')
|
status['enabled'] = (output.split()[0] == 'enabled')
|
||||||
|
|||||||
@ -22,15 +22,6 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% if not status %}
|
|
||||||
|
|
||||||
<p>PageKite is not installed, please install it. PageKite comes
|
|
||||||
pre-installed with {{ cfg.box_name }}. On any Debian based system
|
|
||||||
(such as {{ cfg.box_name }}) you may install it using the command
|
|
||||||
<code>aptitude install pagekite<code></p>
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
<form class="form" method="post">
|
<form class="form" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
@ -52,8 +43,6 @@
|
|||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page_js %}
|
{% block page_js %}
|
||||||
|
|||||||
@ -24,8 +24,6 @@
|
|||||||
|
|
||||||
<h2>Tor</h2>
|
<h2>Tor</h2>
|
||||||
|
|
||||||
{% if is_installed %}
|
|
||||||
|
|
||||||
<h3>Status</h3>
|
<h3>Status</h3>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -80,15 +78,6 @@ port-forwarded, if necessary:</p>
|
|||||||
<p>A Tor SOCKS port is available on your {{ cfg.box_name }} on TCP port
|
<p>A Tor SOCKS port is available on your {{ cfg.box_name }} on TCP port
|
||||||
9050.</p>
|
9050.</p>
|
||||||
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
<p>Tor is not installed, please install it. Tor comes pre-installed
|
|
||||||
with {{ cfg.box_name }}. On any Debian-based system (such as
|
|
||||||
{{ cfg.box_name }}) you may install it using the command
|
|
||||||
<code>aptitude install tor</code>.</p>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block sidebar %}
|
{% block sidebar %}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ from gettext import gettext as _
|
|||||||
|
|
||||||
from plinth import actions
|
from plinth import actions
|
||||||
from plinth import cfg
|
from plinth import cfg
|
||||||
|
from plinth import package
|
||||||
|
|
||||||
|
|
||||||
class TorForm(forms.Form): # pylint: disable=W0232
|
class TorForm(forms.Form): # pylint: disable=W0232
|
||||||
@ -43,6 +44,7 @@ def init():
|
|||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@package.required('tor')
|
||||||
def index(request):
|
def index(request):
|
||||||
"""Service the index page"""
|
"""Service the index page"""
|
||||||
status = get_status()
|
status = get_status()
|
||||||
@ -61,7 +63,6 @@ def index(request):
|
|||||||
|
|
||||||
return TemplateResponse(request, 'tor.html',
|
return TemplateResponse(request, 'tor.html',
|
||||||
{'title': _('Tor Control Panel'),
|
{'title': _('Tor Control Panel'),
|
||||||
'is_installed': status['is_installed'],
|
|
||||||
'is_running': status['is_running'],
|
'is_running': status['is_running'],
|
||||||
'tor_ports': status['ports'],
|
'tor_ports': status['ports'],
|
||||||
'tor_hs_enabled': status['hs_enabled'],
|
'tor_hs_enabled': status['hs_enabled'],
|
||||||
@ -72,9 +73,6 @@ def index(request):
|
|||||||
|
|
||||||
def get_status():
|
def get_status():
|
||||||
"""Return the current status"""
|
"""Return the current status"""
|
||||||
is_installed = actions.superuser_run(
|
|
||||||
'tor',
|
|
||||||
['get-installed']).strip() == 'installed'
|
|
||||||
is_running = actions.superuser_run('tor', ['is-running']).strip() == 'yes'
|
is_running = actions.superuser_run('tor', ['is-running']).strip() == 'yes'
|
||||||
|
|
||||||
output = actions.superuser_run('tor-get-ports')
|
output = actions.superuser_run('tor-get-ports')
|
||||||
@ -103,8 +101,7 @@ def get_status():
|
|||||||
hs_hostname = hs_info[0]
|
hs_hostname = hs_info[0]
|
||||||
hs_ports = hs_info[1]
|
hs_ports = hs_info[1]
|
||||||
|
|
||||||
return {'is_installed': is_installed,
|
return {'is_running': is_running,
|
||||||
'is_running': is_running,
|
|
||||||
'ports': ports,
|
'ports': ports,
|
||||||
'hs_enabled': hs_enabled,
|
'hs_enabled': hs_enabled,
|
||||||
'hs_hostname': hs_hostname,
|
'hs_hostname': hs_hostname,
|
||||||
|
|||||||
@ -22,8 +22,6 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% if is_installed %}
|
|
||||||
|
|
||||||
<p>XMPP is an open and standardized communication protocol. Here you
|
<p>XMPP is an open and standardized communication protocol. Here you
|
||||||
can run and configure your XMPP server, called ejabberd. To actually
|
can run and configure your XMPP server, called ejabberd. To actually
|
||||||
communicate, you can use the <a href='/jwchat'>web client</a> or any
|
communicate, you can use the <a href='/jwchat'>web client</a> or any
|
||||||
@ -33,16 +31,4 @@
|
|||||||
<p><a href='/jwchat' target='_blank'class='btn btn-primary'> Launch web
|
<p><a href='/jwchat' target='_blank'class='btn btn-primary'> Launch web
|
||||||
client</a></p>
|
client</a></p>
|
||||||
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
<h2>XMPP Server</h2>
|
|
||||||
|
|
||||||
<p>The XMPP server <i>ejabberd</i> is not installed.</p>
|
|
||||||
|
|
||||||
<p>ejabberd comes pre-installed with {{ cfg.box_name }}. On any Debian-based
|
|
||||||
system (such as {{ cfg.box_name }}) you may install it using the command
|
|
||||||
<code>aptitude install ejabberd</code>.</p>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -25,7 +25,10 @@ import logging
|
|||||||
|
|
||||||
from plinth import actions
|
from plinth import actions
|
||||||
from plinth import cfg
|
from plinth import cfg
|
||||||
|
from plinth import package
|
||||||
from plinth import service
|
from plinth import service
|
||||||
|
from plinth.signals import pre_hostname_change, post_hostname_change
|
||||||
|
from plinth.signals import domainname_change
|
||||||
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
@ -53,23 +56,18 @@ def init():
|
|||||||
'xmpp-bosh', _('Chat Server - web interface'), is_external=True,
|
'xmpp-bosh', _('Chat Server - web interface'), is_external=True,
|
||||||
enabled=True)
|
enabled=True)
|
||||||
|
|
||||||
|
pre_hostname_change.connect(on_pre_hostname_change)
|
||||||
|
post_hostname_change.connect(on_post_hostname_change)
|
||||||
|
domainname_change.connect(on_domainname_change)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@package.required('jwchat', 'ejabberd')
|
||||||
def index(request):
|
def index(request):
|
||||||
"""Serve XMPP page"""
|
"""Serve XMPP page"""
|
||||||
is_installed = actions.superuser_run(
|
|
||||||
'xmpp',
|
|
||||||
['get-installed']).strip() == 'installed'
|
|
||||||
|
|
||||||
if is_installed:
|
|
||||||
index_subsubmenu = subsubmenu
|
|
||||||
else:
|
|
||||||
index_subsubmenu = None
|
|
||||||
|
|
||||||
return TemplateResponse(request, 'xmpp.html',
|
return TemplateResponse(request, 'xmpp.html',
|
||||||
{'title': _('XMPP Server'),
|
{'title': _('XMPP Server'),
|
||||||
'is_installed': is_installed,
|
'subsubmenu': subsubmenu})
|
||||||
'subsubmenu': index_subsubmenu})
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigureForm(forms.Form): # pylint: disable-msg=W0232
|
class ConfigureForm(forms.Form): # pylint: disable-msg=W0232
|
||||||
@ -178,3 +176,44 @@ def _register_user(request, data):
|
|||||||
messages.error(request,
|
messages.error(request,
|
||||||
_('Failed to register account for %s: %s') %
|
_('Failed to register account for %s: %s') %
|
||||||
(data['username'], output))
|
(data['username'], output))
|
||||||
|
|
||||||
|
|
||||||
|
def on_pre_hostname_change(sender, old_hostname, new_hostname, **kwargs):
|
||||||
|
"""
|
||||||
|
Backup ejabberd database before hostname is changed.
|
||||||
|
"""
|
||||||
|
del sender # Unused
|
||||||
|
del kwargs # Unused
|
||||||
|
|
||||||
|
actions.superuser_run('xmpp',
|
||||||
|
['pre-change-hostname',
|
||||||
|
'--old-hostname', old_hostname,
|
||||||
|
'--new-hostname', new_hostname])
|
||||||
|
|
||||||
|
|
||||||
|
def on_post_hostname_change(sender, old_hostname, new_hostname, **kwargs):
|
||||||
|
"""
|
||||||
|
Update ejabberd and jwchat config after hostname is changed.
|
||||||
|
"""
|
||||||
|
del sender # Unused
|
||||||
|
del kwargs # Unused
|
||||||
|
|
||||||
|
actions.superuser_run('xmpp',
|
||||||
|
['change-hostname',
|
||||||
|
'--old-hostname', old_hostname,
|
||||||
|
'--new-hostname', new_hostname],
|
||||||
|
async=True)
|
||||||
|
|
||||||
|
|
||||||
|
def on_domainname_change(sender, old_domainname, new_domainname, **kwargs):
|
||||||
|
"""
|
||||||
|
Update ejabberd and jwchat config after domain name is changed.
|
||||||
|
"""
|
||||||
|
del sender # Unused
|
||||||
|
del old_domainname # Unused
|
||||||
|
del kwargs # Unused
|
||||||
|
|
||||||
|
actions.superuser_run('xmpp',
|
||||||
|
['change-domainname',
|
||||||
|
'--domainname', new_domainname],
|
||||||
|
async=True)
|
||||||
|
|||||||
184
plinth/package.py
Normal file
184
plinth/package.py
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Framework for installing and updating distribution packages
|
||||||
|
"""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
from gi.repository import PackageKitGlib as packagekit
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import plinth
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
transactions = {}
|
||||||
|
packages_resolved = {}
|
||||||
|
|
||||||
|
|
||||||
|
class Transaction(object):
|
||||||
|
"""Information about an ongoing transaction."""
|
||||||
|
|
||||||
|
def __init__(self, package_names):
|
||||||
|
"""Initialize transaction object.
|
||||||
|
|
||||||
|
Set most values to None until they are sent as progress update.
|
||||||
|
"""
|
||||||
|
self.package_names = package_names
|
||||||
|
|
||||||
|
# Progress
|
||||||
|
self.allow_cancel = None
|
||||||
|
self.percentage = None
|
||||||
|
self.status = None
|
||||||
|
self.status_string = None
|
||||||
|
self.flags = None
|
||||||
|
self.package = None
|
||||||
|
self.package_id = None
|
||||||
|
self.item_progress = None
|
||||||
|
self.role = None
|
||||||
|
self.caller_active = None
|
||||||
|
self.download_size_remaining = None
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
"""Return a identifier to use as a key in a map of transactions."""
|
||||||
|
return frozenset(self.package_names)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Return the string representation of the object"""
|
||||||
|
return ('Transaction(packages={0}, allow_cancel={1}, status={2}, '
|
||||||
|
' percentage={3}, package={4}, item_progress={5})').format(
|
||||||
|
self.package_names, self.allow_cancel, self.status_string,
|
||||||
|
self.percentage, self.package, self.item_progress)
|
||||||
|
|
||||||
|
def start_install(self):
|
||||||
|
"""Start a PackageKit transaction to install given list of packages.
|
||||||
|
|
||||||
|
This operation is non-blocking at it spawns a new thread.
|
||||||
|
"""
|
||||||
|
thread = threading.Thread(target=self._install)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def _install(self):
|
||||||
|
"""Run a PackageKit transaction to install given packages."""
|
||||||
|
package_ids = [packages_resolved[package_name].get_id()
|
||||||
|
for package_name in self.package_names]
|
||||||
|
client = packagekit.Client()
|
||||||
|
client.set_interactive(False)
|
||||||
|
client.install_packages(packagekit.TransactionFlagEnum.ONLY_TRUSTED,
|
||||||
|
package_ids + [None], None,
|
||||||
|
self.progress_callback, self)
|
||||||
|
|
||||||
|
def progress_callback(self, progress, progress_type, user_data):
|
||||||
|
"""Process progress updates on package resolve operation"""
|
||||||
|
if progress_type == packagekit.ProgressType.PERCENTAGE:
|
||||||
|
self.percentage = progress.props.percentage
|
||||||
|
elif progress_type == packagekit.ProgressType.PACKAGE:
|
||||||
|
self.package = progress.props.package
|
||||||
|
elif progress_type == packagekit.ProgressType.ALLOW_CANCEL:
|
||||||
|
self.allow_cancel = progress.props.allow_cancel
|
||||||
|
elif progress_type == packagekit.ProgressType.PACKAGE_ID:
|
||||||
|
self.package_id = progress.props.package_id
|
||||||
|
elif progress_type == packagekit.ProgressType.ITEM_PROGRESS:
|
||||||
|
self.item_progress = progress.props.item_progress
|
||||||
|
elif progress_type == packagekit.ProgressType.STATUS:
|
||||||
|
self.status = progress.props.status
|
||||||
|
self.status_string = \
|
||||||
|
packagekit.StatusEnum.to_string(progress.props.status)
|
||||||
|
if self.status == packagekit.StatusEnum.FINISHED:
|
||||||
|
self.finish()
|
||||||
|
elif progress_type == packagekit.ProgressType.TRANSACTION_FLAGS:
|
||||||
|
self.flags = progress.props.transaction_flags
|
||||||
|
elif progress_type == packagekit.ProgressType.ROLE:
|
||||||
|
self.role = progress.props.role
|
||||||
|
elif progress_type == packagekit.ProgressType.CALLER_ACTIVE:
|
||||||
|
self.caller_active = progress.props.caller_active
|
||||||
|
elif progress_type == packagekit.ProgressType.DOWNLOAD_SIZE_REMAINING:
|
||||||
|
self.download_size_remaining = \
|
||||||
|
progress.props.download_size_remaining
|
||||||
|
else:
|
||||||
|
logger.info('Unhandle packagekit progress callback - %s, %s',
|
||||||
|
progress, progress_type)
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
"""Perform clean up operations on the transaction.
|
||||||
|
|
||||||
|
Remove self from global transactions list.
|
||||||
|
"""
|
||||||
|
del transactions[self.get_id()]
|
||||||
|
|
||||||
|
|
||||||
|
def required(*package_names):
|
||||||
|
"""Decorate a view to check and install required packages."""
|
||||||
|
|
||||||
|
def wrapper2(func):
|
||||||
|
"""Return a function to check and install packages."""
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(request, *args, **kwargs):
|
||||||
|
"""Check and install packages required by a view."""
|
||||||
|
if not is_installing(package_names) and \
|
||||||
|
check_installed(package_names):
|
||||||
|
return func(request, *args, **kwargs)
|
||||||
|
|
||||||
|
view = plinth.views.PackageInstallView.as_view()
|
||||||
|
return view(request, package_names=package_names, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return wrapper2
|
||||||
|
|
||||||
|
|
||||||
|
def check_installed(package_names):
|
||||||
|
"""Return a boolean installed status of package.
|
||||||
|
|
||||||
|
This operation is blocking and waits until the check is finished.
|
||||||
|
"""
|
||||||
|
def _callback(progress, progress_type, user_data):
|
||||||
|
"""Process progress updates on package resolve operation."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
client = packagekit.Client()
|
||||||
|
response = client.resolve(packagekit.FilterEnum.INSTALLED,
|
||||||
|
package_names + (None, ), None,
|
||||||
|
_callback, None)
|
||||||
|
|
||||||
|
installed_package_names = []
|
||||||
|
for package in response.get_package_array():
|
||||||
|
if package.get_info() == packagekit.InfoEnum.INSTALLED:
|
||||||
|
installed_package_names.append(package.get_name())
|
||||||
|
|
||||||
|
packages_resolved[package.get_name()] = package
|
||||||
|
|
||||||
|
return set(installed_package_names) == set(package_names)
|
||||||
|
|
||||||
|
|
||||||
|
def is_installing(package_names):
|
||||||
|
"""Return whether a set of packages are currently being installed."""
|
||||||
|
return frozenset(package_names) in transactions
|
||||||
|
|
||||||
|
|
||||||
|
def start_install(package_names):
|
||||||
|
"""Start a PackageKit transaction to install given list of packages.
|
||||||
|
|
||||||
|
This operation is non-blocking at it spawns a new thread.
|
||||||
|
"""
|
||||||
|
transaction = Transaction(package_names)
|
||||||
|
transactions[frozenset(package_names)] = transaction
|
||||||
|
|
||||||
|
transaction.start_install()
|
||||||
@ -25,3 +25,6 @@ from django.dispatch import Signal
|
|||||||
service_enabled = Signal(providing_args=['service_id', 'enabled'])
|
service_enabled = Signal(providing_args=['service_id', 'enabled'])
|
||||||
pre_module_loading = Signal()
|
pre_module_loading = Signal()
|
||||||
post_module_loading = Signal()
|
post_module_loading = Signal()
|
||||||
|
pre_hostname_change = Signal(providing_args=['old_hostname', 'new_hostname'])
|
||||||
|
post_hostname_change = Signal(providing_args=['old_hostname', 'new_hostname'])
|
||||||
|
domainname_change = Signal(providing_args=['old_domainname', 'new_domainname'])
|
||||||
|
|||||||
79
plinth/templates/package_install.html
Normal file
79
plinth/templates/package_install.html
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
{% 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 page_head %}
|
||||||
|
|
||||||
|
{% if is_installing %}
|
||||||
|
<meta http-equiv="refresh" content="3" />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2>Installation</h2>
|
||||||
|
|
||||||
|
{% if not is_installing %}
|
||||||
|
|
||||||
|
<p>This feature requires addtional packages to be installed. Do you
|
||||||
|
wish to install them?</p>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Package</th><th>Summary</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for package in packages %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ package.get_name }}</td>
|
||||||
|
<td>{{ package.get_summary }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" class="btn btn-md btn-primary" value="Install" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% for key, transaction in transactions.items %}
|
||||||
|
<div>Installing {{ transaction.package_names|join:", " }}:
|
||||||
|
{{ transaction.status_string }}
|
||||||
|
</div>
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar progress-bar-striped active"
|
||||||
|
role="progressbar" aria-valuemin="0" aria-valuemax="100"
|
||||||
|
aria-valuenow="{{ transaction.percentage }}"
|
||||||
|
style="width: {{ transaction.percentage }}%">
|
||||||
|
<span class="sr-only">{{ transaction.percentage }}% complete</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -21,11 +21,42 @@ Main Plinth views
|
|||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.http.response import HttpResponseRedirect
|
from django.http.response import HttpResponseRedirect
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
from plinth import package as package_module
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
"""Serve the main index page"""
|
"""Serve the main index page."""
|
||||||
if request.user.is_authenticated():
|
if request.user.is_authenticated():
|
||||||
return HttpResponseRedirect(reverse('apps:index'))
|
return HttpResponseRedirect(reverse('apps:index'))
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse('help:about'))
|
return HttpResponseRedirect(reverse('help:about'))
|
||||||
|
|
||||||
|
|
||||||
|
class PackageInstallView(TemplateView):
|
||||||
|
"""View to prompt and install packages."""
|
||||||
|
template_name = 'package_install.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""Return the context data rendering the template."""
|
||||||
|
context = super(PackageInstallView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
if 'packages_names' not in context:
|
||||||
|
context['package_names'] = self.kwargs.get('package_names', [])
|
||||||
|
context['packages'] = [package_module.packages_resolved[package_name]
|
||||||
|
for package_name in context['package_names']]
|
||||||
|
context['is_installing'] = \
|
||||||
|
package_module.is_installing(context['package_names'])
|
||||||
|
context['transactions'] = package_module.transactions
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def post(self, *args, **kwargs):
|
||||||
|
"""Handle installing packages
|
||||||
|
|
||||||
|
Start the package installation, and refresh the page every x seconds to
|
||||||
|
keep displaying PackageInstallView.get() with the installation status.
|
||||||
|
"""
|
||||||
|
package_module.start_install(self.kwargs['package_names'])
|
||||||
|
return self.render_to_response(self.get_context_data())
|
||||||
|
|||||||
3
setup.py
3
setup.py
@ -114,7 +114,8 @@ setuptools.setup(
|
|||||||
install_requires=[
|
install_requires=[
|
||||||
'cherrypy >= 3.0',
|
'cherrypy >= 3.0',
|
||||||
'django >= 1.7.0',
|
'django >= 1.7.0',
|
||||||
'django-bootstrap-form'
|
'django-bootstrap-form',
|
||||||
|
'pygobject'
|
||||||
],
|
],
|
||||||
tests_require=['coverage >= 3.7'],
|
tests_require=['coverage >= 3.7'],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user