Esempio n. 1
0
def make_storage(config,
                 today,
                 quiet=False,
                 verbose=False,
                 logfile=os.devnull):
    arclist = list()
    logdate = datetime.date.strftime(today, r'%y%m%d')

    dskpath = config['Path']['dskdir']
    tarpath = os.path.join(config['Path']['logdir'], 'tarfile')

    if not os.path.isdir(dskpath):
        return arclist
    if not os.path.isdir(tarpath):
        return arclist

    text = f'Storing ancient archives at external hard disk {under}{dskpath}{reset}'
    print_info(text, logfile, redirect=quiet)

    days = calendar.monthrange(year=today.year, month=today.month)[1]
    ctime = datetime.datetime.fromtimestamp(os.stat(tarpath).st_birthtime)
    if (today - ctime) > datetime.timedelta(days=days):
        glob_list = glob.glob(os.path.join(tarpath, '*/*.tar.xz'))
        if glob_list:
            with tempfile.TemporaryDirectory() as tmppath:
                arcdate = datetime.date.strftime(ctime, r'%y%m%d')
                tarname = os.path.join(tmppath, f'{arcdate}-{logdate}.tar.bz')
                if verbose:
                    print_scpt(f'tar -cjf {tarname} {tarpath}',
                               logfile,
                               redirect=quiet)
                else:
                    print_scpt(f'tar -cjvf {tarname} {tarpath}',
                               logfile,
                               redirect=verbose)
                with tarfile.open(tarname, 'w:bz2') as bz:
                    for absname in glob_list:
                        arcname = pathlib.Path(absname).relative_to(tarpath)
                        bz.add(absname, arcname)
                        arclist.append(absname)
                        print_text(absname, logfile, redirect=verbose)

                arcfile = os.path.join(config['Path']['arcdir'], 'archive.zip')
                if verbose:
                    print_scpt(f'tar -cZf {arcfile} {tarname}',
                               logfile,
                               redirect=quiet)
                else:
                    print_scpt(f'tar -cZvf {arcfile} {tarname}',
                               logfile,
                               redirect=verbose)
                with zipfile.ZipFile(arcfile, 'a', zipfile.ZIP_DEFLATED) as zf:
                    arcname = os.path.split(tarname)[1]
                    zf.write(tarname, arcname)
                    print_text(tarname, logfile, redirect=verbose)
        shutil.rmtree(tarpath)
    return arclist
Esempio n. 2
0
def launch_askpass(password=None, quiet=False, verbose=False, logfile=os.devnull, *args, **kwargs):  # pylint: disable=unused-argument,keyword-arg-before-vararg
    text = 'Launching MacDaily SSH-AskPass program'
    print_info(text, logfile, quiet)

    path = f'Macintosh HD{ROOT.replace(os.path.sep, ":")}:img:askpass.icns'
    ASKPASS = ['#!/usr/bin/env osascript',
               '',
               '-- script based on https://github.com/theseal/ssh-askpass',
               '',
               'on run argv',
               '    set args to argv as text',
               '    if args starts with "--help" or args starts with "-h" then',
               '        return "macdaily-askpass [-h|--help] [prompt]"',
               '    end if',
               f'    display dialog args with icon file ("{path}") default button "OK" default answer "" with hidden answer',  # pylint: disable=line-too-long
               "    return result's text returned",
               'end run',
               '']
    askpass = os.path.join(ROOT, 'res', 'askpass.applescript')
    text = f'Making executable {askpass!r}'
    print_misc(text, logfile, verbose)

    user = owner = getpass.getuser()
    if os.path.isfile(askpass):
        owner = pwd.getpwuid(os.stat(askpass).st_uid).pw_name
        if user != owner:
            run_script(['chown', user, askpass], quiet, verbose,
                       sudo=True, password=password, logfile=logfile)
    else:
        try:
            pathlib.Path(askpass).touch()
        except PermissionError:
            owner = 'root'
            run_script(['touch', askpass], quiet, verbose,
                       sudo=True, password=password, logfile=logfile)
            run_script(['chown', user, askpass], quiet, verbose,
                       sudo=True, password=password, logfile=logfile)

    with open(askpass, 'w') as file:
        file.write(os.linesep.join(ASKPASS))
    if user != owner:
        run_script(['chmod', '+x', askpass], quiet, verbose,
                   sudo=True, password=password, logfile=logfile)
        run_script(['chown', owner, askpass], quiet, verbose,
                   sudo=True, password=password, logfile=logfile)
    else:
        run_script(['chmod', '+x', askpass], quiet, verbose, logfile=logfile)

    return askpass
