def checkup_git_repo_legacy(url, name=None, base_dir='~/repos',
                            verbose=False, prefix='', postfix=''):
    '''Checkout or update a git repo.'''
    if not name:
        match = re.match(r'.*/(.+)\.git', url)
        assert match, flo("Unable to extract repo name from '{url}'")
        name = match.group(1)
    assert name is not None, flo('Cannot extract repo name from repo: {url}')
    assert name != '', flo('Cannot extract repo name from repo: {url} (empty)')
    if verbose:
        name_blue = blue(name)
        print_msg(flo('{prefix}Checkout or update {name_blue}{postfix}'))
    if not exists(base_dir):
        run(flo('mkdir -p {base_dir}'))
    if not exists(flo('{base_dir}/{name}/.git')):
        run(flo('  &&  '.join([
                'cd {base_dir}',
                'git clone  {url}  {name}'])),
            msg='clone repo')
    else:
        if verbose:
            print_msg('update: pull from origin')
        run(flo('cd {base_dir}/{name}  &&  git pull'))
    return name def exists(*args, **kwargs):
    func, kwargs = _func(kwargs, func_local=os.path.exists,
                         func_remote=fabric.contrib.files.exists)
    if func == os.path.exists:
        args_list = list(args)
        args_list[0] = os.path.expanduser(args[0])
        args = tuple(args_list)
    return func(*args, **kwargs) def needs_packages(*packages):
    '''Decorator: ensure that packages are installed on host given by fabric
    argument `-H` (local or remote).
    '''
    def real_decorator(func):

        @wraps(func)
        def wrapper(*args, **kwargs):
            non_installed = _non_installed(packages)
            if non_installed:
                what_for = 'in order to run this task'
                install_packages(non_installed, what_for=what_for)
            return func(*args, **kwargs)

        return wrapper

    return real_decorator def put(*args, **kwargs):
    func, kwargs = _func(kwargs, func_remote=fabric.operations.put)
    if func == fabric.api.local:
        from_, to = [os.path.expanduser(arg) for arg in args]
        args = [flo('cp  {from_}  {to}')]
        kwargs = {}
    return func(*args, **kwargs) def run(*args, **kwargs):
    func, kwargs = _func(kwargs)
    return func(*args, **kwargs) def custom_task(func, *args, **kwargs):
    '''Decorator task() composed with decorator needs_repo_fabsetup_custom().
    '''
    return task(needs_repo_fabsetup_custom(func, *args, **kwargs)) def subtask(*args, **kwargs):
    '''Decorator which prints out the name of the decorated function on
    execution.
    '''
    depth = kwargs.get('depth', 2)
    prefix = kwargs.get('prefix', '\n' + '#' * depth + ' ')
    tail = kwargs.get('tail', '\n')
    doc1 = kwargs.get('doc1', False)
    color = kwargs.get('color', cyan)

    def real_decorator(func):
        if doc1:
            return print_full_name(color=color, prefix=prefix,
                                   tail=tail)(print_doc1(func))
        return print_full_name(color=color, prefix=prefix, tail=tail)(func)

    invoked = bool(not args or kwargs)
    if not invoked:
        # invoke decorator function which returns the wrapper function
        return real_decorator(func=args[0])
    return real_decorator def subsubtask(*args, **kwargs):

    def real_decorator(func):
        return subtask(depth=3, color=default_color, *args, **kwargs)(func)

    invoked = bool(not args or kwargs)
    if not invoked:
        # invoke decorator function which returns the wrapper function
        return real_decorator(func=args[0])
    return real_decorator def comment_out_line(filename, line, comment='#'):
    '''Comment line out by putting a comment sign in front of the line.

    If the file does not contain the line, the files content will not be
    changed (but the file will be touched in every case).
    '''
    return comment_out_local(filename, line, comment,
                             update_or_append_line=update_or_append_line) def print_msg(msg):
    if msg is not None:
        print(cyan(flo('{msg}'))) def install_file_legacy(path, sudo=False, from_path=None, **substitutions):
    '''Install file with path on the host target.

    The from file is the first of this list which exists:
     * custom file
     * custom file.template
     * common file
     * common file.template
    '''
    # source paths 'from_custom' and 'from_common'
    from_path = from_path or path
    # remove beginning '/' (if any), eg '/foo/bar' -> 'foo/bar'
    from_tail = join('files', from_path.lstrip(os.sep))
    if from_path.startswith('~/'):
        from_tail = join('files', 'home', 'USERNAME',
                         from_path[2:])  # without beginning '~/'
    from_common = join(FABFILE_DATA_DIR, from_tail)
    from_custom = join(FABSETUP_CUSTOM_DIR, from_tail)

    # target path 'to_' (path or tempfile)
    for subst in ['SITENAME', 'USER', 'ADDON', 'TASK']:
        sitename = substitutions.get(subst, False)
        if sitename:
            path = path.replace(subst, sitename)
    to_ = path
    if sudo:
        to_ = join(os.sep, 'tmp', 'fabsetup_' + os.path.basename(path))
    path_dir = dirname(path)

    # copy file
    if isfile(from_custom):
        run(flo('mkdir -p  {path_dir}'))
        put(from_custom, to_)
    elif isfile(from_custom + '.template'):
        _install_file_from_template_legacy(from_custom + '.template', to_=to_,
                                    **substitutions)
    elif isfile(from_common):
        run(flo('mkdir -p  {path_dir}'))
        put(from_common, to_)
    else:
        _install_file_from_template_legacy(from_common + '.template', to_=to_,
                                    **substitutions)
    if sudo:
        run(flo('sudo mv --force  {to_}  {path}')) @needs_host
