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
This commit is contained in:
Blake Ridgway
2026-09-02 16:32:40 -05:00
parent ae72614636
commit 0817d6a815
6 changed files with 319 additions and 75 deletions

View File

@@ -1,7 +1,8 @@
#!/bin/bash
# 00-system-prep.sh
# Updates system and sets up Flathub.
# Updates system, bootstraps sudo, enables Debian extra components,
# and sets up Flathub.
# Relies on DISTRO and PACKAGE_MANAGER being set by the caller.
echo "--- Starting System Preparation ---"
@@ -11,6 +12,27 @@ if [ -z "$DISTRO" ] || [ -z "$PACKAGE_MANAGER" ]; then
exit 1
fi
# Ensure 'sudo' exists so all later scripts can use it.
# - Running as root on a fresh Debian: installing sudo lets root invoke
# `sudo ...` directly (root is not subject to sudoers).
# - Non-root users without sudo are handled by main-setup.sh's preflight.
if ! command -v sudo &>/dev/null; then
echo "sudo not found. Bootstrapping it..."
if [ "$(id -u)" -eq 0 ]; then
if [ "$PACKAGE_MANAGER" == "apt" ]; then
apt-get update && apt-get install -y sudo
elif [ "$PACKAGE_MANAGER" == "dnf" ]; then
dnf install -y sudo
fi
echo "sudo installed. Add your normal user to the sudo group if desired:"
echo " usermod -aG sudo <your-user> (then log out/in)"
else
echo "ERROR: 'sudo' is required but missing, and you are not root."
echo "As root, run: apt install -y sudo && usermod -aG sudo \$USER"
exit 1
fi
fi
# Update system before installing packages
echo "Updating system packages..."
if [ "$PACKAGE_MANAGER" == "dnf" ]; then
@@ -21,6 +43,66 @@ else
echo "WARNING: Unknown package manager '$PACKAGE_MANAGER'. Skipping system update."
fi
# ---------------------------------------------------------------------------
# Debian extras (only on Debian; idempotent)
# ---------------------------------------------------------------------------
if [ "$DISTRO" == "debian" ]; then
# Enable contrib + non-free-firmware components on the main archive.
# Needed for some WiFi/GPU firmware and non-free tooling. Skip with SKIP_NONFREE=1.
if [ -z "$SKIP_NONFREE" ]; then
echo "Enabling 'contrib' and 'non-free-firmware' apt components..."
_enable_debian_components() {
local f="/etc/apt/sources.list.d/debian.sources"
local tmp
# deb822 format used by Debian 12+ (trixie ships debian.sources)
if [ -f "$f" ]; then
tmp="$(mktemp)"
if awk '
/^URIs:[[:space:]]/ { in_archive = ($0 ~ /deb\.debian\.org\/debian/) }
in_archive && /^Components:/ && $0 !~ /contrib/ {
print $0 " contrib non-free-firmware"
in_archive = 0
next
}
{ print }
' "$f" > "$tmp"; then
if ! diff -q "$f" "$tmp" >/dev/null; then
sudo cp "$f" "${f}.bak"
sudo mv "$tmp" "$f"
echo " -> updated ${f}"
else
rm -f "$tmp"
echo " -> components already present in ${f}"
fi
else
rm -f "$tmp"
echo "WARNING: could not parse ${f}; skipping component changes."
fi
fi
# Legacy single-line /etc/apt/sources.list fallback
if [ -f /etc/apt/sources.list ] \
&& grep -qE '^deb[[:space:]]+https?://deb\.debian\.org/debian' /etc/apt/sources.list \
&& ! grep -qE '^deb[[:space:]]+https?://deb\.debian\.org/debian[^#]*non-free-firmware' /etc/apt/sources.list; then
sudo sed -i -E 's/^(deb[[:space:]]+https?:\/\/deb\.debian\.org\/debian[^#]*main)([[:space:]]|$)/\1 contrib non-free-firmware/' /etc/apt/sources.list
echo " -> updated /etc/apt/sources.list"
fi
sudo apt update
}
_enable_debian_components
else
echo "SKIP_NONFREE is set - leaving apt components unchanged."
fi
# needrestart prompts (or auto-restarts) services after library upgrades.
if ! dpkg-query -W -f='${Status}' needrestart 2>/dev/null | grep -q "ok installed"; then
echo "Installing needrestart..."
sudo apt install -y needrestart
else
echo "needrestart already installed."
fi
fi
# Setup Flatpak
echo "Setting up Flathub repository..."
if command -v flatpak &> /dev/null; then