Esempio n. 3
0
def launch_confirm(password=None, quiet=False, verbose=False, logfile=os.devnull, *args, **kwargs):  # pylint: disable=unused-argument,keyword-arg-before-vararg
    text = 'Launching MacDaily Confirmation program'
    print_info(text, logfile, quiet)

    path = f'Macintosh HD{ROOT.replace(os.path.sep, ":")}:img:confirm.icns'
    ASKPASS = ['#!/usr/bin/env osascript',
               '',
               'on run argv',
               '    set args to argv as text',
               '    if args starts with "--help" or args starts with "-h" then',
               '        return "macdaily-confirm [-h|--help] [prompt]"',
               '    end if',
               f'    display dialog args with icon file ("{path}") default button "Cancel"',
               "    return result's button returned",
               'end run',
               '']
    confirm = os.path.join(ROOT, 'res', 'confirm.applescript')
    text = f'Making executable {confirm!r}'
    print_misc(text, logfile, verbose)

    user = owner = getpass.getuser()
    if os.path.isfile(confirm):
        owner = pwd.getpwuid(os.stat(confirm).st_uid).pw_name
        if user != owner:
            run_script(['chown', user, confirm], quiet, verbose,
                       sudo=True, password=password, logfile=logfile)
    else:
        try:
            pathlib.Path(confirm).touch()
        except PermissionError:
            owner = 'root'
            run_script(['touch', confirm], quiet, verbose,
                       sudo=True, password=password, logfile=logfile)
            run_script(['chown', user, confirm], quiet, verbose,
                       sudo=True, password=password, logfile=logfile)

    with open(confirm, 'w') as file:
        file.write(os.linesep.join(ASKPASS))
    if user != owner:
        run_script(['chmod', '+x', confirm], quiet, verbose,
                   sudo=True, password=password, logfile=logfile)
        run_script(['chown', owner, confirm], quiet, verbose,
                   sudo=True, password=password, logfile=logfile)
    else:
        run_script(['chmod', '+x', confirm], quiet, verbose, logfile=logfile)

    return confirm
Esempio n. 4
0
                def _move(root, stem):
                    arch = os.path.join(self._disk_dir, stem)
                    pathlib.Path(arch).mkdir(parents=True, exist_ok=True)

                    file_list = list()
                    for name in os.listdir(root):
                        path = os.path.join(root, name)
                        if os.path.isdir(path):
                            if name != 'Cask':
                                file_list.extend(
                                    _move(path, os.path.join(stem, name)))
                        elif os.path.splitext(name)[
                                1] != '.incomplete' and path not in cask_list:  # pylint: disable=cell-var-from-loop
                            try:
                                shutil.move(path, os.path.join(arch, name))
                            except (shutil.Error, FileExistsError):
                                os.remove(path)
                            # with contextlib.suppress(shutil.Error, FileExistsError):
                            #     shutil.move(path, os.path.join(arch, name))
                            file_list.append(path)
                    return file_list
Esempio n. 5
0
    def __init__(
            self,
            namespace,
            filename,
            timeout,
            confirm,  # pylint: disable=super-init-not-called
            askpass,
            password,
            disk_dir,
            brew_renew=None):
        self._qflag = namespace.get('quiet', False)
        self._vflag = self._qflag or (not namespace.get('verbose', False))

        text = f'Running {self.cmd} command for {self.mode}'
        print_info(text, filename, redirect=self._qflag)

        # assign members
        self._file = filename
        self._timeout = timeout
        self._confirm = confirm
        self._askpass = askpass
        self._password = password
        self._disk_dir = disk_dir
        self._brew_renew = brew_renew
        self._logroot = str(pathlib.Path(filename).resolve().parents[1])

        # exit if no executable found
        if self._check_exec():
            # mainloop process
            self._pkg_args(namespace)
            self._loc_exec()
            self._run_proc()

        # remove temp vars
        [
            delattr(self, attr)
            for attr in filter(lambda s: s.startswith('_var_'), dir(self))
        ]  # pylint: disable=expression-not-assigned
