[clean] Remove YunoHost files

This commit is contained in:
Jérôme Lebleu 2014-05-16 16:19:40 +02:00
parent 6563c693f3
commit a442e61e06
20 changed files with 0 additions and 5373 deletions

View file

@ -1,50 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import os.path
from_source = False
# Run from source
basedir = os.path.abspath('%s/../' % os.path.dirname(__file__))
if os.path.isdir('%s/moulinette' % basedir):
sys.path.insert(0, basedir)
from_source = True
from moulinette import init, cli, MoulinetteError
from moulinette.helpers import YunoHostError, colorize
## Main action
if __name__ == '__main__':
# Run from source
init(_from_source=from_source)
# Additional arguments
use_cache = True
if '--no-cache' in sys.argv:
use_cache = False
sys.argv.remove('--no-cache')
args = list(sys.argv)
args.pop(0)
# Check that YunoHost is installed
if not os.path.isfile('/etc/yunohost/installed') and \
(len(args) < 2 or args[1] != 'tools' or args[2] != 'postinstall'):
from moulinette.interfaces.cli import colorize, get_locale
# Init i18n
m18n.load_namespace('yunohost')
m18n.set_locale(get_locale())
# Print error and exit
print('%s %s' % (colorize(m18n.g('error'), 'red'),
m18n.n('yunohost_not_installed')))
sys.exit(1)
# Execute the action
ret = cli(['yunohost'], args, use_cache)
sys.exit(ret)

View file

@ -1,52 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import os.path
from_source = False
# Run from source
basedir = os.path.abspath('%s/../' % os.path.dirname(__file__))
if os.path.isdir('%s/moulinette' % basedir):
sys.path.insert(0, basedir)
from_source = True
from moulinette import init, api, MoulinetteError
## Callbacks for additional routes
def is_installed():
"""
Check whether YunoHost is installed or not
"""
installed = False
if os.path.isfile('/etc/yunohost/installed'):
installed = True
return { 'installed': installed }
## Main action
if __name__ == '__main__':
# Run from source
init(_from_source=from_source)
# Additional arguments
use_cache = True
if '--no-cache' in sys.argv:
use_cache = False
sys.argv.remove('--no-cache')
# TODO: Add log argument
try:
# Run the server
api(['yunohost'], 6787,
{('GET', '/installed'): is_installed}, use_cache)
except MoulinetteError as e:
from moulinette.interfaces.cli import colorize
print('%s %s' % (colorize(m18n.g('error'), 'red'), e.strerror))
sys.exit(e.errno)
sys.exit(0)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2013 YunoHost
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
"""
""" yunohost_backup.py
Manage backups
"""
import os
import sys
import json
import yaml
import glob
from moulinette.core import MoulinetteError
def backup_init(helper=False):
"""
Init Tahoe-LAFS configuration
Keyword argument:
helper -- Init as a helper node rather than a "helped" one
"""
tahoe_cfg_dir = '/usr/share/yunohost/yunohost-config/backup'
if helper:
configure_cmd = '/configure_tahoe.sh helper'
else:
configure_cmd = '/configure_tahoe.sh'
os.system('tahoe create-client /home/yunohost.backup/tahoe')
os.system('/bin/bash %s%s' % (tahoe_cfg_dir, configure_cmd))
os.system('cp %s/tahoe.cfg /home/yunohost.backup/tahoe/' % tahoe_cfg_dir)
#os.system('update-rc.d tahoe-lafs defaults')
#os.system('service tahoe-lafs restart')

View file

@ -1,67 +0,0 @@
#!/bin/bash
if [ ! -d /tmp/yunohost ];
then
mkdir /tmp/yunohost
fi
if [ -f /tmp/yunohost/changelog ];
then
rm /tmp/yunohost/changelog
fi
apt-get update -y > /dev/null 2>&1
if [[ $? != 0 ]];
then
exit 2
else
echo OK > /tmp/yunohost/update_status
fi
# Set $DIRCACHE
eval `/usr/bin/apt-config shell DIRCACHE Dir::Cache`
# get the list of packages which are pending an upgrade
PKGNAMES=`/usr/bin/apt-get -q -y --ignore-hold --allow-unauthenticated -s dist-upgrade | \
/bin/grep ^Inst | /usr/bin/cut -d\ -f2 | /usr/bin/sort`
if [[ $PKGNAMES = "" ]];
then
exit 1
fi
if [ -n "$PKGNAMES" ] ; then
# do the upgrade downloads
/usr/bin/apt-get --ignore-hold -qq -d --allow-unauthenticated --force-yes dist-upgrade > /dev/null
fi
PKGPATH="/${DIRCACHE}archives/"
for PKG in $PKGNAMES ; do
VER=`LC_ALL=C /usr/bin/apt-cache policy $PKG |\
/bin/grep Candidate: | /usr/bin/cut -f 4 -d \ `
OLDVER=`LC_ALL=C /usr/bin/apt-cache policy $PKG |\
/bin/grep Installed: | /usr/bin/cut -f 4 -d \ `
VERFILE=`echo "$VER" | /bin/sed -e "s/:/%3a/g"`
if ls ${PKGPATH}${PKG}_${VERFILE}_*.deb >& /dev/null ; then
DEBS="$DEBS ${PKGPATH}${PKG}_${VERFILE}_*.deb"
fi
echo -e "$PKG $OLDVER -> $VER"
done
MISSING_DEBS=`apt-get -y --ignore-hold --allow-unauthenticated --print-uris dist-upgrade \
| grep "file:" \
| sed "s/'file:\(.*\)' .*/\1/g"`
DEBS=`echo $MISSING_DEBS $DEBS | /usr/bin/sort`
if [[ $DEBS = "" ]];
then
exit 3
else
if [ -x /usr/bin/apt-listchanges ] ; then
/usr/bin/apt-listchanges --which=both -f text $DEBS > /tmp/yunohost/changelog 2>/dev/null
fi
fi

View file

@ -1,10 +0,0 @@
uPnP:
enabled: false
TCP: [22, 25, 53, 80, 443, 465, 993, 5222, 5269, 5290]
UDP: [53]
ipv4:
TCP: [22, 25, 53, 80, 443, 465, 993, 5222, 5269, 5290]
UDP: [53]
ipv6:
TCP: [22, 25, 53, 80, 443, 465, 993, 5222, 5269, 5290]
UDP: [53]

View file

@ -1,56 +0,0 @@
parents:
ou=users:
ou: users
objectClass:
- organizationalUnit
- top
ou=domains:
ou: domains
objectClass:
- organizationalUnit
- top
ou=apps:
ou: apps
objectClass:
- organizationalUnit
- top
ou=groups:
ou: groups
objectClass:
- organizationalUnit
- top
ou=sudo:
ou: sudo
objectClass:
- organizationalUnit
- top
children:
cn=admins,ou=groups:
cn: admins
gidNumber: "4001"
memberUid: admin
objectClass:
- posixGroup
- top
cn=sftpusers,ou=groups:
cn: sftpusers
gidNumber: "4002"
memberUid: admin
objectClass:
- posixGroup
- top
cn=admin,ou=sudo:
cn: admin
sudoUser: admin
sudoHost: ALL
sudoCommand: ALL
sudoOption: "!authenticate"
objectClass:
- sudoRole
- top

View file

@ -1,38 +0,0 @@
nginx:
status: service
log: /var/log/nginx
bind9:
status: service
log: /var/log/daemon.log
dovecot:
status: service
log: [/var/log/mail.log,/var/log/mail.err]
postfix:
status: service
log: [/var/log/mail.log,/var/log/mail.err]
mysql:
status: service
log: [/var/log/mysql.log,/var/log/mysql.err]
glances:
status: service
tahoe-lafs:
status: ps aux | grep tahoe |grep -v grep
log: /home/yunohost.backup/tahoe/logs/twistd.log
ssh:
status: service
log: /var/log/auth.log
metronome:
status: metronomectl status
log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err]
slapd:
status: service
log: /var/log/syslog
php5-fpm:
status: service
log: /var/log/php5-fpm.log
yunohost-api:
status: cat /usr/share/pyshared/yunohost-cli/twistd.pid
log: /var/log/yunohost.log
postgrey:
status: service
log: /var/log/mail.log

View file

@ -1,5 +0,0 @@
/bin/bash
rm /tmp/yunohost/update_status
sudo apt-get upgrade -y > /tmp/yunohost/update_log 2>&1
if [ $(echo $?) = 0 ]; then echo "OK" > /tmp/yunohost/upgrade_status; else echo "NOK" > /tmp/yunohost/upgrade_status; fi
rm /tmp/yunohost/upgrade.run

View file

@ -1,308 +0,0 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2013 YunoHost
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
"""
""" yunohost_domain.py
Manage domains
"""
import os
import sys
import datetime
import re
import shutil
import json
import yaml
import errno
from urllib import urlopen
from moulinette.core import MoulinetteError
def domain_list(auth, filter=None, limit=None, offset=None):
"""
List domains
Keyword argument:
filter -- LDAP filter used to search
offset -- Starting number for domain fetching
limit -- Maximum number of domain fetched
"""
result_list = []
# Set default arguments values
if offset is None:
offset = 0
if limit is None:
limit = 1000
if filter is None:
filter = 'virtualdomain=*'
result = auth.search('ou=domains,dc=yunohost,dc=org', filter, ['virtualdomain'])
if len(result) > offset and limit > 0:
for domain in result[offset:offset+limit]:
result_list.append(domain['virtualdomain'][0])
return { 'domains': result_list }
def domain_add(auth, domains, main=False, dyndns=False):
"""
Create a custom domain
Keyword argument:
domains -- Domain name to add
main -- Is the main domain
dyndns -- Subscribe to DynDNS
"""
attr_dict = { 'objectClass' : ['mailDomain', 'top'] }
ip = str(urlopen('http://ip.yunohost.org').read())
now = datetime.datetime.now()
timestamp = str(now.year) + str(now.month) + str(now.day)
result = []
if not isinstance(domains, list):
domains = [ domains ]
for domain in domains:
if domain in domain_list(auth)['domains']:
continue
# DynDNS domain
if dyndns:
if len(domain.split('.')) < 3:
raise MoulinetteError(errno.EINVAL, m18n.n('domain_dyndns_invalid'))
import requests
from yunohost.dyndns import dyndns_subscribe
r = requests.get('http://dyndns.yunohost.org/domains')
dyndomains = json.loads(r.text)
dyndomain = '.'.join(domain.split('.')[1:])
if dyndomain in dyndomains:
if os.path.exists('/etc/cron.d/yunohost-dyndns'):
raise MoulinetteError(errno.EPERM,
m18n.n('domain_dyndns_already_subscribed'))
dyndns_subscribe(domain=domain)
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('domain_dyndns_root_unknown'))
# Commands
ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
ssl_domain_path = '/etc/yunohost/certs/%s' % domain
with open('%s/serial' % ssl_dir, 'r') as f:
serial = f.readline().rstrip()
try: os.listdir(ssl_domain_path)
except OSError: os.makedirs(ssl_domain_path)
command_list = [
'cp %s/openssl.cnf %s' % (ssl_dir, ssl_domain_path),
'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, ssl_domain_path),
'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch'
% (ssl_domain_path, ssl_dir, ssl_dir),
'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch'
% (ssl_domain_path, ssl_dir, ssl_dir),
'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' % ssl_domain_path,
'cp %s/certs/yunohost_key.pem %s/key.pem' % (ssl_dir, ssl_domain_path),
'cp %s/newcerts/%s.pem %s/crt.pem' % (ssl_dir, serial, ssl_domain_path),
'chmod 755 %s' % ssl_domain_path,
'chmod 640 %s/key.pem' % ssl_domain_path,
'chmod 640 %s/crt.pem' % ssl_domain_path,
'chmod 600 %s/openssl.cnf' % ssl_domain_path,
'chown root:metronome %s/key.pem' % ssl_domain_path,
'chown root:metronome %s/crt.pem' % ssl_domain_path
]
for command in command_list:
if os.system(command) != 0:
raise MoulinetteError(errno.EIO,
m18n.n('domain_cert_gen_failed'))
try:
auth.validate_uniqueness({ 'virtualdomain': domain })
except MoulinetteError:
raise MoulinetteError(errno.EEXIST, m18n.n('domain_exists'))
attr_dict['virtualdomain'] = domain
try:
with open('/var/lib/bind/%s.zone' % domain) as f: pass
except IOError as e:
zone_lines = [
'$TTL 38400',
'%s. IN SOA ns.%s. root.%s. %s 10800 3600 604800 38400' % (domain, domain, domain, timestamp),
'%s. IN NS ns.%s.' % (domain, domain),
'%s. IN A %s' % (domain, ip),
'%s. IN MX 5 %s.' % (domain, domain),
'%s. IN TXT "v=spf1 mx a -all"' % domain,
'ns.%s. IN A %s' % (domain, ip),
'_xmpp-client._tcp.%s. IN SRV 0 5 5222 %s.' % (domain, domain),
'_xmpp-server._tcp.%s. IN SRV 0 5 5269 %s.' % (domain, domain),
'_jabber._tcp.%s. IN SRV 0 5 5269 %s.' % (domain, domain),
]
if main:
zone_lines.extend([
'pubsub.%s. IN A %s' % (domain, ip),
'muc.%s. IN A %s' % (domain, ip),
'vjud.%s. IN A %s' % (domain, ip)
])
with open('/var/lib/bind/%s.zone' % domain, 'w') as zone:
for line in zone_lines:
zone.write(line + '\n')
os.system('chown bind /var/lib/bind/%s.zone' % domain)
else:
raise MoulinetteError(errno.EEXIST,
m18n.n('domain_zone_exists'))
conf_lines = [
'zone "%s" {' % domain,
' type master;',
' file "/var/lib/bind/%s.zone";' % domain,
' allow-transfer {',
' 127.0.0.1;',
' localnets;',
' };',
'};'
]
with open('/etc/bind/named.conf.local', 'a') as conf:
for line in conf_lines:
conf.write(line + '\n')
os.system('service bind9 reload')
# XMPP
try:
with open('/etc/metronome/conf.d/%s.cfg.lua' % domain) as f: pass
except IOError as e:
conf_lines = [
'VirtualHost "%s"' % domain,
' ssl = {',
' key = "%s/key.pem";' % ssl_domain_path,
' certificate = "%s/crt.pem";' % ssl_domain_path,
' }',
' authentication = "ldap2"',
' ldap = {',
' hostname = "localhost",',
' user = {',
' basedn = "ou=users,dc=yunohost,dc=org",',
' filter = "(&(objectClass=posixAccount)(mail=*@%s))",' % domain,
' usernamefield = "mail",',
' namefield = "cn",',
' },',
' }',
]
with open('/etc/metronome/conf.d/%s.cfg.lua' % domain, 'w') as conf:
for line in conf_lines:
conf.write(line + '\n')
os.system('mkdir -p /var/lib/metronome/%s/pep' % domain.replace('.', '%2e'))
os.system('chown -R metronome: /var/lib/metronome/')
os.system('chown -R metronome: /etc/metronome/conf.d/')
os.system('service metronome restart')
# Nginx
os.system('cp /usr/share/yunohost/yunohost-config/nginx/template.conf /etc/nginx/conf.d/%s.conf' % domain)
os.system('mkdir /etc/nginx/conf.d/%s.d/' % domain)
os.system('sed -i s/yunohost.org/%s/g /etc/nginx/conf.d/%s.conf' % (domain, domain))
os.system('service nginx reload')
if auth.add('virtualdomain=%s,ou=domains' % domain, attr_dict):
result.append(domain)
continue
else:
raise MoulinetteError(errno.EIO, m18n.n('domain_creation_failed'))
os.system('yunohost app ssowatconf > /dev/null 2>&1')
msignals.display(m18n.n('domain_created'), 'success')
return { 'domains': result }
def domain_remove(auth, domains):
"""
Delete domains
Keyword argument:
domains -- Domain(s) to delete
"""
result = []
domains_list = domain_list(auth)['domains']
if not isinstance(domains, list):
domains = [ domains ]
for domain in domains:
if domain not in domains_list:
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
# Check if apps are installed on the domain
for app in os.listdir('/etc/yunohost/apps/'):
with open('/etc/yunohost/apps/' + app +'/settings.yml') as f:
try:
app_domain = yaml.load(f)['domain']
except:
continue
else:
if app_domain == domain:
raise MoulinetteError(errno.EPERM,
m18n.n('domain_uninstall_app_first'))
if auth.remove('virtualdomain=' + domain + ',ou=domains'):
try:
shutil.rmtree('/etc/yunohost/certs/%s' % domain)
os.remove('/var/lib/bind/%s.zone' % domain)
shutil.rmtree('/var/lib/metronome/%s' % domain.replace('.', '%2e'))
os.remove('/etc/metronome/conf.d/%s.cfg.lua' % domain)
shutil.rmtree('/etc/nginx/conf.d/%s.d' % domain)
os.remove('/etc/nginx/conf.d/%s.conf' % domain)
except:
pass
with open('/etc/bind/named.conf.local', 'r') as conf:
conf_lines = conf.readlines()
with open('/etc/bind/named.conf.local', 'w') as conf:
in_block = False
for line in conf_lines:
if re.search(r'^zone "%s' % domain, line):
in_block = True
if in_block:
if re.search(r'^};$', line):
in_block = False
else:
conf.write(line)
result.append(domain)
continue
else:
raise MoulinetteError(errno.EIO, m18n.n('domain_deletion_failed'))
os.system('yunohost app ssowatconf > /dev/null 2>&1')
os.system('service nginx reload')
os.system('service bind9 reload')
os.system('service metronome restart')
msignals.display(m18n.n('domain_deleted'), 'success')
return { 'domains': result }

