Beispiel #1
0
def app_is_installed(app):

    if app == "permissions_app":
        return _is_installed(app)

    # These are files we know should be installed by the app
    app_files = []
    app_files.append("/etc/nginx/conf.d/%s.d/%s.conf" % (maindomain, app))
    app_files.append("/var/www/%s/index.html" % app)
    app_files.append("/etc/importantfile")

    return _is_installed(app) and all(os.path.exists(f) for f in app_files)
Beispiel #2
0
def test_restore_app_not_in_backup(mocker):

    assert not _is_installed("wordpress")
    assert not _is_installed("yoloswag")

    with message(mocker, "backup_archive_app_not_found", app="yoloswag"):
        with raiseYunohostError(mocker, "restore_nothings_done"):
            backup_restore(
                system=None, name=backup_list()["archives"][0], apps=["yoloswag"]
            )

    assert not _is_installed("wordpress")
    assert not _is_installed("yoloswag")
Beispiel #3
0
def test_restore_app_not_enough_free_space(monkeypatch, mocker):
    def custom_free_space_in_directory(dirpath):
        return 0

    monkeypatch.setattr("yunohost.backup.free_space_in_directory",
                        custom_free_space_in_directory)

    assert not _is_installed("wordpress")

    with raiseYunohostError(mocker, 'restore_not_enough_disk_space'):
        backup_restore(system=None,
                       name=backup_list()["archives"][0],
                       apps=["wordpress"])

    assert not _is_installed("wordpress")
Beispiel #4
0
def test_backup_app_not_installed(mocker):

    assert not _is_installed("wordpress")

    with message(mocker, "unbackup_app", app="wordpress"):
        with raiseYunohostError(mocker, "backup_nothings_done"):
            backup_create(system=None, apps=["wordpress"])
Beispiel #5
0
def test_restore_app_already_installed(mocker):

    assert not _is_installed("wordpress")

    with message(mocker, "restore_complete"):
        backup_restore(
            system=None, name=backup_list()["archives"][0], apps=["wordpress"]
        )

    assert _is_installed("wordpress")

    with raiseYunohostError(mocker, "restore_already_installed_apps"):
        backup_restore(
            system=None, name=backup_list()["archives"][0], apps=["wordpress"]
        )

    assert _is_installed("wordpress")
Beispiel #6
0
def test_restore_app_script_failure_handling(monkeypatch, mocker):
    def custom_hook_exec(name, *args, **kwargs):
        if os.path.basename(name).startswith("restore"):
            monkeypatch.undo()
            raise Exception

    monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec)

    assert not _is_installed("wordpress")

    with message(mocker, "restore_app_failed", app="wordpress"):
        with raiseYunohostError(mocker, "restore_nothings_done"):
            backup_restore(
                system=None, name=backup_list()["archives"][0], apps=["wordpress"]
            )

    assert not _is_installed("wordpress")
Beispiel #7
0
def uninstall_test_apps_if_needed():

    for app in [
            "legacy_app", "backup_recommended_app", "wordpress",
            "permissions_app"
    ]:
        if _is_installed(app):
            app_remove(app)
Beispiel #8
0
    def run(self):
        # Get list of php7.0 pool files
        php70_pool_files = glob.glob("{}/*.conf".format(PHP70_POOLS))

        # Keep only basenames
        php70_pool_files = [os.path.basename(f) for f in php70_pool_files]

        # Ignore the "www.conf" (default stuff, probably don't want to touch it ?)
        php70_pool_files = [f for f in php70_pool_files if f != "www.conf"]

        for f in php70_pool_files:

            # Copy the files to the php7.3 pool
            src = "{}/{}".format(PHP70_POOLS, f)
            dest = "{}/{}".format(PHP73_POOLS, f)
            copy2(src, dest)

            # Replace the socket prefix if it's found
            c = "sed -i -e 's@{}@{}@g' {}".format(PHP70_SOCKETS_PREFIX,
                                                  PHP73_SOCKETS_PREFIX, dest)
            os.system(c)

            # Also add a comment that it was automatically moved from php7.0
            # (for human traceability and backward migration)
            c = "sed -i '1i {}' {}".format(MIGRATION_COMMENT, dest)
            os.system(c)

            app_id = os.path.basename(f)[:-len(".conf")]
            if _is_installed(app_id):
                _patch_legacy_php_versions_in_settings(
                    "/etc/yunohost/apps/%s/" % app_id)

            nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/%s.conf" %
                                         app_id)
            for f in nginx_conf_files:
                # Replace the socket prefix if it's found
                c = "sed -i -e 's@{}@{}@g' {}".format(PHP70_SOCKETS_PREFIX,
                                                      PHP73_SOCKETS_PREFIX, f)
                os.system(c)

        os.system(
            "rm /etc/logrotate.d/php7.0-fpm"
        )  # We remove this otherwise the logrotate cron will be unhappy

        # Reload/restart the php pools
        _run_service_command("restart", "php7.3-fpm")
        _run_service_command("enable", "php7.3-fpm")
        os.system("systemctl stop php7.0-fpm")
        os.system("systemctl disable php7.0-fpm")

        # Reload nginx
        _run_service_command("reload", "nginx")
