homelab/scripts/lib/detection.sh
nathan e16f98a183 feat(bootstrap)!: introduce unified bootstrap system with modular libraries
BREAKING CHANGE: day0bootstrap.sh deprecated in favor of bootstrap.sh

- Add scripts/bootstrap.sh (488 lines): Unified entrypoint supporting multiple hardware types (Proxmox/Docker VMs/Pi)
- Create scripts/lib/ modular library system:
  - detection.sh: OS/hardware/container detection (362 lines)
  - fingerprint.sh: System fingerprinting and inventory (494 lines)
  - network.sh: IP configuration and VLAN placement (356 lines)
  - proxmox.sh: PVE post-install automation (453 lines)
  - validation.sh: Comprehensive pre-flight checks (510 lines)
- Add validation tools: validate-node.sh, onboarding.sh, pi_init.sh
- Deprecate scripts/day0bootstrap.sh with graceful redirect wrapper
- Document architecture in scripts/README.md (495 lines) and PROXMOX-COMPARISON.md
- Update SOP-002 with new bootstrap workflow
- Add nodes/watchtower/compose.yaml (Raspberry Pi 5 stack)

Migration: Existing day0bootstrap.sh users automatically redirected to new system after 5-second warning. No manual intervention required.

Ref: Infrastructure automation modernization per active-tasks.md
2026-04-12 22:48:19 -04:00

363 lines
9.1 KiB
Bash