Esempio n. 6
0
def install(argv=None):
    # parse args & set context redirection flags
    args = parse_args(argv)
    quiet = args.quiet
    verbose = (args.quiet or not args.verbose)

    # parse config & change environ
    config = parse_config(quiet, verbose)
    os.environ['SUDO_ASKPASS'] = config['Miscellaneous']['askpass']

    # fetch current time
    today = datetime.datetime.today()
    logdate = datetime.date.strftime(today, r'%y%m%d')
    logtime = datetime.date.strftime(today, r'%H%M%S')

    # mkdir for logs
    logpath = pathlib.Path(
        os.path.join(config['Path']['logdir'], 'install', logdate))
    logpath.mkdir(parents=True, exist_ok=True)

    # prepare command paras
    filename = os.path.join(logpath, f'{logtime}-{uuid.uuid4()!s}.log')
    os.environ['MACDAILY_LOGFILE'] = filename

    confirm = config['Miscellaneous']['confirm']
    askpass = config['Miscellaneous']['askpass']
    timeout = config['Miscellaneous']['limit']
    disk_dir = config['Path']['arcdir']
    brew_renew = None

    # record program status
    text = f'{bold}{green}|๐Ÿšจ|{reset} {bold}Running MacDaily version {__version__}{reset}'
    print_term(text, filename, redirect=quiet)
    record(filename, args, today, config, redirect=verbose)

    # ask for password
    text = f'{bold}{purple}|๐Ÿ”‘|{reset} {bold}Your {under}sudo{reset}{bold} password may be necessary{reset}'
    print_term(text, filename, redirect=quiet)
    password = get_pass(askpass)

    cmd_list = list()
    for mode in {'apm', 'brew', 'cask', 'gem', 'mas', 'npm', 'pip', 'system'}:
        # skip disabled commands
        if not config['Mode'].get(mode, False):
            text = f'macdaily-install: {yellow}{mode}{reset}: command disabled'
            print_term(text, filename, redirect=verbose)
            continue

        # skip commands with no package spec
        packages = getattr(args, f'{mode}_pkgs', list())
        namespace = getattr(args, mode, None)
        if not (packages or namespace):
            text = f'macdaily-install: {yellow}{mode}{reset}: nothing to install'
            print_term(text, filename, redirect=verbose)
            continue

        # update package specifications
        if namespace is None:
            namespace = dict(vars(args), packages=list())
        namespace['packages'].extend(packages)

        # check master controlling flags
        if args.yes:
            namespace['yes'] = True
        if args.quiet:
            namespace['quiet'] = True
        if args.verbose:
            namespace['verbose'] = True
        if args.no_cleanup:
            namespace['no_cleanup'] = True

        # run command
        cmd_cls = globals()[f'{mode.capitalize()}Install']
        command = cmd_cls(make_namespace(namespace), filename, timeout,
                          confirm, askpass, password, disk_dir, brew_renew)

        # record command
        cmd_list.append(command)
        brew_renew = command.time

    archive = None
    if not args.no_cleanup:
        archive = make_archive(config,
                               'install',
                               today,
                               quiet=quiet,
                               verbose=verbose,
                               logfile=filename)

    text = f'{bold}{green}|๐Ÿ“–|{reset} {bold}MacDaily report of install command{reset}'
    print_term(text, filename, redirect=quiet)

    for command in cmd_list:
        desc = make_description(command)
        pkgs = f'{reset}{bold}, {green}'.join(command.packages)
        fail = f'{reset}{bold}, {red}'.join(command.failed)

        if pkgs:
            flag = (len(pkgs) == 1)
            text = f'Installed following {under}{desc(flag)}{reset}{bold}: {green}{pkgs}{reset}'
            print_misc(text, filename, redirect=quiet)
        else:
            text = f'No {under}{desc(False)}{reset}{bold} installed'
            print_misc(text, filename, redirect=quiet)

        if fail:
            flag = (len(fail) == 1)
            text = f'Installation of following {under}{desc(flag)}{reset}{bold} failed: {red}{fail}{reset}'
            print_misc(text, filename, redirect=quiet)
        else:
            verb, noun = ('s', '') if len(fail) == 1 else ('', 's')
            text = f'All {under}{desc(False)}{reset}{bold} installation{noun} succeed{verb}'
            print_misc(text, filename, redirect=verbose)

    if archive:
        formatted_list = f'{reset}{bold}, {under}'.join(archive)
        text = (
            f'Archived following ancient logs: {under}{formatted_list}{reset}')
        print_misc(text, filename, redirect=quiet)

    if len(cmd_list) == 0:  # pylint: disable=len-as-condition
        text = f'macdaily: {purple}install{reset}: no packages installed'
        print_term(text, filename, redirect=quiet)

    if args.show_log:
        try:
            subprocess.check_call([
                'open', '-a', '/Applications/Utilities/Console.app', filename
            ])
        except subprocess.CalledProcessError:
            print_text(traceback.format_exc(), filename, redirect=verbose)
            print(
                f'macdaily: {red}install{reset}: cannot show log file {filename!r}',
                file=sys.stderr)

    mode_lst = [command.mode for command in cmd_list]
    mode_str = ', '.join(mode_lst) if mode_lst else 'none'
    text = (
        f'{bold}{green}|๐Ÿบ|{reset} {bold}MacDaily successfully performed install process '
        f'for {mode_str} package managers{reset}')
    print_term(text, filename, redirect=quiet)
Esempio n. 7
0
    def _proc_cleanup(self):
        if self._no_cleanup:
            return

        text = 'Pruning caches and archives'
        print_info(text, self._file, redirect=self._qflag)

        if not os.path.isdir(self._disk_dir):
            text = (
                f'macdaily-{self.cmd}: {yellow}cask{reset}: '
                f'archive directory {bold}{self._disk_dir}{reset} not found')
            print_term(text, self._file, redirect=self._vflag)
            return

        argv = ['brew', 'cask', 'cleanup']
        if self._verbose:
            argv.append('--verbose')
        if self._quiet:
            argv.append('--quiet')
        print_scpt(' '.join(argv), self._file, redirect=self._qflag)

        path_cask = os.path.join(self._disk_dir, 'Homebrew', 'Cask')
        path_down = os.path.join(self._disk_dir, 'Homebrew', 'download')

        pathlib.Path(path_cask).mkdir(parents=True, exist_ok=True)
        pathlib.Path(path_down).mkdir(parents=True, exist_ok=True)

        for path in self._exec:
            argv = [path, '--cache']
            args = ' '.join(argv)
            print_scpt(args, self._file, redirect=self._vflag)
            with open(self._file, 'a') as file:
                file.write(f'Script started on {date()}\n')
                file.write(f'command: {args!r}\n')

            fail = False
            try:
                proc = subprocess.check_output(argv,
                                               stderr=make_stderr(self._vflag))
            except subprocess.CalledProcessError:
                print_text(traceback.format_exc(),
                           self._file,
                           redirect=self._vflag)
                fail = True
            else:
                context = proc.decode()
                print_text(context, self._file, redirect=self._vflag)
            finally:
                with open(self._file, 'a') as file:
                    file.write(f'Script done on {date()}\n')
            if fail:
                continue

            cache = context.strip()
            if os.path.isdir(cache):
                argv = [path, 'cask', 'caches', 'archive']
                if self._verbose:
                    argv.append('--verbose')
                if self._quiet:
                    argv.append('--quiet')
                print_scpt(' '.join(argv), self._file, redirect=self._qflag)

                file_list = list()
                link_list = glob.glob(os.path.join(cache, 'Cask/*'))
                cask_list = [os.path.realpath(name) for name in link_list]
                for link in link_list:
                    file_list.append(link)
                    try:
                        shutil.move(link, path_cask)
                    except (shutil.Error, FileExistsError):
                        os.remove(link)
                for cask in filter(
                        lambda p: os.path.splitext(p)[1] != '.incomplete',
                        cask_list):
                    try:
                        shutil.move(cask, path_down)
                    except (shutil.Error, FileExistsError):
                        os.remove(cask)
                    file_list.append(cask)
                print_text(os.linesep.join(sorted(file_list)),
                           self._file,
                           redirect=self._vflag)