Beispiel #9
0
def clean():

    # Make sure we have a ssowat
    os.system("mkdir -p /etc/ssowat/")
    app_ssowatconf()

    test_apps = ["break_yo_system", "legacy_app", "legacy_app__2", "full_domain_app"]

    for test_app in test_apps:

        if _is_installed(test_app):
            app_remove(test_app)

        for filepath in glob.glob("/etc/nginx/conf.d/*.d/*%s*" % test_app):
            os.remove(filepath)
        for folderpath in glob.glob("/etc/yunohost/apps/*%s*" % test_app):
            shutil.rmtree(folderpath, ignore_errors=True)
        for folderpath in glob.glob("/var/www/*%s*" % test_app):
            shutil.rmtree(folderpath, ignore_errors=True)

        os.system(
            "bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP DATABASE %s' \""
            % test_app
        )
        os.system(
            "bash -c \"mysql -u root --password=$(cat /etc/yunohost/mysql) 2>/dev/null <<< 'DROP USER %s@localhost'\""
            % test_app
        )

    os.system(
        "systemctl reset-failed nginx"
    )  # Reset failed quota for service to avoid running into start-limit rate ?
    os.system("systemctl start nginx")

    # Clean permissions
    for permission_name in user_permission_list(short=True)["permissions"]:
        if any(test_app in permission_name for test_app in test_apps):
            permission_delete(permission_name, force=True)
Beispiel #10
0
def app_is_not_installed(domain, app):

    return not _is_installed(app) and not all(
        os.path.exists(f) for f in app_expected_files(domain, app)
    )