View file

@ -1,171 +0,0 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2013 YunoHost
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
"""
""" yunohost_dyndns.py
Subscribe and Update DynDNS Hosts
"""
import os
import sys
import requests
import json
import glob
import base64
import errno
from moulinette.core import MoulinetteError
def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None):
"""
Subscribe to a DynDNS service
Keyword argument:
domain -- Full domain to subscribe with
key -- Public DNS key
subscribe_host -- Dynette HTTP API to subscribe to
"""
if domain is None:
with open('/etc/yunohost/current_host', 'r') as f:
domain = f.readline().rstrip()
# Verify if domain is available
if requests.get('http://%s/test/%s' % (subscribe_host, domain)).status_code != 200:
raise MoulinetteError(errno.EEXIST, m18n.n('dyndns_unavailable'))
if key is None:
if len(glob.glob('/etc/yunohost/dyndns/*.key')) == 0:
os.makedirs('/etc/yunohost/dyndns')
print(_("DNS key is being generated, it may take a while..."))
os.system('cd /etc/yunohost/dyndns && dnssec-keygen -a hmac-md5 -b 128 -n USER %s' % domain)
os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private')
key_file = glob.glob('/etc/yunohost/dyndns/*.key')[0]
with open(key_file) as f:
key = f.readline().strip().split(' ')[-1]
# Send subscription
r = requests.post('http://%s/key/%s' % (subscribe_host, base64.b64encode(key)), data={ 'subdomain': domain })
if r.status_code != 201:
try: error = json.loads(r.text)['error']
except: error = "Server error"
raise MoulinetteError(errno.EPERM,
m18n.n('dyndns_registration_failed') % error)
msignals.display(m18n.n('dyndns_registered'), 'success')
dyndns_installcron()
def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None, ip=None):
"""
Update IP on DynDNS platform
Keyword argument:
domain -- Full domain to subscribe with
dyn_host -- Dynette DNS server to inform
key -- Public DNS key
ip -- IP address to send
"""
if domain is None:
with open('/etc/yunohost/current_host', 'r') as f:
domain = f.readline().rstrip()
if ip is None:
new_ip = requests.get('http://ip.yunohost.org').text
else:
new_ip = ip
try:
with open('/etc/yunohost/dyndns/old_ip', 'r') as f:
old_ip = f.readline().rstrip()
except IOError:
old_ip = '0.0.0.0'
if old_ip != new_ip:
host = domain.split('.')[1:]
host = '.'.join(host)
lines = [
'server %s' % dyn_host,
'zone %s' % host,
'update delete %s. A' % domain,
'update delete %s. MX' % domain,
'update delete %s. TXT' % domain,
'update delete pubsub.%s. A' % domain,
'update delete muc.%s. A' % domain,
'update delete vjud.%s. A' % domain,
'update delete _xmpp-client._tcp.%s. SRV' % domain,
'update delete _xmpp-server._tcp.%s. SRV' % domain,
'update add %s. 1800 A %s' % (domain, new_ip),
'update add %s. 14400 MX 5 %s.' % (domain, domain),
'update add %s. 14400 TXT "v=spf1 a mx -all"' % domain,
'update add pubsub.%s. 1800 A %s' % (domain, new_ip),
'update add muc.%s. 1800 A %s' % (domain, new_ip),
'update add vjud.%s. 1800 A %s' % (domain, new_ip),
'update add _xmpp-client._tcp.%s. 14400 SRV 0 5 5222 %s.' % (domain, domain),
'update add _xmpp-server._tcp.%s. 14400 SRV 0 5 5269 %s.' % (domain, domain),
'show',
'send'
]
with open('/etc/yunohost/dyndns/zone', 'w') as zone:
for line in lines:
zone.write(line + '\n')
if key is None:
private_key_file = glob.glob('/etc/yunohost/dyndns/*.private')[0]
else:
private_key_file = key
if os.system('/usr/bin/nsupdate -k %s /etc/yunohost/dyndns/zone' % private_key_file) == 0:
msignals.display(m18n.n('dyndns_ip_updated'), 'success')
with open('/etc/yunohost/dyndns/old_ip', 'w') as f:
f.write(new_ip)
else:
os.system('rm /etc/yunohost/dyndns/old_ip > /dev/null 2>&1')
raise MoulinetteError(errno.EPERM,
m18n.n('dyndns_ip_update_failed'))
def dyndns_installcron():
"""
Install IP update cron
"""
with open('/etc/cron.d/yunohost-dyndns', 'w+') as f:
f.write('*/2 * * * * root yunohost dyndns update >> /dev/null')
msignals.display(m18n.n('dyndns_cron_installed'), 'success')
def dyndns_removecron():
"""
Remove IP update cron
"""
try:
os.remove("/etc/cron.d/yunohost-dyndns")
except:
raise MoulinetteError(errno.EIO, m18n.n('dyndns_cron_remove_failed'))
msignals.display(m18n.n('dyndns_cron_removed'), 'success')

View file

@ -1,274 +0,0 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2013 YunoHost
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
"""
""" yunohost_firewall.py
Manage firewall rules
"""
import os
import sys
import yaml
import errno
try:
import miniupnpc
except ImportError:
sys.stderr.write('Error: Yunohost CLI Require miniupnpc lib\n')
sys.exit(1)
from moulinette.core import MoulinetteError
def firewall_allow(port=None, protocol='TCP', ipv6=False, no_upnp=False):
"""
Allow connection port/protocol
Keyword argument:
port -- Port to open
protocol -- Protocol associated with port
ipv6 -- ipv6
no_upnp -- Do not request for uPnP
"""
port = int(port)
ipv = "ipv4"
protocols = [protocol]
firewall = firewall_list(raw=True)
upnp = not no_upnp and firewall['uPnP']['enabled']
if ipv6:
ipv = "ipv6"
if protocol == "Both":
protocols = ['UDP', 'TCP']
for protocol in protocols:
if upnp and port not in firewall['uPnP'][protocol]:
firewall['uPnP'][protocol].append(port)
if port not in firewall[ipv][protocol]:
firewall[ipv][protocol].append(port)
else:
msignals.display(m18n.n('port_already_opened') % port, 'warning')
with open('/etc/yunohost/firewall.yml', 'w') as f:
yaml.safe_dump(firewall, f, default_flow_style=False)
return firewall_reload()
def firewall_disallow(port=None, protocol='TCP', ipv6=False):
"""
Allow connection port/protocol
Keyword argument:
port -- Port to open
protocol -- Protocol associated with port
ipv6 -- ipv6
"""
port = int(port)
ipv = "ipv4"
protocols = [protocol]
firewall = firewall_list(raw=True)
if ipv6:
ipv = "ipv6"
if protocol == "Both":
protocols = ['UDP', 'TCP']
for protocol in protocols:
if port in firewall['uPnP'][protocol]:
firewall['uPnP'][protocol].remove(port)
if port in firewall[ipv][protocol]:
firewall[ipv][protocol].remove(port)
else:
msignals.display(m18n.n('port_already_closed') % port, 'warning')
with open('/etc/yunohost/firewall.yml', 'w') as f:
yaml.safe_dump(firewall, f, default_flow_style=False)
return firewall_reload()
def firewall_list(raw=False):
"""
List all firewall rules
Keyword argument:
raw -- Return the complete YAML dict
"""
with open('/etc/yunohost/firewall.yml') as f:
firewall = yaml.load(f)
if raw:
return firewall
else:
return { "openned_ports": firewall['ipv4']['TCP'] }
def firewall_reload():
"""
Reload all firewall rules
"""
from yunohost.hook import hook_callback
firewall = firewall_list(raw=True)
upnp = firewall['uPnP']['enabled']
# IPv4
if os.system("iptables -P INPUT ACCEPT") != 0:
raise MoulinetteError(errno.ESRCH, m18n.n('iptables_unavailable'))
if upnp:
try:
upnpc = miniupnpc.UPnP()
upnpc.discoverdelay = 200
if upnpc.discover() == 1:
upnpc.selectigd()
for protocol in ['TCP', 'UDP']:
for port in firewall['uPnP'][protocol]:
if upnpc.getspecificportmapping(port, protocol):
try: upnpc.deleteportmapping(port, protocol)
except: pass
upnpc.addportmapping(port, protocol, upnpc.lanaddr, port, 'yunohost firewall : port %d' % port, '')
else:
raise MoulinetteError(errno.ENXIO, m18n.n('upnp_dev_not_found'))
except:
msignals.display(m18n.n('upnp_port_open_failed'), 'warning')
os.system("iptables -F")
os.system("iptables -X")
os.system("iptables -A INPUT -m state --state ESTABLISHED -j ACCEPT")
if 22 not in firewall['ipv4']['TCP']:
firewall_allow(22)
# Loop
for protocol in ['TCP', 'UDP']:
for port in firewall['ipv4'][protocol]:
os.system("iptables -A INPUT -p %s --dport %d -j ACCEPT" % (protocol, port))
hook_callback('post_iptable_rules', [upnp, os.path.exists("/proc/net/if_inet6")])
os.system("iptables -A INPUT -i lo -j ACCEPT")
os.system("iptables -A INPUT -p icmp -j ACCEPT")
os.system("iptables -P INPUT DROP")
# IPv6
if os.path.exists("/proc/net/if_inet6"):
os.system("ip6tables -P INPUT ACCEPT")
os.system("ip6tables -F")
os.system("ip6tables -X")
os.system("ip6tables -A INPUT -m state --state ESTABLISHED -j ACCEPT")
if 22 not in firewall['ipv6']['TCP']:
firewall_allow(22, ipv6=True)
# Loop v6
for protocol in ['TCP', 'UDP']:
for port in firewall['ipv6'][protocol]:
os.system("ip6tables -A INPUT -p %s --dport %d -j ACCEPT" % (protocol, port))
os.system("ip6tables -A INPUT -i lo -j ACCEPT")
os.system("ip6tables -A INPUT -p icmpv6 -j ACCEPT")
os.system("ip6tables -P INPUT DROP")
os.system("service fail2ban restart")
msignals.display(m18n.n('firewall_reloaded'), 'success')
return firewall_list()
def firewall_upnp(action=None):
"""
Add uPnP cron and enable uPnP in firewall.yml, or the opposite.
Keyword argument:
action -- enable/disable
"""
firewall = firewall_list(raw=True)
if action:
action = action[0]
if action == 'enable':
firewall['uPnP']['enabled'] = True
with open('/etc/cron.d/yunohost-firewall', 'w+') as f:
f.write('*/50 * * * * root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin yunohost firewall reload >>/dev/null')
msignals.display(m18n.n('upnp_enabled'), 'success')
if action == 'disable':
firewall['uPnP']['enabled'] = False
try:
upnpc = miniupnpc.UPnP()
upnpc.discoverdelay = 200
if upnpc.discover() == 1:
upnpc.selectigd()
for protocol in ['TCP', 'UDP']:
for port in firewall['uPnP'][protocol]:
if upnpc.getspecificportmapping(port, protocol):
try: upnpc.deleteportmapping(port, protocol)
except: pass
except: pass
try: os.remove('/etc/cron.d/yunohost-firewall')
except: pass
msignals.display(m18n.n('upnp_disabled'), 'success')
if action:
os.system("cp /etc/yunohost/firewall.yml /etc/yunohost/firewall.yml.old")
with open('/etc/yunohost/firewall.yml', 'w') as f:
yaml.safe_dump(firewall, f, default_flow_style=False)
return { "enabled": firewall['uPnP']['enabled'] }
def firewall_stop():
"""
Stop iptables and ip6tables
"""
if os.system("iptables -P INPUT ACCEPT") != 0:
raise MoulinetteError(errno.ESRCH, m18n.n('iptables_unavailable'))
os.system("iptables -F")
os.system("iptables -X")
if os.path.exists("/proc/net/if_inet6"):
os.system("ip6tables -P INPUT ACCEPT")
os.system("ip6tables -F")
os.system("ip6tables -X")
if os.path.exists("/etc/cron.d/yunohost-firewall"):
firewall_upnp('disable')

