def backup_configs(backup_path): """ Creates `configs` directory and places config backups there. Configs are application settings, generally. .plist files count. """ print_section_header("CONFIGS", Fore.BLUE) mkdir_warn_overwrite(backup_path) config = get_config() configs_dir_mapping = config["config_path_to_dest_map"] plist_files = config["plist_path_to_dest_map"] print(Fore.BLUE + Style.BRIGHT + "Backing up configs..." + Style.RESET_ALL) # backup config dirs in backup_path/<target>/ for config, target in configs_dir_mapping.items(): src_dir = home_prefix(config) configs_backup_path = os.path.join(backup_path, target) if os.path.isdir(src_dir): # TODO: Exclude Sublime/Atom/VS Code Packages here to speed things up copytree(src_dir, configs_backup_path, symlinks=True) # backup plist files in backup_path/configs/plist/ print(Fore.BLUE + Style.BRIGHT + "Backing up plist files..." + Style.RESET_ALL) plist_backup_path = os.path.join(backup_path, "plist") mkdir_or_pass(plist_backup_path) for plist, dest in plist_files.items(): plist_path = home_prefix(plist) if os.path.exists(plist_path): copyfile(plist_path, os.path.join(backup_path, dest))
def backup_dotfiles(backup_path): """ Create `dotfiles` dir and makes copies of dotfiles and dotfolders. """ print_section_header("DOTFILES", Fore.BLUE) mkdir_warn_overwrite(backup_path) # assumes dotfiles are stored in home directory home_path = os.path.expanduser('~') # get dotfolders and dotfiles config = get_config() dotfiles_for_backup = config["dotfiles"] dotfolders_for_backup = config["dotfolders"] # Add dotfile/folder for backup if it exists on the machine dotfiles = [ file for file in dotfiles_for_backup if os.path.isfile(os.path.join(home_path, file)) ] dotfolders = [ folder for folder in dotfolders_for_backup if os.path.exists(os.path.join(home_path, folder)) ] # dotfiles/folders multiprocessing format: [(full_dotfile_path, full_dest_path), ...] dotfolders_mp_in = [] for dotfolder in dotfolders: dotfolders_mp_in.append((os.path.join(home_path, dotfolder), backup_path)) dotfiles_mp_in = [] for dotfile in dotfiles: dotfiles_mp_in.append( (os.path.join(home_path, dotfile), os.path.join(backup_path, dotfile))) # Multiprocessing with mp.Pool(mp.cpu_count()): print(Fore.BLUE + Style.BRIGHT + "Backing up dotfolders..." + Style.RESET_ALL) for x in dotfolders_mp_in: x = list(x) mp.Process(target=copy_dir, args=( x[0], x[1], )).start() with mp.Pool(mp.cpu_count()): print(Fore.BLUE + Style.BRIGHT + "Backing up dotfiles..." + Style.RESET_ALL) for x in dotfiles_mp_in: x = list(x) mp.Process(target=copyfile, args=( x[0], x[1], )).start()
def overwrite_dir_prompt_if_needed(path, needed): """ Prompts the user before deleting the directory if needed. This function lets the CLI args silence the prompts. :param path: absolute path :param needed: boolean """ if not needed: mkdir_warn_overwrite(path) else: mkdir_overwrite(path)
def backup_fonts(path): """ Creates list of all .ttf and .otf files in ~/Library/Fonts/ """ print_section_header("FONTS", Fore.BLUE) mkdir_warn_overwrite(path) print(Fore.BLUE + "Copying '.otf' and '.ttf' fonts..." + Style.RESET_ALL) fonts_path = home_prefix("Library/Fonts/") fonts = [ os.path.join(fonts_path, font) for font in os.listdir(fonts_path) if font.endswith(".otf") or font.endswith(".ttf") ] for font in fonts: if os.path.exists(font): copyfile(font, os.path.join(path, font.split("/")[-1]))
def prompt_for_path_update(config): """ Ask user if they'd like to update the backup path or not. If yes, update. If no... don't. """ current_path = config["backup_path"] print("{}{}Current shallow-backup path: {}{}{}".format( Fore.BLUE, Style.BRIGHT, Style.NORMAL, current_path, Style.RESET_ALL)) if prompt_yes_no("Would you like to update this?", Fore.GREEN): print(Fore.GREEN + Style.BRIGHT + "Enter relative path:" + Style.RESET_ALL) abs_path = os.path.abspath(input()) print(Fore.BLUE + "\nUpdating shallow-backup path to {}".format(abs_path) + Style.RESET_ALL) config["backup_path"] = abs_path write_config(config) mkdir_warn_overwrite(abs_path) move_git_repo(current_path, abs_path)
def cli(add, rm, show, all, dotfiles, configs, packages, fonts, old_path, new_path, remote, reinstall_all, reinstall_configs, reinstall_dots, reinstall_fonts, reinstall_packages, delete_config, destroy_backup, v): """ Easily back up installed packages, dotfiles, and more. You can edit which dotfiles are backed up in ~/.shallow-backup. Written by Aaron Lichtman (@alichtman). """ # Process CLI args admin_action = any([v, delete_config, destroy_backup, show, rm ]) or None not in add has_cli_arg = any([ old_path, all, dotfiles, packages, fonts, configs, reinstall_dots, reinstall_fonts, reinstall_all, reinstall_configs, reinstall_packages ]) skip_prompt = any([ all, dotfiles, configs, packages, fonts, reinstall_packages, reinstall_configs, reinstall_dots, reinstall_fonts ]) # Perform administrative action and exit. if admin_action: if v: print_version_info() elif delete_config: # TODO: Error checking. os.remove(get_config_path()) print_red_bold("Removed config file...") elif destroy_backup: backup_home_path = get_config()["backup_path"] destroy_backup_dir(backup_home_path) elif None not in add: add_to_config(add[0], add[1]) elif rm: rm_from_config(rm) elif show: show_config() sys.exit() # Start CLI splash_screen() safe_create_config() backup_config = get_config() # User entered a new path, so update the config if new_path: abs_path = os.path.abspath(new_path) print(Fore.BLUE + Style.NORMAL + "\nUpdating shallow-backup path to -> " + Style.BRIGHT + "{}".format(abs_path) + Style.RESET_ALL) backup_config["backup_path"] = abs_path write_config(backup_config) # User didn't enter any CLI args so prompt for path update before showing menu elif not has_cli_arg: prompt_for_path_update(backup_config) # Create backup directory and do git setup backup_home_path = get_config()["backup_path"] mkdir_warn_overwrite(backup_home_path) repo, new_git_repo_created = safe_git_init(backup_home_path) # Create default gitignore if we just ran git init if new_git_repo_created: safe_create_gitignore(backup_home_path) # Prompt user for remote URL if not remote: prompt_for_git_url(repo) # Set remote URL from CLI arg if remote: git_set_remote(repo, remote) dotfiles_path = os.path.join(backup_home_path, "dotfiles") configs_path = os.path.join(backup_home_path, "configs") packages_path = os.path.join(backup_home_path, "packages") fonts_path = os.path.join(backup_home_path, "fonts") # Command line options if skip_prompt: if reinstall_packages: reinstall_packages_sb(packages_path) elif reinstall_configs: reinstall_configs_sb(configs_path) elif reinstall_fonts: reinstall_fonts_sb(fonts_path) elif reinstall_dots: reinstall_dots_sb(dotfiles_path) elif reinstall_all: reinstall_all_sb(dotfiles_path, packages_path, fonts_path, configs_path) elif all: backup_all(dotfiles_path, packages_path, fonts_path, configs_path, skip=True) git_add_all_commit_push(repo, "all") elif dotfiles: backup_dotfiles(dotfiles_path, skip=True) git_add_all_commit_push(repo, "dotfiles") elif configs: backup_configs(configs_path, skip=True) git_add_all_commit_push(repo, "configs") elif packages: backup_packages(packages_path, skip=True) git_add_all_commit_push(repo, "packages") elif fonts: backup_fonts(fonts_path, skip=True) git_add_all_commit_push(repo, "fonts") # No CL options, show action menu and process selected option. else: selection = actions_menu_prompt().lower().strip() selection_words = selection.split() if selection.startswith("back up"): if selection_words[-1] == "all": backup_all(dotfiles_path, packages_path, fonts_path, configs_path) git_add_all_commit_push(repo, selection_words[-1]) elif selection_words[-1] == "dotfiles": backup_dotfiles(dotfiles_path) git_add_all_commit_push(repo, selection_words[-1]) elif selection_words[-1] == "configs": backup_configs(configs_path) git_add_all_commit_push(repo, selection_words[-1]) elif selection_words[-1] == "packages": backup_packages(packages_path) git_add_all_commit_push(repo, selection_words[-1]) elif selection_words[-1] == "fonts": backup_fonts(fonts_path) git_add_all_commit_push(repo, selection_words[-1]) elif selection.startswith("reinstall"): if selection_words[-1] == "packages": reinstall_packages_sb(packages_path) elif selection_words[-1] == "configs": reinstall_configs_sb(configs_path) elif selection_words[-1] == "fonts": reinstall_fonts_sb(fonts_path) elif selection_words[-1] == "dotfiles": reinstall_dots_sb(dotfiles_path) elif selection_words[-1] == "all": reinstall_all_sb(dotfiles_path, packages_path, fonts_path, configs_path) else: if selection == "show config": show_config() elif selection == "destroy backup": if prompt_yes_no( "Erase backup directory: {}?".format(backup_home_path), Fore.RED): destroy_backup_dir(backup_home_path) else: print_red_bold( "Exiting to prevent accidental deletion of backup directory." ) sys.exit()
def cli(add, rm, show, complete, dotfiles, configs, packages, fonts, old_path, new_path, remote, reinstall_packages, reinstall_configs, delete_config, destroy_backup, v): """ Easily back up installed packages, dotfiles, and more. You can edit which dotfiles are backed up in ~/.shallow-backup. Written by Aaron Lichtman (@alichtman). """ backup_config_path = get_config_path() # No interface going to be displayed if any([v, delete_config, destroy_backup, show, rm]) or None not in add: if v: print_version_info() elif delete_config: os.remove(backup_config_path) print_bright_red("Removed config file...") elif destroy_backup: backup_home_path = get_config()["backup_path"] destroy_backup_dir(backup_home_path) elif None not in add: add_path_to_config(add[0], add[1]) elif rm: rm_path_from_config(rm) elif show: show_config() sys.exit() # Start CLI splash_screen() create_config_file_if_needed() backup_config = get_config() # User entered a new path, so update the config if new_path: abs_path = os.path.abspath(new_path) print(Fore.BLUE + Style.NORMAL + "\nUpdating shallow-backup path to -> " + Style.BRIGHT + "{}".format( abs_path) + Style.RESET_ALL) backup_config["backup_path"] = abs_path write_config(backup_config) # User didn't enter any CLI args so prompt for path update before showing menu elif not (old_path or complete or dotfiles or packages or fonts): prompt_for_path_update(backup_config) # Create backup directory and do git setup backup_home_path = get_config()["backup_path"] mkdir_warn_overwrite(backup_home_path) repo, new_git_repo_created = safe_git_init(backup_home_path) # Create default gitignore if we just ran git init if new_git_repo_created: safe_create_gitignore(backup_home_path) # Prompt user for remote URL if not remote: prompt_for_git_url(repo) # Set remote URL from CLI arg if remote: git_set_remote(repo, remote) dotfiles_path = os.path.join(backup_home_path, "dotfiles") configs_path = os.path.join(backup_home_path, "configs") packages_path = os.path.join(backup_home_path, "packages") fonts_path = os.path.join(backup_home_path, "fonts") # Command line options if any([complete, dotfiles, configs, packages, fonts, reinstall_packages, reinstall_configs]): if reinstall_packages: reinstall_packages_from_lists(packages_path) elif reinstall_configs: reinstall_config_files(configs_path) elif complete: backup_all(dotfiles_path, packages_path, fonts_path, configs_path) git_add_all_commit_push(repo, "everything") elif dotfiles: backup_dotfiles(dotfiles_path) git_add_all_commit_push(repo, "dotfiles") elif configs: backup_configs(configs_path) git_add_all_commit_push(repo, "configs") elif packages: backup_packages(packages_path) git_add_all_commit_push(repo, "packages") elif fonts: backup_fonts(fonts_path) git_add_all_commit_push(repo, "fonts") # No CL options, prompt for selection else: selection = actions_menu_prompt().lower().strip() if selection == "back up everything": backup_all(dotfiles_path, packages_path, fonts_path, configs_path) git_add_all_commit_push(repo, "everything") elif selection == "back up dotfiles": backup_dotfiles(dotfiles_path) git_add_all_commit_push(repo, "dotfiles") elif selection == "back up configs": backup_configs(configs_path) git_add_all_commit_push(repo, "configs") elif selection == "back up packages": backup_packages(packages_path) git_add_all_commit_push(repo, "packages") elif selection == "back up fonts": backup_fonts(fonts_path) git_add_all_commit_push(repo, "fonts") elif selection == "reinstall packages": reinstall_packages_from_lists(packages_path) elif selection == "reinstall configs": reinstall_config_files(configs_path) elif selection == "show config": show_config() elif selection == "destroy backup": if prompt_yes_no("Erase backup directory: {}?".format(backup_home_path), Fore.RED): destroy_backup_dir(backup_home_path) else: print_bright_red("Exiting to prevent accidental deletion of backup directory.") sys.exit()
def backup_packages(backup_path): """ Creates `packages` directory and places install list text files there. """ print_section_header("PACKAGES", Fore.BLUE) mkdir_warn_overwrite(backup_path) std_package_managers = ["brew", "brew cask", "gem"] for mgr in std_package_managers: # deal with package managers that have spaces in them. print_pkg_mgr_backup(mgr) command = "{} list".format(mgr) dest = "{}/{}_list.txt".format(backup_path, mgr.replace(" ", "-")) run_cmd_write_stdout(command, dest) # cargo print_pkg_mgr_backup("cargo") command = "ls {}".format(home_prefix(".cargo/bin/")) dest = "{}/cargo_list.txt".format(backup_path) run_cmd_write_stdout(command, dest) # pip print_pkg_mgr_backup("pip") command = "pip list --format=freeze".format(backup_path) dest = "{}/pip_list.txt".format(backup_path) run_cmd_write_stdout(command, dest) # npm print_pkg_mgr_backup("npm") command = "npm ls --global --parseable=true --depth=0" temp_file_path = "{}/npm_temp_list.txt".format(backup_path) run_cmd_write_stdout(command, temp_file_path) npm_dest_file = "{0}/npm_list.txt".format(backup_path) # Parse npm output with open(temp_file_path, mode="r+") as temp_file: # Skip first line of file temp_file.seek(1) with open(npm_dest_file, mode="w+") as dest: for line in temp_file: dest.write(line.split("/")[-1]) os.remove(temp_file_path) # atom package manager print_pkg_mgr_backup("Atom") command = "apm list --installed --bare" dest = "{}/apm_list.txt".format(backup_path) run_cmd_write_stdout(command, dest) # sublime text 2 packages sublime_2_path = home_prefix( "Library/Application Support/Sublime Text 2/Packages/") if os.path.isdir(sublime_2_path): print_pkg_mgr_backup("Sublime Text 2") command = ["ls", sublime_2_path] dest = "{}/sublime2_list.txt".format(backup_path) run_cmd_write_stdout(command, dest) # sublime text 3 packages sublime_3_path = home_prefix( "Library/Application Support/Sublime Text 3/Installed Packages/") if os.path.isdir(sublime_3_path): print_pkg_mgr_backup("Sublime Text 3") command = ["ls", sublime_3_path] dest = "{}/sublime3_list.txt".format(backup_path) run_cmd_write_stdout(command, dest) else: print(sublime_3_path, "IS NOT DIR") # macports print_pkg_mgr_backup("macports") command = "port installed requested" dest = "{}/macports_list.txt".format(backup_path) run_cmd_write_stdout(command, dest) # system installs print_pkg_mgr_backup("macOS Applications") command = "ls /Applications/" dest = "{}/system_apps_list.txt".format(backup_path) run_cmd_write_stdout(command, dest) # Clean up empty package list files print(Fore.BLUE + "Cleaning up empty package lists..." + Style.RESET_ALL) for file in get_subfiles(backup_path): if os.path.getsize(file) == 0: os.remove(file)