Esempio n. 8
0
def launch_daemons(config, password, quiet=False, verbose=False, logfile=os.devnull):
    text = 'Launching MacDaily LaunchAgent program'
    print_info(text, logfile, quiet)

    def make_daemon(mode, argv):
        DAEMON = ['#!/usr/bin/env osascript',
                  '',
                  '-- show notification',
                  f'display notification "Running scheduled {mode} scripts..." with title "MacDaily"',
                  '',
                  '-- run script',
                  f'do shell script "{PYTHON} -m macdaily {mode} {argv}"',
                  '']
        return os.linesep.join(DAEMON)

    # Property List
    osascript = shutil.which('osascript')
    PLIST_BASE = collections.OrderedDict(
        Label=str(),
        UserName=getpass.getuser(),
        Program=osascript,
        ProgramArguments=[osascript, ''],
        # RunAtLoad=True,
        RootDirectory=str(pathlib.Path.home()),
        EnvironmentVariables=dict(os.environ),
        StartCalendarInterval=list(),
        StandardOutPath=str(),
        StandardErrorPath=str(),
    )

    library = os.path.expanduser('~/Library/LaunchAgents/')
    os.makedirs(library, exist_ok=True)

    root = pathlib.Path(config['Path']['logdir'])
    for mode, time in config['Daemon'].items():
        (root / mode).mkdir(parents=True, exist_ok=True)

        name = f'com.macdaily.{mode}'
        path = os.path.join(ROOT, 'res', f'daemon-{mode}.applescript')
        pout = str(root / mode / 'stdout.log')
        perr = str(root / mode / 'stderr.log')
        argv = ' '.join(config['Command'].get(mode, ['--help']))

        text = f'Adding {under}{mode}{reset}{bold} command LaunchAgent {name!r}'
        print_misc(text, logfile, quiet)

        user = owner = getpass.getuser()
        if os.path.isfile(path):
            owner = pwd.getpwuid(os.stat(path).st_uid).pw_name
            if user != owner:
                run_script(['chown', user, path], quiet, verbose,
                           sudo=True, password=password, logfile=logfile)
        else:
            try:
                pathlib.Path(path).touch()
            except PermissionError:
                owner = 'root'
                run_script(['touch', path], quiet, verbose,
                           sudo=True, password=password, logfile=logfile)
                run_script(['chown', user, path], quiet, verbose,
                           sudo=True, password=password, logfile=logfile)

        with open(path, 'w') as file:
            file.write(make_daemon(mode, argv))
        run_script(['chmod', '+x', path], quiet, verbose,
                   sudo=True, password=password, logfile=logfile)
        if user != owner:
            run_script(['chown', owner, path], quiet, verbose,
                       sudo=True, password=password, logfile=logfile)

        PLIST = copy.copy(PLIST_BASE)
        PLIST['Label'] = name
        PLIST['ProgramArguments'][1] = path
        PLIST['StartCalendarInterval'] = time
        PLIST['StandardOutPath'] = pout
        PLIST['StandardErrorPath'] = perr

        plist = os.path.join(library, f'{name}.plist')
        text = f'Adding Launch Agent {name!r}'
        print_misc(text, logfile, verbose)
        if os.path.exists(plist):
            run_script(['launchctl', 'unload', '-w', plist], quiet, verbose, logfile=logfile)
        with open(plist, 'wb') as file:
            plistlib.dump(PLIST, file, sort_keys=False)
        run_script(['launchctl', 'load', '-w', plist], quiet, verbose, logfile=logfile)

    return library
