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

@@ -51,11 +51,12 @@ Distro-agnostic .NET development environment:
The setup is broken down into focused scripts: The setup is broken down into focused scripts:
- `00-system-prep.sh` - System updates and Flathub setup - `00-system-prep.sh` - System updates, `sudo` bootstrap, Debian `contrib`/`non-free-firmware` + `needrestart`, Flathub setup
- `01-package-install.sh` - Core packages, upstream tooling, and Flatpak apps - `01-package-install.sh` - Core packages, upstream tooling, and Flatpak apps
- `02-dev-tools-setup.sh` - Development tools (Neovim, Go, Rust, Oh-My-Posh, Helm, kubectx, stern, dive, trivy, AWS CLI) - `02-dev-tools-setup.sh` - Development tools (Neovim, Go, Rust, Oh-My-Posh, Helm, kubectx, stern, dive, trivy, AWS CLI)
- `03-dotnet-setup.sh` - .NET SDK + PostgreSQL + Podman (distro-agnostic) - `03-dotnet-setup.sh` - .NET SDK + PostgreSQL + Podman (distro-agnostic)
- `04-config-symlinks.sh` - Dotfile symlinking - `04-config-symlinks.sh` - Dotfile symlinking
- `verify-debian.sh` - Post-install sanity checker (run manually, no root needed)
You can run individual scripts if you only need specific components. You can run individual scripts if you only need specific components.

View File

