#!/bin/bash # ============================================================================== # UNIFIED HOMELAB BOOTSTRAP SCRIPT # ============================================================================== # Intelligent day0 onboarding for all homelab hardware types # Replaces: day0bootstrap.sh, pi_init.sh, onboarding.sh # # Usage: # ./bootstrap.sh [OPTIONS] # # Options: # --help Show this help message # --dry-run Show what would be done without making changes # --hardware-type TYPE Override auto-detection (proxmox|docker-vm|pi|physical-docker|ai-workstation) # --skip-network Skip network configuration # --skip-validation Skip post-bootstrap validation # --output-json Generate JSON output instead of YAML # --target-ip IP Target static IP address (default: auto-assigned) # # Examples: # ./bootstrap.sh # Auto-detect and configure # ./bootstrap.sh --dry-run # Preview actions # ./bootstrap.sh --hardware-type proxmox # Force Proxmox mode # ./bootstrap.sh --target-ip 10.0.0.205 # Custom IP address # # ============================================================================== set -euo pipefail # --- SCRIPT METADATA --- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" VERSION="4.0.0" TIMESTAMP=$(date +%Y%m%dT%H%M%S) # --- LOAD LIBRARIES --- # shellcheck source=./lib/detection.sh source "${SCRIPT_DIR}/lib/detection.sh" # shellcheck source=./lib/network.sh source "${SCRIPT_DIR}/lib/network.sh" # shellcheck source=./lib/validation.sh source "${SCRIPT_DIR}/lib/validation.sh" # shellcheck source=./lib/fingerprint.sh source "${SCRIPT_DIR}/lib/fingerprint.sh" # shellcheck source=./lib/proxmox.sh source "${SCRIPT_DIR}/lib/proxmox.sh" # --- COMMAND LINE ARGUMENTS --- SHOW_HELP=false DRY_RUN=false HARDWARE_TYPE="auto" SKIP_NETWORK=false SKIP_VALIDATION=false OUTPUT_JSON=false TARGET_IP="" GATEWAY="10.0.0.1" DNS_SERVER="10.0.0.2" parse_arguments() { while [[ $# -gt 0 ]]; do case "$1" in --help|-h) SHOW_HELP=true shift ;; --dry-run) DRY_RUN=true shift ;; --hardware-type) HARDWARE_TYPE="$2" shift 2 ;; --skip-network) SKIP_NETWORK=true shift ;; --skip-validation) SKIP_VALIDATION=true shift ;; --output-json) OUTPUT_JSON=true shift ;; --target-ip) TARGET_IP="$2" shift 2 ;; --gateway) GATEWAY="$2" shift 2 ;; --dns) DNS_SERVER="$2" shift 2 ;; *) echo "ERROR: Unknown option: $1" >&2 echo "Use --help for usage information" >&2 exit 1 ;; esac done } show_help() { cat <<'EOF' ============================================================================= UNIFIED HOMELAB BOOTSTRAP SCRIPT v4.0.0 ============================================================================= Intelligent day0 onboarding for all homelab hardware types. Auto-detects OS, hardware type, and applies appropriate configuration. USAGE: ./bootstrap.sh [OPTIONS] OPTIONS: --help Show this help message --dry-run Preview actions without making changes --hardware-type TYPE Override auto-detection Types: proxmox, docker-vm, pi, physical-docker, ai-workstation --skip-network Skip network configuration (use if already configured) --skip-validation Skip post-bootstrap validation checks --output-json Generate JSON output instead of YAML --target-ip IP Set static IP address (default: auto-assigned) --gateway IP Set gateway IP (default: 10.0.0.1) --dns IP Set DNS server (default: 10.0.0.2) EXAMPLES: ./bootstrap.sh Auto-detect hardware and configure with defaults ./bootstrap.sh --dry-run Preview what would be done without making changes ./bootstrap.sh --hardware-type proxmox --target-ip 10.0.0.201 Force Proxmox mode and set specific IP ./bootstrap.sh --skip-network Skip network configuration (already configured manually) WORKFLOW: 1. System Detection → Identify OS, hardware type, CPU, GPU 2. Network Config → Apply static IP via netplan (skippable) 3. Package Install → Docker, Ansible, proxmoxer (as needed) 4. SSH Keys → Generate/verify ED25519 keys 5. Validation → Comprehensive health checks 6. Fingerprinting → Generate hardware inventory YAML OUTPUT FILES: ansible/archive/outputs/bootstrap-validation-{hostname}-{timestamp}.log ansible/archive/outputs/hardware-facts-{hostname}-{timestamp}.yml ansible/archive/inventory/discovered-hosts.yml (auto-discovery) NOTES: - Network configuration will disconnect SSH (reconnect to new IP) - Run from console or plan for reconnection - Safe to re-run (idempotent where possible) - Logs saved even if script interrupted ============================================================================= EOF } # --- MAIN WORKFLOW --- main() { # Parse CLI arguments parse_arguments "$@" if [ "$SHOW_HELP" == "true" ]; then show_help exit 0 fi # === PHASE 1: DETECTION === echo "=======================================" echo "HOMELAB BOOTSTRAP v${VERSION}" echo "Timestamp: $(date -u +"%Y-%m-%d %H:%M:%S UTC")" echo "=======================================" echo "" echo "[PHASE 1] System Detection" echo "---" # Detect hardware type if [ "$HARDWARE_TYPE" == "auto" ]; then HARDWARE_TYPE=$(detect_hardware_type) echo " Auto-detected hardware type: $HARDWARE_TYPE" else echo " Hardware type (forced): $HARDWARE_TYPE" fi # Print detection summary print_detection_summary echo "" # Determine target IP if not specified if [ -z "$TARGET_IP" ]; then TARGET_IP=$(get_desired_vlan_ip) echo " Auto-assigned target IP: $TARGET_IP" echo " (Based on hardware type and environment-constraints.md)" else echo " Target IP (user-specified): $TARGET_IP" fi echo "" # Dry-run check if [ "$DRY_RUN" == "true" ]; then echo "[DRY-RUN MODE] Would perform the following actions:" echo "" echo " 1. Configure network: $TARGET_IP (gateway: $GATEWAY, DNS: $DNS_SERVER)" echo " 2. Install Docker (with Debian Trixie workaround if needed)" echo " 3. Install Ansible" [ "$HARDWARE_TYPE" == "proxmox" ] && echo " 4. Apply Proxmox repository fixes" [ "$HARDWARE_TYPE" == "proxmox" ] && echo " 5. Install proxmoxer Python library" echo " 6. Generate/verify SSH keys (ED25519)" echo " 7. Run validation suite" echo " 8. Generate hardware fingerprint" echo "" echo "No changes made. Re-run without --dry-run to execute." exit 0 fi # === PHASE 2: NETWORK CONFIGURATION === if [ "$SKIP_NETWORK" == "false" ]; then echo "[PHASE 2] Network Configuration" echo "---" configure_network_safe "$TARGET_IP" "$GATEWAY" "$DNS_SERVER" # Wait for network to stabilize sleep 3 wait_for_network 15 echo "" else echo "[PHASE 2] Network Configuration (SKIPPED)" echo "" fi # === PHASE 3: PACKAGE INSTALLATION === echo "[PHASE 3] Package Installation" echo "---" # Update package lists echo " [⚙] Updating package lists..." sudo apt-get update -qq # Install prerequisites echo " [⚙] Installing prerequisites (ca-certificates, curl, gnupg)..." sudo apt-get install -y -qq ca-certificates curl gnupg lsb-release # --- DOCKER INSTALLATION --- if ! command -v docker &>/dev/null; then echo " [⚙] Installing Docker..." # Remove existing Docker repo configs sudo rm -f /etc/apt/sources.list.d/docker.list sudo rm -f /etc/apt/sources.list.d/docker*.list # Add Docker GPG key sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg --yes # Determine repository codename local os_version=$(detect_os_version) local repo_codename="$os_version" # Debian Trixie workaround (use Bookworm repos) if is_debian_trixie; then echo " [!] Debian Trixie detected - using Bookworm repos for Docker" repo_codename="bookworm" fi # Add Docker repository echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $repo_codename stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker sudo apt-get update -qq sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # Add current user to docker group sudo usermod -aG docker "$USER" echo " [✓] Docker installed: $(docker --version)" else echo " [✓] Docker already installed: $(docker --version)" fi # --- ANSIBLE INSTALLATION --- if ! command -v ansible &>/dev/null; then echo " [⚙] Installing Ansible..." sudo apt-get install -y ansible echo " [✓] Ansible installed: $(ansible --version | head -n1)" else echo " [✓] Ansible already installed: $(ansible --version | head -n1)" fi # --- PROXMOX-SPECIFIC PACKAGES --- if [ "$HARDWARE_TYPE" == "proxmox" ]; then echo " [⚙] Installing Proxmox-specific packages..." # Install Python pip if needed if ! command -v pip3 &>/dev/null; then sudo apt-get install -y python3-pip fi # Install proxmoxer if ! python3 -c "import proxmoxer" 2>/dev/null; then echo " [⚙] Installing proxmoxer Python library..." pip3 install proxmoxer --break-system-packages 2>/dev/null || pip3 install proxmoxer echo " [✓] proxmoxer installed" else echo " [✓] proxmoxer already installed" fiInstall jq for Proxmox post-install tasks if ! command -v jq &>/dev/null; then sudo apt-get install -y jq fi echo "" echo "=== PROXMOX POST-INSTALL CONFIGURATION ===" # Run comprehensive Proxmox post-install routine # This includes: repository fixes, subscription nag removal, HA management run_proxmox_post_install "auto" echo "==========================================" echo "" echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" | \ sudo tee /etc/apt/sources.list.d/pve-no-subscription.list > /dev/null fi fi # --- UTILITY PACKAGES --- echo " [⚙] Installing utility packages..." sudo apt-get install -y -qq git vim htop curl wget nfs-common net-tools dnsutils echo " [✓] Package installation complete" echo "" # === PHASE 4: SSH KEY MANAGEMENT === echo "[PHASE 4] SSH Key Management" echo "---" local ssh_key_path="" # Check for existing keys (prefer ED25519) if [ -f "$HOME/.ssh/id_ed25519" ]; then ssh_key_path="$HOME/.ssh/id_ed25519" echo " [✓] Found existing ED25519 key: $ssh_key_path" elif [ -f "$HOME/.ssh/id_rsa" ]; then ssh_key_path="$HOME/.ssh/id_rsa" echo " [✓] Found existing RSA key: $ssh_key_path" else # Generate new ED25519 key ssh_key_path="$HOME/.ssh/id_ed25519" echo " [⚙] Generating new ED25519 key pair..." ssh-keygen -t ed25519 -f "$ssh_key_path" -N "" -C "$(whoami)@$(hostname)-$(date +%Y%m%d)" echo " [✓] SSH key generated: $ssh_key_path" fi # Display public key echo "" echo " Public key (add to authorized_keys on managed nodes):" echo " ---" cat "${ssh_key_path}.pub" echo " ---" echo "" # === PHASE 5: VALIDATION === if [ "$SKIP_VALIDATION" == "false" ]; then echo "[PHASE 5] System Validation" echo "---" # Run comprehensive validation if run_validation_suite; then echo "" echo " [✓] All validation checks passed" else echo "" echo " [!] Validation completed with errors - review above" echo " [!] You may proceed, but manual intervention may be required" fi echo "" # Save validation log local log_dir="${SCRIPT_DIR}/../ansible/archive/outputs" mkdir -p "$log_dir" local log_file="${log_dir}/bootstrap-validation-$(hostname)-${TIMESTAMP}.log" # Re-run validation to capture log run_validation_suite 2>&1 | tee "$log_file" >/dev/null echo " [✓] Validation log saved: $log_file" echo "" else echo "[PHASE 5] System Validation (SKIPPED)" echo "" fi # === PHASE 6: HARDWARE FINGERPRINTING === echo "[PHASE 6] Hardware Fingerprinting" echo "---" # Print summary print_hardware_summary echo "" # Validate against standards echo " Checking against environment-constraints.md standards..." validate_against_standards || true echo "" # Save hardware facts local facts_dir="${SCRIPT_DIR}/../ansible/archive/outputs" local facts_file=$(save_hardware_facts "$facts_dir") echo " [✓] Hardware facts saved: $facts_file" # Save JSON if requested if [ "$OUTPUT_JSON" == "true" ]; then local json_file="${facts_dir}/hardware-facts-$(hostname)-${TIMESTAMP}.json" generate_json_output > "$json_file" echo " [✓] JSON output saved: $json_file" fi # Generate inventory snippet local inventory_dir="${SCRIPT_DIR}/../ansible/archive/inventory" mkdir -p "$inventory_dir" local inventory_file=$(append_to_discovered_inventory "${inventory_dir}/discovered-hosts.yml") echo " [✓] Inventory snippet appended: $inventory_file" echo "" # === COMPLETION === echo "=======================================" echo "BOOTSTRAP COMPLETE" echo "=======================================" echo "" echo "Summary:" echo " Hostname: $(hostname)" echo " IP Address: $(get_current_ip)" echo " Hardware Type: $HARDWARE_TYPE" echo " OS: $(detect_os_family) $(detect_os_version)" echo "" echo "Next Steps:" echo " 1. Reconnect SSH if network was reconfigured: ssh $(whoami)@$(get_current_ip)" echo " 2. Verify Docker: docker ps" echo " 3. Verify Ansible: ansible --version" echo " 4. Review hardware facts: cat $facts_file" echo " 5. Run Ansible playbook: ansible-playbook playbooks/onboarding/generic_host.yml" echo "" echo "Files Generated:" echo " - $facts_file" [ "$OUTPUT_JSON" == "true" ] && echo " - ${facts_dir}/hardware-facts-$(hostname)-${TIMESTAMP}.json" [ "$SKIP_VALIDATION" == "false" ] && echo " - ${log_dir}/bootstrap-validation-$(hostname)-${TIMESTAMP}.log" echo " - $inventory_file" echo "" echo "Documentation:" echo " - SOP: documentation/SOPs/SOP-002-Initial-Infrastructure-Deployment.md" echo " - Technical Runbook: documentation/TECHNICAL_RUNBOOK.md" echo "" echo "Have a great day! 🚀" echo "" } # --- ENTRY POINT --- # Trap errors trap 'echo "ERROR: Bootstrap failed at line $LINENO. Check logs for details." >&2' ERR # Run main workflow main "$@"