Esempio n. 9
0
def make_archive(config,
                 mode,
                 today,
                 zipfile=True,
                 quiet=False,
                 verbose=False,
                 logfile=os.devnull):  # pylint: disable=redefined-outer-name
    logdir = config['Path']['logdir']
    logdate = datetime.date.strftime(today, r'%y%m%d')

    logpath = pathlib.Path(os.path.join(logdir, mode))
    arcpath = pathlib.Path(os.path.join(logdir, 'arcfile', mode))
    tarpath = pathlib.Path(os.path.join(logdir, 'tarfile', mode))

    logpath.mkdir(parents=True, exist_ok=True)
    arcpath.mkdir(parents=True, exist_ok=True)
    tarpath.mkdir(parents=True, exist_ok=True)

    text = f'Moving ancient logs into {under}GNU Zip Archives{reset}'
    print_info(text, logfile, redirect=quiet)

    filelist = list()
    for subdir in filter(
            lambda subdir: os.path.isdir(os.path.join(logpath, subdir)),
            os.listdir(logpath)):
        if subdir == logdate:
            continue
        absdir = os.path.abspath(os.path.join(logpath, subdir))
        glob_list = glob.glob(os.path.join(absdir, '*.log'))
        if glob_list:
            tarname = os.path.join(arcpath, f'{subdir}.tar.gz')
            if verbose:
                print_scpt(f'tar -czf {tarname} {absdir}',
                           logfile,
                           redirect=quiet)
            else:
                print_scpt(f'tar -czvf {tarname} {absdir}',
                           logfile,
                           redirect=verbose)
            with tarfile.open(tarname, 'w:gz') as gz:
                for absname in glob_list:
                    arcname = os.path.split(absname)[1]
                    gz.add(absname, arcname)
                    filelist.append(absname)
                    print_text(absname, logfile, redirect=verbose)
        shutil.rmtree(absdir)

    misc_path = os.path.join(get_logdir(), 'misc')
    if os.path.isdir(misc_path):
        this_version = distutils.version.LooseVersion(VERSION)  # pylint: disable=no-member
        for entry in os.scandir(misc_path):
            if not entry.is_dir:
                continue
            try:
                that_version = distutils.version.LooseVersion(entry.name)  # pylint: disable=no-member
                if this_version <= that_version:
                    continue
            except ValueError:
                pass
            glob_list = glob.glob(os.path.join(entry.path, '*.log'))
            if glob_list:
                tarname = os.path.join(arcpath, f'{entry.name}.tar.gz')
                with tarfile.open(tarname, 'w:gz') as gz:
                    for absname in glob_list:
                        arcname = os.path.split(absname)[1]
                        gz.add(absname, arcname)
            shutil.rmtree(entry.path)

    text = f'Moving ancient archives into {under}XZ Compressed Archives{reset}'
    print_info(text, logfile, redirect=quiet)

    ctime = datetime.datetime.fromtimestamp(os.stat(arcpath).st_birthtime)
    if (today - ctime) > datetime.timedelta(days=7):
        glob_list = glob.glob(os.path.join(arcpath, '*.tar.gz'))
        if glob_list:
            arcdate = datetime.date.strftime(ctime, r'%y%m%d')
            tarname = os.path.join(tarpath, f'{arcdate}-{logdate}.tar.xz')
            if verbose:
                print_scpt(f'tar -cJf {tarname} {arcpath}',
                           logfile,
                           redirect=quiet)
            else:
                print_scpt(f'tar -cJvf {tarname} {arcpath}',
                           logfile,
                           redirect=verbose)
            with tarfile.open(tarname, 'w:xz') as xz:
                for absname in glob_list:
                    arcname = os.path.split(absname)[1]
                    xz.add(absname, arcname)
                    filelist.append(absname)
                    print_text(absname, logfile, redirect=verbose)
        shutil.rmtree(arcpath)

    if zipfile:  # pylint:disable=redefined-outer-name
        filelist.extend(make_storage(config, today, quiet, verbose, logfile))
    return filelist
Esempio n. 10
0
def launch(argv=None):
    # parse args & set context redirection flags
    args = parse_args(argv)
    quiet = args.quiet
    verbose = (args.quiet or not args.verbose)

    # parse config & change environ
    config = parse_config(quiet, verbose)
    os.environ['SUDO_ASKPASS'] = config['Miscellaneous']['askpass']

    # fetch current time
    today = datetime.datetime.today()
    logdate = datetime.date.strftime(today, r'%y%m%d')
    logtime = datetime.date.strftime(today, r'%H%M%S')

    # mkdir for logs
    logpath = pathlib.Path(os.path.join(config['Path']['logdir'], 'launch', logdate))
    logpath.mkdir(parents=True, exist_ok=True)

    # prepare command paras
    filename = os.path.join(logpath, f'{logtime}-{uuid.uuid4()!s}.log')
    os.environ['MACDAILY_LOGFILE'] = filename

    askpass = config['Miscellaneous']['askpass']

    # record program status
    text = f'{bold}{green}|๐Ÿšจ|{reset} {bold}Running MacDaily version {__version__}{reset}'
    print_term(text, filename, redirect=quiet)
    record(filename, args, today, config, redirect=verbose)

    # ask for password
    text = f'{bold}{purple}|๐Ÿ”‘|{reset} {bold}Your {under}sudo{reset}{bold} password may be necessary{reset}'
    print_term(text, filename, redirect=quiet)
    password = get_pass(askpass)

    # update program list
    if args.all:
        args.program.extend(('askpass', 'confirm', 'daemons'))

    prog_dict = dict()
    for program in set(args.program):
        if re.match(r'^(askpass|confirm|daemons)$', program, re.IGNORECASE) is None:
            parser = get_launch_parser()
            parser.error(f"argument PROG: invalid choice: {program!r} (choose from 'askpass', 'confirm', 'daemons')")

        # launch program
        launch_func = globals()[f'launch_{program.lower()}']
        path = launch_func(quiet=quiet, verbose=verbose, config=config, password=password, logfile=filename)

        # record program
        prog_dict[program] = path

    archive = None
    if not args.no_cleanup:
        archive = make_archive(config, 'launch', today, quiet=quiet, verbose=verbose, logfile=filename)

    text = f'{bold}{green}|๐Ÿ“–|{reset} {bold}MacDaily report of launch command{reset}'
    print_term(text, filename, redirect=quiet)

    for prog, path in prog_dict.items():
        text = f'Launched helper program {under}{prog}{reset}{bold} at {under}{path}{reset}'
        print_misc(text, filename, redirect=quiet)

    if archive:
        formatted_list = f'{reset}{bold}, {under}'.join(archive)
        text = (f'Archived following ancient logs: {under}{formatted_list}{reset}')
        print_misc(text, filename, redirect=quiet)

    if len(prog_dict) == 0:
        text = f'macdaily: {purple}launch{reset}: no program launched'
        print_term(text, filename, redirect=quiet)

    if args.show_log:
        try:
            subprocess.check_call(['open', '-a', '/Applications/Utilities/Console.app', filename])
        except subprocess.CalledProcessError:
            print_text(traceback.format_exc(), filename, redirect=verbose)
            print(f'macdaily: {red}launch{reset}: cannot show log file {filename!r}', file=sys.stderr)

    mode_str = ', '.join(prog_dict) if prog_dict else 'none'
    text = (f'{bold}{green}|๐Ÿบ|{reset} {bold}MacDaily successfully performed launch process '
            f'for {mode_str} helper programs{reset}')
    print_term(text, filename, redirect=quiet)
