dotfiles/package_install.py

528 lines
13 KiB
Python
Executable file

#!/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 = {
"apk": {
"name": "apk",
"install_cmd": ["apk", "add"],
"check_installed": ["apk", "info", "--installed"],
},
"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",
"apk": "alacritty",
"apt": "alacritty",
"dnf": "alacritty",
"pacman": "alacritty",
},
{
"global_name": "bat",
"executable_name": "bat",
"apk": "bat",
"apt": "bat",
"dnf": "bat",
"pacman": "bat",
},
{
"global_name": "bash",
"executable_name": "bash",
"apk": "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",
"apk": "curl",
"apt": "curl",
"dnf": "curl",
"pacman": "curl",
},
{
"global_name": "dust",
"executable_name": "dust",
"apk": "dust",
"pacman": "dust",
},
{
"global_name": "exa -> eza",
"executable_name": "exa",
"apk": "exa",
"apt": "exa",
"dnf": "eza",
"pacman": "eza",
},
{
"global_name": "fd-find",
"executable_name": "fd",
"apk": "fd",
"apt": "fd-find",
"dnf": "fd-find",
"pacman": "fd",
},
{
"global_name": "foliate",
"executable_name": "foliate",
"apk": "foliate",
"apt": "foliate",
"dnf": "foliate",
"pacman": "foliate",
},
{
"global_name": "fuzzel",
"executable_name": "fuzzel",
"apk": "fuzzel",
"apt": "fuzzel",
"dnf": "fuzzel",
"pacman": "fuzzel",
},
{
"global_name": "fzf",
"executable_name": "fzf",
"apk": "fzf",
"apt": "fzf",
"dnf": "fzf",
"pacman": "fzf",
},
{
"global_name": "gawk",
"executable_name": "gawk",
"apk": "gawk",
"apt": "gawk",
"dnf": "gawk",
"pacman": "gawk",
},
{
"global_name": "git",
"executable_name": "git",
"apk": "git",
"apt": "git",
"dnf": "git",
"pacman": "git",
},
{
"global_name": "git-lfs",
"executable_name": "git-lfs",
"apk": "git-lfs",
"apt": "git-lfs",
"dnf": "git-lfs",
"pacman": "git-lfs",
},
{
"global_name": "grim",
"executable_name": "grim",
"apk": "grim",
"apt": "grim",
"dnf": "grim",
"pacman": "grim",
},
{
"global_name": "kanshi",
"executable_name": "kanshictl",
"apk": "kanshi",
"apt": "kanshi",
"dnf": "kanshi",
"pacman": "kanshi",
},
{
"global_name": "pass",
"executable_name": "pass",
"apk": "pass",
"apt": "pass",
"dnf": "pass",
"pacman": "pass",
},
{
"global_name": "slurp",
"executable_name": "slurp",
"apk": "slurp",
"apt": "slurp",
"dnf": "slurp",
"pacman": "slurp",
},
{
"global_name": "swappy",
"executable_name": "swappy",
"dnf": "swappy",
"pacman": "swappy",
},
{
"global_name": "imv",
"executable_name": "imv",
"apk": "imv",
"apt": "imv",
"dnf": "imv",
"pacman": "imv",
},
{
"global_name": "jq",
"executable_name": "jq",
"apk": "jq",
"apt": "jq",
"dnf": "jq",
"pacman": "jq",
},
{
"global_name": "Meslo Nerd font",
"apk": "font-meslo-nerd",
"pacman": "ttf-meslo-nerd",
},
{
"global_name": "mmv",
"executable_name": "mmv",
"apt": "mmv",
"dnf": "mmv",
},
{
"global_name": "mpv",
"executable_name": "mpv",
"apk": "mpv",
"apt": "mpv",
"dnf": "mpv",
"pacman": "mpv",
},
{
"global_name": "neofetch",
"executable_name": "neofetch",
"apk": "neofetch",
"apt": "neofetch",
"dnf": "neofetch",
"pacman": "neofetch",
},
{
"global_name": "neovim",
"executable_name": "nvim",
"apk": "neovim",
"apt": "neovim",
"dnf": "neovim",
"pacman": "neovim",
},
{
"global_name": "pynvim",
"apk": "py3-pynvim",
"apt": "python3-pynvim",
"dnf": "python3-neovim",
"pacman": "python-pynvim",
},
{
"global_name": "ripgrep",
"executable_name": "rg",
"apk": "ripgrep",
"apt": "ripgrep",
"dnf": "ripgrep",
"pacman": "ripgrep",
},
{
"global_name": "socat",
"executable_name": "socat",
"apk": "socat",
"apt": "socat",
"dnf": "socat",
"pacman": "socat",
},
{
"global_name": "sway",
"executable_name": "sway",
"apk": "sway",
"apt": "sway",
"dnf": "sway",
"pacman": "sway",
},
{
"global_name": "swaybg",
"executable_name": "swaybg",
"apk": "swaybg",
"apt": "swaybg",
"dnf": "swaybg",
"pacman": "swaybg",
},
{
"global_name": "swaylock",
"executable_name": "swaylock",
"apk": "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",
"apk": "tmux",
"apt": "tmux",
"dnf": "tmux",
"pacman": "tmux",
},
{
"global_name": "udisks2",
"executable_name": "udisksctl",
"apk": "udisks2",
"apt": "udisks2",
"dnf": "udisks2",
"pacman": "udisks2",
},
{
"global_name": "vifm",
"executable_name": "vifm",
"apk": "vifm",
"apt": "vifm",
"dnf": "vifm",
"pacman": "vifm",
},
{
"global_name": "viu",
"executable_name": "viu",
"apk": "viu",
"pacman": "viu",
},
{
"global_name": "wl-clipboard",
"executable_name": "wl-copy",
"apk": "wl-clipboard",
"apt": "wl-clipboard",
"dnf": "wl-clipboard",
"pacman": "wl-clipboard",
},
{
"global_name": "wlsunset",
"executable_name": "wlsunset",
"apk": "wlsunset",
"apt": "wlsunset",
"dnf": "wlsunset",
"pacman": "wlsunset",
},
{
"global_name": "wtype",
"executable_name": "wtype",
"apk": "wtype",
"apt": "wtype",
"dnf": "wtype",
"pacman": "wtype",
},
{
"global_name": "ydotool",
"executable_name": "ydotool",
"apt": "ydotool",
"dnf": "ydotool",
"pacman": "ydotool",
},
{
"global_name": "zathura-pdf-mupdf",
"apk": "zathura-pdf-mupdf",
"dnf": "zathura-pdf-mupdf",
"pacman": "zathura-pdf-mupdf",
},
{
"global_name": "zathura",
"executable_name": "zathura",
"apk": "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("apk"):
return PACKAGE_MANAGERS["apk"]
elif 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 == 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")