Merge branch 'unstable' into regen-conf

This commit is contained in:
kload 2015-10-01 16:39:15 -04:00
commit df8390151e
8 changed files with 168 additions and 67 deletions

View file

@ -596,6 +596,9 @@ backup:
--hooks: --hooks:
help: List of backup hooks names to execute help: List of backup hooks names to execute
nargs: "*" nargs: "*"
--apps:
help: List of application names to backup
nargs: "*"
--ignore-apps: --ignore-apps:
help: Do not backup apps help: Do not backup apps
action: store_true action: store_true
@ -603,15 +606,16 @@ backup:
### backup_restore() ### backup_restore()
restore: restore:
action_help: Restore from a local backup archive action_help: Restore from a local backup archive
api: POST /restore api: POST /backup/restore/<name>
configuration:
authenticate: false
arguments: arguments:
name: name:
help: Name of the local backup archive help: Name of the local backup archive
--hooks: --hooks:
help: List of restauration hooks names to execute help: List of restauration hooks names to execute
nargs: "*" nargs: "*"
--apps:
help: List of application names to restore
nargs: "*"
--ignore-apps: --ignore-apps:
help: Do not restore apps help: Do not restore apps
action: store_true action: store_true
@ -625,6 +629,15 @@ backup:
api: GET /backup/archives api: GET /backup/archives
configuration: configuration:
lock: false lock: false
arguments:
-i:
full: --with-info
help: Show backup information for each archive
action: store_true
-H:
full: --human-readable
help: Print sizes in human readable format
action: store_true
### backup_info() ### backup_info()
info: info:
@ -635,6 +648,14 @@ backup:
arguments: arguments:
name: name:
help: Name of the local backup archive help: Name of the local backup archive
-d:
full: --with-details
help: Show additional backup information
action: store_true
-H:
full: --human-readable
help: Print sizes in human readable format
action: store_true
############################# #############################

View file

@ -1,5 +1,5 @@
backup_dir="$1/mysql" backup_dir="$1/mysql"
mysqlpwd=$(sudo cat /etc/yunohost/mysql) mysqlpwd=$(sudo cat /etc/yunohost/mysql)
sudo mysql -uroot -p"$mysqlpwd" mysql < $backup_dir/mysql.sql #sudo mysql -uroot -p"$mysqlpwd" mysql < $backup_dir/mysql.sql
sudo mysqladmin flush-privileges -p"$mysqlpwd" sudo mysqladmin flush-privileges -p"$mysqlpwd"

29
debian/control vendored
View file

@ -9,17 +9,22 @@ Homepage: https://yunohost.org/
Package: moulinette-yunohost Package: moulinette-yunohost
Architecture: all Architecture: all
Depends: moulinette (>= 2.2.1), Depends: moulinette (>= 2.2.1),
python-psutil, python-psutil,
python-requests, python-requests,
glances, glances,
python-pip, python-pip,
pyminiupnpc, pyminiupnpc,
dnsutils, dnsutils,
bind9utils, bind9utils,
python-apt, python-apt,
ca-certificates, ca-certificates,
python-dnspython, python-dnspython,
netcat-openbsd, netcat-openbsd,
iproute iproute,
unzip,
git-core,
curl,
mariadb-server | mysql-server, php5-mysql | php5-mysqlnd
Conflicts: iptables-persistent
Description: YunoHost Python scripts Description: YunoHost Python scripts
Python functions to manage a YunoHost instance Python functions to manage a YunoHost instance

View file

@ -351,7 +351,8 @@ def app_upgrade(auth, app=[], url=None, file=None):
for line in lines: for line in lines:
sources.write(re.sub(r''+ original_app_id +'', app_id, line)) sources.write(re.sub(r''+ original_app_id +'', app_id, line))
# Add hooks # Clean hooks and add new ones
hook_remove(app_id)
if 'hooks' in os.listdir(app_tmp_folder): if 'hooks' in os.listdir(app_tmp_folder):
for hook in os.listdir(app_tmp_folder +'/hooks'): for hook in os.listdir(app_tmp_folder +'/hooks'):
hook_add(app_id, app_tmp_folder +'/hooks/'+ hook) hook_add(app_id, app_tmp_folder +'/hooks/'+ hook)
@ -451,7 +452,8 @@ def app_install(auth, app, label=None, args=None):
os.makedirs(app_setting_path) os.makedirs(app_setting_path)
os.system('touch %s/settings.yml' % app_setting_path) os.system('touch %s/settings.yml' % app_setting_path)
# Add hooks # Clean hooks and add new ones
hook_remove(app_id)
if 'hooks' in os.listdir(app_tmp_folder): if 'hooks' in os.listdir(app_tmp_folder):
for file in os.listdir(app_tmp_folder +'/hooks'): for file in os.listdir(app_tmp_folder +'/hooks'):
hook_add(app_id, app_tmp_folder +'/hooks/'+ file) hook_add(app_id, app_tmp_folder +'/hooks/'+ file)
@ -1034,7 +1036,7 @@ def _fetch_app_from_git(app):
if os.system('wget "%s" -O "%s.zip" > /dev/null 2>&1' % (url, app_tmp_folder)) == 0: if os.system('wget "%s" -O "%s.zip" > /dev/null 2>&1' % (url, app_tmp_folder)) == 0:
return _extract_app_from_file(app_tmp_folder +'.zip', remove=True) return _extract_app_from_file(app_tmp_folder +'.zip', remove=True)
git_result = os.system('git clone %s %s' % (app, app_tmp_folder)) git_result = os.system('git clone --depth=1 %s %s' % (app, app_tmp_folder))
git_result_2 = 0 git_result_2 = 0
try: try:
with open(app_tmp_folder + '/manifest.json') as json_manifest: with open(app_tmp_folder + '/manifest.json') as json_manifest:

View file

@ -29,8 +29,9 @@ import sys
import json import json
import errno import errno
import time import time
import shutil
import tarfile import tarfile
import subprocess
from collections import OrderedDict
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
@ -42,7 +43,7 @@ logger = getActionLogger('yunohost.backup')
def backup_create(name=None, description=None, output_directory=None, def backup_create(name=None, description=None, output_directory=None,
no_compress=False, hooks=[], ignore_apps=False): no_compress=False, hooks=[], apps=[], ignore_apps=False):
""" """
Create a backup local archive Create a backup local archive
@ -52,12 +53,12 @@ def backup_create(name=None, description=None, output_directory=None,
output_directory -- Output directory for the backup output_directory -- Output directory for the backup
no_compress -- Do not create an archive file no_compress -- Do not create an archive file
hooks -- List of backup hooks names to execute hooks -- List of backup hooks names to execute
apps -- List of application names to backup
ignore_apps -- Do not backup apps ignore_apps -- Do not backup apps
""" """
# TODO: Add a 'clean' argument to clean output directory # TODO: Add a 'clean' argument to clean output directory
from yunohost.hook import hook_add from yunohost.hook import hook_callback, hook_exec
from yunohost.hook import hook_callback
tmp_dir = None tmp_dir = None
@ -121,33 +122,58 @@ def backup_create(name=None, description=None, output_directory=None,
'description': description or '', 'description': description or '',
'created_at': timestamp, 'created_at': timestamp,
'apps': {}, 'apps': {},
'hooks': {},
} }
# Add apps backup hook # Run system hooks
msignals.display(m18n.n('backup_running_hooks'))
hooks_ret = hook_callback('backup', hooks, args=[tmp_dir])
# Add hooks results to the info
info['hooks'] = hooks_ret['succeed']
# Backup apps
if not ignore_apps: if not ignore_apps:
from yunohost.app import app_info from yunohost.app import app_info
try:
for app_id in os.listdir('/etc/yunohost/apps'):
hook = '/etc/yunohost/apps/%s/scripts/backup' % app_id
if os.path.isfile(hook):
hook_add(app_id, hook)
# Add app info # Filter applications to backup
i = app_info(app_id) apps_list = set(os.listdir('/etc/yunohost/apps'))
info['apps'][app_id] = { apps_filtered = set()
'version': i['version'], if apps:
} for a in apps:
if a not in apps_list:
logger.warning("app '%s' not found", a)
msignals.display(m18n.n('unbackup_app', a), 'warning')
else: else:
logger.warning("unable to find app's backup hook '%s'", apps_filtered.add(a)
hook) else:
msignals.display(m18n.n('unbackup_app', app_id), apps_filtered = apps_list
'warning')
except IOError as e:
logger.info("unable to add apps backup hook: %s", str(e))
# Run hooks # Run apps backup scripts
msignals.display(m18n.n('backup_running_hooks')) tmp_script = '/tmp/backup_' + str(timestamp)
hook_callback('backup', hooks, args=[tmp_dir]) for app_id in apps_filtered:
script = '/etc/yunohost/apps/{:s}/scripts/backup'.format(app_id)
if not os.path.isfile(script):
logger.warning("backup script '%s' not found", script)
msignals.display(m18n.n('unbackup_app', app_id),
'warning')
continue
try:
msignals.display(m18n.n('backup_running_app_script', app_id))
subprocess.call(['install', '-Dm555', script, tmp_script])
hook_exec(tmp_script, args=[tmp_dir])
except:
logger.exception("error while executing script '%s'", script)
msignals.display(m18n.n('unbackup_app', app_id),
'error')
else:
# Add app info
i = app_info(app_id)
info['apps'][app_id] = {
'version': i['version'],
}
subprocess.call(['rm', '-f', tmp_script])
# Create backup info file # Create backup info file
with open("%s/info.json" % tmp_dir, 'w') as f: with open("%s/info.json" % tmp_dir, 'w') as f:
@ -191,14 +217,19 @@ def backup_create(name=None, description=None, output_directory=None,
msignals.display(m18n.n('backup_complete'), 'success') msignals.display(m18n.n('backup_complete'), 'success')
# Return backup info
info['name'] = name
return { 'archive': info }
def backup_restore(name, hooks=[], ignore_apps=False, force=False):
def backup_restore(name, hooks=[], apps=[], ignore_apps=False, force=False):
""" """
Restore from a local backup archive Restore from a local backup archive
Keyword argument: Keyword argument:
name -- Name of the local backup archive name -- Name of the local backup archive
hooks -- List of restoration hooks names to execute hooks -- List of restoration hooks names to execute
apps -- List of application names to restore
ignore_apps -- Do not restore apps ignore_apps -- Do not restore apps
force -- Force restauration on an already installed system force -- Force restauration on an already installed system
@ -239,15 +270,6 @@ def backup_restore(name, hooks=[], ignore_apps=False, force=False):
logger.info("restoring from backup '%s' created on %s", name, logger.info("restoring from backup '%s' created on %s", name,
time.ctime(info['created_at'])) time.ctime(info['created_at']))
# Retrieve domain from the backup
try:
with open("%s/yunohost/current_host" % tmp_dir, 'r') as f:
domain = f.readline().rstrip()
except IOError:
logger.error("unable to retrieve domain from '%s/yunohost/current_host'",
tmp_dir)
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
# Check if YunoHost is installed # Check if YunoHost is installed
if os.path.isfile('/etc/yunohost/installed'): if os.path.isfile('/etc/yunohost/installed'):
msignals.display(m18n.n('yunohost_already_installed'), 'warning') msignals.display(m18n.n('yunohost_already_installed'), 'warning')
@ -265,12 +287,35 @@ def backup_restore(name, hooks=[], ignore_apps=False, force=False):
raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed')) raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed'))
else: else:
from yunohost.tools import tools_postinstall from yunohost.tools import tools_postinstall
# Retrieve the domain from the backup
try:
with open("%s/yunohost/current_host" % tmp_dir, 'r') as f:
domain = f.readline().rstrip()
except IOError:
logger.error("unable to retrieve domain from '%s/yunohost/current_host'",
tmp_dir)
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
logger.info("executing the post-install...") logger.info("executing the post-install...")
tools_postinstall(domain, 'yunohost', True) tools_postinstall(domain, 'yunohost', True)
# Add apps restore hook # Add apps restore hook
if not ignore_apps: if not ignore_apps:
for app_id in info['apps'].keys(): # Filter applications to restore
apps_list = set(info['apps'].keys())
apps_filtered = set()
if apps:
for a in apps:
if a not in apps_list:
logger.warning("app '%s' not found", a)
msignals.display(m18n.n('unrestore_app', a), 'warning')
else:
apps_filtered.add(a)
else:
apps_filtered = apps_list
for app_id in apps_filtered:
hook = "/etc/yunohost/apps/%s/scripts/restore" % app_id hook = "/etc/yunohost/apps/%s/scripts/restore" % app_id
if os.path.isfile(hook): if os.path.isfile(hook):
hook_add(app_id, hook) hook_add(app_id, hook)
@ -288,10 +333,14 @@ def backup_restore(name, hooks=[], ignore_apps=False, force=False):
msignals.display(m18n.n('restore_complete'), 'success') msignals.display(m18n.n('restore_complete'), 'success')
def backup_list(): def backup_list(with_info=False, human_readable=False):
""" """
List available local backup archives List available local backup archives
Keyword arguments:
with_info -- Show backup information for each archive
human_readable -- Print sizes in human readable format
""" """
result = [] result = []
@ -308,18 +357,29 @@ def backup_list():
except ValueError: except ValueError:
continue continue
result.append(name) result.append(name)
result.sort()
if result and with_info:
d = OrderedDict()
for a in result:
d[a] = backup_info(a, human_readable=human_readable)
result = d
return { 'archives': result } return { 'archives': result }
def backup_info(name): def backup_info(name, with_details=False, human_readable=False):
""" """
Get info about a local backup archive Get info about a local backup archive
Keyword arguments: Keyword arguments:
name -- Name of the local backup archive name -- Name of the local backup archive
with_details -- Show additional backup information
human_readable -- Print sizes in human readable format
""" """
from yunohost.monitor import binary_to_human
archive_file = '%s/%s.tar.gz' % (archives_path, name) archive_file = '%s/%s.tar.gz' % (archives_path, name)
if not os.path.isfile(archive_file): if not os.path.isfile(archive_file):
logger.error("no local backup archive found at '%s'", archive_file) logger.error("no local backup archive found at '%s'", archive_file)
@ -336,10 +396,19 @@ def backup_info(name):
info_file) info_file)
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive')) raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
return { size = os.path.getsize(archive_file)
if human_readable:
size = binary_to_human(size) + 'B'
result = {
'path': archive_file, 'path': archive_file,
'created_at': time.strftime(m18n.n('format_datetime_short'), 'created_at': time.strftime(m18n.n('format_datetime_short'),
time.gmtime(info['created_at'])), time.gmtime(info['created_at'])),
'description': info['description'], 'description': info['description'],
'apps': info['apps'], 'size': size,
} }
if with_details:
for d in ['apps', 'hooks']:
result[d] = info[d]
return result