def update_or_append_line(filename, prefix, new_line, keep_backup=True,
                          append=True):
    '''Search in file 'filename' for a line starting with 'prefix' and replace
    the line by 'new_line'.

    If a line starting with 'prefix' not exists 'new_line' will be appended.
    If the file not exists, it will be created.

    Return False if new_line was appended, else True (i.e. if the prefix was
    found within of the file).
    '''
    result = None
    if env.host_string == 'localhost':
        result = update_or_append_local(filename, prefix, new_line,
                                        keep_backup, append)
    else:
        tmp_dir = tempfile.mkdtemp(suffix='', prefix='fabsetup_')
        # fabric.api.local(flo('chmod 777 {tmp_dir}'))
        local_path = os.path.join(tmp_dir, os.path.basename(filename))
        fabric.operations.get(remote_path=filename, local_path=local_path,
                              use_sudo=True, temp_dir='/tmp')
        result = update_or_append_local(local_path, prefix, new_line,
                                        keep_backup, append)
        put(local_path, remote_path=filename, use_sudo=True, temp_dir='/tmp')
        with quiet():
            fabric.api.local(flo('rm -rf {tmp_dir}'))
    return result def uncomment_or_update_or_append_line(filename, prefix, new_line, comment='#',
                                       keep_backup=True):
    '''Remove the comment of an commented out line and make the line "active".

    If such an commented out line not exists it would be appended.
    '''
    return uua_local(filename, prefix, new_line, comment, keep_backup,
                     update_or_append_line=update_or_append_line) FABFILE_DATA_DIR = join(FABSETUP_DIR, 'fabfile-data') def flo(string):
    '''Return the string given by param formatted with the callers locals.'''
    callers_locals = {}
    frame = inspect.currentframe()
    try:
        outerframe = frame.f_back
        callers_locals = outerframe.f_locals
    finally:
        del frame
    return string.format(**callers_locals) def query_input(question, default=None, color=default_color):
    """Ask a question for input via raw_input() and return their answer.

    "question" is a string that is presented to the user.
    "default" is the presumed answer if the user just hits <Enter>.

    The "answer" return value is a str.
    """
    if default is None or default == '':
        prompt = ' '
    elif type(default) == str:
        prompt = flo(' [{default}] ')
    else:
        raise ValueError("invalid default answer: '%s'" % default)

    while True:
        sys.stdout.write(color(question + prompt))
        choice = raw_input()
        if default is not None and choice == '':
            return default
        if choice != '':
            return choice def filled_out_template(filename, **substitutions):
    '''Return content of file filename with applied substitutions.'''
    res = None
    with open(filename, 'r') as fp:
        template = fp.read()
        res = filled_out_template_str(template, **substitutions)
    return res
import random
import re
import tempfile
from fabric.api import env
from fabsetup.fabutils import checkup_git_repo_legacy, exists, needs_packages
from fabsetup.fabutils import put, run
from fabsetup.fabutils import custom_task as task, subtask, subsubtask
from fabsetup.fabutils import comment_out_line, print_msg, install_file_legacy
from fabsetup.fabutils import update_or_append_line
from fabsetup.fabutils import uncomment_or_update_or_append_line
from fabsetup.fabutils import FABFILE_DATA_DIR
from fabsetup.utils import flo, query_input, filled_out_template
    # run(flo('cd ~/repos/selfoss && git checkout $({latest_tag})'))
    latest_working_tag = '2.16'
    run(flo('cd ~/repos/selfoss && git checkout {latest_working_tag}'))

    run(flo('cd ~/repos/selfoss && git status'))  # show latest version


