From 0c8ab51def011b465b36f999ec527be236ba78aa Mon Sep 17 00:00:00 2001 From: Akemi Izuko Date: Sun, 19 Nov 2023 21:20:40 -0700 Subject: [PATCH] Multi-platform package install script --- package_install.py | 472 +++++++++++++++++++++++++++++++++++++++++++++ package_install.sh | 308 ----------------------------- 2 files changed, 472 insertions(+), 308 deletions(-) create mode 100755 package_install.py delete mode 100755 package_install.sh diff --git a/package_install.py b/package_install.py new file mode 100755 index 0000000..515b21e --- /dev/null +++ b/package_install.py @@ -0,0 +1,472 @@ +#!/usr/bin/env python3 +# A base package installer for many systems! +# Supported package managers: +# - apt (debian/ubuntu) +# - dnf (fedora/redhat) +# - pacman (arch/endeavour) +import os +import shutil +import subprocess + +PACKAGE_MANAGERS = { + "apt": { + "name": "apt", + "install_cmd": ["apt", "install"], + "check_installed": ["dpkg", "-s"], # Must return 0 + }, + "dnf": { + "name": "dnf", + "install_cmd": ["dnf", "install"], + "check_installed": ["dnf", "list"], + }, + "pacman": { + "name": "pacman", + "install_cmd": ["pacman", "-S"], + "check_installed": ["pacman", "-Qi"], + }, +} + +PACKAGES = [ + { + "global_name": "alacritty", + "executable_name": "alacritty", + "apt": "alacritty", + "dnf": "alacritty", + "pacman": "alacritty", + }, + { + "global_name": "bat", + "executable_name": "bat", + "apt": "bat", + "dnf": "bat", + "pacman": "bat", + }, + { + "global_name": "bash", + "executable_name": "bash", + "apt": "bash", + "dnf": "bash", + "pacman": "bash", + }, + { + "global_name": "bash-completion", + "apt": "bash-completion", + "dnf": "bash-completion", + "pacman": "bash-completion", + }, + { + "global_name": "calc", + "executable_name": "calc", + "apt": "calc", + "dnf": "calc", + "pacman": "calc", + }, + { + "global_name": "curl", + "executable_name": "curl", + "apt": "curl", + "dnf": "curl", + "pacman": "curl", + }, + { + "global_name": "dust", + "executable_name": "dust", + "pacman": "dust", + }, + { + "global_name": "exa -> eza", + "executable_name": "exa", + "apt": "exa", + "dnf": "eza", + "pacman": "eza", + }, + { + "global_name": "fd-find", + "executable_name": "fd", + "apt": "fd-find", + "dnf": "fd-find", + "pacman": "fd", + }, + { + "global_name": "foliate", + "executable_name": "foliate", + "apt": "foliate", + "dnf": "foliate", + "pacman": "foliate", + }, + { + "global_name": "fuzzel", + "executable_name": "fuzzel", + "apt": "fuzzel", + "dnf": "fuzzel", + "pacman": "fuzzel", + }, + { + "global_name": "fzf", + "executable_name": "fzf", + "apt": "fzf", + "dnf": "fzf", + "pacman": "fzf", + }, + { + "global_name": "gawk", + "executable_name": "gawk", + "apt": "gawk", + "dnf": "gawk", + "pacman": "gawk", + }, + { + "global_name": "git", + "executable_name": "git", + "apt": "git", + "dnf": "git", + "pacman": "git", + }, + { + "global_name": "git-lfs", + "executable_name": "git-lfs", + "apt": "git-lfs", + "dnf": "git-lfs", + "pacman": "git-lfs", + }, + { + "global_name": "grim", + "executable_name": "grim", + "apt": "grim", + "dnf": "grim", + "pacman": "grim", + }, + { + "global_name": "pass", + "executable_name": "pass", + "apt": "pass", + "dnf": "pass", + "pacman": "pass", + }, + { + "global_name": "slurp", + "executable_name": "slurp", + "apt": "slurp", + "dnf": "slurp", + "pacman": "slurp", + }, + { + "global_name": "swappy", + "executable_name": "swappy", + "dnf": "swappy", + "pacman": "swappy", + }, + { + "global_name": "imv", + "executable_name": "imv", + "apt": "imv", + "dnf": "imv", + "pacman": "imv", + }, + { + "global_name": "jq", + "executable_name": "jq", + "apt": "jq", + "dnf": "jq", + "pacman": "jq", + }, + { + "global_name": "Meslo Nerd font", + "pacman": "ttf-meslo-nerd", + }, + { + "global_name": "mmv", + "executable_name": "mmv", + "apt": "mmv", + "dnf": "mmv", + }, + { + "global_name": "mpv", + "executable_name": "mpv", + "apt": "mpv", + "dnf": "mpv", + "pacman": "mpv", + }, + { + "global_name": "neofetch", + "executable_name": "neofetch", + "apt": "neofetch", + "dnf": "neofetch", + "pacman": "neofetch", + }, + { + "global_name": "neovim", + "executable_name": "nvim", + "apt": "neovim", + "dnf": "neovim", + "pacman": "neovim", + }, + { + "global_name": "pynvim", + "apt": "python3-pynvim", + "dnf": "python3-neovim", + "pacman": "python-pynvim", + }, + { + "global_name": "ripgrep", + "executable_name": "rg", + "apt": "ripgrep", + "dnf": "ripgrep", + "pacman": "ripgrep", + }, + { + "global_name": "socat", + "executable_name": "socat", + "apt": "socat", + "dnf": "socat", + "pacman": "socat", + }, + { + "global_name": "sway", + "executable_name": "sway", + "apt": "sway", + "dnf": "sway", + "pacman": "sway", + }, + { + "global_name": "swaybg", + "executable_name": "swaybg", + "apt": "swaybg", + "dnf": "swaybg", + "pacman": "swaybg", + }, + { + "global_name": "swaylock", + "executable_name": "swaylock", + "apt": "swaylock", + "dnf": "swaylock", + "pacman": "swaylock", + }, + { + "global_name": "tealdeer", + "executable_name": "tldr", + "apt": "tealdeer", + "dnf": "tealdeer", + "pacman": "tealdeer", + }, + { + "global_name": "tmux", + "executable_name": "tmux", + "apt": "tmux", + "dnf": "tmux", + "pacman": "tmux", + }, + { + "global_name": "udisks2", + "executable_name": "udisksctl", + "apt": "udisks2", + "dnf": "udisks2", + "pacman": "udisks2", + }, + { + "global_name": "vifm", + "executable_name": "vifm", + "apt": "vifm", + "dnf": "vifm", + "pacman": "vifm", + }, + { + "global_name": "viu", + "executable_name": "viu", + "pacman": "viu", + }, + { + "global_name": "wl-clipboard", + "executable_name": "wl-copy", + "apt": "wl-clipboard", + "dnf": "wl-clipboard", + "pacman": "wl-clipboard", + }, + { + "global_name": "wlsunset", + "executable_name": "wlsunset", + "apt": "wlsunset", + "dnf": "wlsunset", + "pacman": "wlsunset", + }, + { + "global_name": "wtype", + "executable_name": "wtype", + "apt": "wtype", + "dnf": "wtype", + "pacman": "wtype", + }, + { + "global_name": "ydotool", + "executable_name": "ydotool", + "apt": "ydotool", + "dnf": "ydotool", + "pacman": "ydotool", + }, + { + "global_name": "zathura-pdf-mupdf", + "dnf": "zathura-pdf-mupdf", + "pacman": "zathura-pdf-mupdf", + }, + { + "global_name": "zathura", + "executable_name": "zathura", + "apt": "zathura", + "dnf": "zathura", + "pacman": "zathura", + }, +] + +WEB_INSTALLS = [ + { + "name": "git prompt for bash", + "dest": f"{os.environ['HOME']}/.git-prompt.bash", + "url": "https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh", + }, + { + "name": "Git completion for bash", + "dest": f"{os.environ['HOME']}/.git-completion.bash", + "url": "https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash", + }, + { + "name": "Fzf completion for bash", + "dest": f"{os.environ['HOME']}/.fzf.bash", + "url": "https://raw.githubusercontent.com/junegunn/fzf/master/shell/completion.bash", + }, +] + +def extra_installs(): + rustup_cmd = [ + "bash", + "-c", + "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh", + ] + + tmp_cmds = [ + [ + "git", + "clone", + "--depth", + "1", + "https://github.com/tmux-plugins/tpm", + f"{os.environ['HOME']}/.config/tmux/plugins/tpm", + ], + [f"{os.environ['HOME']}/.config/tmux/plugins/tpm/tpm"], + [f"{os.environ['HOME']}/.config/tmux/plugins/tpm/bin/install_plugins"], + [f"{os.environ['HOME']}/.config/tmux/plugins/tpm/tpm"], + ] + + if not shutil.which("rustc") and prompt_user("rustup"): + subprocess.call(rustup_cmd) + + has_tmp = os.path.exists( + f"{os.environ['HOME']}/.config/tmux/plugins/tpm/tpm" + ) + + if not has_tmp and prompt_user("tmp, tmux's package manager"): + success = subprocess.call(tmp_cmds[0]) == 0 + + if success: + success = subprocess.call(tmp_cmds[1]) == 0 + if success: + success = subprocess.call(tmp_cmds[2]) == 0 + if success: + success = subprocess.call(tmp_cmds[3]) == 0 + + +def find_package_manager(): + global PACKAGE_MANAGERS + + if shutil.which("apt"): + return PACKAGE_MANAGERS["apt"] + elif shutil.which("dnf"): + return PACKAGE_MANAGERS["dnf"] + elif shutil.which("pacman"): + return PACKAGE_MANAGERS["pacman"] + else: + raise Exception("Supported package manager not found") + +def check_installed(pkg, apt): + if pkg.get("executable_name") and shutil.which(pkg['executable_name']): + return "true" + elif not pkg.get(apt['name']): + return "unavailable" + + check_cmd = list(apt["check_installed"]) + check_cmd.append(pkg[apt['name']]) + + check = subprocess.Popen( + check_cmd, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + check.wait() + if check.returncode == 0: + return "true" + else: + return "false" + +def prompt_user(pkg_name): + while True: + user_in = input(f"Install {pkg_name}? (Y/n) ") + + if user_in == "" or user_in[0].lower() == 'y': + return True + elif user_in[0].lower() == 'n': + return False + +def install_package(apt, package): + install_cmd = list(apt['install_cmd']) + install_cmd.append(package[apt['name']]) + + install = subprocess.call(install_cmd) + + return install.returncode == 0 + + +def install_packages(apt): + global PACKAGES + + for package in PACKAGES: + pkg_name = package['global_name'] + installed = check_installed(package, apt) + + if installed == "unavailable": + print(f"≠ Cannot install {pkg_name} with {apt['name']}") + elif installed == "true": + print(f"✓ Already installed {pkg_name}") + elif prompt_user(pkg_name): + if install_package(apt, package): + print(f"★ Newly installed {pkg_name}") + else: + print(f"✗ Failed to install {pkg_name}") + else: + print(f"φ Not installing {pkg_name}") + +def web_installs(): + global WEB_INSTALLS + + for package in WEB_INSTALLS: + if not os.path.exists(package['dest']) and prompt_user(package['name']): + subprocess.call( + "curl", + "--output", + package['dest'], + package['url'], + ) + + +if __name__ == '__main__': + try: + apt = find_package_manager() + print(f"Identified `{apt['name']}` as system package manager") + + install_packages(apt) + + if shutil.which("curl"): + web_installs() + extra_installs() + except KeyboardInterrupt: + print("\nExiting installation. Feel free to restart where you left off") diff --git a/package_install.sh b/package_install.sh deleted file mode 100755 index 871a37b..0000000 --- a/package_install.sh +++ /dev/null @@ -1,308 +0,0 @@ -#!/usr/bin/env bash -declare PACMAN="" - -# Linux and Cargo packages are written in pairs: (package_name, binary_name). -# Brew packages only use their binary name and are intended for MacOS. -# The binary name is used to check if the package is already installed from -# another source -declare -r \ - LINUX_PACKAGES=(\ - 'dante' 'aerc' - 'w3m' 'aerc' - 'aerc' 'aerc' - 'alacritty' 'alacritty' - 'bat' 'bat' - 'bash-completion' 'bash-completion' - 'calc' 'calc' - 'dust' 'dust' - 'exa' 'exa' - 'fd' 'fd' - 'foliate' 'foliate' - 'fuzzel' 'fuzzel' - 'fzf' 'fzf' - 'gawk' 'gawk' - 'git' 'git' - 'git-lfs' 'git-lfs' - 'grim' 'grim' - 'pass' 'pass' - 'slurp' 'slurp' - 'swappy' 'swappy' - 'imv' 'imv' - 'jq' 'jq' - 'mpv' 'mpv' - 'neofetch' 'neofetch' - 'neovim' 'nvim' - 'paru' 'paru' - 'ripgrep' 'rg' - 'socat' 'socat' - 'sway' 'sway' - 'swaybg' 'swaybg' - 'swaylock' 'swaylock' - 'tealdeer' 'tldr' - 'tmux' 'tmux' - 'udisks2' 'udisksctl' - 'vifm' 'vifm' - 'viu' 'viu' - 'wl-clipboard' 'wl-paste' - 'wtype' 'wtype' - 'ydotool' 'ydotool' - 'zathura-pdf-mupdf' 'zathura' - 'zathura' 'zathura' - ) \ - CARGO_PACKAGES=(\ - 'alacritty' 'alacritty' - 'bat' 'bat' - 'du-dust' 'dust' - 'exa' 'exa' - 'fd-find' 'fd' - 'ripgrep' 'rg' - 'tealdeer' 'tldr' - 'viu' 'viu' - ) \ - BREW_PACKAGES=(\ - 'bash' 'bat' 'calc' 'coreutils' 'dust' 'exa' 'fd' 'fzf' 'gawk' 'git' - 'imagemagick' 'jq' 'mmv' 'mpv' 'neofetch' 'node' 'nvim' 'pngpaste' - 'python3' 'ripgrep' 'skhd' 'tealdeer' 'tmux' 'vifm' 'viu' 'yabai' - ) \ - AUR_PACKAGES=(\ - 'mmv' 'mmv' - 'nerd-fonts-meslo' 'font-manager' - 'vimiv-qt-git' 'vimiv' - 'warpd-wayland-git' 'warpd' - 'wev' 'wev' - 'wlsunset' 'wlsunset' - ) - -print_help() { - cat </dev/null && [[ "$(uname -s)" == "Darwin" ]]; then - eval "$(curl -fsSL 'https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh')" - fi - - if command -v paru &> /dev/null; then PACMAN='paru -S' - elif command -v pacman &> /dev/null; then PACMAN='sudo pacman -S' - elif command -v apt-get &> /dev/null; then PACMAN='sudo apt-get install' - elif command -v apk &> /dev/null; then PACMAN='sudo apk add' - elif command -v rpm &> /dev/null; then PACMAN='rpm -ihv' - elif command -v brew &> /dev/null; then PACMAN='brew install' - else - return 1 - fi -} - -list_packages() { - if [[ "$(uname -s)" == "Darwin" ]]; then - printf "==== Installed by \`%s\` =============\n" "${PACMAN}" - - for pkg in "${BREW_PACKAGES[@]}"; do - print_package_status "$pkg" "brew" - done - elif [[ "$(uname -s)" == "Linux" ]]; then - printf "==== Installed by \`%s\` =============\n" "${PACMAN}" - - for ((i = 0; i < "${#LINUX_PACKAGES[@]}"; i=(i+2) )); do - print_package_status "${LINUX_PACKAGES[$i + 1]}" "$PACMAN" - done - fi - - printf "==== Installed by \`cargo install\` =============\n" - for ((i = 0; i < "${#CARGO_PACKAGES[@]}"; i=(i+2) )); do - print_package_status "${CARGO_PACKAGES[$i + 1]}" "cargo" - done -} - -print_package_status() { - if [[ "$2" == "cargo" ]] && [[ -x "${HOME}/.cargo/bin/$1" ]]; then - printf "✓ %s :: Installed\n" "$1" - elif [[ "$2" == "cargo" ]]; then - printf "✗ %s :: Not installed\n" "$1" - elif [[ "$2" == brew* ]] && brew list "$1" &>/dev/null; then - printf "✓ %s :: Installed\n" "$1" - elif [[ "$2" == brew* ]]; then - printf "✗ %s :: Not installed\n" "$1" - elif command -v "$1" &> /dev/null; then - printf "✓ %s :: Installed\n" "$1" - else - printf "✗ %s :: Not installed\n" "$1" - fi -} - -#╔─────────────────────────────────────────────────────────────────────────────╗ -#│ Pαckαgε iηsταllεrs | -#╚─────────────────────────────────────────────────────────────────────────────╝ -install_web_packages() { - if ! command -v rustup &>/dev/null && ask "Install rustup?"; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - fi - - if ! [[ -e ~/.config/tmux/plugins/tpm ]] && ask "Install tmux's plugin manager?"; then - git clone --depth 1 'https://github.com/tmux-plugins/tpm' ~/.config/tmux/plugins/tpm - ~/.config/tmux/plugins/tpm/tpm - ~/.config/tmux/plugins/tpm/bin/install_plugins - ~/.config/tmux/plugins/tpm/tpm - fi - - if [[ ! -f ~/.git-prompt.bash ]] && ask "Download git-prompt for bash?"; then - curl --output ~/.git-prompt.bash\ - 'https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh' - fi - - if [[ ! -f ~/.git-completion.bash ]] && ask "Download git-completion for bash?"; then - curl --output ~/.git-completion.bash\ - 'https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash' - fi - - if [[ ! -f ~/.fzf.bash ]] && ask "Download fzf-completion for bash?"; then - curl --output ~/.fzf.bash\ - 'https://raw.githubusercontent.com/junegunn/fzf/master/shell/completion.bash' - fi - - if ask "Install vim plugins?"; then - if command -v vim &>/dev/null; then - vim +$'if !has(\'nvim\') | PlugInstall --sync | endif' +qall - elif command -v vi &>/dev/null; then - vi +$'if !has(\'nvim\') | PlugInstall --sync | endif' +qall - else - printf "Run :PlugInstall in nVim to finish setting up vim's plugins\n" - fi - - if command -v pip3 &>/dev/null && ! pip3 list 2>&1 | grep -q '^pynvim'; then - pip3 install pynvim - fi - fi -} - -install_cargo_packages() { - if ! command -v cargo &> /dev/null; then - printf 'Cargo package manager not found\n' - return 1 - fi - - for ((i = 0; i < "${#CARGO_PACKAGES[@]}"; i=(i+2) )); do - local bin="${CARGO_PACKAGES[$i + 1]}" - local name="${CARGO_PACKAGES[$i]}" - - if ! command -v "${bin}" &>/dev/null && ask "Cargo install ${name}?"; then - cargo install "${name}" - fi - done -} - -install_brew_packages() { - brew tap koekeishiya/formulae - - printf 'Checking brew packages. Brew is slow. This may take a while...\n' - - for pkg in "${BREW_PACKAGES[@]}"; do - if [[ -z "$(brew list "${pkg}")" ]] \ - && ! command -v "${pkg}" &>/dev/null \ - && ask "Install ${pkg}?"; then - brew install "${pkg}" - fi - done -} - -# May or may not work. Tested with pacman on arch -install_linux_packages() { - for ((i = 0; i < "${#LINUX_PACKAGES[@]}"; i=(i+2) )); do - local bin="${LINUX_PACKAGES[$i + 1]}" - local name="${LINUX_PACKAGES[$i]}" - - if ! command -v "${bin}" &>/dev/null && ask "Install ${name}?"; then - eval "${PACMAN} ${name}" - fi - done -} - -# Very tentative. Only works on linux and only installs essentials for ssh -install_curl_packages() { - mkdir -p ~/bin - - if ! command -v fzf &>/dev/null && ask 'Download fzf?'; then - curl --output 'fzf_download.tar.gz' \ - -LO 'https://github.com/junegunn/fzf/releases/download/0.28.0/fzf-0.28.0-linux_amd64.tar.gz' - tar xf 'fzf_download.tar.gz' - chmod u+x ./fzf - rm -f 'fzf_download.tar.gz' - fi - - if ! command -v neofetch &>/dev/null && ask 'Download neofetch?'; then - curl --output "neofetch.tar.gz" \ - -LO 'https://github.com/dylanaraps/neofetch/archive/refs/tags/7.1.0.tar.gz' - tar xf 'neofetch.tar.gz' - mv ./neofetch-*/neofetch ./neofetch - rm -rf 'neofetch.tar.gz' 'neofetch-7.1.0' - fi - - if ! command -v tmux &>/dev/null && ask 'Download tmux?'; then - curl --output 'tmux.tar.gz' \ - -LO 'https://github.com/tmux/tmux/releases/download/3.2a/tmux-3.2a.tar.gz' - tar xf 'tmux.tar.gz' - (cd tmux-* && ./configure && make && mv ./tmux ../tmux) - rm -rf 'tmux.tar.gz' ./tmux-* - fi - - if ! command -v nvim &>/dev/null && ask 'Download neovim?'; then - curl -LO 'https://github.com/neovim/neovim/releases/latest/download/nvim.appimage' - chmod u+x ./nvim.appimage - mv ./nvim.appimage ./nvim - fi -} - -####################################### -# Arguments: -# 1: Prompt message -# Return: -# 0 for yes, 1 for no -####################################### -ask () { - read -rp $'\n'"$1 ([y]/n) " - [[ "${REPLY:-y}" =~ ^[Yy][es]?$ ]] -} - - -main "$@"