Beispiel #11
0
def backup_restore(auth,
                   name,
                   hooks=[],
                   ignore_hooks=False,
                   apps=[],
                   ignore_apps=False,
                   force=False):
    """
    Restore from a local backup archive

    Keyword argument:
        name -- Name of the local backup archive
        hooks -- List of restoration hooks names to execute
        ignore_hooks -- Do not execute backup hooks
        apps -- List of application names to restore
        ignore_apps -- Do not restore apps
        force -- Force restauration on an already installed system

    """
    # Validate what to restore
    if ignore_hooks and ignore_apps:
        raise MoulinetteError(errno.EINVAL, m18n.n('restore_action_required'))

    # Retrieve and open the archive
    info = backup_info(name)
    archive_file = info['path']
    try:
        tar = tarfile.open(archive_file, "r:gz")
    except:
        logger.debug("cannot open backup archive '%s'",
                     archive_file,
                     exc_info=1)
        raise MoulinetteError(errno.EIO, m18n.n('backup_archive_open_failed'))

    # Check temporary directory
    tmp_dir = "%s/tmp/%s" % (backup_path, name)
    if os.path.isdir(tmp_dir):
        logger.debug("temporary directory for restoration '%s' already exists",
                     tmp_dir)
        os.system('rm -rf %s' % tmp_dir)

    # Check available disk space
    statvfs = os.statvfs(backup_path)
    free_space = statvfs.f_frsize * statvfs.f_bavail
    if free_space < info['size']:
        logger.debug("%dB left but %dB is needed", free_space, info['size'])
        raise MoulinetteError(
            errno.EIO, m18n.n('not_enough_disk_space', path=backup_path))

    def _clean_tmp_dir(retcode=0):
        ret = hook_callback('post_backup_restore', args=[tmp_dir, retcode])
        if not ret['failed']:
            filesystem.rm(tmp_dir, True, True)
        else:
            logger.warning(m18n.n('restore_cleaning_failed'))

    # Extract the tarball
    logger.info(m18n.n('backup_extracting_archive'))
    tar.extractall(tmp_dir)
    tar.close()

    # Retrieve backup info
    info_file = "%s/info.json" % tmp_dir
    try:
        with open(info_file, 'r') as f:
            info = json.load(f)
    except IOError:
        logger.debug("unable to load '%s'", info_file, exc_info=1)
        raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
    else:
        logger.debug("restoring from backup '%s' created on %s", name,
                     time.ctime(info['created_at']))

    # Initialize restauration summary result
    result = {
        'apps': [],
        'hooks': {},
    }

    # Check if YunoHost is installed
    if os.path.isfile('/etc/yunohost/installed'):
        logger.warning(m18n.n('yunohost_already_installed'))
        if not force:
            try:
                # Ask confirmation for restoring
                i = msignals.prompt(
                    m18n.n('restore_confirm_yunohost_installed',
                           answers='y/N'))
            except NotImplemented:
                pass
            else:
                if i == 'y' or i == 'Y':
                    force = True
            if not force:
                _clean_tmp_dir()
                raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed'))
    else:
        # Retrieve the domain from the backup
        try:
            with open("%s/conf/ynh/current_host" % tmp_dir, 'r') as f:
                domain = f.readline().rstrip()
        except IOError:
            logger.debug("unable to retrieve current_host from the backup",
                         exc_info=1)
            raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))

        logger.debug("executing the post-install...")
        tools_postinstall(domain, 'yunohost', True)

    # Run system hooks
    if not ignore_hooks:
        # Filter hooks to execute
        hooks_list = set(info['hooks'].keys())
        _is_hook_in_backup = lambda h: True
        if hooks:

            def _is_hook_in_backup(h):
                if h in hooks_list:
                    return True
                logger.error(m18n.n('backup_archive_hook_not_exec', hook=h))
                return False
        else:
            hooks = hooks_list

        # Check hooks availibility
        hooks_filtered = set()
        for h in hooks:
            if not _is_hook_in_backup(h):
                continue
            try:
                hook_info('restore', h)
            except:
                tmp_hooks = glob('{:s}/hooks/restore/*-{:s}'.format(
                    tmp_dir, h))
                if not tmp_hooks:
                    logger.exception(m18n.n('restore_hook_unavailable',
                                            hook=h))
                    continue
                # Add restoration hook from the backup to the system
                # FIXME: Refactor hook_add and use it instead
                restore_hook_folder = custom_hook_folder + 'restore'
                filesystem.mkdir(restore_hook_folder, 755, True)
                for f in tmp_hooks:
                    logger.debug(
                        "adding restoration hook '%s' to the system "
                        "from the backup archive '%s'", f, archive_file)
                    shutil.copy(f, restore_hook_folder)
            hooks_filtered.add(h)

        if hooks_filtered:
            logger.info(m18n.n('restore_running_hooks'))
            ret = hook_callback('restore', hooks_filtered, args=[tmp_dir])
            result['hooks'] = ret['succeed']

    # Add apps restore hook
    if not ignore_apps:
        # 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.error(m18n.n('backup_archive_app_not_found', app=a))
                else:
                    apps_filtered.add(a)
        else:
            apps_filtered = apps_list

        for app_instance_name in apps_filtered:
            tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_instance_name)
            tmp_app_bkp_dir = tmp_app_dir + '/backup'

            # Parse app instance name and id
            # TODO: Use app_id to check if app is installed?
            app_id, app_instance_nb = _parse_app_instance_name(
                app_instance_name)

            # Check if the app is not already installed
            if _is_installed(app_instance_name):
                logger.error(
                    m18n.n('restore_already_installed_app',
                           app=app_instance_name))
                continue

            # Check if the app has a restore script
            app_script = tmp_app_dir + '/settings/scripts/restore'
            if not os.path.isfile(app_script):
                logger.warning(m18n.n('unrestore_app', app=app_instance_name))
                continue

            tmp_script = '/tmp/restore_' + app_instance_name
            app_setting_path = '/etc/yunohost/apps/' + app_instance_name
            logger.info(
                m18n.n('restore_running_app_script', app=app_instance_name))
            try:
                # Copy app settings and set permissions
                # TODO: Copy app hooks too
                shutil.copytree(tmp_app_dir + '/settings', app_setting_path)
                filesystem.chmod(app_setting_path, 0555, 0444, True)
                filesystem.chmod(app_setting_path + '/settings.yml', 0400)

                # Copy restore script in a tmp file
                subprocess.call(['install', '-Dm555', app_script, tmp_script])

                # Prepare env. var. to pass to script
                env_dict = {}
                env_dict["YNH_APP_ID"] = app_id
                env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name
                env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
                env_dict["YNH_APP_BACKUP_DIR"] = tmp_app_bkp_dir

                # Execute app restore script
                hook_exec(tmp_script,
                          args=[tmp_app_bkp_dir, app_instance_name],
                          raise_on_error=True,
                          chdir=tmp_app_bkp_dir,
                          env=env_dict)
            except:
                logger.exception(
                    m18n.n('restore_app_failed', app=app_instance_name))

                # Copy remove script in a tmp file
                filesystem.rm(tmp_script, force=True)
                app_script = tmp_app_dir + '/settings/scripts/remove'
                tmp_script = '/tmp/remove_' + app_instance_name
                subprocess.call(['install', '-Dm555', app_script, tmp_script])

                # Setup environment for remove script
                env_dict_remove = {}
                env_dict_remove["YNH_APP_ID"] = app_id
                env_dict_remove["YNH_APP_INSTANCE_NAME"] = app_instance_name
                env_dict_remove["YNH_APP_INSTANCE_NUMBER"] = str(
                    app_instance_nb)

                # Execute remove script
                # TODO: call app_remove instead
                if hook_exec(tmp_script,
                             args=[app_instance_name],
                             env=env_dict_remove) != 0:
                    logger.warning(
                        m18n.n('app_not_properly_removed',
                               app=app_instance_name))

                # Cleaning app directory
                shutil.rmtree(app_setting_path, ignore_errors=True)
            else:
                result['apps'].append(app_instance_name)
            finally:
                filesystem.rm(tmp_script, force=True)

    # Check if something has been restored
    if not result['hooks'] and not result['apps']:
        _clean_tmp_dir(1)
        raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done'))
    if result['apps']:
        app_ssowatconf(auth)

    _clean_tmp_dir()
    logger.success(m18n.n('restore_complete'))

    return result