Esempio n. 11
0
    os.getenv('MACDAILY_DEVMODE', 'false').strip().lower(), False)

###########################################################
# Miscellaneous Constants
###########################################################

# ansi escape pattern
# from http://www.umich.edu/~archive/apple2/misc/programmers/vt100.codes.txt
ANSI = r'([\x1B\x9B][\[\]\(\)#;?]*(?:(?:(?:[a-zA-Z0-9]*(?:;[-a-zA-Z0-9\\/#&.:=?%@~_]*)*)?\x07)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[0-9A-PR-TZcf-ntqry=><~])))'  # pylint: disable=line-too-long

# script utilities
SCRIPT = shutil.which('script')
UNBUFFER = shutil.which('unbuffer')

# environment macros
ROOT = str(pathlib.Path(__file__).resolve().parents[2])
SHELL = os.getenv('SHELL', shutil.which('sh'))

# user information
USR = getpass.getuser()
PWD = pwd.getpwnam(USR)
USER = PWD.pw_gecos
PASS = os.getenv('SUDO_PASSWORD', PWD.pw_passwd)

# emoji mappings
ORIG_BEER = 'รฐลธ๏ฟฝยบ'
RESP_BEER = 'รƒยฐ\x9f\x8dร‚ยบ'

# homebrew aliases
NODE = {'node', 'node.js', 'node@11', 'nodejs', 'npm'}
PYTHON2 = {'python@2', 'python2'}
Esempio n. 12
0
def logging(argv=None):
    # parse args & set context redirection flags
    args = parse_args(argv)
    quiet = args.quiet
    verbose = (args.quiet or not args.verbose)

    # parse config & change environ
    config = parse_config(quiet, verbose)
    os.environ['SUDO_ASKPASS'] = config['Miscellaneous']['askpass']

    # fetch current time
    today = datetime.datetime.today()
    logdate = datetime.date.strftime(today, r'%y%m%d')
    logtime = datetime.date.strftime(today, r'%H%M%S')

    # prepare command paras
    confirm = config['Miscellaneous']['confirm']
    askpass = config['Miscellaneous']['askpass']
    timeout = config['Miscellaneous']['limit']
    disk_dir = config['Path']['arcdir']
    brew_renew = None

    # record program status
    text_record = f'{bold}{green}|๐Ÿšจ|{reset} {bold}Running MacDaily version {__version__}{reset}'
    print_term(text_record, get_logfile(), redirect=quiet)
    record(get_logfile(), args, today, config, redirect=verbose)

    # ask for password
    text_askpass = f'{bold}{purple}|๐Ÿ”‘|{reset} {bold}Your {under}sudo{reset}{bold} password may be necessary{reset}'
    print_term(text_askpass, get_logfile(), redirect=quiet)
    password = get_pass(askpass)

    cmd_list = list()
    log_list = list()
    file_list = list()
    for mode in {
            'apm', 'app', 'brew', 'cask', 'gem', 'mas', 'npm', 'pip', 'tap'
    }:
        # mkdir for logs
        logpath = pathlib.Path(
            os.path.join(config['Path']['logdir'], 'logging', mode, logdate))
        logpath.mkdir(parents=True, exist_ok=True)
        filename = os.path.join(logpath, f'{logtime}-{uuid.uuid4()!s}.log')
        os.environ['MACDAILY_LOGFILE'] = filename

        # redo program status records
        print_term(text_record, filename, redirect=True)
        record(filename, args, today, config, redirect=True)
        print_term(text_askpass, filename, redirect=True)

        # skip disabled commands
        if (not config['Mode'].get(mode, False)) or getattr(
                args, f'no_{mode}', False):
            text = f'macdaily-logging: {yellow}{mode}{reset}: command disabled'
            print_term(text, filename, redirect=verbose)
            continue

        # update logging specifications
        namespace = getattr(args, mode, vars(args))

        # check master controlling flags
        if args.quiet:
            namespace['quiet'] = True
        if args.verbose:
            namespace['verbose'] = True
        if args.show_log:
            namespace['show_log'] = True
        if args.no_cleanup:
            namespace['no_cleanup'] = True

        # run command
        cmd_cls = globals()[f'{mode.capitalize()}Logging']
        command = cmd_cls(make_namespace(namespace), filename, timeout,
                          confirm, askpass, password, disk_dir, brew_renew)

        # record command
        cmd_list.append(command)
        log_list.append(filename)
        brew_renew = command.time

        if not namespace['no_cleanup']:
            archive = make_archive(config,
                                   f'logging/{mode}',
                                   today,
                                   zipfile=False,
                                   quiet=quiet,
                                   verbose=verbose,
                                   logfile=filename)
            file_list.extend(archive)

        if namespace.get('show_log', False):
            try:
                subprocess.check_call([
                    'open', '-a', '/Applications/Utilities/Console.app',
                    filename
                ])
            except subprocess.CalledProcessError:
                print_text(traceback.format_exc(), filename, redirect=verbose)
                print(
                    f'macdaily: {red}logging{reset}: cannot show log file {filename!r}',
                    file=sys.stderr)

    if not args.no_cleanup:
        storage = make_storage(config,
                               today,
                               quiet=quiet,
                               verbose=verbose,
                               logfile=filename)
        file_list.extend(storage)

    text = f'{bold}{green}|๐Ÿ“–|{reset} {bold}MacDaily report of logging command{reset}'
    print_term(text, get_logfile(), redirect=quiet)
    for file in log_list:
        print_term(text, file, redirect=True)

    for command in cmd_list:
        text = f'Recorded existing {under}{command.desc[1]}{reset}{bold} at {under}{command.sample}{reset}'
        print_misc(text, get_logfile(), redirect=quiet)
        for file in log_list:
            print_misc(text, file, redirect=True)

    if file_list:
        formatted_list = f'{reset}{bold}, {under}'.join(file_list)
        text = (
            f'Archived following ancient logs: {under}{formatted_list}{reset}')
        print_misc(text, filename, redirect=quiet)

    if len(cmd_list) == 0:  # pylint: disable=len-as-condition
        text = f'macdaily: {purple}logging{reset}: no packages recorded'
        print_term(text, get_logfile(), redirect=quiet)
        for file in log_list:
            print_term(text, file, redirect=True)

    mode_lst = [command.mode for command in cmd_list]
    mode_str = ', '.join(mode_lst) if mode_lst else 'none'
    text = (
        f'{bold}{green}|๐Ÿบ|{reset} {bold}MacDaily successfully performed logging process '
        f'for {mode_str} package managers{reset}')
    print_term(text, get_logfile(), redirect=quiet)
    for file in log_list:
        print_term(text, file, redirect=True)