View file

@ -1,183 +0,0 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2013 YunoHost
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
"""
""" yunohost_hook.py
Manage hooks
"""
import os
import sys
import re
import json
import errno
from moulinette.core import MoulinetteError
hook_folder = '/usr/share/yunohost/hooks/'
def hook_add(app, file):
"""
Store hook script to filsystem
Keyword argument:
app -- App to link with
file -- Script to add (/path/priority-file)
"""
path, filename = os.path.split(file)
if '-' in filename:
priority, action = filename.split('-')
else:
priority = '50'
action = filename
try: os.listdir(hook_folder + action)
except OSError: os.makedirs(hook_folder + action)
finalpath = hook_folder + action +'/'+ priority +'-'+ app
print app
os.system('cp %s %s' % (file, finalpath))
os.system('chown -hR admin: %s' % hook_folder)
return { 'hook': finalpath }
def hook_remove(app):
"""
Remove hooks linked to a specific app
Keyword argument:
app -- Scripts related to app will be removed
"""
try:
for action in os.listdir(hook_folder):
for script in os.listdir(hook_folder + action):
if script.endswith(app):
os.remove(hook_folder + action +'/'+ script)
except OSError: pass
def hook_callback(action, args=None):
"""
Execute all scripts binded to an action
Keyword argument:
action -- Action name
args -- Ordered list of arguments to pass to the script
"""
try: os.listdir(hook_folder + action)
except OSError: pass
else:
if args is None:
args = []
elif not isinstance(args, list):
args = [args]
for hook in os.listdir(hook_folder + action):
try:
hook_exec(file=hook_folder + action +'/'+ hook, args=args)
except: pass
def hook_check(file):
"""
Parse the script file and get arguments
Keyword argument:
file -- File to check
"""
try:
with open(file[:file.index('scripts/')] + 'manifest.json') as f:
manifest = json.loads(str(f.read()))
except:
raise MoulinetteError(errno.EIO, m18n.n('app_manifest_invalid'))
action = file[file.index('scripts/') + 8:]
if 'arguments' in manifest and action in manifest['arguments']:
return manifest['arguments'][action]
else:
return {}
def hook_exec(file, args=None):
"""
Execute hook from a file with arguments
Keyword argument:
file -- Script to execute
args -- Arguments to pass to the script
"""
if isinstance(args, list):
arg_list = args
else:
required_args = hook_check(file)
if args is None:
args = {}
arg_list = []
for arg in required_args:
if arg['name'] in args:
if 'choices' in arg and args[arg['name']] not in arg['choices']:
raise MoulinetteError(errno.EINVAL,
m18n.n('hook_choice_invalid')
% args[arg['name']])
arg_list.append(args[arg['name']])
else:
if os.isatty(1) and 'ask' in arg:
# Retrieve proper ask string
ask_string = None
for lang in [m18n.locale, m18n.default_locale]:
if lang in arg['ask']:
ask_string = arg['ask'][lang]
break
if not ask_string:
# Fallback to en
ask_string = arg['ask']['en']
# Append extra strings
if 'choices' in arg:
ask_string += ' (%s)' % '|'.join(arg['choices'])
if 'default' in arg:
ask_string += ' (default: %s)' % arg['default']
input_string = msignals.prompt(ask_string)
if input_string == '' and 'default' in arg:
input_string = arg['default']
arg_list.append(input_string)
elif 'default' in arg:
arg_list.append(arg['default'])
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('hook_argument_missing')
% arg['name'])
file_path = "./"
if "/" in file and file[0:2] != file_path:
file_path = os.path.dirname(file)
file = file.replace(file_path +"/", "")
return os.system('su - admin -c "cd \\"%s\\" && bash \\"%s\\" %s"' % (file_path, file, ' '.join(arg_list)))
#TODO: Allow python script

View file

@ -1,128 +0,0 @@
{
"yunohost" : "YunoHost",
"yunohost_not_installed" : "YunoHost is not or not correctly installed. Please execute 'yunohost tools postinstall'.",
"upgrade_complete" : "Upgrade complete",
"installation_complete" : "Installation complete",
"installation_failed" : "Installation failed",
"no_list_found" : "No list found",
"custom_list_name_required" : "You must provide a name for your custom list",
"list_retrieve_error" : "Unable to retrieve the remote list",
"list_feteched" : "List successfully fetched",
"list_unknown" : "Unknown list",
"list_removed" : "List successfully removed",
"app_unknown" : "Unknown app",
"app_no_upgrade" : "No app to upgrade",
"app_not_installed" : "%s is not installed",
"custom_app_url_required" : "You must provide an URL to upgrade your custom app %s",
"app_recent_version_required" : "%s requires a more recent version of the moulinette",
"app_upgraded" : "%s successfully upgraded",
"app_id_invalid" : "Invalid app id",
"app_already_installed" : "%s is already installed",
"app_removed" : "%s successfully removed",
"app_location_already_used" : "An app is already installed on this location",
"app_location_install_failed" : "Unable to install the app on this location",
"app_extraction_failed" : "Unable to extract installation files",
"app_install_files_invalid" : "Invalid installation files",
"app_manifest_invalid" : "Invalid app manifest",
"app_sources_fetch_failed" : "Unable to fetch sources files",
"ssowat_conf_updated" : "SSOwat persistent configuration successfully updated",
"ssowat_conf_generated" : "SSOwat configuration successfully generated",
"mysql_db_creation_failed" : "MySQL database creation failed",
"mysql_db_init_failed" : "MySQL database init failed",
"mysql_db_initialized" : "MySQL database successfully initialized",
"extracting" : "Extracting...",
"downloading" : "Downloading...",
"done" : "Done.",
"domain_unknown" : "Unknown domain",
"domain_dyndns_invalid" : "Invalid domain to use with DynDNS",
"domain_dyndns_already_subscribed" : "You already have subscribed to a DynDNS domain",
"domain_dyndns_root_unknown" : "Unknown DynDNS root domain",
"domain_cert_gen_failed" : "Unable to generate certificate",
"domain_exists" : "Domain already exists",
"domain_zone_exists" : "Zone file already exists",
"domain_creation_failed" : "Unable to create domain",
"domain_created" : "Domain successfully created",
"domain_uninstall_app_first" : "One or more apps are installed on this domain. Please uninstall them before proceed to domain removal.",
"domain_deletion_failed" : "Unable to delete domain",
"domain_deleted" : "Domain successfully deleted",
"dyndns_unavailable" : "Unavailable DynDNS subdomain",
"dyndns_registration_failed" : "Unable to register DynDNS domain: %s",
"dyndns_registered" : "DynDNS domain successfully registered",
"dyndns_ip_update_failed" : "Unable to update IP address on DynDNS",
"dyndns_ip_updated" : "IP address successfully updated on DynDNS",
"dyndns_cron_installed" : "DynDNS cron job successfully installed",
"dyndns_cron_remove_failed" : "Unable to remove DynDNS cron job",
"dyndns_cron_removed" : "DynDNS cron job successfully removed",
"port_available" : "Port %d is available",
"port_unavailable" : "Port %d is not available",
"port_already_opened" : "Port %d is already opened",
"port_already_closed" : "Port %d is already closed",
"iptables_unavailable" : "You cannot play with iptables here. You are either in a container or your kernel does not support it.",
"upnp_dev_not_found" : "No uPnP device found",
"upnp_port_open_failed" : "Unable to open uPnP ports",
"upnp_enabled" : "uPnP successfully enabled",
"upnp_disabled" : "uPnP successfully disabled",
"firewall_reloaded" : "Firewall successfully reloaded",
"hook_choice_invalid" : "Invalid choice '%s'",
"hook_argument_missing" : "Missing argument '%s'",
"mountpoint_unknown" : "Unknown mountpoint",
"unit_unknown" : "Unknown unit '%s'",
"monitor_period_invalid" : "Invalid time period",
"monitor_stats_no_update" : "No monitoring statistics to update",
"monitor_stats_file_not_found" : "Statistics file not found",
"monitor_stats_period_unavailable" : "No available statistics for the period",
"monitor_enabled" : "Server monitoring successfully enabled",
"monitor_disabled" : "Server monitoring successfully disabled",
"monitor_not_enabled" : "Server monitoring is not enabled",
"monitor_glances_con_failed" : "Unable to connect to Glances server",
"service_unknown" : "Unknown service '%s'",
"service_start_failed" : "Unable to start service '%s'",
"service_already_started" : "Service '%s' is already started",
"service_started" : "Service '%s' successfully started",
"service_stop_failed" : "Unable to stop service '%s'",
"service_already_stopped" : "Service '%s' is already stopped",
"service_stopped" : "Service '%s' successfully stopped",
"service_enable_failed" : "Unable to enable service '%s'",
"service_enabled" : "Service '%s' successfully enabled",
"service_disable_failed" : "Unable to disable service '%s'",
"service_disabled" : "Service '%s' successfully disabled",
"service_status_failed" : "Unable to determine status of service '%s'",
"service_no_log" : "No log to display for service '%s'",
"service_cmd_exec_failed" : "Unable to execute command '%s'",
"ldap_initialized" : "LDAP successfully initialized",
"password_too_short" : "Password is too short",
"admin_password_change_failed" : "Unable to change password",
"admin_password_changed" : "Administration password successfully changed",
"maindomain_change_failed" : "Unable to change main domain",
"maindomain_changed" : "Main domain successfully changed",
"yunohost_installing" : "Installing YunoHost...",
"yunohost_already_installed" : "YunoHost is already installed",
"yunohost_ca_creation_failed" : "Unable to create certificate authority",
"yunohost_configured" : "YunoHost successfully configured",
"update_cache_failed" : "Unable to update APT cache",
"system_no_upgrade" : "There is no packages to upgrade",
"system_upgraded" : "System successfully upgraded",
"field_invalid" : "Invalid field '%s'",
"mail_domain_unknown" : "Unknown mail address domain '%s'",
"mail_alias_remove_failed" : "Unable to remove mail alias '%s'",
"mail_forward_remove_failed" : "Unable to remove mail forward '%s'",
"user_unknown" : "Unknown user",
"user_creation_failed" : "Unable to create user",
"user_created" : "User successfully created",
"user_deletion_failed" : "Unable to delete user",
"user_deleted" : "User successfully deleted",
"user_update_failed" : "Unable to update user",
"user_updated" : "User successfully updated",
"user_info_failed" : "Unable to retrieve user information"
}

View file

@ -1,710 +0,0 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2013 YunoHost
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
"""
""" yunohost_monitor.py
Monitoring functions
"""
import re
import json
import time
import psutil
import calendar
import subprocess
import xmlrpclib
import os.path
import errno
import cPickle as pickle
from urllib import urlopen
from datetime import datetime, timedelta
from moulinette.core import MoulinetteError
glances_uri = 'http://127.0.0.1:61209'
stats_path = '/var/lib/yunohost/stats'
crontab_path = '/etc/cron.d/yunohost-monitor'
def monitor_disk(units=None, mountpoint=None, human_readable=False):
"""
Monitor disk space and usage
Keyword argument:
units -- Unit(s) to monitor
mountpoint -- Device mountpoint
human_readable -- Print sizes in human readable format
"""
glances = _get_glances_api()
result_dname = None
result = {}
if units is None:
units = ['io', 'filesystem']
_format_dname = lambda d: (os.path.realpath(d)).replace('/dev/', '')
# Get mounted devices
devices = {}
for p in psutil.disk_partitions(all=True):
if not p.device.startswith('/dev/') or not p.mountpoint:
continue
if mountpoint is None:
devices[_format_dname(p.device)] = p.mountpoint
elif mountpoint == p.mountpoint:
dn = _format_dname(p.device)
devices[dn] = p.mountpoint
result_dname = dn
if len(devices) == 0:
if mountpoint is not None:
raise MoulinetteError(errno.ENODEV, m18n.n('mountpoint_unknown'))
return result
# Retrieve monitoring for unit(s)
for u in units:
if u == 'io':
## Define setter
if len(units) > 1:
def _set(dn, dvalue):
try:
result[dn][u] = dvalue
except KeyError:
result[dn] = { u: dvalue }
else:
def _set(dn, dvalue):
result[dn] = dvalue
# Iterate over values
devices_names = devices.keys()
for d in json.loads(glances.getDiskIO()):
dname = d.pop('disk_name')
try:
devices_names.remove(dname)
except:
continue
else:
_set(dname, d)
for dname in devices_names:
_set(dname, 'not-available')
elif u == 'filesystem':
## Define setter
if len(units) > 1:
def _set(dn, dvalue):
try:
result[dn][u] = dvalue
except KeyError:
result[dn] = { u: dvalue }
else:
def _set(dn, dvalue):
result[dn] = dvalue
# Iterate over values
devices_names = devices.keys()
for d in json.loads(glances.getFs()):
dname = _format_dname(d.pop('device_name'))
try:
devices_names.remove(dname)
except:
continue
else:
if human_readable:
for i in ['used', 'avail', 'size']:
d[i] = _binary_to_human(d[i]) + 'B'
_set(dname, d)
for dname in devices_names:
_set(dname, 'not-available')
else:
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown') % u)
if result_dname is not None:
return result[result_dname]
return result
def monitor_network(units=None, human_readable=False):
"""
Monitor network interfaces
Keyword argument:
units -- Unit(s) to monitor
human_readable -- Print sizes in human readable format
"""
glances = _get_glances_api()
result = {}
if units is None:
units = ['usage', 'infos']
# Get network devices and their addresses
devices = {}
output = subprocess.check_output('ip addr show'.split())
for d in re.split('^(?:[0-9]+: )', output, flags=re.MULTILINE):
d = re.sub('\n[ ]+', ' % ', d) # Replace new lines by %
m = re.match('([a-z]+[0-9]?): (.*)', d) # Extract device name (1) and its addresses (2)
if m:
devices[m.group(1)] = m.group(2)
# Retrieve monitoring for unit(s)
for u in units:
if u == 'usage':
result[u] = {}
for i in json.loads(glances.getNetwork()):
iname = i['interface_name']
if iname in devices.keys():
del i['interface_name']
if human_readable:
for k in i.keys():
if k != 'time_since_update':
i[k] = _binary_to_human(i[k]) + 'B'
result[u][iname] = i
elif u == 'infos':
try:
p_ip = str(urlopen('http://ip.yunohost.org').read())
except:
p_ip = 'unknown'
l_ip = 'unknown'
for name, addrs in devices.items():
if name == 'lo':
continue
if not isinstance(l_ip, dict):
l_ip = {}
l_ip[name] = _extract_inet(addrs)
gateway = 'unknown'
output = subprocess.check_output('ip route show'.split())
m = re.search('default via (.*) dev ([a-z]+[0-9]?)', output)
if m:
addr = _extract_inet(m.group(1), True)
if len(addr) == 1:
proto, gateway = addr.popitem()
result[u] = {
'public_ip': p_ip,
'local_ip': l_ip,
'gateway': gateway
}
else:
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown') % u)
if len(units) == 1:
return result[units[0]]
return result
def monitor_system(units=None, human_readable=False):
"""
Monitor system informations and usage
Keyword argument:
units -- Unit(s) to monitor
human_readable -- Print sizes in human readable format
"""
glances = _get_glances_api()
result = {}
if units is None:
units = ['memory', 'cpu', 'process', 'uptime', 'infos']
# Retrieve monitoring for unit(s)
for u in units:
if u == 'memory':
ram = json.loads(glances.getMem())
swap = json.loads(glances.getMemSwap())
if human_readable:
for i in ram.keys():
if i != 'percent':
ram[i] = _binary_to_human(ram[i]) + 'B'
for i in swap.keys():
if i != 'percent':
swap[i] = _binary_to_human(swap[i]) + 'B'
result[u] = {
'ram': ram,
'swap': swap
}
elif u == 'cpu':
result[u] = {
'load': json.loads(glances.getLoad()),
'usage': json.loads(glances.getCpu())
}
elif u == 'process':
result[u] = json.loads(glances.getProcessCount())
elif u == 'uptime':
result[u] = (str(datetime.now() - datetime.fromtimestamp(psutil.BOOT_TIME)).split('.')[0])
elif u == 'infos':
result[u] = json.loads(glances.getSystem())
else:
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown') % u)
if len(units) == 1 and type(result[units[0]]) is not str:
return result[units[0]]
return result
def monitor_update_stats(period):
"""
Update monitoring statistics
Keyword argument:
period -- Time period to update (day, week, month)
"""
if period not in ['day', 'week', 'month']:
raise MoulinetteError(errno.EINVAL, m18n.n('monitor_period_invalid'))
stats = _retrieve_stats(period)
if not stats:
stats = { 'disk': {}, 'network': {}, 'system': {}, 'timestamp': [] }
monitor = None
# Get monitoring stats
if period == 'day':
monitor = _monitor_all('day')
else:
t = stats['timestamp']
p = 'day' if period == 'week' else 'week'
if len(t) > 0:
monitor = _monitor_all(p, t[len(t) - 1])
else:
monitor = _monitor_all(p, 0)
if not monitor:
raise MoulinetteError(errno.ENODATA, m18n.n('monitor_stats_no_update'))
stats['timestamp'].append(time.time())
# Append disk stats
for dname, units in monitor['disk'].items():
disk = {}
# Retrieve current stats for disk name
if dname in stats['disk'].keys():
disk = stats['disk'][dname]
for unit, values in units.items():
# Continue if unit doesn't contain stats
if not isinstance(values, dict):
continue
# Retrieve current stats for unit and append new ones
curr = disk[unit] if unit in disk.keys() else {}
if unit == 'io':
disk[unit] = _append_to_stats(curr, values, 'time_since_update')
elif unit == 'filesystem':
disk[unit] = _append_to_stats(curr, values, ['fs_type', 'mnt_point'])
stats['disk'][dname] = disk
# Append network stats
net_usage = {}
for iname, values in monitor['network']['usage'].items():
# Continue if units doesn't contain stats
if not isinstance(values, dict):
continue
# Retrieve current stats and append new ones
curr = {}
if 'usage' in stats['network'] and iname in stats['network']['usage']:
curr = stats['network']['usage'][iname]
net_usage[iname] = _append_to_stats(curr, values, 'time_since_update')
stats['network'] = { 'usage': net_usage, 'infos': monitor['network']['infos'] }
# Append system stats
for unit, values in monitor['system'].items():
# Continue if units doesn't contain stats
if not isinstance(values, dict):
continue
# Set static infos unit
if unit == 'infos':
stats['system'][unit] = values
continue
# Retrieve current stats and append new ones
curr = stats['system'][unit] if unit in stats['system'].keys() else {}
stats['system'][unit] = _append_to_stats(curr, values)
_save_stats(stats, period)
def monitor_show_stats(period, date=None):
"""
Show monitoring statistics
Keyword argument:
period -- Time period to show (day, week, month)
"""
if period not in ['day', 'week', 'month']:
raise MoulinetteError(errno.EINVAL, m18n.n('monitor_period_invalid'))
result = _retrieve_stats(period, date)
if result is False:
raise MoulinetteError(errno.ENOENT,
m18n.n('monitor_stats_file_not_found'))
elif result is None:
raise MoulinetteError(errno.EINVAL,
m18n.n('monitor_stats_period_unavailable'))
return result
def monitor_enable(no_stats=False):
"""
Enable server monitoring
Keyword argument:
no_stats -- Disable monitoring statistics
"""
from yunohost.service import (service_status, service_enable,
service_start)
glances = service_status('glances')
if glances['status'] != 'running':
service_start('glances')
if glances['loaded'] != 'enabled':
service_enable('glances')
# Install crontab
if not no_stats:
cmd = 'yunohost monitor update-stats'
# day: every 5 min # week: every 1 h # month: every 4 h #
rules = ('*/5 * * * * root %(cmd)s day --no-ldap >> /dev/null\n' + \
'3 * * * * root %(cmd)s week --no-ldap >> /dev/null\n' + \
'6 */4 * * * root %(cmd)s month --no-ldap >> /dev/null') % {'cmd': cmd}
os.system("touch %s" % crontab_path)
os.system("echo '%s' >%s" % (rules, crontab_path))
msignals.display(m18n.n('monitor_enabled'), 'success')
def monitor_disable():
"""
Disable server monitoring
"""
from yunohost.service import (service_status, service_disable,
service_stop)
glances = service_status('glances')
if glances['status'] != 'inactive':
service_stop('glances')
if glances['loaded'] != 'disabled':
try:
service_disable('glances')
except MoulinetteError as e:
msignals.display(e.strerror, 'warning')
# Remove crontab
try:
os.remove(crontab_path)
except:
pass
msignals.display(m18n.n('monitor_disabled'), 'success')
def _get_glances_api():
"""
Retrieve Glances API running on the local server
"""
try:
p = xmlrpclib.ServerProxy(glances_uri)
p.system.methodHelp('getAll')
except (xmlrpclib.ProtocolError, IOError):
pass
else:
return p
from yunohost.service import service_status
if service_status('glances')['status'] != 'running':
raise MoulinetteError(errno.EPERM, m18n.n('monitor_not_enabled'))
raise MoulinetteError(errno.EIO, m18n.n('monitor_glances_con_failed'))
def _extract_inet(string, skip_netmask=False, skip_loopback=True):
"""
Extract IP addresses (v4 and/or v6) from a string limited to one
address by protocol
Keyword argument:
string -- String to search in
skip_netmask -- True to skip subnet mask extraction
skip_loopback -- False to include addresses reserved for the
loopback interface
Returns:
A dict of {protocol: address} with protocol one of 'ipv4' or 'ipv6'
"""
ip4_pattern = '((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'
ip6_pattern = '(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)'
ip4_pattern += '/[0-9]{1,2})' if not skip_netmask else ')'
ip6_pattern += '/[0-9]{1,3})' if not skip_netmask else ')'
result = {}
for m in re.finditer(ip4_pattern, string):
addr = m.group(1)
if skip_loopback and addr.startswith('127.'):
continue
# Limit to only one result
result['ipv4'] = addr
break
for m in re.finditer(ip6_pattern, string):
addr = m.group(1)
if skip_loopback and addr == '::1':
continue
# Limit to only one result
result['ipv6'] = addr
break
return result
def _binary_to_human(n, customary=False):
"""
Convert bytes or bits into human readable format with binary prefix
Keyword argument:
n -- Number to convert
customary -- Use customary symbol instead of IEC standard
"""
symbols = ('Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
if customary:
symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
prefix = {}
for i, s in enumerate(symbols):
prefix[s] = 1 << (i+1)*10
for s in reversed(symbols):
if n >= prefix[s]:
value = float(n) / prefix[s]
return '%.1f%s' % (value, s)
return "%s" % n
def _retrieve_stats(period, date=None):
"""
Retrieve statistics from pickle file
Keyword argument:
period -- Time period to retrieve (day, week, month)
date -- Date of stats to retrieve
"""
pkl_file = None
# Retrieve pickle file
if date is not None:
timestamp = calendar.timegm(date)
pkl_file = '%s/%d_%s.pkl' % (stats_path, timestamp, period)
else:
pkl_file = '%s/%s.pkl' % (stats_path, period)
if not os.path.isfile(pkl_file):
return False
# Read file and process its content
with open(pkl_file, 'r') as f:
result = pickle.load(f)
if not isinstance(result, dict):
return None
return result
def _save_stats(stats, period, date=None):
"""
Save statistics to pickle file
Keyword argument:
stats -- Stats dict to save
period -- Time period of stats (day, week, month)
date -- Date of stats
"""
pkl_file = None
# Set pickle file name
if date is not None:
timestamp = calendar.timegm(date)
pkl_file = '%s/%d_%s.pkl' % (stats_path, timestamp, period)
else:
pkl_file = '%s/%s.pkl' % (stats_path, period)
if not os.path.isdir(stats_path):
os.makedirs(stats_path)
# Limit stats
if date is None:
t = stats['timestamp']
limit = { 'day': 86400, 'week': 604800, 'month': 2419200 }
if (t[len(t) - 1] - t[0]) > limit[period]:
begin = t[len(t) - 1] - limit[period]
stats = _filter_stats(stats, begin)
# Write file content
with open(pkl_file, 'w') as f:
pickle.dump(stats, f)
return True
def _monitor_all(period=None, since=None):
"""
Monitor all units (disk, network and system) for the given period
If since is None, real-time monitoring is returned. Otherwise, the
mean of stats since this timestamp is calculated and returned.
Keyword argument:
period -- Time period to monitor (day, week, month)
since -- Timestamp of the stats beginning
"""
result = { 'disk': {}, 'network': {}, 'system': {} }
# Real-time stats
if period == 'day' and since is None:
result['disk'] = monitor_disk()
result['network'] = monitor_network()
result['system'] = monitor_system()
return result
# Retrieve stats and calculate mean
stats = _retrieve_stats(period)
if not stats:
return None
stats = _filter_stats(stats, since)
if not stats:
return None
result = _calculate_stats_mean(stats)
return result
def _filter_stats(stats, t_begin=None, t_end=None):
"""
Filter statistics by beginning and/or ending timestamp
Keyword argument:
stats -- Dict stats to filter
t_begin -- Beginning timestamp
t_end -- Ending timestamp
"""
if t_begin is None and t_end is None:
return stats
i_begin = i_end = None
# Look for indexes of timestamp interval
for i, t in enumerate(stats['timestamp']):
if t_begin and i_begin is None and t >= t_begin:
i_begin = i
if t_end and i != 0 and i_end is None and t > t_end:
i_end = i
# Check indexes
if i_begin is None:
if t_begin and t_begin > stats['timestamp'][0]:
return None
i_begin = 0
if i_end is None:
if t_end and t_end < stats['timestamp'][0]:
return None
i_end = len(stats['timestamp'])
if i_begin == 0 and i_end == len(stats['timestamp']):
return stats
# Filter function
def _filter(s, i, j):
for k, v in s.items():
if isinstance(v, dict):
s[k] = _filter(v, i, j)
elif isinstance(v, list):
s[k] = v[i:j]
return s
stats = _filter(stats, i_begin, i_end)
return stats
def _calculate_stats_mean(stats):
"""
Calculate the weighted mean for each statistic
Keyword argument:
stats -- Stats dict to process
"""
timestamp = stats['timestamp']
t_sum = sum(timestamp)
del stats['timestamp']
# Weighted mean function
def _mean(s, t, ts):
for k, v in s.items():
if isinstance(v, dict):
s[k] = _mean(v, t, ts)
elif isinstance(v, list):
try:
nums = [ float(x * t[i]) for i, x in enumerate(v) ]
except:
pass
else:
s[k] = sum(nums) / float(ts)
return s
stats = _mean(stats, timestamp, t_sum)
return stats
def _append_to_stats(stats, monitor, statics=[]):
"""
Append monitoring statistics to current statistics
Keyword argument:
stats -- Current stats dict
monitor -- Monitoring statistics
statics -- List of stats static keys
"""
if isinstance(statics, str):
statics = [statics]
# Appending function
def _append(s, m, st):
for k, v in m.items():
if k in st:
s[k] = v
elif isinstance(v, dict):
if k not in s:
s[k] = {}
s[k] = _append(s[k], v, st)
else:
if k not in s:
s[k] = []
if isinstance(v, list):
s[k].extend(v)
else:
s[k].append(v)
return s
stats = _append(stats, monitor, statics)
return stats

View file

@ -1,271 +0,0 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2013 YunoHost
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
"""
""" yunohost_service.py
Manage services
"""
import yaml
import glob
import subprocess
import errno
import os.path
from moulinette.core import MoulinetteError
def service_start(names):
"""
Start one or more services
Keyword argument:
names -- Services name to start
"""
if isinstance(names, str):
names = [names]
for name in names:
if _run_service_command('start', name):
msignals.display(m18n.n('service_started') % name, 'success')
else:
if service_status(name)['status'] != 'running':
raise MoulinetteError(errno.EPERM,
m18n.n('service_start_failed') % name)
msignals.display(m18n.n('service_already_started') % name)
def service_stop(names):
"""
Stop one or more services
Keyword argument:
name -- Services name to stop
"""
if isinstance(names, str):
names = [names]
for name in names:
if _run_service_command('stop', name):
msignals.display(m18n.n('service_stopped') % name, 'success')
else:
if service_status(name)['status'] != 'inactive':
raise MoulinetteError(errno.EPERM,
m18n.n('service_stop_failed') % name)
msignals.display(m18n.n('service_already_stopped') % name)
def service_enable(names):
"""
Enable one or more services
Keyword argument:
names -- Services name to enable
"""
if isinstance(names, str):
names = [names]
for name in names:
if _run_service_command('enable', name):
msignals.display(m18n.n('service_enabled') % name, 'success')
else:
raise MoulinetteError(errno.EPERM,
m18n.n('service_enable_failed') % name)
def service_disable(names):
"""
Disable one or more services
Keyword argument:
names -- Services name to disable
"""
if isinstance(names, str):
names = [names]
for name in names:
if _run_service_command('disable', name):
msignals.display(m18n.n('service_disabled') % name, 'success')
else:
raise MoulinetteError(errno.EPERM,
m18n.n('service_disable_failed') % name)
def service_status(names=[]):
"""
Show status information about one or more services (all by default)
Keyword argument:
names -- Services name to show
"""
services = _get_services()
check_names = True
result = {}
if isinstance(names, str):
names = [names]
elif len(names) == 0:
names = services.keys()
check_names = False
for name in names:
if check_names and name not in services.keys():
raise MoulinetteError(errno.EINVAL,
m18n.n('service_unknown') % name)
status = None
if services[name]['status'] == 'service':
status = 'service %s status' % name
else:
status = str(services[name]['status'])
runlevel = 5
if 'runlevel' in services[name].keys():
runlevel = int(services[name]['runlevel'])
result[name] = { 'status': 'unknown', 'loaded': 'unknown' }
# Retrieve service status
try:
ret = subprocess.check_output(status.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
if 'usage:' not in e.output.lower():
result[name]['status'] = 'inactive'
else:
# TODO: Log output?
msignals.display(m18n.n('service_status_failed') % name,
'warning')
else:
result[name]['status'] = 'running'
# Retrieve service loading
rc_path = glob.glob("/etc/rc%d.d/S[0-9][0-9]%s" % (runlevel, name))
if len(rc_path) == 1 and os.path.islink(rc_path[0]):
result[name]['loaded'] = 'enabled'
elif os.path.isfile("/etc/init.d/%s" % name):
result[name]['loaded'] = 'disabled'
else:
result[name]['loaded'] = 'not-found'
if len(names) == 1:
return result[names[0]]
return result
def service_log(name, number=50):
"""
Log every log files of a service
Keyword argument:
name -- Service name to log
number -- Number of lines to display
"""
services = _get_services()
if name not in services.keys():
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown') % name)
if 'log' in services[name]:
log_list = services[name]['log']
result = {}
if not isinstance(log_list, list):
log_list = [log_list]
for log_path in log_list:
if os.path.isdir(log_path):
for log in [ f for f in os.listdir(log_path) if os.path.isfile(os.path.join(log_path, f)) and f[-4:] == '.log' ]:
result[os.path.join(log_path, log)] = _tail(os.path.join(log_path, log), int(number))
else:
result[log_path] = _tail(log_path, int(number))
else:
raise MoulinetteError(errno.EPERM, m18n.n('service_no_log') % name)
return result
def _run_service_command(action, service):
"""
Run services management command (start, stop, enable, disable)
Keyword argument:
service -- Service name
action -- Action to perform
"""
if service not in _get_services().keys():
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown') % name)
cmd = None
if action in ['start', 'stop']:
cmd = 'service %s %s' % (service, action)
elif action in ['enable', 'disable']:
arg = 'defaults' if action == 'enable' else 'remove'
cmd = 'update-rc.d %s %s' % (service, arg)
else:
raise ValueError("Unknown action '%s'" % action)
try:
ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
# TODO: Log output?
msignals.display(m18n.n('service_cmd_exec_failed') % ' '.join(e.cmd),
'warning')
return False
return True
def _get_services():
"""
Get a dict of managed services with their parameters
"""
with open('/etc/yunohost/services.yml', 'r') as f:
services = yaml.load(f)
return services
def _tail(file, n, offset=None):
"""
Reads a n lines from f with an offset of offset lines. The return
value is a tuple in the form ``(lines, has_more)`` where `has_more` is
an indicator that is `True` if there are more lines in the file.
"""
avg_line_length = 74
to_read = n + (offset or 0)
try:
with open(file, 'r') as f:
while 1:
try:
f.seek(-(avg_line_length * to_read), 2)
except IOError:
# woops. apparently file is smaller than what we want
# to step back, go to the beginning instead
f.seek(0)
pos = f.tell()
lines = f.read().splitlines()
if len(lines) >= to_read or pos == 0:
return lines[-to_read:offset and -offset or None]
avg_line_length *= 1.3
except IOError: return []

View file

@ -1,388 +0,0 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2013 YunoHost
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
"""
""" yunohost_tools.py
Specific tools
"""
import os
import sys
import yaml
import re
import getpass
import requests
import json
import errno
import apt
import apt.progress
from moulinette.core import MoulinetteError
apps_setting_path= '/etc/yunohost/apps/'
def tools_ldapinit(auth):
"""
YunoHost LDAP initialization
"""
with open('ldap_scheme.yml') as f:
ldap_map = yaml.load(f)
for rdn, attr_dict in ldap_map['parents'].items():
try: auth.add(rdn, attr_dict)
except: pass
for rdn, attr_dict in ldap_map['children'].items():
try: auth.add(rdn, attr_dict)
except: pass
admin_dict = {
'cn': 'admin',
'uid': 'admin',
'description': 'LDAP Administrator',
'gidNumber': '1007',
'uidNumber': '1007',
'homeDirectory': '/home/admin',
'loginShell': '/bin/bash',
'objectClass': ['organizationalRole', 'posixAccount', 'simpleSecurityObject'],
'userPassword': 'yunohost'
}
auth.update('cn=admin', admin_dict)
msignals.display(m18n.n('ldap_ initialized'), 'success')
def tools_adminpw(old_password, new_password):
"""
Change admin password
Keyword argument:
new_password
old_password
"""
# Validate password length
if len(new_password) < 4:
raise MoulinetteError(errno.EINVAL, m18n.n('password_too_short'))
old_password.replace('"', '\\"')
old_password.replace('&', '\\&')
new_password.replace('"', '\\"')
new_password.replace('&', '\\&')
result = os.system('ldappasswd -h localhost -D cn=admin,dc=yunohost,dc=org -w "%s" -a "%s" -s "%s"' % (old_password, old_password, new_password))
if result == 0:
msignals.display(m18n.n('admin_password_changed'), 'success')
else:
raise MoulinetteError(errno.EPERM,
m18n.n('admin_password_change_failed'))
def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False):
"""
Main domain change tool
Keyword argument:
new_domain
old_domain
"""
from yunohost.domain import domain_add
from yunohost.dyndns import dyndns_subscribe
if not old_domain:
with open('/etc/yunohost/current_host', 'r') as f:
old_domain = f.readline().rstrip()
if not new_domain:
return { 'current_main_domain': old_domain }
config_files = [
'/etc/postfix/main.cf',
'/etc/metronome/metronome.cfg.lua',
'/etc/dovecot/dovecot.conf',
'/usr/share/yunohost/yunohost-config/others/startup',
'/home/yunohost.backup/tahoe/tahoe.cfg',
'/etc/amavis/conf.d/05-node_id',
'/etc/amavis/conf.d/50-user'
]
config_dir = []
for dir in config_dir:
for file in os.listdir(dir):
config_files.append(dir + '/' + file)
for file in config_files:
with open(file, "r") as sources:
lines = sources.readlines()
with open(file, "w") as sources:
for line in lines:
sources.write(re.sub(r''+ old_domain +'', new_domain, line))
domain_add(auth, [new_domain], main=True)
os.system('rm /etc/ssl/private/yunohost_key.pem')
os.system('rm /etc/ssl/certs/yunohost_crt.pem')
command_list = [
'ln -s /etc/yunohost/certs/%s/key.pem /etc/ssl/private/yunohost_key.pem' % new_domain,
'ln -s /etc/yunohost/certs/%s/crt.pem /etc/ssl/certs/yunohost_crt.pem' % new_domain,
'echo %s > /etc/yunohost/current_host' % new_domain,
'service nginx restart',
'service metronome restart',
'service postfix restart',
'service dovecot restart',
'service amavis restart'
]
try:
with open('/etc/yunohost/light') as f: pass
except IOError:
command_list.append('service amavis restart')
#command_list.append('service tahoe-lafs restart')
for command in command_list:
if os.system(command) != 0:
raise MoulinetteError(errno.EPERM,
m18n.n('maindomain_change_failed'))
if dyndns: dyndns_subscribe(domain=new_domain)
elif len(new_domain.split('.')) >= 3:
r = requests.get('http://dyndns.yunohost.org/domains')
dyndomains = json.loads(r.text)
dyndomain = '.'.join(new_domain.split('.')[1:])
if dyndomain in dyndomains:
dyndns_subscribe(domain=new_domain)
msignals.display(m18n.n('maindomain_changed'), 'success')
def tools_postinstall(domain, password, dyndns=False):
"""
YunoHost post-install
Keyword argument:
domain -- YunoHost main domain
dyndns -- Subscribe domain to a DynDNS service
password -- YunoHost admin password
"""
from yunohost.backup import backup_init
from yunohost.app import app_ssowatconf
try:
with open('/etc/yunohost/installed') as f: pass
except IOError:
msignals.display(m18n.n('yunohost_installing'))
else:
raise MoulinetteError(errno.EPERM, m18n.n('yunohost_already_installed'))
if len(domain.split('.')) >= 3:
r = requests.get('http://dyndns.yunohost.org/domains')
dyndomains = json.loads(r.text)
dyndomain = '.'.join(domain.split('.')[1:])
if dyndomain in dyndomains:
if requests.get('http://dyndns.yunohost.org/test/%s' % domain).status_code == 200:
dyndns=True
else:
raise MoulinetteError(errno.EEXIST,
m18n.n('dyndns_unavailable'))
# Create required folders
folders_to_create = [
'/etc/yunohost/apps',
'/etc/yunohost/certs',
'/var/cache/yunohost/repo',
'/home/yunohost.backup',
'/home/yunohost.app'
]
for folder in folders_to_create:
try: os.listdir(folder)
except OSError: os.makedirs(folder)
# Set hostname to avoid amavis bug
if os.system('hostname -d') != 0:
os.system('hostname yunohost.yunohost.org')
# Add a temporary SSOwat rule to redirect SSO to admin page
try:
with open('/etc/ssowat/conf.json.persistent') as json_conf:
ssowat_conf = json.loads(str(json_conf.read()))
except IOError:
ssowat_conf = {}
if 'redirected_urls' not in ssowat_conf:
ssowat_conf['redirected_urls'] = {}
ssowat_conf['redirected_urls']['/'] = domain +'/yunohost/admin'
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
# Create SSL CA
ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
command_list = [
'echo "01" > %s/serial' % ssl_dir,
'rm %s/index.txt' % ssl_dir,
'touch %s/index.txt' % ssl_dir,
'cp %s/openssl.cnf %s/openssl.ca.cnf' % (ssl_dir, ssl_dir),
'sed -i "s/yunohost.org/%s/g" %s/openssl.ca.cnf ' % (domain, ssl_dir),
'openssl req -x509 -new -config %s/openssl.ca.cnf -days 3650 -out %s/ca/cacert.pem -keyout %s/ca/cakey.pem -nodes -batch' % (ssl_dir, ssl_dir, ssl_dir),
'cp %s/ca/cacert.pem /etc/ssl/certs/ca-yunohost_crt.pem' % ssl_dir,
'update-ca-certificates'
]
for command in command_list:
if os.system(command) != 0:
raise MoulinetteError(errno.EPERM,
m18n.n('yunohost_ca_creation_failed'))
# Initialize YunoHost LDAP base
tools_ldapinit(auth)
# Initialize backup system
backup_init()
# New domain config
tools_maindomain(auth, old_domain='yunohost.org', new_domain=domain, dyndns=dyndns)
# Generate SSOwat configuration file
app_ssowatconf(auth)
# Change LDAP admin password
tools_adminpw(old_password='yunohost', new_password=password)
os.system('touch /etc/yunohost/installed')
os.system('service yunohost-api restart &')
msignals.display(m18n.n('yunohost_configured'), 'success')
def tools_update(ignore_apps=False, ignore_packages=False):
"""
Update apps & package cache, then display changelog
Keyword arguments:
ignore_apps -- Ignore app list update and changelog
ignore_packages -- Ignore apt cache update and changelog
"""
from yunohost.app import app_fetchlist, app_info
packages = []
if not ignore_packages:
cache = apt.Cache()
# Update APT cache
if not cache.update():
raise MoulinetteError(errno.EPERM, m18n.n('update_cache_failed'))
cache.open(None)
cache.upgrade(True)
# Add changelogs to the result
for pkg in cache.get_changes():
packages.append({
'name': pkg.name,
'fullname': pkg.fullname,
'changelog': pkg.get_changelog()
})
apps = []
if not ignore_apps:
app_fetchlist()
app_list = os.listdir(apps_setting_path)
if len(app_list) > 0:
for app_id in app_list:
if '__' in app_id:
original_app_id = app_id[:app_id.index('__')]
else:
original_app_id = app_id
current_app_dict = app_info(app_id, raw=True)
new_app_dict = app_info(original_app_id, raw=True)
# Custom app
if 'lastUpdate' not in new_app_dict or 'git' not in new_app_dict:
continue
if (new_app_dict['lastUpdate'] > current_app_dict['lastUpdate']) \
or ('update_time' not in current_app_dict['settings'] \
and (new_app_dict['lastUpdate'] > current_app_dict['settings']['install_time'])) \
or ('update_time' in current_app_dict['settings'] \
and (new_app_dict['lastUpdate'] > current_app_dict['settings']['update_time'])):
apps.append({
'id': app_id,
'label': current_app_dict['settings']['label']
})
if len(apps) == 0 and len(packages) == 0:
msignals.display(m18n.n('system_no_upgrade'), 'success')
return { 'packages': packages, 'apps': apps }
def tools_upgrade(ignore_apps=False, ignore_packages=False):
"""
Update apps & package cache, then display changelog
Keyword arguments:
ignore_apps -- Ignore apps upgrade
ignore_packages -- Ignore APT packages upgrade
"""
from yunohost.app import app_upgrade
if not ignore_packages:
cache = apt.Cache()
cache.open(None)
cache.upgrade(True)
# If API call
if not os.isatty(1):
critical_packages = ["yunohost-cli", "yunohost-admin", "yunohost-config-nginx", "ssowat", "python"]
for pkg in cache.get_changes():
if pkg.name in critical_packages:
# Temporarily keep package ...
pkg.mark_keep()
# ... and set a hourly cron up to upgrade critical packages
with open('/etc/cron.d/yunohost-upgrade', 'w+') as f:
f.write('00 * * * * root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin apt-get install '+ ' '.join(critical_packages) + ' -y && rm -f /etc/cron.d/yunohost-upgrade')
try:
# Apply APT changes
cache.commit(apt.progress.text.AcquireProgress(), apt.progress.base.InstallProgress())
except: pass
if not ignore_apps:
try:
app_upgrade()
except: pass
msignals.display(m18n.n('system_upgraded'), 'success')
# Return API logs if it is an API call
if not os.isatty(1):
return { "log": service_log('yunohost-api', number="100").values()[0] }