Beispiel #12
0
def backup_restore(auth, name, hooks=[], ignore_hooks=False,
                   apps=[], ignore_apps=False, force=False):
    """
    Restore from a local backup archive

    Keyword argument:
        name -- Name of the local backup archive
        hooks -- List of restoration hooks names to execute
        ignore_hooks -- Do not execute backup hooks
        apps -- List of application names to restore
        ignore_apps -- Do not restore apps
        force -- Force restauration on an already installed system

    """
    # Validate what to restore
    if ignore_hooks and ignore_apps:
        raise MoulinetteError(errno.EINVAL,
            m18n.n('restore_action_required'))

    # Retrieve and open the archive
    info = backup_info(name)
    archive_file = info['path']
    try:
        tar = tarfile.open(archive_file, "r:gz")
    except:
        logger.debug("cannot open backup archive '%s'",
            archive_file, exc_info=1)
        raise MoulinetteError(errno.EIO, m18n.n('backup_archive_open_failed'))

    # Check temporary directory
    tmp_dir = "%s/tmp/%s" % (backup_path, name)
    if os.path.isdir(tmp_dir):
        logger.debug("temporary directory for restoration '%s' already exists",
            tmp_dir)
        os.system('rm -rf %s' % tmp_dir)

    # Check available disk space
    statvfs = os.statvfs(backup_path)
    free_space = statvfs.f_frsize * statvfs.f_bavail
    if free_space < info['size']:
        logger.debug("%dB left but %dB is needed", free_space, info['size'])
        raise MoulinetteError(
            errno.EIO, m18n.n('not_enough_disk_space', path=backup_path))

    def _clean_tmp_dir(retcode=0):
        ret = hook_callback('post_backup_restore', args=[tmp_dir, retcode])
        if not ret['failed']:
            filesystem.rm(tmp_dir, True, True)
        else:
            logger.warning(m18n.n('restore_cleaning_failed'))

    # Extract the tarball
    logger.info(m18n.n('backup_extracting_archive'))
    tar.extractall(tmp_dir)
    tar.close()

    # Retrieve backup info
    info_file = "%s/info.json" % tmp_dir
    try:
        with open(info_file, 'r') as f:
            info = json.load(f)
    except IOError:
        logger.debug("unable to load '%s'", info_file, exc_info=1)
        raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
    else:
        logger.debug("restoring from backup '%s' created on %s", name,
            time.ctime(info['created_at']))

    # Initialize restauration summary result
    result = {
        'apps': [],
        'hooks': {},
    }

    # Check if YunoHost is installed
    if os.path.isfile('/etc/yunohost/installed'):
        logger.warning(m18n.n('yunohost_already_installed'))
        if not force:
            try:
                # Ask confirmation for restoring
                i = msignals.prompt(m18n.n('restore_confirm_yunohost_installed',
                                           answers='y/N'))
            except NotImplemented:
                pass
            else:
                if i == 'y' or i == 'Y':
                    force = True
            if not force:
                _clean_tmp_dir()
                raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed'))
    else:
        # Retrieve the domain from the backup
        try:
            with open("%s/conf/ynh/current_host" % tmp_dir, 'r') as f:
                domain = f.readline().rstrip()
        except IOError:
            logger.debug("unable to retrieve current_host from the backup",
                         exc_info=1)
            raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))

        logger.debug("executing the post-install...")
        tools_postinstall(domain, 'yunohost', True)

    # Run system hooks
    if not ignore_hooks:
        # Filter hooks to execute
        hooks_list = set(info['hooks'].keys())
        _is_hook_in_backup = lambda h: True
        if hooks:
            def _is_hook_in_backup(h):
                if h in hooks_list:
                    return True
                logger.error(m18n.n('backup_archive_hook_not_exec', hook=h))
                return False
        else:
            hooks = hooks_list

        # Check hooks availibility
        hooks_filtered = set()
        for h in hooks:
            if not _is_hook_in_backup(h):
                continue
            try:
                hook_info('restore', h)
            except:
                tmp_hooks = glob('{:s}/hooks/restore/*-{:s}'.format(tmp_dir, h))
                if not tmp_hooks:
                    logger.exception(m18n.n('restore_hook_unavailable', hook=h))
                    continue
                # Add restoration hook from the backup to the system
                # FIXME: Refactor hook_add and use it instead
                restore_hook_folder = custom_hook_folder + 'restore'
                filesystem.mkdir(restore_hook_folder, 755, True)
                for f in tmp_hooks:
                    logger.debug("adding restoration hook '%s' to the system "
                        "from the backup archive '%s'", f, archive_file)
                    shutil.copy(f, restore_hook_folder)
            hooks_filtered.add(h)

        if hooks_filtered:
            logger.info(m18n.n('restore_running_hooks'))
            ret = hook_callback('restore', hooks_filtered, args=[tmp_dir])
            result['hooks'] = ret['succeed']

    # Add apps restore hook
    if not ignore_apps:
        # 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.error(m18n.n('backup_archive_app_not_found', app=a))
                else:
                    apps_filtered.add(a)
        else:
            apps_filtered = apps_list

        for app_instance_name in apps_filtered:
            tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_instance_name)
            tmp_app_bkp_dir = tmp_app_dir + '/backup'

            # Check if the app is not already installed
            if _is_installed(app_instance_name):
                logger.error(m18n.n('restore_already_installed_app',
                        app=app_instance_name))
                continue

            # Check if the app has a restore script
            app_script = tmp_app_dir + '/settings/scripts/restore'
            if not os.path.isfile(app_script):
                logger.warning(m18n.n('unrestore_app', app=app_instance_name))
                continue

            tmp_script = '/tmp/restore_' + app_instance_name
            app_setting_path = '/etc/yunohost/apps/' + app_instance_name
            logger.info(m18n.n('restore_running_app_script', app=app_instance_name))
            try:
                # Copy app settings and set permissions
                shutil.copytree(tmp_app_dir + '/settings', app_setting_path)
                filesystem.chmod(app_setting_path, 0555, 0444, True)
                filesystem.chmod(app_setting_path + '/settings.yml', 0400)

                # Execute app restore script
                subprocess.call(['install', '-Dm555', app_script, tmp_script])

                # Prepare env. var. to pass to script
                env_dict = {}
                app_id, app_instance_nb = _parse_app_instance_name(app_instance_name)
                env_dict["YNH_APP_ID"] = app_id
                env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name
                env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
                env_dict["YNH_APP_BACKUP_DIR"] = tmp_app_bkp_dir

                hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_instance_name],
                          raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict)
            except:
                logger.exception(m18n.n('restore_app_failed', app=app_instance_name))
                # Cleaning app directory
                shutil.rmtree(app_setting_path, ignore_errors=True)
            else:
                result['apps'].append(app_instance_name)
            finally:
                filesystem.rm(tmp_script, force=True)

    # Check if something has been restored
    if not result['hooks'] and not result['apps']:
        _clean_tmp_dir(1)
        raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done'))
    if result['apps']:
        app_ssowatconf(auth)

    _clean_tmp_dir()
    logger.success(m18n.n('restore_complete'))

    return result