#!/bin/bash source ~/.bashrc # Improve Template VM 9000 with Recommended Enhancements # This script applies all recommended improvements to template 9000 set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # Load environment variables if [ -f "$PROJECT_ROOT/.env" ]; then set -a source <(grep -v '^#' "$PROJECT_ROOT/.env" | grep -v '^$' | sed 's/#.*$//' | grep '=') set +a fi # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_step() { echo "" echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}$1${NC}" echo -e "${BLUE}========================================${NC}" echo "" } PROXMOX_HOST="${PROXMOX_ML110_IP:-192.168.1.206}" SSH_KEY="${SSH_KEY:-$HOME/.ssh/id_ed25519_proxmox}" SSH_OPTS="-i $SSH_KEY -o StrictHostKeyChecking=no" TEMPLATE_VMID=9000 TEMP_VMID=9999 TEMP_VM_NAME="template-update-temp" VM_USER="${VM_USER:-ubuntu}" # Check if running on Proxmox host or remotely if command -v qm >/dev/null 2>&1; then RUN_LOCAL=true PROXMOX_CMD="" else RUN_LOCAL=false PROXMOX_CMD="ssh $SSH_OPTS root@$PROXMOX_HOST" fi run_proxmox_cmd() { if [ "$RUN_LOCAL" = true ]; then eval "$1" else ssh $SSH_OPTS "root@$PROXMOX_HOST" "$1" fi } wait_for_ssh() { local ip="$1" local max_attempts=30 local attempt=0 log_info "Waiting for SSH to be available on $ip..." while [ $attempt -lt $max_attempts ]; do if ssh $SSH_OPTS -o ConnectTimeout=5 "${VM_USER}@${ip}" "echo 'SSH ready'" &>/dev/null; then log_info "✓ SSH is ready" return 0 fi attempt=$((attempt + 1)) sleep 2 done log_error "SSH not available after $max_attempts attempts" return 1 } get_vm_ip() { local vmid="$1" local ip="" # Try to use helper library if available (when running on Proxmox host) if [ "$RUN_LOCAL" = true ] && [ -f "$PROJECT_ROOT/scripts/lib/proxmox_vm_helpers.sh" ]; then source "$PROJECT_ROOT/scripts/lib/proxmox_vm_helpers.sh" 2>/dev/null || true if command -v get_vm_ip_from_guest_agent &>/dev/null; then ip=$(get_vm_ip_from_guest_agent "$vmid" 2>/dev/null || echo "") if [[ -n "$ip" && "$ip" != "null" ]]; then echo "$ip" return 0 fi fi fi # Try to get IP from guest agent using jq directly (suppress errors) if run_proxmox_cmd "command -v jq >/dev/null 2>&1" 2>/dev/null; then ip=$(run_proxmox_cmd "qm guest cmd $vmid network-get-interfaces 2>/dev/null | jq -r '.[]?.\"ip-addresses\"[]? | select(.[\"ip-address-type\"] == \"ipv4\" and .\"ip-address\" != \"127.0.0.1\") | .\"ip-address\"' | head -n1" 2>/dev/null || echo "") if [[ -n "$ip" && "$ip" != "null" && "$ip" != "" ]]; then echo "$ip" return 0 fi fi # Try MAC-based discovery: get VM MAC and match with ARP table local mac mac=$(run_proxmox_cmd "qm config $vmid 2>/dev/null | grep -E '^net0:' | cut -d',' -f1 | cut -d'=' -f2 | tr '[:upper:]' '[:lower:]' | tr -d ':'" 2>/dev/null || echo "") if [[ -n "$mac" ]]; then # Format MAC for matching (with colons) local mac_formatted="${mac:0:2}:${mac:2:2}:${mac:4:2}:${mac:6:2}:${mac:8:2}:${mac:10:2}" # Try to find IP in ARP table ip=$(run_proxmox_cmd "ip neigh show 2>/dev/null | grep -i '$mac_formatted' | grep -oE '192\.168\.1\.[0-9]+' | head -n1" 2>/dev/null || echo "") if [[ -n "$ip" ]]; then echo "$ip" return 0 fi fi # Return empty string (not a warning message) echo "" return 1 } main() { log_step "Template 9000 Improvement Script" log_warn "This script will:" log_warn " 1. Clone template 9000 to temporary VM 9999" log_warn " 2. Boot the temporary VM" log_warn " 3. Apply all recommended improvements" log_warn " 4. Convert back to template" log_warn " 5. Replace original template 9000" echo "" # Check if template exists if ! run_proxmox_cmd "qm config $TEMPLATE_VMID &>/dev/null"; then log_error "Template VM $TEMPLATE_VMID not found" exit 1 fi # Check if temp VM already exists if run_proxmox_cmd "qm config $TEMP_VMID &>/dev/null" 2>/dev/null; then log_warn "Temporary VM $TEMP_VMID already exists. Destroying it..." run_proxmox_cmd "qm stop $TEMP_VMID" 2>/dev/null || true sleep 2 run_proxmox_cmd "qm destroy $TEMP_VMID --purge" 2>/dev/null || true sleep 2 fi # Step 1: Clone template log_step "Step 1: Cloning Template to Temporary VM" log_info "Cloning template $TEMPLATE_VMID to VM $TEMP_VMID..." run_proxmox_cmd "qm clone $TEMPLATE_VMID $TEMP_VMID --name $TEMP_VM_NAME" log_info "✓ Template cloned" # Step 2: Boot temporary VM log_step "Step 2: Booting Temporary VM" log_info "Starting VM $TEMP_VMID..." run_proxmox_cmd "qm start $TEMP_VMID" log_info "Waiting for VM to boot and get DHCP IP (this may take 60-90 seconds)..." sleep 60 # Step 3: Get IP and wait for SSH log_step "Step 3: Getting VM IP and Waiting for SSH" local vm_ip="" # Try multiple times to get IP (VM may still be booting) log_info "Attempting to discover VM IP (may take a few attempts)..." for attempt in {1..10}; do vm_ip=$(get_vm_ip "$TEMP_VMID" 2>/dev/null || echo "") if [[ -n "$vm_ip" ]]; then log_info "✓ Discovered IP: $vm_ip" break fi if [ $attempt -lt 10 ]; then log_info "Attempt $attempt/10: Waiting for VM to finish booting..." sleep 10 fi done # If still no IP, try to get from Proxmox API or prompt user if [[ -z "$vm_ip" ]]; then log_warn "Could not automatically discover IP via guest agent." log_info "Please check Proxmox web UI or router DHCP leases for VM $TEMP_VMID IP address." log_info "You can also check with: ssh root@$PROXMOX_HOST 'qm config $TEMP_VMID'" echo "" read -p "Enter the VM IP address (or press Enter to skip and try again later): " vm_ip if [[ -z "$vm_ip" ]]; then log_error "IP address required. Exiting." log_info "VM $TEMP_VMID is running. You can manually:" log_info " 1. Get the IP from Proxmox UI or router" log_info " 2. SSH into the VM and apply improvements manually" log_info " 3. Run this script again with the IP" exit 1 fi fi wait_for_ssh "$vm_ip" || { log_error "Failed to connect to VM. Please check:" log_error " 1. VM is booted: qm status $TEMP_VMID" log_error " 2. IP address is correct: $vm_ip" log_error " 3. SSH key is correct: $SSH_KEY" exit 1 } # Step 4: Apply improvements log_step "Step 4: Applying Template Improvements" log_info "Installing essential packages and QEMU Guest Agent..." ssh $SSH_OPTS "${VM_USER}@${vm_ip}" <<'EOF' set -e sudo apt-get update -qq sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq # Install essential packages sudo apt-get install -y \ jq \ curl \ wget \ git \ vim \ nano \ net-tools \ htop \ unattended-upgrades \ apt-transport-https \ ca-certificates \ qemu-guest-agent \ ufw # Enable and start QEMU Guest Agent sudo systemctl enable qemu-guest-agent sudo systemctl start qemu-guest-agent # Configure automatic security updates echo 'Unattended-Upgrade::Automatic-Reboot "false";' | sudo tee -a /etc/apt/apt.conf.d/50unattended-upgrades > /dev/null echo 'Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";' | sudo tee -a /etc/apt/apt.conf.d/50unattended-upgrades > /dev/null # Set timezone sudo timedatectl set-timezone UTC # Configure locale sudo locale-gen en_US.UTF-8 sudo update-locale LANG=en_US.UTF-8 # SSH hardening (disable root login, password auth) sudo sed -i 's/#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config sudo sed -i 's/#PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config sudo sed -i 's/#PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config sudo systemctl restart sshd # Install UFW (firewall) but don't enable it - let VMs configure as needed # UFW is installed but not enabled, so VMs can configure firewall rules per their needs # Clean up disk sudo apt-get autoremove -y -qq sudo apt-get autoclean -qq sudo rm -rf /tmp/* sudo rm -rf /var/tmp/* sudo truncate -s 0 /var/log/*.log 2>/dev/null || true sudo journalctl --vacuum-time=1d --quiet # Create template version file echo "template-9000-v1.1.0-$(date +%Y%m%d)" | sudo tee /etc/template-version > /dev/null echo "✓ All improvements applied" EOF if [ $? -ne 0 ]; then log_error "Failed to apply improvements" exit 1 fi log_info "✓ All improvements applied successfully" # Step 5: Stop VM and convert to template log_step "Step 5: Converting Back to Template" log_info "Stopping VM $TEMP_VMID..." run_proxmox_cmd "qm stop $TEMP_VMID" sleep 5 log_info "Converting VM $TEMP_VMID to template..." run_proxmox_cmd "qm template $TEMP_VMID" log_info "✓ VM converted to template" # Step 6: Replace original template log_step "Step 6: Replacing Original Template" log_warn "This will destroy the original template 9000 and replace it with the improved version" echo "" if [ -t 0 ]; then read -p "Continue? (yes/no): " confirm if [ "$confirm" != "yes" ]; then log_info "Cancelled. Improved template is available as VM $TEMP_VMID" log_info "You can manually:" log_info " 1. Destroy template 9000: qm destroy 9000" log_info " 2. Change VMID: qm set $TEMP_VMID --vmid 9000" exit 0 fi else log_info "Non-interactive mode: auto-confirming" fi log_info "Destroying original template 9000..." run_proxmox_cmd "qm destroy $TEMPLATE_VMID --purge" 2>/dev/null || true sleep 2 log_info "Changing VMID from $TEMP_VMID to $TEMPLATE_VMID..." run_proxmox_cmd "qm set $TEMP_VMID --vmid $TEMPLATE_VMID" log_step "Template Improvement Complete!" log_info "✓ Template 9000 has been improved with:" log_info " - QEMU Guest Agent pre-installed and enabled" log_info " - Essential utilities (jq, curl, wget, git, vim, nano, htop, net-tools, etc.)" log_info " - Automatic security updates configured (unattended-upgrades)" log_info " - Timezone set to UTC" log_info " - Locale configured (en_US.UTF-8)" log_info " - SSH hardened (no root login, no password auth, pubkey only)" log_info " - UFW firewall installed (not enabled - VMs configure as needed)" log_info " - Disk optimized and cleaned" log_info " - Template version tracking (/etc/template-version)" log_info "" log_info "You can now clone VMs from template 9000 and they will have all these improvements!" } main "$@"