# The Mac OS Updater ## Install macOS updates from the TUI. import os; import time as clock ## app information class app: title = "The Mac OS Updater" description = "Install macOS updates from the TUI." version = 22.0616 ### YY.MMDD ## localization class texts: current_language = "en" class screens: class titles: s_2_0 = 'Download macOS' s_2_1 = 'Advanced Options' s_2_2 = 'Beta Update Branch Removal' s_2_2_1 = 'Disabling Beta Branch Search…' class messages: s_2_1 = 'Select the beta update branch to be included.' s_2_2 = 'Do you want to remove the beta update branch when looking for updates? ' s_2_3 = 'Please read and agree to the lisence before to include the public beta update branch.' s_2_4 = 'Please read and agree to the lisence before to include the developer beta update branch.' class menus: main = ['quit', 'refresh', 'install updates', 'download macOS', 'help'] s_2_0 = ['⬅︎' , 'refresh', 'advanced', 'download: Type an OS version number to download it.'] s_2_1 = ['⬅︎' , '🚫', 'beta release', 'developer release'] s_2_2 = ['⬅︎' , 'revert'] s_2_3 = ['⬅︎' , 'agree'] s_2_4 = ['⬅︎' , 'agree'] class status: class checkCompatibility: checking = 'Checking compatibility…' done = '' class errors: class compatibility: incompatibility = "There is no use running this outside macOS." noSoftwareUpdater = "There is no CLI software updater. \nAre you using an old version of macOS?" class quit: terminationPrompt = "The program refuses to proceed, so it will be closed. \nTo continue, click enter or quit the program yourself." class default: syntaxerr = "There is a spelling mistake!" typeerr = "Too bad, something couldn't be converted." attributeerr = "Something was not linked in properly. " class screens: s_2_2_1: 'Sorry. You are still stuck with the beta branch search enabled. \nPress ^c to go back, and come back to this page to try again.' try: ## on Terminal outputs ### text formatting class formatting: clear = '\033[0m' class color: purple = '\033[95m' cyan = '\033[96m' cyan_dark = '\033[36m' blue = '\033[94m' green = '\033[92m' yellow = '\033[93m' red = '\033[91m' class font: bold = '\033[1m' underline = '\033[4m' ### screen functions def cls(times = 1): #### to clear screen times = int(abs(times)); for _ in range(times): try: ipy_str = str(type(get_ipython())) if 'zmqshell' in ipy_str: from IPython.display import clear_output as clear; clear() elif 'terminal' in ipy_str: clear except NameError: ##### The user is running directly from the terminal, which is expected. import os; return(os.system('clear')) class info: ### screen messages def status(message): print(formatting.font.bold + 'ℹ️\t' + formatting.clear + message) def err(message): print(formatting.font.bold + formatting.color.red + '❌\t' + formatting.clear + formatting.clear + message) def warning(message): print(formatting.font.bold + formatting.color.yellow + '⚠️\t' + formatting.clear + formatting.clear + message) def success(message): print(formatting.font.bold + formatting.color.green + '✅\t' + formatting.clear + formatting.clear + message) ### terminate with fail function def fail(message = '', displayAppInformation = True, critical = True): import time; if critical: try: if displayAppInformation: operations.display_title_screen(); print('\n') info.err(message); input('\n\n' + texts.errors.quit.terminationPrompt); raise KeyboardInterrupt except KeyboardInterrupt: cls(); exit() else: print(message) time.sleep(1); class user: ## user data class navigation: ### TLI data screen = 0 response = '' class window: try: width = int((os.get_terminal_size()).columns) lines = int((os.get_terminal_size()).lines) except OSError: width = False lines = False class compatibility: ### for operations.check_compatibility() isRunningMacOS = (os.name =='posix' and not(os.system('cd /Applications')) and not(os.system('cd /Library')) and not(os.system('cd /System/Library'))) hasCLIUpdaterInstalled = not(os.system('softwareupdate --help >/dev/null 2>&1')) #### Check if softwareupdate command is there. class shell: try: #### determine Python type, if running in terminal, ipython, or jupyter ipy_str = str(type(get_ipython())) if 'zmqshell' in ipy_str: pythonType = 'jupyter' elif 'terminal' in ipy_str: pythonType = 'ipython' except: pythonType = 'terminal' class operations: ## all operations def display_title_screen(message = ''): ### display title screen and optional message screenManager.menu.titleBar(app.title); print (app.description + '\nV' + str(app.version)) #### display app information message = str(message).strip() if message != '': print(message) #### display message def check_compatibility(): ### compatibility check cls() operations.display_title_screen(texts.status.checkCompatibility.checking) import os if user.compatibility.isRunningMacOS: if user.compatibility.hasCLIUpdaterInstalled: return True else: fail(texts.errors.compatibility.noSoftwareUpdater); return False else: fail(texts.errors.compatibility.noSoftwareUpdater); return False class software: def check_updates(): import os return(os.system('softwareupdate -l')) def check_OS_installers(): import os return(os.system('softwareupdate --list-full-installers')) class installer_branch: def none(): import os; return(os.system('sudo /System/Library/PrivateFrameworks/Seeding.framework/Versions/A/Resources/seedutil unenroll')) def publicBeta(): import os; return(os.system('sudo /System/Library/PrivateFrameworks/Seeding.framework/Versions/A/Resources/seedutil enroll PublicSeed')) def privateBeta(): import os; return(os.system('sudo /System/Library/PrivateFrameworks/Seeding.framework/Versions/A/Resources/seedutil enroll DeveloperSeed')) class screenManager: ## TLI class graphical: def horizontalLine(times = 1): times = int(abs(times)) if times == 0: times = 1 for _ in range(times): screenManager.redefineDimensions() if user.navigation.window.width != False: totalText = '' for _ in range(user.navigation.window.width): totalText = totalText + '—' print(totalText) else: print('') class menu: ### for menus def titleBar(title): title = title.strip() titleLength = len(title) totalText = '' if user.navigation.window.width != False: for _ in range(int(((user.navigation.window.width - titleLength)/2)-1)): totalText = totalText + '—' totalText = (totalText + ' ') totalText = (totalText + formatting.font.bold + title + formatting.clear) if user.navigation.window.width != False: totalText = (totalText + ' ') for _ in range(int(((user.navigation.window.width - titleLength)/2)-1)): totalText = totalText + '—' print(totalText); return(totalText) def topMenu(menuShortcut, menuList, displayHorizontalLineBelow = True): #### bottom menu entryNumber = 0; displayText = '|' for menuItem in menuList: entryNumber = menuList.index(menuItem) if menuShortcut[entryNumber].strip() == '': displayText = (displayText + menuItem + '|') else: displayText = (displayText + formatting.font.bold + menuShortcut[entryNumber] + formatting.clear + ' ' + menuItem + '|') print(displayText); if displayHorizontalLineBelow: screenManager.graphical.horizontalLine() def inputMenu(menuShortcut, menuList, backSupported = True, passNumbers = False, passAnything = False): #### input while True: screenManager.graphical.horizontalLine(); try: response = input() response = response.strip() except KeyboardInterrupt: ##### User cancelled. if backSupported: return -1; else: print("", end="\r") ##### Loop again. else: if response in menuShortcut or response in menuList or passAnything: return(response) elif passNumbers: try: response = float(response) except: print("", end="\r") else: return(response) else: print("", end="\r") def promptMenu(promptTitle, promptDescription, promptOptions, promptShortcuts, canCancel = True, extraInputAllowed = False, numbersAllowed = False): screenManager.menu.titleBar(promptTitle.strip()) print(promptDescription) if user.navigation.window.lines != False: for _ in range((user.navigation.window.lines)/4): print('') else: print('\n\n') screenManager.menu.topMenu(promptShortcuts, promptOptions) user.navigation.response = screenManager.menu.inputMenu(promptShortcuts, promptOptions, canCancel, numbersAllowed, extraInputAllowed) return(user.navigation.response) def redefineDimensions(): try: user.navigation.window.width = int((os.get_terminal_size()).columns) except OSError: user.navigation.window.width = False try: user.navigation.window.lines = int((os.get_terminal_size()).lines) except OSError: user.navigation.window.lines = False def do(): while user.navigation.screen >= 0: cls(); screenManager.redefineDimensions() if user.navigation.screen < 1: ### when opened if user.navigation.screen == 0: operations.display_title_screen() clock.sleep(1) user.navigation.screen = 0.1 elif user.navigation.screen == 0.1: if operations.check_compatibility(): user.navigation.screen = 1 elif user.navigation.screen < 2: if user.navigation.screen == 1: menuShortcut = ['^c', 'r', 'i', 'd', '?'] screenManager.menu.titleBar(app.title); screenManager.menu.topMenu(menuShortcut, texts.menus.main) operations.software.check_updates() user.navigation.response = screenManager.menu.inputMenu(menuShortcut, texts.menus.main) if user.navigation.response == -1 or user.navigation.response == '^c': user.navigation.screen = -1 elif user.navigation.response == 'd': user.navigation.screen = 2 elif user.navigation.screen < 3: ### full installer downloader, necessary and recommended to upgrade the OS if user.navigation.screen == 2: menuShortcut = ['^c', 'r', 'A', ''] screenManager.menu.titleBar(texts.screens.titles.s_2_0); screenManager.menu.topMenu(menuShortcut, texts.menus.s_2_0) operations.software.check_OS_installers() user.navigation.response = screenManager.menu.inputMenu(menuShortcut, texts.menus.s_2_0, True, True) if user.navigation.response == -1 or user.navigation.response == '^c': user.navigation.screen = 1 elif user.navigation.response == 'A': user.navigation.screen = 2.1 elif user.navigation.screen == 2.1: menuShortcut = ['^c', 'n', 'b', 'D'] screenManager.menu.promptMenu(texts.screens.titles.s_2_1, texts.screens.messages.s_2_1, texts.menus.s_2_1, menuShortcut) if user.navigation.response == -1 or user.navigation.response == '^c': user.navigation.screen = 2 elif user.navigation.response == 'n' or user.navigation.response == '🚫': user.navigation.screen = 2.2 elif user.navigation.screen == 2.2: menuShortcut = ['^c', ''] screenManager.menu.promptMenu(texts.screens.titles.s_2_2, texts.screens.messages.s_2_2, texts.menus.s_2_2, menuShortcut) if user.navigation.response == -1 or user.navigation.response == '^c': user.navigation.screen = 2.1 elif user.navigation.response == '' or user.navigation.response == 'revert': user.navigation.screen == 2.21 elif user.navigation.screen == 2.21: screenManager.menu.titleBar(texts.screens.titles.s_2_2_1); if (operations.software.installer_branch.none()): try: info.err(texts.errors.screens.s_2_2_1) except KeyboardInterrupt: user.navigation.screen = 2.1 else: user.navigation.screen = 2.1 if user.navigation.screen == -1: cls() def main(): screenManager.do() main() except SyntaxError as e: fail(texts.errors.default.syntaxerr + '\n' + str(e)) except NameError as e: fail(texts.errors.default.syntaxerr + '\n' + str(e)) except IndentationError as e: fail(texts.errors.default.syntaxerr + '\n' + str(e)) except TypeError as e: fail(texts.errors.default.typeerr + '\n' + str(e)) except AttributeError as e: fail(texts.errors.default.attributeerr + '\n' + str(e))