View file

@ -174,7 +174,7 @@ def hook_callback(action, hooks=[], args=None):
args -- Ordered list of arguments to pass to the script args -- Ordered list of arguments to pass to the script
""" """
result = { 'succeed': list(), 'failed': list() } result = { 'succeed': {}, 'failed': {} }
hooks_dict = {} hooks_dict = {}
# Retrieve hooks # Retrieve hooks
@ -220,15 +220,18 @@ def hook_callback(action, hooks=[], args=None):
# Iterate over hooks and execute them # Iterate over hooks and execute them
for priority in sorted(hooks_dict): for priority in sorted(hooks_dict):
for name, info in iter(hooks_dict[priority].items()): for name, info in iter(hooks_dict[priority].items()):
state = 'succeed'
filename = '%s-%s' % (priority, name) filename = '%s-%s' % (priority, name)
try: try:
hook_exec(info['path'], args=args) hook_exec(info['path'], args=args)
except: except:
logger.exception("error while executing hook '%s'", logger.exception("error while executing hook '%s'",
info['path']) info['path'])
result['failed'].append(filename) state = 'failed'
else: try:
result['succeed'].append(filename) result[state][name].append(info['path'])
except KeyError:
result[state][name] = [info['path']]
return result return result

View file

@ -130,7 +130,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False):
else: else:
if human_readable: if human_readable:
for i in ['used', 'avail', 'size']: for i in ['used', 'avail', 'size']:
d[i] = _binary_to_human(d[i]) + 'B' d[i] = binary_to_human(d[i]) + 'B'
_set(dname, d) _set(dname, d)
for dname in devices_names: for dname in devices_names:
_set(dname, 'not-available') _set(dname, 'not-available')
@ -201,7 +201,7 @@ def monitor_network(units=None, human_readable=False):
if human_readable: if human_readable:
for k in i.keys(): for k in i.keys():
if k != 'time_since_update': if k != 'time_since_update':
i[k] = _binary_to_human(i[k]) + 'B' i[k] = binary_to_human(i[k]) + 'B'
result[u][iname] = i result[u][iname] = i
elif u == 'infos': elif u == 'infos':
try: try:
@ -261,10 +261,10 @@ def monitor_system(units=None, human_readable=False):
if human_readable: if human_readable:
for i in ram.keys(): for i in ram.keys():
if i != 'percent': if i != 'percent':
ram[i] = _binary_to_human(ram[i]) + 'B' ram[i] = binary_to_human(ram[i]) + 'B'
for i in swap.keys(): for i in swap.keys():
if i != 'percent': if i != 'percent':
swap[i] = _binary_to_human(swap[i]) + 'B' swap[i] = binary_to_human(swap[i]) + 'B'
result[u] = { result[u] = {
'ram': ram, 'ram': ram,
'swap': swap 'swap': swap
@ -510,7 +510,7 @@ def _extract_inet(string, skip_netmask=False, skip_loopback=True):
return result return result
def _binary_to_human(n, customary=False): def binary_to_human(n, customary=False):
""" """
Convert bytes or bits into human readable format with binary prefix Convert bytes or bits into human readable format with binary prefix

View file

@ -143,6 +143,7 @@
"backup_output_directory_forbidden" : "Forbidden output directory", "backup_output_directory_forbidden" : "Forbidden output directory",
"backup_output_directory_not_empty" : "Output directory is not empty", "backup_output_directory_not_empty" : "Output directory is not empty",
"backup_running_hooks" : "Running backup hooks...", "backup_running_hooks" : "Running backup hooks...",
"backup_running_app_script" : "Running backup script of app '{:s}'...",
"backup_creating_archive" : "Creating the backup archive...", "backup_creating_archive" : "Creating the backup archive...",
"backup_extracting_archive" : "Extracting the backup archive...", "backup_extracting_archive" : "Extracting the backup archive...",
"backup_archive_open_failed" : "Unable to open the backup archive", "backup_archive_open_failed" : "Unable to open the backup archive",