def wrapper(*args, **kwargs): from macdaily.util.tools.get import get_logfile, get_boolean if get_boolean('NULL_PASSWORD'): return PASS SUDO_PASSWORD = os.getenv('SUDO_PASSWORD') if SUDO_PASSWORD is None: password = func(*args, **kwargs) else: password = SUDO_PASSWORD if not get_boolean('MACDAILY_NO_CHECK'): try: subprocess.run(['sudo', '--reset-timestamp'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) with make_pipe(password, redirect=True) as pipe: subprocess.check_call( ['sudo', '--stdin', '--prompt=""', "true"], stdin=pipe.stdout, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: global ERR_FLAG ERR_FLAG = False print_term( f'macdaily: {red}error{reset}: incorrect password {dim}{password!r}{reset} for ' f'{bold}{USER}{reset} ({under}{USR}{reset})', get_logfile()) raise IncorrectPassword from None return password
def install(formula): if BREW is None: return False try: subprocess.check_call([BREW, 'install', formula], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: return False return True
def _check_exec(self): try: subprocess.check_call(['brew', 'command', 'bundle'], stdout=subprocess.DEVNULL, stderr=make_stderr(self._vflag)) except subprocess.CalledProcessError: print_text(traceback.format_exc(), self._file, redirect=self._vflag) print(f'macdaily-{self.cmd}: {red_bg}{flash}mas{reset}: command not found', file=sys.stderr) text = (f'macdaily-{self.cmd}: {red}mas{reset}: you may find Bundler on ' f'{purple_bg}{under}https://github.com/Homebrew/homebrew-bundle{reset}, ' f'or install Bundler through following command -- ' f"`{bold}brew tap homebrew/bundle{reset}'") print_term(text, self._file, redirect=self._qflag) return False self._var__exec_path = shutil.which('brew') return True
def get_input(confirm, prompt='Input: ', *, prefix='', suffix='', queue=None): if sys.stdin.isatty(): try: return input(f'{prompt}{suffix}') except KeyboardInterrupt: print(reset) raise try: subprocess.check_call(['osascript', confirm, f'{prefix}{prompt}'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: RETURN = 'N' else: RETURN = 'Y' finally: if queue is not None: queue.put(RETURN) return RETURN
def _script(argv=SHELL, file='typescript', password=None, yes=None, redirect=False, executable=SHELL, prefix=None, suffix=None, timeout=None): if suffix is not None: argv = f'{_merge(argv)} {suffix}' argc = f'script -q /dev/null {executable} -c "' if yes is not None: argc = f'{argc} yes {yes} |' argv = f'{argc} {_merge(argv)}" | tee -a >({_ansi2text(password)} | col -b >> {file}) | {_text2dim(password)}' if prefix is not None: argv = f'{prefix} {argv}' # argv = f'set -x; {argv}' mode = None with contextlib.suppress(tty.error): mode = tty.tcgetattr(0) try: returncode = subprocess.check_call(argv, shell=True, executable=executable, timeout=timeout, stderr=make_stderr(redirect)) except subprocess.SubprocessError as error: if mode is not None: with contextlib.suppress(tty.error): if tty.tcgetattr(0) != mode: tty.tcsetattr(0, tty.TCSAFLUSH, mode) text = traceback.format_exc().replace('\n', '\\n') if password is not None: text = text.replace(password, PWD.pw_passwd) print_text(text, file, redirect=redirect) returncode = getattr(error, 'returncode', 1) # if password is not None: # with contextlib.suppress(subprocess.SubprocessError): # subprocess.run(['chown', getpass.getuser(), file], stdout=subprocess.DEVNULL) return returncode
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)
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)
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)
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)
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)