View File

@@ -76,7 +76,7 @@ fi
# Combine package lists
PACKAGE_LIST=("${BASE_PACKAGE_LIST[@]}" "${DISTRO_SPECIFIC_PACKAGES[@]}")
PACKAGE_LIST=($(printf "%s\n" "${PACKAGE_LIST[@]}" | LC_ALL=C sort -u))
mapfile -t PACKAGE_LIST < <(printf "%s\n" "${PACKAGE_LIST[@]}" | LC_ALL=C sort -u)
# --- apt helpers ---------------------------------------------------------
_apt_is_installed() {
@@ -210,10 +210,21 @@ _apt_add_hashicorp_repo() {
| sudo tee /etc/apt/sources.list.d/hashicorp.list >/dev/null
}
# Kubernetes apt repo. Bump K8S_MINOR to track the current stable minor.
K8S_MINOR="v1.32"
# 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

176
scripts/verify-debian.sh Executable file
View File

@@ -0,0 +1,176 @@
#!/bin/bash
# verify-debian.sh
# Non-destructive sanity checks for a freshly set-up Debian 13 machine.
# Verifies the pieces that 00-03 + 04-config-symlinks.sh are expected to leave behind.
# Exit code: 0 = all critical checks passed; 1 = at least one critical check failed.
#
# Usage: bash scripts/verify-debian.sh (no root needed)
# --- output helpers -------------------------------------------------------
if [ -t 1 ]; then
C_GREEN=$'\e[32m'; C_RED=$'\e[31m'; C_YELLOW=$'\e[33m'; C_BOLD=$'\e[1m'; C_OFF=$'\e[0m'
else
C_GREEN=""; C_RED=""; C_YELLOW=""; C_BOLD=""; C_OFF=""
fi
PASS=0
WARN=0
FAIL=0
ok() { PASS=$((PASS+1)); echo "${C_GREEN}${C_OFF} $1"; }
warn() { WARN=$((WARN+1)); echo "${C_YELLOW}${C_OFF} $1"; }
bad() { FAIL=$((FAIL+1)); echo "${C_RED}${C_OFF} $1"; }
# --- helpers --------------------------------------------------------------
check_cmd() { # check_cmd "label" "command-name"
if command -v "$2" &>/dev/null; then ok "$1: found ($(command -v "$2"))"; else bad "$1: NOT FOUND"; fi
}
check_symlink_target() { # check_symlink_target "label" "link-path" "expected-target"
if [ -L "$2" ]; then
local target; target="$(readlink -f "$2")"
if [ "$target" = "$3" ] || [ "$(basename "$target")" = "$(basename "$3")" ]; then
ok "$1: $2 -> $target"
else
warn "$1: $2 points to $target (expected $3)"
fi
else
warn "$1: $2 is not a symlink"
fi
}
check_service() { # check_service "label" "unit"
if systemctl is-active --quiet "$2" 2>/dev/null; then ok "$1: active"; else warn "$1: not active"; fi
}
# --- 1. OS ------------------------------------------------------------------
echo "${C_BOLD}== OS ==${C_OFF}"
. /etc/os-release
echo " distro: $PRETTY_NAME (id=$ID, codename=${VERSION_CODENAME:-unknown})"
if [ "$ID" = "debian" ]; then
ok "Debian detected"
else
warn "Not Debian (id=$ID) - this script targets Debian 13"
fi
# --- 2. Package manager ------------------------------------------------------
echo ""
echo "${C_BOLD}== Package manager ==${C_OFF}"
if command -v apt-get &>/dev/null; then ok "apt present"; else bad "apt missing"; fi
if command -v dpkg-query &>/dev/null; then :; else bad "dpkg missing"; fi
# --- 3. Core packages ---------------------------------------------------------
echo ""
echo "${C_BOLD}== Core tools ==${C_OFF}"
for c in git curl wget jq ripgrep tmux zsh fzf zoxide eza unzip btop direnv tldr socat mtr nmap whois traceroute tcpdump iperf3 httpie ansible virt-manager flatpak python3; do
check_cmd "$c" "$c"
done
# --- 4. Debian binary renames -------------------------------------------------
echo ""
echo "${C_BOLD}== Debian binary renames ==${C_OFF}"
if command -v batcat &>/dev/null; then
check_symlink_target "bat -> batcat" "/usr/local/bin/bat" "/usr/bin/batcat"
elif command -v bat &>/dev/null; then
ok "bat present directly"
else
bad "bat/batcat not found"
fi
if command -v fdfind &>/dev/null; then
check_symlink_target "fd -> fdfind" "/usr/local/bin/fd" "/usr/bin/fdfind"
elif command -v fd &>/dev/null; then
ok "fd present directly"
else
bad "fd/fdfind not found"
fi
if command -v dig &>/dev/null; then ok "dig present"; else bad "dig missing (install bind9-dnsutils)"; fi
# --- 5. Upstream-only tools ----------------------------------------------------
echo ""
echo "${C_BOLD}== Upstream tooling ==${C_OFF}"
for c in gh terraform kubectl k9s minikube; do check_cmd "$c" "$c"; done
# --- 6. Development tools -------------------------------------------------------
echo ""
echo "${C_BOLD}== Development tools ==${C_OFF}"
for c in nvim go rustc cargo oh-my-posh helm kubectx kubens stern dive trivy aws; do check_cmd "$c" "$c"; done
# Go binary path sanity
if command -v go &>/dev/null; then ok "GOPATH=$(go env GOPATH 2>/dev/null)"; fi
# pynvim (PEP 668 note: system pip is externally managed on Debian 13)
if /usr/bin/python3 -m pip show pynvim &>/dev/null; then
ok "pynvim installed for python3"
else
warn "pynvim not found for /usr/bin/python3 (install with --break-system-packages if wanted)"
fi
# --- 7. .NET / backend -----------------------------------------------------------
echo ""
echo "${C_BOLD}== .NET / backend services ==${C_OFF}"
if command -v dotnet &>/dev/null; then
ok "dotnet: $(dotnet --version 2>/dev/null)"
else
bad "dotnet not on PATH (install via 03-dotnet-setup.sh or add ~/.dotnet to PATH)"
fi
check_cmd "podman" "podman"
if command -v docker &>/dev/null; then
ok "docker shim present ($(readlink -f "$(command -v docker)" 2>/dev/null || echo docker))"
else
warn "docker shim missing (install podman-docker)"
fi
if command -v podman-compose &>/dev/null; then ok "podman-compose present"; else warn "podman-compose missing"; fi
check_service "postgresql" "postgresql"
check_service "libvirtd" "libvirtd"
# --- 8. Groups -------------------------------------------------------------------
echo ""
echo "${C_BOLD}== Group membership ==${C_OFF}"
for g in libvirt kvm; do
if id -nG 2>/dev/null | tr ' ' '\n' | grep -qx "$g"; then
ok "in group '$g'"
else
warn "not in group '$g' (needed for virt-manager/KVM; log out/in after usermod)"
fi
done
# --- 9. Flatpak --------------------------------------------------------------------
echo ""
echo "${C_BOLD}== Flatpak ==${C_OFF}"
if command -v flatpak &>/dev/null && flatpak remotes 2>/dev/null | grep -q flathub; then
ok "flathub remote present"
else
warn "flathub remote not configured"
fi
# --- 10. Shell rc symlinks ----------------------------------------------------------
echo ""
echo "${C_BOLD}== Dotfile symlinks ==${C_OFF}"
for d in bashrc zshrc gitconfig aliases.bash aliases.zsh; do
if [ -e "$HOME/.$d" ] || [ -L "$HOME/.$d" ]; then
# shellcheck disable=SC2088
ok "~/.$d linked"
else
# shellcheck disable=SC2088
warn "~/.$d missing"
fi
done
check_symlink_target "oh-my-posh theme" "$HOME/.config/oh-my-posh/theme.omp.json" "theme.omp.json"
check_symlink_target "nvim config" "$HOME/.config/nvim" "nvim"
check_symlink_target "nushell config" "$HOME/.config/nushell" "nushell"
# --- Summary ------------------------------------------------------------------------
echo ""
echo "${C_BOLD}========================================${C_OFF}"
echo "${C_GREEN}${C_OFF} ${PASS} passed ${C_YELLOW}${C_OFF} ${WARN} warnings ${C_RED}${C_OFF} ${FAIL} failed"
echo "${C_BOLD}========================================${C_OFF}"
echo ""
if [ "$FAIL" -gt 0 ]; then
echo "Some critical checks failed. See ✘ items above."
exit 1
else
echo "All critical checks passed. Warnings above are optional/suggested."
exit 0
fi