Some checks failed
Test / test (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
337 lines
11 KiB
Bash
Executable File
337 lines
11 KiB
Bash
Executable File
#!/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 "$@"
|
|
|