#!/bin/bash
# ==============================================================================
# DETECTION LIBRARY: OS, Hardware, and Environment Detection
# ==============================================================================
# Part of unified bootstrap system for homelab infrastructure
# Provides functions to identify OS family, hardware type, CPU, GPU, and
# deployment environment for intelligent provisioning decisions.
# ==============================================================================
# --- OS DETECTION ---
detect_os_family() {
# Detect OS family: debian, ubuntu, raspbian, or unknown
# Returns lowercase OS identifier
if [ -f /etc/os-release ]; then
. /etc/os-release
case "${ID,,}" in
debian)
echo "debian"
return 0
;;
ubuntu)
echo "ubuntu"
return 0
;;
raspbian)
echo "raspbian"
return 0
;;
*)
echo "unknown"
return 1
;;
esac
else
echo "unknown"
return 1
fi
}
detect_os_version() {
# Get OS version codename (e.g., trixie, bookworm, noble)
if [ -f /etc/os-release ]; then
. /etc/os-release
echo "${VERSION_CODENAME:-unknown}"
else
echo "unknown"
fi
}
is_debian_trixie() {
# Special detection for Debian Trixie (requires Docker repo workaround)
local os_family=$(detect_os_family)
local os_version=$(detect_os_version)
[[ "$os_family" == "debian" && "$os_version" == "trixie" ]]
}
# --- HARDWARE TYPE DETECTION ---
detect_hardware_type() {
# Auto-detect hardware deployment type
# Returns: proxmox, docker-vm, pi, physical-docker, ai-workstation, unknown
# Check for Proxmox VE installation
if command -v pveversion &>/dev/null; then
echo "proxmox"
return 0
fi
# Check if running inside a VM (common indicators)
local is_vm=0
if systemd-detect-virt --vm &>/dev/null; then
is_vm=1
elif [ -d /sys/class/dmi/id ]; then
local product_name=$(cat /sys/class/dmi/id/product_name 2>/dev/null || echo "")
if [[ "$product_name" =~ (VirtualBox|VMware|KVM|QEMU) ]]; then
is_vm=1
fi
fi
# Check for Raspberry Pi
if grep -q "Raspberry Pi" /proc/cpuinfo 2>/dev/null; then
echo "pi"
return 0
fi
# Check for Docker installation (distinguishes VM vs physical)
local has_docker=0
if command -v docker &>/dev/null || [ -f /usr/bin/docker ]; then
has_docker=1
fi
# Check for high-end GPU (indicates AI workstation)
local gpu_type=$(detect_gpu)
if [[ "$gpu_type" =~ (nvidia|amd) ]]; then
local gpu_info=$(lspci 2>/dev/null | grep -i vga | head -n1)
# High-end NVIDIA cards (RTX, Tesla, Quadro) or AMD (Radeon Pro, Instinct)
if [[ "$gpu_info" =~ (RTX|Tesla|Quadro|A[0-9]{3,4}|Radeon Pro|Instinct) ]]; then
echo "ai-workstation"
return 0
fi
fi
# Classify based on VM + Docker
if [ $is_vm -eq 1 ]; then
echo "docker-vm"
return 0
else
echo "physical-docker"
return 0
fi
}
# --- CPU DETECTION ---
detect_cpu_vendor() {
# Detect CPU vendor: intel, amd, arm, or unknown
if [ -f /proc/cpuinfo ]; then
if grep -qi "intel" /proc/cpuinfo; then
echo "intel"
return 0
elif grep -qi "amd" /proc/cpuinfo; then
echo "amd"
return 0
elif grep -qi "arm\|aarch64" /proc/cpuinfo; then
echo "arm"
return 0
fi
fi
echo "unknown"
return 1
}
detect_cpu_generation() {
# Detect Intel CPU generation for kernel parameter tuning
# Returns generation number (e.g., 12, 13, 14) or "unknown"
local vendor=$(detect_cpu_vendor)
if [ "$vendor" != "intel" ]; then
echo "unknown"
return 1
fi
local model_name=$(grep "model name" /proc/cpuinfo | head -n1 | cut -d: -f2 | xargs)
# Extract generation from model name patterns
# Example: "12th Gen Intel(R) Core(TM) i7-12700"
if [[ "$model_name" =~ ([0-9]{2})th\ Gen ]]; then
echo "${BASH_REMATCH[1]}"
return 0
elif [[ "$model_name" =~ i[3579]-([0-9]{2})[0-9]{2,3} ]]; then
echo "${BASH_REMATCH[1]}"
return 0
fi
echo "unknown"
return 1
}
needs_intel_hybrid_core_tuning() {
# 12th Gen+ Intel CPUs with hybrid architecture need special kernel params
# Returns 0 (true) if tuning required, 1 (false) otherwise
local gen=$(detect_cpu_generation)
# 12th Gen (Alder Lake) and newer have P-cores + E-cores
if [[ "$gen" =~ ^[0-9]+$ ]] && [ "$gen" -ge 12 ]; then
return 0
fi
return 1
}
# --- GPU DETECTION ---
detect_gpu() {
# Detect GPU vendor: nvidia, amd, intel, or none
if ! command -v lspci &>/dev/null; then
echo "none"
return 1
fi
local gpu_info=$(lspci 2>/dev/null | grep -i vga)
if echo "$gpu_info" | grep -qi nvidia; then
echo "nvidia"
return 0
elif echo "$gpu_info" | grep -qi amd; then
echo "amd"
return 0
elif echo "$gpu_info" | grep -qi intel; then
echo "intel"
return 0
fi
echo "none"
return 1
}
get_gpu_model() {
# Get detailed GPU model information
if ! command -v lspci &>/dev/null; then
echo "unknown"
return 1
fi
local gpu_line=$(lspci 2>/dev/null | grep -i "vga\|3d\|display" | head -n1)
if [ -n "$gpu_line" ]; then
# Extract model after the vendor info
echo "$gpu_line" | cut -d: -f3 | xargs
else
echo "none"
fi
}
# --- NETWORK INTERFACE DETECTION ---
detect_primary_interface() {
# Find the primary physical network interface (excludes lo, docker, veth)
# Try to find interface with default route
local iface=$(ip route show default 2>/dev/null | awk '/^default/ {print $5; exit}')
if [ -n "$iface" ]; then
echo "$iface"
return 0
fi
# Fallback: first non-loopback physical interface
iface=$(ip -o link show | awk -F': ' '$2 != "lo" && $2 !~ /^(docker|veth|br-)/ {print $2; exit}')
if [ -n "$iface" ]; then
echo "$iface"
return 0
fi
echo "unknown"
return 1
}
get_current_ip() {
# Get current IPv4 address of primary interface
local iface=$(detect_primary_interface)
if [ "$iface" == "unknown" ]; then
echo "unknown"
return 1
fi
local ip=$(ip -4 addr show "$iface" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -n1)
if [ -n "$ip" ]; then
echo "$ip"
else
echo "unknown"
return 1
fi
}
# --- ENVIRONMENT DETECTION ---
is_running_in_container() {
# Check if running inside a container (Docker, LXC, etc.)
if [ -f /.dockerenv ]; then
return 0
fi
if grep -q "lxc\|docker" /proc/1/cgroup 2>/dev/null; then
return 0
fi
return 1
}
detect_init_system() {
# Detect init system: systemd, sysvinit, or unknown
if [ -d /run/systemd/system ]; then
echo "systemd"
return 0
elif [ -f /sbin/init ]; then
local init_path=$(readlink -f /sbin/init)
if [[ "$init_path" =~ systemd ]]; then
echo "systemd"
return 0
fi
fi
echo "unknown"
return 1
}
# --- PACKAGE MANAGER DETECTION ---
detect_package_manager() {
# Detect primary package manager: apt, dnf, yum, or unknown
if command -v apt-get &>/dev/null; then
echo "apt"
return 0
elif command -v dnf &>/dev/null; then
echo "dnf"
return 0
elif command -v yum &>/dev/null; then
echo "yum"
return 0
fi
echo "unknown"
return 1
}
# --- SUMMARY FUNCTION ---
print_detection_summary() {
# Print comprehensive detection summary to stderr for logging
cat >&2 <<EOF
=== System Detection Summary ===
OS Family: $(detect_os_family)
OS Version: $(detect_os_version)
Hardware Type: $(detect_hardware_type)
CPU Vendor: $(detect_cpu_vendor)
CPU Generation: $(detect_cpu_generation)
GPU: $(detect_gpu) ($(get_gpu_model))
Primary NIC: $(detect_primary_interface)
Current IP: $(get_current_ip)
Init System: $(detect_init_system)
Package Manager: $(detect_package_manager)
In Container: $(is_running_in_container && echo "yes" || echo "no")
================================
EOF
}
# Export functions for use in other scripts
export -f detect_os_family
export -f detect_os_version
export -f is_debian_trixie
export -f detect_hardware_type
export -f detect_cpu_vendor
export -f detect_cpu_generation
export -f needs_intel_hybrid_core_tuning
export -f detect_gpu
export -f get_gpu_model
export -f detect_primary_interface
export -f get_current_ip
export -f is_running_in_container
export -f detect_init_system
export -f detect_package_manager
export -f print_detection_summary