Files
dotfiles/scripts/01-package-install.sh
Blake Ridgway 0817d6a815 feat: Debian prep hardening, verify script, and quality pass
- 00-system-prep.sh: bootstrap sudo when run as root; on Debian enable
  contrib/non-free-firmware (SKIP_NONFREE to opt out) + install needrestart
- scripts/verify-debian.sh: non-destructive post-install sanity checker
  (exit 0/1) covering OS, tools, Debian renames, upstream tooling,
  .NET/Podman/Postgres, groups, flatpak, dotfile symlinks
- 01-package-install.sh: auto-detect current Kubernetes minor from upstream
  (fallback v1.36, override K8S_MINOR) instead of stale v1.32 pin
- fix shellcheck findings (SC2155, SC2207, SC2088); all scripts clean at
  warning severity
- docs: fold decisions/status into plan; README lists new tools
2026-09-02 16:32:40 -05:00

319 lines
12 KiB
Bash
Executable File

#!/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 ---"