diff --git a/data/apps/helpers.d/filesystem b/data/apps/helpers.d/filesystem new file mode 100644 index 000000000..c3332a794 --- /dev/null +++ b/data/apps/helpers.d/filesystem @@ -0,0 +1,29 @@ +CAN_BIND=1 + +# Bind a directory or copy it on error +# +# usage: ynh_bind_or_cp srcdir destdir as_root +# | arg: srcdir - directory to bind or copy +# | arg: destdir - mountpoint or destination directory +# | arg: as_root - 1 to execute commands as root +ynh_bind_or_cp() { + SRCDIR=$1 + DESTDIR=$2 + SUDO_CMD="sudo" + [[ "$3" != "1" ]] && SUDO_CMD="" + + if [[ $CAN_BIND == 1 ]]; then + $SUDO_CMD mkdir -p $DESTDIR + $SUDO_CMD mount --bind "$SRCDIR" "$DESTDIR" + if [[ $? == 0 ]]; then + for m in $(mount | grep " $SRCDIR" | awk '{ print $3 }'); do + $SUDO_CMD mount --bind "$m" "${DESTDIR}${m#${SRCDIR}}" + done + return + fi + echo "Error: bind mounting seems to be disabled on your system." + echo "You have maybe to check your apparmor configuration." + CAN_BIND=0 + fi + $SUDO_CMD cp -r "$SRCDIR" "$DESTDIR" +} diff --git a/data/hooks/backup/17-data_home b/data/hooks/backup/17-data_home index 96061f733..acc999a81 100644 --- a/data/hooks/backup/17-data_home +++ b/data/hooks/backup/17-data_home @@ -1,4 +1,10 @@ backup_dir="$1/data/home" mkdir -p $backup_dir -sudo rsync -a --exclude='/yunohost*' /home/ $backup_dir/ +. /usr/share/yunohost/apps/helpers + +for f in $(find /home/* -type d -prune | awk -F/ '{print $NF}'); do + if [[ ! "$f" =~ ^yunohost|lost\+found ]]; then + ynh_bind_or_cp "/home/$f" "${backup_dir}/$f" 1 + fi +done diff --git a/data/hooks/backup/23-data_mail b/data/hooks/backup/23-data_mail index 01b362fea..66067fcf3 100644 --- a/data/hooks/backup/23-data_mail +++ b/data/hooks/backup/23-data_mail @@ -1,3 +1,5 @@ backup_dir="$1/data/mail" -sudo cp -a /var/mail/. $backup_dir +. /usr/share/yunohost/apps/helpers + +ynh_bind_or_cp /var/mail $backup_dir 1 diff --git a/data/hooks/post_backup_create/99-umount b/data/hooks/post_backup_create/99-umount new file mode 100644 index 000000000..a9ad5efec --- /dev/null +++ b/data/hooks/post_backup_create/99-umount @@ -0,0 +1,13 @@ + +tmp_dir=$1 +retcode=$2 + +FAILURE=0 + +# Iterate over inverted ordered mountpoints to prevent issues +for m in $(mount | grep " ${tmp_dir}" | awk '{ print $3 }' | tac); do + sudo umount $m + [[ $? != 0 ]] && FAILURE=1 +done + +exit $FAILURE diff --git a/lib/yunohost/backup.py b/lib/yunohost/backup.py index 6c710b4ac..9220c338c 100644 --- a/lib/yunohost/backup.py +++ b/lib/yunohost/backup.py @@ -119,8 +119,12 @@ def backup_create(name=None, description=None, output_directory=None, filesystem.rm(tmp_dir, recursive=True) filesystem.mkdir(tmp_dir, 0750, parents=True, uid='admin') - def _clean_tmp_dir(): - filesystem.rm(tmp_dir, True, True) + def _clean_tmp_dir(retcode=0): + ret = hook_callback('post_backup_create', args=[tmp_dir, retcode]) + if not ret['failed']: + filesystem.rm(tmp_dir, True, True) + else: + msignals.display(m18n.n('backup_cleaning_failed'), 'warning') # Initialize backup info info = { @@ -194,7 +198,7 @@ def backup_create(name=None, description=None, output_directory=None, # Check if something has been saved if ignore_hooks and not info['apps']: - _clean_tmp_dir() + _clean_tmp_dir(1) raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done')) # Create backup info file @@ -224,7 +228,7 @@ def backup_create(name=None, description=None, output_directory=None, logger.exception("unable to open the archive '%s' for writing", archive_file) if tar is None: - _clean_tmp_dir() + _clean_tmp_dir(2) raise MoulinetteError(errno.EIO, m18n.n('backup_archive_open_failed')) tar.add(tmp_dir, arcname='') @@ -435,8 +439,8 @@ def backup_info(name, with_details=False, human_readable=False): for d in ['apps', 'hooks']: result[d] = info[d] return result - - + + def backup_delete(name): """ Delete a backup @@ -444,12 +448,12 @@ def backup_delete(name): Keyword arguments: name -- Name of the local backup archive - """ + """ from yunohost.hook import hook_callback hook_callback('pre_backup_delete', args=[name]) - + archive_file = '%s/%s.tar.gz' % (archives_path, name) - + info_file = "%s/%s.info.json" % (archives_path, name) for backup_file in [archive_file,info_file]: if not os.path.isfile(backup_file): @@ -459,8 +463,9 @@ def backup_delete(name): os.remove(backup_file) except: logger.exception("unable to delete '%s'", backup_file) - raise MoulinetteError(errno.EIO, m18n.n('backup_delete_error',backup_file)) - + raise MoulinetteError(errno.EIO, + m18n.n('backup_delete_error',backup_file)) + hook_callback('post_backup_delete', args=[name]) msignals.display(m18n.n('backup_deleted'), 'success') diff --git a/lib/yunohost/hook.py b/lib/yunohost/hook.py index 30d08ba1f..bb0518f62 100644 --- a/lib/yunohost/hook.py +++ b/lib/yunohost/hook.py @@ -212,11 +212,15 @@ def hook_callback(action, hooks=[], args=None): state = 'succeed' filename = '%s-%s' % (priority, name) try: - hook_exec(info['path'], args=args) + ret = hook_exec(info['path'], args=args) except: logger.exception("error while executing hook '%s'", info['path']) state = 'failed' + if ret != 0: + logger.error("error while executing hook '%s', retcode: %d", + info['path'], ret) + state = 'failed' try: result[state][name].append(info['path']) except KeyError: diff --git a/locales/en.json b/locales/en.json index 63d05529f..6838c7ddb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -152,6 +152,7 @@ "backup_archive_name_exists" : "Backup archive name already exists", "backup_app_failed" : "Unable to back up the app '{app:s}'", "backup_nothings_done" : "There is nothing to save", + "backup_cleaning_failed" : "Unable to clean backup directory", "backup_complete" : "Backup complete", "backup_invalid_archive" : "Invalid backup archive", "restore_confirm_yunohost_installed" : "Do you really want to restore an already installed system? [{answers:s}]",