@@ -46,87 +46,60 @@ The repo is mostly distro-agnostic already (shell rc files, aliases, nvim via Nv
--- ---
## 3. File-by-file plan ## 3. File-by-file status (as of 2026-09-02)
### `main-setup.sh` ### `main-setup.sh` — DONE
- [ ] Keep existing `detect_linux_distro()` — already returns `debian` for trixie (`ID=debian`). Verify `sudo` present; if missing, warn to `su -`/install `sudo` first (or auto-detect root). - [x] `detect_linux_distro()` returns `debian` for trixie; `PACKAGE_MANAGER=apt`.
- [ ] Replace the Fedora-only prompt for `03-fedora-dotnet-setup.sh` with a distro-agnostic `.NET` prompt (new `03-dotnet-setup.sh`), OR gate on both `fedora` and `debian`. - [x] Preflight: if non-root and `sudo` missing → clear guidance (run as root or install `sudo`); `sudo -n` note.
- [ ] Consider exporting `DEBIAN_CODENAME`/`DISTRO_VERSION` (from `/etc/os-release`) for downstream repo URLs. - [x] `.NET` prompt is now distro-agnostic → runs `03-dotnet-setup.sh` on every supported distro.
### `scripts/00-system-prep.sh` ### `scripts/00-system-prep.sh` — DONE
- [ ] `apt update && apt upgrade` path already correct. Add optional: install `sudo`, `curl`, `gnupg`, `ca-certificates`, `software-properties-common`/`apt-transport-https` prerequisites early. - [x] `sudo` bootstrap when running as root on a fresh install (apt/dnf).
- [ ] (Optional) enable `contrib`/`non-free-firmware` components in `/etc/apt/sources.list.d/debian.sources` — needed for some firmware/tools. - [x] Debian: idempotently add `contrib` + `non-free-firmware` to `debian.sources` (deb822) or legacy `sources.list` (skip with `SKIP_NONFREE=1`); `needrestart` installed on apt.
### `scripts/01-package-install.sh` ### `scripts/01-package-install.sh` — DONE
- [ ] Split `BASE_PACKAGE_LIST` into truly-common + distro lists; on Debian drop anything not in trixie and add Debian equivalents. - [x] Split base vs distro lists; `dnsutils``bind9-dnsutils`; `bat``batcat` symlink; libvirt/QEMU stack (`libvirt-daemon-system`, `libvirt-clients`, `qemu-system-x86`, `virtinst`, `bridge-utils`); prereqs (`sudo`, `ca-certificates`, `gnupg`, `curl`).
- [ ] **Rename fixes (Debian branch of `DISTRO_SPECIFIC_PACKAGES`):** - [x] `setup_upstream_tooling()`: vendor repos for `gh` (GitHub CLI), `terraform` (HashiCorp), `kubectl` (pkgs.k8s.io); `k9s` (GitHub tarball) + `minikube` (official `.deb`) on apt; best-effort `dnf` on Fedora.
- `dnsutils``bind9-dnsutils` - [x] Kubernetes minor auto-detected from the latest k8s release (fallback `v1.36`; override with `K8S_MINOR`).
- add `bat` handling: after install, `ln -sf /usr/bin/batcat /usr/local/bin/bat` - [x] Flatpak list unchanged (VS Code stays Flatpak per decision).
- add `sudo`, `ca-certificates`, `gnupg`, `curl` (ensure present)
- add `libvirt-daemon-system`, `virtinst`, `qemu-kvm`, `bridge-utils` for virt-manager
- add `zsh` post: `chsh -s /usr/bin/zsh` (or instruct)
- `python3-pip` stays, but flag PEP 668 handling (see 02)
- `docker.io` + `docker-compose` **or** wire Docker CE repo (defer to decision in §4)
- [ ] **Upstream repo tools not in Debian main** — install in this script or a new `05-` script:
- `gh` → GitHub CLI apt repo (`https://cli.github.com/packages`)
- `terraform` → HashiCorp apt repo
- `kubectl` → Kubernetes apt repo (`pkgs.k8s.io`)
- `k9s`, `minikube` → GitHub `.deb` releases (minikube official `.deb` exists)
- [ ] Flatpak list unchanged (distro-agnostic). Note VS Code Flatpak vs MS repo decision in §4.
### `scripts/02-dev-tools-setup.sh` ### `scripts/02-dev-tools-setup.sh` — DONE
- [ ] **pynvim PEP 668 fix:** replace `pip install --user pynvim` with `python3 -m pip install --user --break-system-packages pynvim`, or (cleaner) install into NvChad's venv, or note `pipx`. - [x] `pynvim` PEP 668 fix: try `pip install --user`, fall back to `--break-system-packages`.
- [ ] Rest is GitHub-download based → distro-agnostic; verify `unzip`, `tar`, `wget` present.
- [ ] `dive` already handles `.deb` via `dpkg -i` on apt — good.
- [ ] Go/Rust/Nerd Font/Oh-My-Posh/Helm/kubectx/stern/trivy/AWS — no changes needed.
### `scripts/03-fedora-dotnet-setup.sh` → split/generalize ### `scripts/03-fedora-dotnet-setup.sh` → `03-dotnet-setup.sh` — DONE
- [ ] Rename to **`03-dotnet-setup.sh`** and branch by `$PACKAGE_MANAGER`: - [x] Renamed to `03-dotnet-setup.sh`, distro-agnostic (apt/dnf).
- **Fedora:** keep existing RPM/MS-repo path. - [x] `.NET SDK` via official `dotnet-install.sh` (channel `STS`, override `DOTNET_CHANNEL`) → `~/.dotnet`, PATH added to `bashrc`/`zshrc`.
- **Debian:** add MS Debian repo `https://packages.microsoft.com/config/debian/13/packages-microsoft-prod.deb` (verify trixie support; fall back to `dotnet-install.sh` if repo is unavailable for trixie) then `apt install dotnet-sdk-10.0` (or 8/9 fallback loop like Fedora). - [x] PostgreSQL: auto-cluster on Debian; `--initdb` only on Fedora.
- Alternatively use official **`dotnet-install.sh`** for both — fully distro-agnostic, no repo dependency. - [x] Containers = **Podman** + `podman-compose` + `podman-docker` (Docker-CLI shim). No MS repo, no moby/docker-ce.
- [ ] Replace Fedora-specific db/container section with Debian equivalents:
- PostgreSQL: `apt install postgresql` (cluster auto-init — remove `postgresql-setup --initdb` step for Debian; just `systemctl enable --now postgresql`).
- Docker: `docker.io` + `docker-compose` + add user to `docker` group, or Docker CE repo.
- [ ] Update `main-setup.sh` gate accordingly.
### `scripts/04-config-symlinks.sh` ### `scripts/04-config-symlinks.sh` — no changes required
- [ ] No functional changes needed (pure symlinking). Optionally handle `~/.bash_aliases` (Debian's default bashrc sources it) or leave as-is since `bashrc` sources `aliases.bash` directly. - [x] Pure symlinking; distro-agnostic as-is.
### Shell rc files (`bashrc`, `zshrc`, aliases) ### Shell rc files (`bashrc`, `zshrc`, aliases) — DONE
- [ ] Already distro-agnostic (PATH for `/usr/local/go`, `~/.local/bin`, cargo, nvm). No Fedora-specific bits. - [x] `zshrc` oh-my-zsh remnants purged (plain zsh + Oh-My-Posh).
- [ ] Note: `zshrc` still references **oh-my-zsh** (`$ZSH/oh-my-zsh.sh`, `plugins=(git)`) but no script installs it anymore (repo moved to Oh-My-Posh). On a fresh Debian this will fail silently. Decide: purge oh-my-zsh remnants or restore its install step. Recommend purging on this branch. - [x] Duplicate Go `PATH` lines (appended by an old 02 run) removed from `bashrc`/`zshrc`.
- [ ] Ensure `~/.fzf.zsh` / `~/.fzf.bash` generation (`fzf --zsh`/`--bash`) or skip gracefully (already guarded). - [x] `nvim/nvim` + `nushell/nushell` self-symlink targets updated to `/home/blake`.
### `README.md` ### `README.md` — DONE
- [ ] Update "Supported Distributions" / feature matrix to reflect **Debian 13 = first-class**, Fedora optional. - [x] Debian 13 first-class; feature lists, modular architecture, post-install steps, and supported-distro matrix updated (Podman, Flatpak VS Code, libvirt/kvm groups, rootless podman socket).
- [ ] Rename "Fedora-Specific (.NET)" section → ".NET Development" with per-distro notes.
- [ ] Update post-install steps (group names `docker`, `libvirt`; remove Fedora-only log-out note if not applicable; add PEP 668 note). ### Tooling / quality — DONE (this pass)
- [ ] Update file listing for `03-*.sh` renames. - [x] **`scripts/verify-debian.sh`** added — non-destructive end-to-end sanity checker (OS, binaries, Debian renames, upstream tools, dev tools, .NET/Podman/Postgres, groups, flatpak, dotfile symlinks); exit code 0/1.
- [x] **ShellCheck** (v0.11.0) run on all scripts — clean at warning severity (remaining notes are pre-existing info/style only).
--- ---
## 4. Open decisions (need your input) ## 4. Decisions (confirmed)
1. **Container runtime:** Podman (`podman-docker` provides the `docker` shim so existing aliases keep working).
2. **VS Code:** Flatpak `com.visualstudio.code` (no MS editor repo).
3. **.NET:** official `dotnet-install.sh` (no MS vendor repo).
4. **oh-my-zsh:** purged from `zshrc`.
5. **Scope:** full rework (applied).
1. **Container runtime on Debian:** `docker.io` (Debian pkg) vs official **Docker CE** repo vs **Podman** (since Fedora used Podman Desktop + `moby-engine`)? This repo mixes docker aliases and Podman Desktop flatpak. ## 5. Remaining work / next steps
2. **VS Code source:** keep **Flatpak** `com.visualstudio.code` (already in list) or switch to MS apt repo `.deb` like Fedora's RPM setup did? - [ ] **Test end-to-end on a real Debian 13 box**: run `./main-setup.sh`, then `bash scripts/verify-debian.sh`, and fix anything the checker flags.
3. **.NET SDK version to target:** repo tries 8.0 → 9.0 on Fedora; pick a default for Debian (e.g. SDK 10 on trixie) or keep the fallback loop. - [ ] Decide whether to keep Fedora/dnf support long-term or trim it later (kept for now).
4. **oh-my-zsh remnants in `zshrc`:** purge (recommended) or keep? - [ ] Merge `debian-13``master` after validation.
5. **Scope of this branch:** full rework of all scripts, or a lighter pass (fix names + PEP 668 + .NET script) to validate on a live trixie box first?
---
## 5. Suggested execution order
1. Do the cheap, high-confidence fixes:
- `main-setup.sh` gating + `.NET` prompt
- `01-package-install.sh` Debian package renames (`bind9-dnsutils`, `bat` symlink, prerequisites, virt stack)
- `02-dev-tools-setup.sh` PEP 668 pynvim fix
2. Rename/generalize `03-fedora-dotnet-setup.sh``03-dotnet-setup.sh`.
3. Add upstream repo/bootstrap logic for `gh`, `terraform`, `kubectl`, `minikube`, `k9s`.
4. Purge oh-my-zsh remnants from `zshrc`.
5. Update `README.md`.
6. Test end-to-end on a Debian 13 VM (or real install) before merging to `master`.
--- ---

View File

@@ -54,7 +54,8 @@ prompt_yes_no() {
# Function to run a script if approved # Function to run a script if approved
run_script() { run_script() {
local script_path="$1" local script_path="$1"
local script_name="$(basename "$script_path")" local script_name
script_name="$(basename "$script_path")"
if ! bash "$script_path"; then if ! bash "$script_path"; then
echo "ERROR: $script_name failed." echo "ERROR: $script_name failed."

View File

@@ -1,7 +1,8 @@
#!/bin/bash #!/bin/bash
# 00-system-prep.sh # 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. # Relies on DISTRO and PACKAGE_MANAGER being set by the caller.
echo "--- Starting System Preparation ---" echo "--- Starting System Preparation ---"
@@ -11,6 +12,27 @@ if [ -z "$DISTRO" ] || [ -z "$PACKAGE_MANAGER" ]; then
exit 1 exit 1
fi 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 # Update system before installing packages
echo "Updating system packages..." echo "Updating system packages..."
if [ "$PACKAGE_MANAGER" == "dnf" ]; then if [ "$PACKAGE_MANAGER" == "dnf" ]; then
@@ -21,6 +43,66 @@ else
echo "WARNING: Unknown package manager '$PACKAGE_MANAGER'. Skipping system update." echo "WARNING: Unknown package manager '$PACKAGE_MANAGER'. Skipping system update."
fi 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 # Setup Flatpak
echo "Setting up Flathub repository..." echo "Setting up Flathub repository..."
if command -v flatpak &> /dev/null; then if command -v flatpak &> /dev/null; then

View File

@@ -76,7 +76,7 @@ fi
# Combine package lists # Combine package lists
PACKAGE_LIST=("${BASE_PACKAGE_LIST[@]}" "${DISTRO_SPECIFIC_PACKAGES[@]}") 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 helpers ---------------------------------------------------------
_apt_is_installed() { _apt_is_installed() {
@@ -210,10 +210,21 @@ _apt_add_hashicorp_repo() {
| sudo tee /etc/apt/sources.list.d/hashicorp.list >/dev/null | sudo tee /etc/apt/sources.list.d/hashicorp.list >/dev/null
} }
# Kubernetes apt repo. Bump K8S_MINOR to track the current stable minor. # Kubernetes apt repo. Auto-detects the latest stable minor from the
K8S_MINOR="v1.32" # 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() { _apt_add_kubernetes_repo() {
[ -f /etc/apt/sources.list.d/kubernetes.list ] && { echo "Kubernetes repo already configured."; return 0; } [ -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})..." echo "Adding Kubernetes apt repository (${K8S_MINOR})..."
curl -fsSL "https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/Release.key" \ 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 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