#!/bin/bash # 01-package-install.sh # Installs system packages and Flatpak applications. # Relies on DISTRO and PACKAGE_MANAGER being set by the caller. echo "--- Starting Package Installation ---" if [ -z "$DISTRO" ] || [ -z "$PACKAGE_MANAGER" ]; then echo "ERROR: DISTRO and PACKAGE_MANAGER must be set in the environment." exit 1 fi # Define base package list # Base packages available in BOTH Debian and Fedora repositories. # NOTE: gh, kubectl, minikube, k9s, terraform are NOT in Debian main — # they are handled later in setup_upstream_tooling() via vendor repos/.deb. BASE_PACKAGE_LIST=( ansible bat btop curl direnv eza fd-find flatpak fzf git httpie iperf3 jq mtr nmap python3 python3-pip ripgrep socat tcpdump tldr tmux traceroute unzip virt-manager wget whois zoxide zsh ) # Distro-specific packages to add to the main list DISTRO_SPECIFIC_PACKAGES=() if [ "$PACKAGE_MANAGER" == "dnf" ]; then DISTRO_SPECIFIC_PACKAGES+=( "bind-utils" # dig, nslookup, etc. "fontconfig-devel" # for fc-cache "nmap-ncat" # netcat utility "openssl-devel" # for various compilations "util-linux-user" # for chsh (if needed) ) elif [ "$PACKAGE_MANAGER" == "apt" ]; then DISTRO_SPECIFIC_PACKAGES+=( "bind9-dnsutils" # dig/nslookup (replaces dnsutils on Debian 13+) "bridge-utils" # virt-manager bridge networking "ca-certificates" # HTTPS repo keys "curl" # ensure present for repo bootstrapping "gnupg" # apt keyring handling "libfontconfig-dev" "libssl-dev" "libvirt-clients" # virsh "libvirt-daemon-system" # libvirtd daemon + default network "netcat-openbsd" "qemu-system-x86" # KVM/QEMU backend for virt-manager "virtinst" # virt-install ) fi # Combine package lists PACKAGE_LIST=("${BASE_PACKAGE_LIST[@]}" "${DISTRO_SPECIFIC_PACKAGES[@]}") mapfile -t PACKAGE_LIST < <(printf "%s\n" "${PACKAGE_LIST[@]}" | LC_ALL=C sort -u) # --- apt helpers --------------------------------------------------------- _apt_is_installed() { dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -q "ok installed" } _apt_install() { if _apt_is_installed "$1"; then echo "$1 already installed." return 0 fi echo "Installing $1 (apt)..." if sudo apt install -y "$1"; then echo "$1 has been installed." return 0 fi echo "WARNING: Failed to install $1." return 1 } # debian/ubuntu codename, e.g. "trixie" (no lsb_release dependency) _get_codename() { . /etc/os-release echo "$VERSION_CODENAME" } # uname -m -> Go/GitHub style arch token (amd64/arm64) _get_upstream_arch() { case "$(uname -m)" in x86_64) echo "amd64" ;; aarch64|arm64) echo "arm64" ;; *) echo "unsupported" ;; esac } FLATPAK_LIST=( com.adamcake.Bolt com.bitwarden.desktop com.discordapp.Discord com.github.IsmaelMartinez.teams_for_linux com.github.tchx84.Flatseal com.valvesoftware.Steam com.visualstudio.code io.podman_desktop.PodmanDesktop net.davidotek.pupgui2 org.mozilla.Thunderbird org.signal.Signal org.videolan.VLC ) echo "Installing System Packages..." for package_name in "${PACKAGE_LIST[@]}"; do if [ "$PACKAGE_MANAGER" == "dnf" ]; then if ! rpm -q "$package_name" &>/dev/null; then echo "Installing $package_name (dnf)..." if sudo dnf install "$package_name" -y; then echo "$package_name has been installed." else echo "WARNING: Failed to install $package_name. It may not be available in the repositories." fi else echo "$package_name already installed." fi elif [ "$PACKAGE_MANAGER" == "apt" ]; then # For apt, check if package provides the command or is installed # dpkg-query is generally more reliable for checking installed status actual_package_name=$package_name if [ "$package_name" == "fd-find" ] && ! dpkg -s fd-find &>/dev/null ; then # On some newer Ubuntu/Debian, 'fd-find' might be the package, # but user might want 'fd' if it's a different source or a metapackage. # For now, we stick to fd-find. : # Keep actual_package_name as fd-find fi if ! dpkg-query -W -f='${Status}' "$actual_package_name" 2>/dev/null | grep -q "ok installed"; then echo "Installing $actual_package_name (apt)..." sudo apt install "$actual_package_name" -y echo "$actual_package_name has been installed." else echo "$actual_package_name already installed." fi fi done # Post-install for fd-find on Debian/Ubuntu (create symlink) if [ "$PACKAGE_MANAGER" == "apt" ] && command -v fdfind &>/dev/null && ! command -v fd &>/dev/null; then if dpkg-query -W -f='${Status}' "fd-find" 2>/dev/null | grep -q "ok installed"; then echo "Creating symlink for fd from fdfind..." sudo ln -sf /usr/bin/fdfind /usr/local/bin/fd fi fi # Post-install for bat on Debian/Ubuntu (package ships the binary as 'batcat') if [ "$PACKAGE_MANAGER" == "apt" ] && command -v batcat &>/dev/null && ! command -v bat &>/dev/null; then echo "Creating symlink for bat from batcat..." sudo ln -sf /usr/bin/batcat /usr/local/bin/bat fi # --------------------------------------------------------------------------- # Upstream-only tooling (NOT packaged in Debian main / Fedora base repos) # --------------------------------------------------------------------------- # gh -> GitHub CLI apt repository # terraform -> HashiCorp apt repository # kubectl -> Kubernetes (pkgs.k8s.io) apt repository # k9s -> GitHub release tarball # minikube -> official .deb from Google's release bucket # On Fedora these are a best-effort dnf attempt (usually need COPR/manual). # --------------------------------------------------------------------------- _apt_add_gh_repo() { [ -f /etc/apt/sources.list.d/github-cli.list ] && { echo "GitHub CLI repo already configured."; return 0; } echo "Adding GitHub CLI apt repository..." curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ | sudo tee /usr/share/keyrings/githubcli-archive-keyring.gpg >/dev/null sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ | sudo tee /etc/apt/sources.list.d/github-cli.list >/dev/null } _apt_add_hashicorp_repo() { [ -f /etc/apt/sources.list.d/hashicorp.list ] && { echo "HashiCorp repo already configured."; return 0; } echo "Adding HashiCorp apt repository..." curl -fsSL https://apt.releases.hashicorp.com/gpg \ | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg sudo chmod go+r /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(_get_codename) main" \ | sudo tee /etc/apt/sources.list.d/hashicorp.list >/dev/null } # Kubernetes apt repo. Auto-detects the latest stable minor from the # kubernetes/kubernetes GitHub release feed, falling back to a pinned minor # if the API is unreachable. Override explicitly with K8S_MINOR=... K8S_MINOR_FALLBACK="v1.36" _k8s_latest_minor() { local tag minor tag="$(curl -fsSL --max-time 10 https://api.github.com/repos/kubernetes/kubernetes/releases/latest \ | grep '"tag_name"' | cut -d'"' -f4)" || return 1 minor="${tag#v}" # e.g. 1.36.0 minor="${minor%.*}" # strip patch -> 1.36 echo "v${minor}" } _apt_add_kubernetes_repo() { [ -f /etc/apt/sources.list.d/kubernetes.list ] && { echo "Kubernetes repo already configured."; return 0; } K8S_MINOR="${K8S_MINOR:-$(_k8s_latest_minor || echo "$K8S_MINOR_FALLBACK")}" echo "Adding Kubernetes apt repository (${K8S_MINOR})..." curl -fsSL "https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/Release.key" \ | sudo gpg --dearmor -o /usr/share/keyrings/kubernetes-archive-keyring.gpg sudo chmod go+r /usr/share/keyrings/kubernetes-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/ /" \ | sudo tee /etc/apt/sources.list.d/kubernetes.list >/dev/null } _install_k9s_bin() { command -v k9s &>/dev/null && { echo "k9s already installed."; return 0; } local K9S_VERSION K9S_URL TMPDIR UPSTREAM_ARCH UPSTREAM_ARCH="$(_get_upstream_arch)" [ "$UPSTREAM_ARCH" = "unsupported" ] && { echo "WARNING: unsupported arch for k9s."; return 1; } K9S_VERSION="$(curl -fsSL https://api.github.com/repos/derailed/k9s/releases/latest | grep '"tag_name"' | cut -d'"' -f4)" K9S_URL="https://github.com/derailed/k9s/releases/download/${K9S_VERSION}/k9s_Linux_${UPSTREAM_ARCH}.tar.gz" echo "Downloading k9s ${K9S_VERSION}..." TMPDIR="$(mktemp -d)" if curl -fsSL -L "${K9S_URL}" -o "${TMPDIR}/k9s.tar.gz"; then tar -C "${TMPDIR}" -xzf "${TMPDIR}/k9s.tar.gz" k9s sudo install -m755 "${TMPDIR}/k9s" /usr/local/bin/k9s echo "k9s ${K9S_VERSION} installed to /usr/local/bin/k9s" else echo "WARNING: Failed to download k9s." fi rm -rf "${TMPDIR}" } _install_minikube_deb() { command -v minikube &>/dev/null && { echo "minikube already installed."; return 0; } local UPSTREAM_ARCH TMPDIR UPSTREAM_ARCH="$(_get_upstream_arch)" [ "$UPSTREAM_ARCH" = "unsupported" ] && { echo "WARNING: unsupported arch for minikube."; return 1; } echo "Downloading minikube (.deb)..." TMPDIR="$(mktemp -d)" if curl -fsSL -L "https://storage.googleapis.com/minikube/releases/latest/minikube_latest_${UPSTREAM_ARCH}.deb" -o "${TMPDIR}/minikube.deb"; then sudo dpkg -i "${TMPDIR}/minikube.deb" 2>/dev/null || sudo apt-get install -f -y echo "minikube installed from .deb." else echo "WARNING: Failed to download minikube." fi rm -rf "${TMPDIR}" } setup_upstream_tooling() { echo "" echo "Installing upstream-only tooling (gh, terraform, kubectl, k9s, minikube)..." if [ "$PACKAGE_MANAGER" == "apt" ]; then _apt_add_gh_repo _apt_add_hashicorp_repo _apt_add_kubernetes_repo echo "Refreshing apt after adding vendor repos..." sudo apt update _apt_install gh _apt_install terraform _apt_install kubectl _install_k9s_bin _install_minikube_deb else # dnf best-effort: these usually need COPR/manual installs on Fedora. for upstream_pkg in gh terraform kubectl k9s minikube; do if ! rpm -q "$upstream_pkg" &>/dev/null; then echo "Attempting $upstream_pkg (dnf)..." if ! sudo dnf install -y "$upstream_pkg"; then echo "WARNING: $upstream_pkg unavailable in dnf repos — install it via COPR/manually." fi else echo "$upstream_pkg already installed." fi done fi } setup_upstream_tooling echo "Installing Flatpak Applications..." if command -v flatpak &> /dev/null; then for flatpak_name in "${FLATPAK_LIST[@]}"; do if ! flatpak list --app | grep -q "$flatpak_name"; then echo "Installing Flatpak $flatpak_name..." flatpak install flathub "$flatpak_name" -y echo "$flatpak_name has been installed." else echo "Flatpak $flatpak_name already installed." fi done else echo "WARNING: flatpak command not found. Skipping Flatpak app installation." fi echo "--- Package Installation Finished ---"