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
363 lines
9.1 KiB
Bash
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
|