Mini Shell

Direktori : /proc/self/root/opt/bakmgr/lib/python3.6/site-packages/bakmgr/
Upload File :
Current File : //proc/self/root/opt/bakmgr/lib/python3.6/site-packages/bakmgr/cli.py

"""Backup Manager CLI"""
from argparse import ArgumentParser
import getpass
import os
import sys
from itertools import chain
from pathlib import Path, PurePath
from typing import Tuple
from bakmgr.dash.dash_helpers import hash_pw, HASH_PATH
from .api.bakauth import MonError, add_problem
from .api.restic import Restic
from .configs import setup_logging, Conf


def main():
    """Main CLI entry point for /opt/bakmgr/bin/bakmgr"""
    if os.getuid() != 0:
        sys.exit('This tool must run as root')
    args = parse_args()
    conf = Conf()
    setup_logging(conf, f'bakmgr {args.command}')
    if args.command == 'dash-password':
        return set_dash_password()
    if args.command == 'restore-files':
        return restore_files(args, conf)
    if args.command == 'restore-mysql':
        return restore_db(args, 'mysql', conf)
    if args.command == 'restore-pgsql':
        return restore_db(args, 'pgsql', conf)
    raise NotImplementedError(args.command)


def parse_args():
    """Parse CLI args to /opt/bakmgr/bin/bakmgr"""
    parser = ArgumentParser(description=__doc__)
    subp = parser.add_subparsers(title='command', dest='command')
    add_cmd = lambda cmd, msg: subp.add_parser(cmd, description=msg, help=msg)
    add_cmd('dash-password', 'set backup dashboard password')
    files = add_cmd('restore-files', 'restore files/folders')
    mysql = add_cmd('restore-mysql', 'restore MySQL data')
    pgsql = add_cmd('restore-pgsql', 'restore PostgreSQL data')
    for bak in (files, mysql, pgsql):
        grp = bak.add_mutually_exclusive_group()
        for opt in ('latest', 'oldest'):
            grp.add_argument(
                f'--{opt}',
                dest=opt,
                action='store_true',
                help=f'Automatically pick the {opt} backup found',
            )
    files.add_argument(
        '--target',
        metavar='PATH',
        default=Path('/'),
        type=PurePath,
        help='alternate directory to extract data to',
    )
    files.add_argument(
        'paths',
        metavar='path',
        type=PurePath,
        nargs='+',
        help='path(s) to restore',
    )
    for sql in (mysql, pgsql):
        sql.add_argument(
            'path', type=Path, help='specify a path restore the sql dump to'
        )
    args = parser.parse_args()
    if args.command is None:
        parser.print_usage()
        sys.exit(1)
    return args


def set_dash_password():
    try:
        cleartext_pw = getpass.getpass()
        while invalid_pw(cleartext_pw):
            cleartext_pw = getpass.getpass()
    except KeyboardInterrupt:
        sys.exit("canceled")
    hashed = hash_pw(cleartext_pw)
    with open(HASH_PATH, 'w', encoding='ascii') as file:
        HASH_PATH.chmod(0o600)
        file.write(hashed)
    print("Password stored.")


def invalid_pw(cleartext_pw: str):
    if len(cleartext_pw) < 8:
        print("Password is too short.")
        return True
    return False


def pick_snap(args, conf: Conf, tag: str) -> Tuple[Restic, str]:
    """Return a restic instance and chosen snapshot id to restore from"""
    print('Looking up backups...')
    try:
        restic = Restic(conf)
    except Exception as exc:
        add_problem(MonError.RESTIC, str(exc))
        sys.exit(1)
    backups = restic.get_backups()[tag]
    if not backups:
        sys.exit('No backups found')
    if len(backups) == 1 or args.latest:
        return restic, backups[0].id
    if args.oldest:
        return restic, backups[-1].id
    print('Index', 'Date', sep=' ' * 4)
    print('=' * 41)
    for index, snap in enumerate(backups):
        print(str(index).rjust(2), snap.datetime, sep=' ' * 7)
    print('=' * 41)
    try:
        idx = int(input('Enter the index of the backup to restore from: '))
        snap_id = backups[idx].id
    except (IndexError, ValueError):
        sys.exit('invalid index')
    except (EOFError, KeyboardInterrupt):
        sys.exit('canceled')
    return restic, snap_id


def restore_files(args, conf: Conf):
    """Restore files/folders"""
    restic, snap_id = pick_snap(args, conf, 'files')
    incl = chain(*[['--include', str(x)] for x in args.paths])
    restic.execv('restore', '--target', str(args.target), *incl, snap_id)


def restore_db(args, dbtype: str, conf: Conf):
    """Restore SQL data"""
    restic, snap_id = pick_snap(args, conf, dbtype)
    with args.path.open('wb') as handle:
        proc = restic.proc(
            'dump',
            snap_id,
            f'/root/{dbtype}_dump.sql',
            mon=False,
            stdout=handle,
            stderr=None,
            encoding=None,
        )
        rcode = proc.complete(check=False).returncode
    sys.exit(rcode)


if __name__ == '__main__':
    main()

Zerion Mini Shell 1.0