View file

@ -1,364 +0,0 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2014 YUNOHOST.ORG
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
"""
""" yunohost_user.py
Manage users
"""
import os
import sys
import crypt
import random
import string
import json
import errno
from moulinette.core import MoulinetteError
def user_list(auth, fields=None, filter=None, limit=None, offset=None):
"""
List users
Keyword argument:
filter -- LDAP filter used to search
offset -- Starting number for user fetching
limit -- Maximum number of user fetched
fields -- fields to fetch
"""
user_attrs = { 'uid': 'username',
'cn': 'fullname',
'mail': 'mail',
'maildrop': 'mail-forward' }
attrs = []
result_list = []
# Set default arguments values
if offset is None:
offset = 0
if limit is None:
limit = 1000
if filter is None:
filter = '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))'
if fields:
keys = user_attrs.keys()
for attr in fields:
if attr in keys:
attrs.append(attr)
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('field_invalid') % attr)
else:
attrs = [ 'uid', 'cn', 'mail' ]
result = auth.search('ou=users,dc=yunohost,dc=org', filter, attrs)
if len(result) > offset and limit > 0:
for user in result[offset:offset+limit]:
entry = {}
for attr, values in user.items():
try:
entry[user_attrs[attr]] = values[0]
except:
pass
result_list.append(entry)
return { 'users' : result_list }
def user_create(auth, username, firstname, lastname, mail, password):
"""
Create user
Keyword argument:
firstname
lastname
username -- Must be unique
mail -- Main mail address must be unique
password
"""
from yunohost.domain import domain_list
from yunohost.hook import hook_callback
# Validate password length
if len(password) < 4:
raise MoulinetteError(errno.EINVAL, m18n.n('password_too_short'))
auth.validate_uniqueness({
'uid' : username,
'mail' : mail
})
if mail[mail.find('@')+1:] not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL,
m18n.n('mail_domain_unknown')
% mail[mail.find('@')+1:])
# Get random UID/GID
uid_check = gid_check = 0
while uid_check == 0 and gid_check == 0:
uid = str(random.randint(200, 99999))
uid_check = os.system("getent passwd %s" % uid)
gid_check = os.system("getent group %s" % uid)
# Adapt values for LDAP
fullname = '%s %s' % (firstname, lastname)
rdn = 'uid=%s,ou=users' % username
char_set = string.ascii_uppercase + string.digits
salt = ''.join(random.sample(char_set,8))
salt = '$1$' + salt + '$'
pwd = '{CRYPT}' + crypt.crypt(str(password), salt)
attr_dict = {
'objectClass' : ['mailAccount', 'inetOrgPerson', 'posixAccount'],
'givenName' : firstname,
'sn' : lastname,
'displayName' : fullname,
'cn' : fullname,
'uid' : username,
'mail' : mail,
'maildrop' : username,
'userPassword' : pwd,
'gidNumber' : uid,
'uidNumber' : uid,
'homeDirectory' : '/home/' + username,
'loginShell' : '/bin/false'
}
# If it is the first user, add some aliases
if not auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'):
with open('/etc/yunohost/current_host') as f:
main_domain = f.readline().rstrip()
aliases = [
'root@'+ main_domain,
'admin@'+ main_domain,
'webmaster@'+ main_domain,
'postmaster@'+ main_domain,
]
attr_dict['mail'] = [ attr_dict['mail'] ] + aliases
# If exists, remove the redirection from the SSO
try:
with open('/etc/ssowat/conf.json.persistent') as json_conf:
ssowat_conf = json.loads(str(json_conf.read()))
if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf['redirected_urls']:
del ssowat_conf['redirected_urls']['/']
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
except IOError: pass
if auth.add(rdn, attr_dict):
# Update SFTP user group
memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid']
memberlist.append(username)
if auth.update('cn=sftpusers,ou=groups', { 'memberUid': memberlist }):
os.system("su - %s -c ''" % username)
os.system('yunohost app ssowatconf > /dev/null 2>&1')
#TODO: Send a welcome mail to user
msignals.display(m18n.n('user_created'), 'success')
hook_callback('post_user_create', [username, mail, password, firstname, lastname])
return { 'fullname' : fullname, 'username' : username, 'mail' : mail }
raise MoulinetteError(169, m18n.n('user_creation_failed'))
def user_delete(auth, users, purge=False):
"""
Delete user
Keyword argument:
users -- Username of users to delete
purge
"""
if not isinstance(users, list):
users = [ users ]
deleted = []
for user in users:
if auth.remove('uid=%s,ou=users' % user):
# Update SFTP user group
memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid']
try: memberlist.remove(user)
except: pass
if auth.update('cn=sftpusers,ou=groups', { 'memberUid': memberlist }):
if purge:
os.system('rm -rf /home/%s' % user)
deleted.append(user)
continue
else:
raise MoulinetteError(169, m18n.n('user_deletion_failed'))
os.system('yunohost app ssowatconf > /dev/null 2>&1')
msignals.display(m18n.n('user_deleted'), 'success')
return { 'users': deleted }
def user_update(auth, username, firstname=None, lastname=None, mail=None, change_password=None, add_mailforward=None, remove_mailforward=None, add_mailalias=None, remove_mailalias=None):
"""
Update user informations
Keyword argument:
lastname
mail
firstname
add_mailalias -- Mail aliases to add
remove_mailforward -- Mailforward addresses to remove
username -- Username of user to update
add_mailforward -- Mailforward addresses to add
change_password -- New password to set
remove_mailalias -- Mail aliases to remove
"""
from yunohost.domain import domain_list
attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop']
new_attr_dict = {}
domains = domain_list(auth)['domains']
# Populate user informations
result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch)
if not result:
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown'))
user = result[0]
# Get modifications from arguments
if firstname:
new_attr_dict['givenName'] = firstname # TODO: Validate
new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + user['sn'][0]
if lastname:
new_attr_dict['sn'] = lastname # TODO: Validate
new_attr_dict['cn'] = new_attr_dict['displayName'] = user['givenName'][0] + ' ' + lastname
if lastname and firstname:
new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + lastname
if change_password:
char_set = string.ascii_uppercase + string.digits
salt = ''.join(random.sample(char_set,8))
salt = '$1$' + salt + '$'
new_attr_dict['userPassword'] = '{CRYPT}' + crypt.crypt(str(change_password), salt)
if mail:
auth.validate_uniqueness({ 'mail': mail })
if mail[mail.find('@')+1:] not in domains:
raise MoulinetteError(errno.EINVAL,
m18n.n('mail_domain_unknown')
% mail[mail.find('@')+1:])
del user['mail'][0]
new_attr_dict['mail'] = [mail] + user['mail']
if add_mailalias:
if not isinstance(add_mailalias, list):
add_mailalias = [ add_mailalias ]
for mail in add_mailalias:
auth.validate_uniqueness({ 'mail': mail })
if mail[mail.find('@')+1:] not in domains:
raise MoulinetteError(errno.EINVAL,
m18n.n('mail_domain_unknown')
% mail[mail.find('@')+1:])
user['mail'].append(mail)
new_attr_dict['mail'] = user['mail']
if remove_mailalias:
if not isinstance(remove_mailalias, list):
remove_mailalias = [ remove_mailalias ]
for mail in remove_mailalias:
if len(user['mail']) > 1 and mail in user['mail'][1:]:
user['mail'].remove(mail)
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('mail_alias_remove_failed') % mail)
new_attr_dict['mail'] = user['mail']
if add_mailforward:
if not isinstance(add_mailforward, list):
add_mailforward = [ add_mailforward ]
for mail in add_mailforward:
if mail in user['maildrop'][1:]:
continue
user['maildrop'].append(mail)
new_attr_dict['maildrop'] = user['maildrop']
if remove_mailforward:
if not isinstance(remove_mailforward, list):
remove_mailforward = [ remove_mailforward ]
for mail in remove_mailforward:
if len(user['maildrop']) > 1 and mail in user['maildrop'][1:]:
user['maildrop'].remove(mail)
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('mail_forward_remove_failed') % mail)
new_attr_dict['maildrop'] = user['maildrop']
if auth.update('uid=%s,ou=users' % username, new_attr_dict):
msignals.display(m18n.n('user_updated'), 'success')
return user_info(auth, username)
else:
raise MoulinetteError(169, m18n.n('user_update_failed'))
def user_info(auth, username):
"""
Get user informations
Keyword argument:
username -- Username or mail to get informations
"""
user_attrs = ['cn', 'mail', 'uid', 'maildrop', 'givenName', 'sn']
if len(username.split('@')) is 2:
filter = 'mail='+ username
else:
filter = 'uid='+ username
result = auth.search('ou=users,dc=yunohost,dc=org', filter, user_attrs)
if result:
user = result[0]
else:
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown'))
result_dict = {
'username': user['uid'][0],
'fullname': user['cn'][0],
'firstname': user['givenName'][0],
'lastname': user['sn'][0],
'mail': user['mail'][0]
}
if len(user['mail']) > 1:
result_dict['mail-aliases'] = user['mail'][1:]
if len(user['maildrop']) > 1:
result_dict['mail-forward'] = user['maildrop'][1:]
if result:
return result_dict
else:
raise MoulinetteError(167, m18n.n('user_info_failed'))