Esempio n. 13
0
def archive(argv=None):
    # parse args & set context redirection flags
    args = parse_args(argv)
    quiet = args.quiet
    verbose = (args.quiet or not args.verbose)

    # parse config & change environ
    config = parse_config(quiet, verbose)
    os.environ['SUDO_ASKPASS'] = config['Miscellaneous']['askpass']

    # fetch current time
    today = datetime.datetime.today()
    logdate = datetime.date.strftime(today, r'%y%m%d')
    logtime = datetime.date.strftime(today, r'%H%M%S')

    # mkdir for logs
    logpath = pathlib.Path(
        os.path.join(config['Path']['logdir'], 'archive', logdate))
    logpath.mkdir(parents=True, exist_ok=True)

    # prepare command paras
    filename = os.path.join(logpath, f'{logtime}-{uuid.uuid4()!s}.log')
    os.environ['MACDAILY_LOGFILE'] = filename

    disk_dir = config['Path']['arcdir']

    # record program status
    text = f'{bold}{green}|๐Ÿšจ|{reset} {bold}Running MacDaily version {__version__}{reset}'
    print_term(text, filename, redirect=quiet)
    record(filename, args, today, config, redirect=verbose)

    log_pth = {
        'logging/apm', 'logging/app', 'logging/brew', 'logging/cask',
        'logging/gem', 'logging/mas', 'logging/npm', 'logging/pip',
        'logging/tap'
    }
    all_pth = log_pth | {
        'archive', 'cleanup', 'dependency', 'postinstall', 'reinstall',
        'tarfile', 'uninstall', 'update'
    }

    # update path list
    if args.all:
        args.path.extend(all_pth)
    if 'logging' in args.path:
        args.path.remove('logging')
        args.path.extend(log_pth)

    file_dict = dict()
    for mode in set(args.path):
        if mode not in all_pth:
            tmp_pth = all_pth.add('logging')
            with open(filename, 'a') as file:
                file.write(f'macdaily: archive: invalid option: {mode!r}')
            parser = get_archive_parser()
            parser.error(
                f"argument CMD: invalid choice: {mode!r} (choose from {', '.join(sorted(tmp_pth))})"
            )

        # make archives
        file_list = make_archive(config,
                                 mode,
                                 today,
                                 zipfile=False,
                                 quiet=quiet,
                                 verbose=verbose)

        # record file list
        file_dict[mode] = copy.copy(file_list)

    storage = None
    if not args.no_storage:
        storage = make_storage(config, today, quiet=quiet, verbose=verbose)

    text = f'{bold}{green}|๐Ÿ“–|{reset} {bold}MacDaily report of archive command{reset}'
    print_term(text, filename, redirect=quiet)

    for mode, file_list in file_dict.items():
        if file_list:
            formatted_list = f'{reset}{bold}, {under}'.join(file_list)
            text = f'Archived following ancient logs of {pink}{mode}{reset}{bold}: {under}{formatted_list}{reset}'
        else:
            text = f'No ancient logs of {under}{mode}{reset}{bold} archived'
        print_misc(text, filename, redirect=quiet)

    if len(file_dict) == 0:  # pylint: disable=len-as-condition
        text = f'macdaily: {purple}archive{reset}: no logs archived'
        print_term(text, filename, redirect=quiet)

    if storage:
        formatted_list = f'{reset}{bold}, {under}'.join(storage)
        text = (
            f'Stored following ancient archived into {pink}external hard disk{reset} '
            f'{bold} at {under}{disk_dir}{reset}{bold}: {under}{formatted_list}{reset}'
        )
        print_misc(text, filename, redirect=quiet)

    if args.show_log:
        try:
            subprocess.check_call([
                'open', '-a', '/Applications/Utilities/Console.app', filename
            ])
        except subprocess.CalledProcessError:
            print_text(traceback.format_exc(), filename, redirect=verbose)
            print(
                f'macdaily: {red}archive{reset}: cannot show log file {filename!r}',
                file=sys.stderr)

    mode_str = ', '.join(file_dict) if file_dict else 'none'
    text = (
        f'{bold}{green}|๐Ÿบ|{reset} {bold}MacDaily successfully performed archive process '
        f'for {mode_str} helper programs{reset}')
    print_term(text, filename, redirect=quiet)