@subsubtask
def save_settings_and_data(site_dir):
    config_saved = False
    data_dir_saved = False

    run(flo('mkdir -p {site_dir}/bak'))

    if exists(flo('{site_dir}/selfoss/config.ini')):
        run(flo('cp {site_dir}/selfoss/config.ini  {site_dir}/bak/config.ini'),
            msg='save config.ini')
        config_saved = True

    if exists(flo('{site_dir}/selfoss/data')):
        run(flo('sudo  rsync -a --owner --group --delete --force  '
                '{site_dir}/selfoss/data  {site_dir}/bak'), msg='save data dir')
        data_dir_saved = True

    result = False
    if config_saved or data_dir_saved:
        result = True
    else:
        print_msg('nothing to save')
    return result


@subsubtask
def restore_settings_and_data(site_dir):
    config_restored = False
    data_dir_restored = False

    if exists(flo('{site_dir}/bak/config.ini')):
        run(flo('cp {site_dir}/bak/config.ini  {site_dir}/selfoss/config.ini'),
            msg='restore config.ini')
        config_restored = True

    if exists(flo('{site_dir}/bak/data')):
        run(flo('sudo  rsync -a --owner --group --delete --force  '
                '{site_dir}/bak/data  {site_dir}/selfoss'),
            msg='restore data dir')

    result = False
    if config_restored or data_dir_restored:
        result = True
    else:
        print_msg('nothing to restore')
    return result


@subsubtask
def install_spout_fulltextrssGoogleBot(sitename):
    install_file_legacy('~/sites/SITENAME/selfoss/spouts/rss/fulltextrssGoogleBot.php',
                 SITENAME=sitename)


@subtask
def install_selfoss(sitename, site_dir, username):
    save_settings_and_data(site_dir)
    run(flo("sudo  rsync -a --delete --force --exclude='.git'  ~/repos/selfoss "
            ' {site_dir}'), msg='\n### install files')
    restored = restore_settings_and_data(site_dir)
    install_spout_fulltextrssGoogleBot(sitename)
    print_msg('\n### set write permissions for group www-data')
    for dirname in ['data/cache', 'data/favicons', 'data/logs',
                    'data/thumbnails', 'data/sqlite', 'public']:
        run(flo('sudo chown --recursive  www-data.www-data  '
                '{site_dir}/selfoss/{dirname}'))
    return restored


@subsubtask
def selfoss_username(username, sitename):
    selfoss_username = query_input('selfoss user name:', default=username)
    update_or_append_line(flo('~/sites/{sitename}/selfoss/config.ini'),
                          prefix='username=',
                          new_line=flo('username={selfoss_username}'))


@subsubtask
def selfoss_password(sitename):
    password = query_input('selfoss password:')
    res = run(flo('curl -d password="{password}" -d form_submit="send" '
                  '--insecure https://{sitename}/password 2>/dev/null | '
                  'grep "Generated Password"'),
              capture=True)
    match = re.search(r'value="([^"]+)"', res)
    pw_hash = match.group(1)
    print(flo('{pw_hash}'))
    update_or_append_line(flo('~/sites/{sitename}/selfoss/config.ini'),
                          prefix='password=',
                          new_line=flo('password={pw_hash}'))


@subtask
def setup_selfoss_user(username, sitename, site_dir):
    if not exists(flo('{site_dir}/selfoss/config.ini')):
        chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
        salt = ''.join(random.SystemRandom().choice(chars) for _ in range(150))
        install_file_legacy('~/sites/SITENAME/selfoss/config.ini',
                     SITENAME=sitename, salt=salt)

    run('sudo service nginx reload')
    run('sudo service php5-fpm restart')

    selfoss_username(username, sitename)
    selfoss_password(sitename)


@subtask
def enable_php5_socket_file():
    filename = '/etc/php5/fpm/pool.d/www.conf'
    print_msg('comment out "listen = 127.0.0.1:9000"')
    comment_out_line(filename, comment=';', line='listen = 127.0.0.1:9000')
    line = 'listen = /var/run/php5-fpm.sock'

    print_msg(flo('\nuncomment "{line}"'))