Esempio n. 14
0
def postinstall(argv=None):
    # parse args & set context redirection flags
    args = parse_args(argv)
    quiet = args.quiet
    verbose = (args.quiet or not args.verbose)

    # parse config & change environ
    config = parse_config(quiet, verbose)
    os.environ['SUDO_ASKPASS'] = config['Miscellaneous']['askpass']

    # fetch current time
    today = datetime.datetime.today()
    logdate = datetime.date.strftime(today, r'%y%m%d')
    logtime = datetime.date.strftime(today, r'%H%M%S')

    # mkdir for logs
    logpath = pathlib.Path(
        os.path.join(config['Path']['logdir'], 'postinstall', logdate))
    logpath.mkdir(parents=True, exist_ok=True)

    # prepare command paras
    filename = os.path.join(logpath, f'{logtime}-{uuid.uuid4()!s}.log')
    os.environ['MACDAILY_LOGFILE'] = filename

    confirm = config['Miscellaneous']['confirm']
    askpass = config['Miscellaneous']['askpass']
    timeout = config['Miscellaneous']['limit']
    disk_dir = config['Path']['arcdir']
    brew_renew = None

    # record program status
    text = f'{bold}{green}|๐Ÿšจ|{reset} {bold}Running MacDaily version {__version__}{reset}'
    print_term(text, filename, redirect=quiet)
    record(filename, args, today, config, redirect=verbose)

    # ask for password
    text = f'{bold}{purple}|๐Ÿ”‘|{reset} {bold}Your {under}sudo{reset}{bold} password may be necessary{reset}'
    print_term(text, filename, redirect=quiet)
    password = get_pass(askpass)

    # check if disabled
    enabled = (not config['Mode'].get('brew', False))

    # run command
    if enabled:
        command = PostinstallCommand(make_namespace(args), filename, timeout,
                                     confirm, askpass, password, disk_dir,
                                     brew_renew)
    else:
        text = f'macdaily-postinstall: {yellow}brew{reset}: command disabled'
        print_term(text, filename, redirect=verbose)

    archive = None
    if not args.no_cleanup:
        archive = make_archive(config,
                               'postinstall',
                               today,
                               quiet=quiet,
                               verbose=verbose,
                               logfile=filename)

    text = f'{bold}{green}|๐Ÿ“–|{reset} {bold}MacDaily report of postinstall command{reset}'
    print_term(text, filename, redirect=quiet)

    if enabled:
        desc = make_description(command)
        pkgs = f'{reset}{bold}, {green}'.join(command.packages)
        miss = f'{reset}{bold}, {yellow}'.join(command.notfound)
        ilst = f'{reset}{bold}, {pink}'.join(command.ignored)
        fail = f'{reset}{bold}, {red}'.join(command.failed)

        if pkgs:
            flag = (len(pkgs) == 1)
            text = f'Postinstalled following {under}{desc(flag)}{reset}{bold}: {green}{pkgs}{reset}'
            print_misc(text, filename, redirect=quiet)
        else:
            text = f'No {under}{desc(False)}{reset}{bold} postinstalled'
            print_misc(text, filename, redirect=quiet)

        if fail:
            flag = (len(fail) == 1)
            text = f'Postinstallation of following {under}{desc(flag)}{reset}{bold} failed: {red}{fail}{reset}'
            print_misc(text, filename, redirect=quiet)
        else:
            verb, noun = ('s', '') if len(fail) == 1 else ('', 's')
            text = f'All {under}{desc(False)}{reset}{bold} postinstallation{noun} succeed{verb}'
            print_misc(text, filename, redirect=verbose)

        if ilst:
            flag = (len(ilst) == 1)
            text = f'Ignored postinstallation of following {under}{desc(flag)}{reset}{bold}: {pink}{ilst}{reset}'
            print_misc(text, filename, redirect=quiet)
        else:
            text = f'No {under}{desc(False)}{reset}{bold} ignored'
            print_misc(text, filename, redirect=verbose)

        if miss:
            flag = (len(miss) == 1)
            text = f'Following {under}{desc(flag)}{reset}{bold} not found: {yellow}{miss}{reset}'
            print_misc(text, filename, redirect=quiet)
        else:
            text = f'Hit all {under}{desc(False)}{reset}{bold} specifications'
            print_misc(text, filename, redirect=verbose)

    if archive:
        formatted_list = f'{reset}{bold}, {under}'.join(archive)
        text = (
            f'Archived following ancient logs: {under}{formatted_list}{reset}')
        print_misc(text, filename, redirect=quiet)

    if not enabled:
        text = f'macdaily: {purple}postinstall{reset}: no Homebrew formulae postinstalled'
        print_term(text, filename, redirect=quiet)

    if args.show_log:
        try:
            subprocess.check_call([
                'open', '-a', '/Applications/Utilities/Console.app', filename
            ])
        except subprocess.CalledProcessError:
            print_text(traceback.format_exc(), filename, redirect=verbose)
            print(
                f'macdaily: {red}postinstall{reset}: cannot show log file {filename!r}',
                file=sys.stderr)

    text = (
        f'{bold}{green}|๐Ÿบ|{reset} {bold}MacDaily successfully performed postinstall process{reset}'
    )
    print_term(text, filename, redirect=quiet)