Initial commit: loc_az_hci (smom-dbis-138 excluded via .gitignore)
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
272
scripts/vm-management/create/create-all-vms.sh
Executable file
272
scripts/vm-management/create/create-all-vms.sh
Executable file
@@ -0,0 +1,272 @@
|
||||
#!/bin/bash
|
||||
source ~/.bashrc
|
||||
# Create All Service VMs via Proxmox API
|
||||
# Attempts to create VMs using available templates or provides detailed instructions
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
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 -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source <(grep -v '^#' .env | grep -v '^$' | sed 's/#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep '=')
|
||||
set +a
|
||||
else
|
||||
log_error ".env file not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PVE_USERNAME="${PVE_USERNAME:-root@pam}"
|
||||
PVE_PASSWORD="${PVE_ROOT_PASS:-}"
|
||||
PROXMOX_HOST="${1:-192.168.1.206}"
|
||||
PROXMOX_URL="https://${PROXMOX_HOST}:8006"
|
||||
PROXMOX_NODE="${2:-pve}"
|
||||
|
||||
# Get authentication ticket
|
||||
get_ticket() {
|
||||
local response=$(curl -k -s -d "username=$PVE_USERNAME&password=$PVE_PASSWORD" \
|
||||
"$PROXMOX_URL/api2/json/access/ticket")
|
||||
|
||||
local ticket=$(echo "$response" | grep -o '"ticket":"[^"]*' | cut -d'"' -f4)
|
||||
local csrf=$(echo "$response" | grep -o '"CSRFPreventionToken":"[^"]*' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$ticket" ] || [ -z "$csrf" ]; then
|
||||
log_error "Failed to authenticate with Proxmox"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$ticket|$csrf"
|
||||
}
|
||||
|
||||
# List available templates
|
||||
list_templates() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
local response=$(curl -k -s -H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu")
|
||||
|
||||
echo "$response" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
templates = [v for v in data.get('data', []) if v.get('template') == 1]
|
||||
if templates:
|
||||
print('Available Templates:')
|
||||
for t in templates:
|
||||
print(f\" - {t.get('name', 'unknown')} (ID: {t.get('vmid', 'N/A')})\")
|
||||
else:
|
||||
print('No templates found')
|
||||
" 2>/dev/null || echo "Could not retrieve templates"
|
||||
}
|
||||
|
||||
# List available ISOs
|
||||
list_isos() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
local response=$(curl -k -s -H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/storage/local/content")
|
||||
|
||||
echo "$response" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
isos = [f for f in data.get('data', []) if f.get('content') == 'iso']
|
||||
if isos:
|
||||
print('Available ISO Images:')
|
||||
for iso in isos[:10]:
|
||||
print(f\" - {iso.get('volid', 'unknown')}\")
|
||||
else:
|
||||
print('No ISO images found')
|
||||
" 2>/dev/null || echo "Could not retrieve ISO images"
|
||||
}
|
||||
|
||||
# Check if VM exists
|
||||
vm_exists() {
|
||||
local auth=$1
|
||||
local vmid=$2
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
local response=$(curl -k -s -H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/cluster/resources?type=vm")
|
||||
|
||||
echo "$response" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
vms = [v for v in data.get('data', []) if v.get('type') == 'qemu' and str(v.get('vmid')) == '$vmid']
|
||||
print('true' if vms else 'false')
|
||||
" 2>/dev/null || echo "false"
|
||||
}
|
||||
|
||||
# Create VM via API (requires template or ISO)
|
||||
create_vm_api() {
|
||||
local auth=$1
|
||||
local vmid=$2
|
||||
local name=$3
|
||||
local cores=$4
|
||||
local memory=$5
|
||||
local disk_size=$6
|
||||
local template_or_iso=$7
|
||||
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
log_info "Creating VM $name (ID: $vmid) via API..."
|
||||
|
||||
# Check if template exists
|
||||
if [ "$(vm_exists "$auth" "$template_or_iso")" = "true" ]; then
|
||||
# Clone from template
|
||||
log_info "Cloning from template $template_or_iso..."
|
||||
curl -k -X POST \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "newid=$vmid" \
|
||||
-d "name=$name" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$template_or_iso/clone" 2>/dev/null
|
||||
|
||||
# Update VM configuration
|
||||
curl -k -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "cores=$cores" \
|
||||
-d "memory=$memory" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$vmid/config" 2>/dev/null
|
||||
|
||||
log_info "✓ VM $name created from template"
|
||||
return 0
|
||||
else
|
||||
log_warn "Template/ISO $template_or_iso not found. Cannot create VM via API."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# VM configurations
|
||||
declare -A VMS=(
|
||||
["100"]="cloudflare-tunnel:2:4096:40"
|
||||
["101"]="k3s-master:4:8192:80"
|
||||
["102"]="git-server:4:8192:100"
|
||||
["103"]="observability:4:8192:200"
|
||||
)
|
||||
|
||||
main() {
|
||||
echo "========================================="
|
||||
echo "Create All Service VMs"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
if [ -z "$PVE_PASSWORD" ]; then
|
||||
log_error "PVE_ROOT_PASS not set in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Connecting to Proxmox: $PROXMOX_URL"
|
||||
|
||||
# Authenticate
|
||||
auth=$(get_ticket)
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Authentication successful"
|
||||
echo ""
|
||||
|
||||
# Check available resources
|
||||
log_step "Checking available resources..."
|
||||
list_templates "$auth"
|
||||
echo ""
|
||||
list_isos "$auth"
|
||||
echo ""
|
||||
|
||||
# Check existing VMs
|
||||
log_step "Checking existing VMs..."
|
||||
for vmid in "${!VMS[@]}"; do
|
||||
IFS=':' read -r name cores memory disk <<< "${VMS[$vmid]}"
|
||||
if [ "$(vm_exists "$auth" "$vmid")" = "true" ]; then
|
||||
log_warn "VM $name (ID: $vmid) already exists"
|
||||
else
|
||||
log_info "VM $name (ID: $vmid) - Ready to create"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
log_warn "VM creation via API requires templates or ISOs."
|
||||
log_info "Generating Proxmox Web UI creation guide..."
|
||||
echo ""
|
||||
|
||||
# Generate creation instructions
|
||||
cat > /tmp/vm-creation-instructions.txt <<EOF
|
||||
========================================
|
||||
VM Creation Instructions for Proxmox Web UI
|
||||
========================================
|
||||
|
||||
Access: $PROXMOX_URL
|
||||
Login: $PVE_USERNAME / (password from PVE_ROOT_PASS)
|
||||
|
||||
For each VM, follow these steps:
|
||||
1. Click "Create VM" (top right)
|
||||
2. Fill in the General tab
|
||||
3. Select OS (ISO image or template)
|
||||
4. Configure System, Hard Disk, CPU, Memory, Network
|
||||
5. Use Cloud-Init for network configuration (if available)
|
||||
|
||||
VM Configurations:
|
||||
EOF
|
||||
|
||||
for vmid in "${!VMS[@]}"; do
|
||||
IFS=':' read -r name cores memory disk <<< "${VMS[$vmid]}"
|
||||
case $vmid in
|
||||
100) ip="192.168.1.60" ;;
|
||||
101) ip="192.168.1.188" ;;
|
||||
102) ip="192.168.1.121" ;;
|
||||
103) ip="192.168.1.82" ;;
|
||||
esac
|
||||
|
||||
cat >> /tmp/vm-creation-instructions.txt <<EOF
|
||||
|
||||
VM: $name
|
||||
- VM ID: $vmid
|
||||
- IP Address: $ip
|
||||
- CPU: $cores cores
|
||||
- RAM: ${memory}MB
|
||||
- Disk: ${disk}GB
|
||||
- Network: vmbr0
|
||||
- Gateway: 192.168.1.254
|
||||
EOF
|
||||
done
|
||||
|
||||
cat /tmp/vm-creation-instructions.txt
|
||||
echo ""
|
||||
log_info "Full instructions saved to: /tmp/vm-creation-instructions.txt"
|
||||
log_info "Also see: CREATE_VMS.md"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
157
scripts/vm-management/create/create-first-vm.sh
Executable file
157
scripts/vm-management/create/create-first-vm.sh
Executable file
@@ -0,0 +1,157 @@
|
||||
#!/bin/bash
|
||||
source ~/.bashrc
|
||||
# Create First VM (Cloudflare Tunnel) via Proxmox API
|
||||
# This script helps create a VM using the Proxmox API
|
||||
|
||||
set -e
|
||||
|
||||
# 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"
|
||||
}
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source <(grep -v '^#' .env | grep -v '^$' | sed 's/#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep '=')
|
||||
set +a
|
||||
else
|
||||
log_error ".env file not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PVE_USERNAME="${PVE_USERNAME:-root@pam}"
|
||||
PVE_PASSWORD="${PVE_ROOT_PASS:-}"
|
||||
PROXMOX_HOST="${1:-192.168.1.206}"
|
||||
PROXMOX_URL="https://${PROXMOX_HOST}:8006"
|
||||
PROXMOX_NODE="${2:-pve}"
|
||||
|
||||
# Get authentication ticket
|
||||
get_ticket() {
|
||||
local response=$(curl -k -s -d "username=$PVE_USERNAME&password=$PVE_PASSWORD" \
|
||||
"$PROXMOX_URL/api2/json/access/ticket")
|
||||
|
||||
local ticket=$(echo "$response" | grep -o '"ticket":"[^"]*' | cut -d'"' -f4)
|
||||
local csrf=$(echo "$response" | grep -o '"CSRFPreventionToken":"[^"]*' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$ticket" ] || [ -z "$csrf" ]; then
|
||||
log_error "Failed to authenticate with Proxmox"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$ticket|$csrf"
|
||||
}
|
||||
|
||||
# Get next available VM ID
|
||||
get_next_vmid() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
local response=$(curl -k -s -H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/cluster/nextid")
|
||||
|
||||
echo "$response" | grep -o '"data":"[^"]*' | cut -d'"' -f4
|
||||
}
|
||||
|
||||
# Check if VM exists
|
||||
vm_exists() {
|
||||
local auth=$1
|
||||
local vmid=$2
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
local response=$(curl -k -s -H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/cluster/resources?type=vm")
|
||||
|
||||
echo "$response" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
vms = [v for v in data.get('data', []) if v.get('type') == 'qemu' and str(v.get('vmid')) == '$vmid']
|
||||
print('true' if vms else 'false')
|
||||
" 2>/dev/null || echo "false"
|
||||
}
|
||||
|
||||
# Main
|
||||
echo "========================================="
|
||||
echo "Create Cloudflare Tunnel VM"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
if [ -z "$PVE_PASSWORD" ]; then
|
||||
log_error "PVE_ROOT_PASS not set in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Connecting to Proxmox: $PROXMOX_URL"
|
||||
|
||||
# Authenticate
|
||||
auth=$(get_ticket)
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Authentication successful"
|
||||
echo ""
|
||||
|
||||
# Check for existing VM
|
||||
if [ "$(vm_exists "$auth" 100)" = "true" ]; then
|
||||
log_warn "VM with ID 100 already exists"
|
||||
read -p "Continue anyway? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get next VM ID
|
||||
next_id=$(get_next_vmid "$auth")
|
||||
log_info "Next available VM ID: $next_id"
|
||||
echo ""
|
||||
|
||||
log_warn "VM creation via Proxmox API requires:"
|
||||
log_warn " 1. A VM template (e.g., ubuntu-22.04-template)"
|
||||
log_warn " 2. Or an ISO image uploaded to Proxmox"
|
||||
echo ""
|
||||
|
||||
log_info "Recommended approach:"
|
||||
echo " 1. Use Proxmox Web UI to create the first VM"
|
||||
echo " 2. Convert it to a template for future use"
|
||||
echo " 3. Then use Terraform or API for additional VMs"
|
||||
echo ""
|
||||
log_info "Proxmox Web UI: $PROXMOX_URL"
|
||||
log_info "See CREATE_VMS.md for step-by-step instructions"
|
||||
echo ""
|
||||
|
||||
log_info "VM Configuration for Cloudflare Tunnel:"
|
||||
echo " - VM ID: 100"
|
||||
echo " - Name: cloudflare-tunnel"
|
||||
echo " - IP: 192.168.1.60"
|
||||
echo " - CPU: 2 cores"
|
||||
echo " - RAM: 4096 MB"
|
||||
echo " - Disk: 40GB"
|
||||
echo " - OS: Ubuntu 22.04 LTS"
|
||||
echo ""
|
||||
|
||||
log_info "After creating the VM:"
|
||||
echo " 1. Install Ubuntu 22.04 LTS"
|
||||
echo " 2. Configure static IP: 192.168.1.60"
|
||||
echo " 3. Run: sudo bash scripts/setup-cloudflare-tunnel.sh"
|
||||
echo ""
|
||||
|
||||
408
scripts/vm-management/create/create-pmg-vm.sh
Executable file
408
scripts/vm-management/create/create-pmg-vm.sh
Executable file
@@ -0,0 +1,408 @@
|
||||
#!/bin/bash
|
||||
source ~/.bashrc
|
||||
# Create Proxmox Mail Gateway VM via Proxmox API using ISO
|
||||
# Downloads ISO if needed, uploads to Proxmox, and creates VM automatically
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
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 -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
log_header() {
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
echo -e "${CYAN}$1${NC}"
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
}
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source <(grep -v '^#' .env | grep -v '^$' | sed 's/#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep '=')
|
||||
set +a
|
||||
else
|
||||
log_error ".env file not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Configuration
|
||||
PVE_USERNAME="${PVE_USERNAME:-root@pam}"
|
||||
PVE_PASSWORD="${PVE_ROOT_PASS:-}"
|
||||
PROXMOX_HOST="${1:-192.168.1.206}"
|
||||
PROXMOX_URL="https://${PROXMOX_HOST}:8006"
|
||||
PROXMOX_NODE="${2:-pve}"
|
||||
STORAGE_POOL="${3:-local}"
|
||||
|
||||
# PMG VM Configuration
|
||||
VMID=105
|
||||
VM_NAME="proxmox-mail-gateway"
|
||||
CORES=2
|
||||
MEMORY=4096
|
||||
DISK_SIZE="50G"
|
||||
|
||||
# ISO Configuration
|
||||
ISO_FILE="proxmox-mail-gateway_9.0-1.iso"
|
||||
ISO_URL="https://enterprise.proxmox.com/iso/proxmox-mail-gateway_9.0-1.iso"
|
||||
ISO_DIR="${4:-./downloads/iso}"
|
||||
ISO_PATH="${ISO_DIR}/${ISO_FILE}"
|
||||
|
||||
# Get authentication ticket
|
||||
get_ticket() {
|
||||
local response=$(curl -k -s -d "username=$PVE_USERNAME&password=$PVE_PASSWORD" \
|
||||
"$PROXMOX_URL/api2/json/access/ticket")
|
||||
|
||||
local ticket=$(echo "$response" | grep -o '"ticket":"[^"]*' | cut -d'"' -f4)
|
||||
local csrf=$(echo "$response" | grep -o '"CSRFPreventionToken":"[^"]*' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$ticket" ] || [ -z "$csrf" ]; then
|
||||
log_error "Failed to authenticate with Proxmox"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$ticket|$csrf"
|
||||
}
|
||||
|
||||
# Download ISO if not present
|
||||
download_iso() {
|
||||
if [ -f "$ISO_PATH" ]; then
|
||||
log_info "ISO already exists locally: $ISO_PATH"
|
||||
ISO_SIZE=$(du -h "$ISO_PATH" | cut -f1)
|
||||
log_info "ISO size: $ISO_SIZE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_step "Downloading PMG ISO..."
|
||||
log_info "URL: $ISO_URL"
|
||||
log_info "Destination: $ISO_PATH"
|
||||
log_warn "This may take several minutes depending on network speed..."
|
||||
|
||||
# Create ISO directory if it doesn't exist
|
||||
mkdir -p "$ISO_DIR"
|
||||
|
||||
# Download ISO with progress
|
||||
if command -v wget >/dev/null 2>&1; then
|
||||
wget --progress=bar:force -O "$ISO_PATH" "$ISO_URL" 2>&1 | grep --line-buffered -oP '\d+%' | while read -r line; do
|
||||
echo -ne "\r${GREEN}[INFO]${NC} Download progress: $line"
|
||||
done
|
||||
echo ""
|
||||
elif command -v curl >/dev/null 2>&1; then
|
||||
curl -L --progress-bar -o "$ISO_PATH" "$ISO_URL"
|
||||
else
|
||||
log_error "Neither wget nor curl is available. Cannot download ISO."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$ISO_PATH" ]; then
|
||||
log_error "ISO download failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ISO_SIZE=$(du -h "$ISO_PATH" | cut -f1)
|
||||
log_info "✓ ISO downloaded successfully: $ISO_SIZE"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check if ISO exists locally
|
||||
check_iso() {
|
||||
if [ ! -f "$ISO_PATH" ]; then
|
||||
log_warn "ISO file not found: $ISO_PATH"
|
||||
log_info "Attempting to download from: $ISO_URL"
|
||||
if ! download_iso; then
|
||||
log_error "Failed to download ISO. Please download manually and place it at: $ISO_PATH"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_info "Found ISO: $ISO_PATH"
|
||||
ISO_SIZE=$(du -h "$ISO_PATH" | cut -f1)
|
||||
log_info "ISO size: $ISO_SIZE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if ISO already exists in Proxmox
|
||||
iso_exists() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
local response=$(curl -k -s -H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/storage/${STORAGE_POOL}/content")
|
||||
|
||||
echo "$response" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
isos = [f for f in data.get('data', []) if f.get('content') == 'iso' and '$ISO_FILE' in f.get('volid', '')]
|
||||
print('true' if isos else 'false')
|
||||
" 2>/dev/null || echo "false"
|
||||
}
|
||||
|
||||
# Upload ISO to Proxmox
|
||||
upload_iso() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
log_step "Uploading ISO to Proxmox..."
|
||||
log_warn "This may take several minutes depending on ISO size and network speed..."
|
||||
|
||||
# Upload ISO using multipart form
|
||||
local result=$(curl -k -X POST \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-F "content=iso" \
|
||||
-F "filename=@$ISO_PATH" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/storage/${STORAGE_POOL}/upload" 2>&1)
|
||||
|
||||
if echo "$result" | grep -q "error"; then
|
||||
log_error "ISO upload failed: $result"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "✓ ISO uploaded successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check if VM exists
|
||||
vm_exists() {
|
||||
local auth=$1
|
||||
local vmid=$2
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
local response=$(curl -k -s -H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/cluster/resources?type=vm")
|
||||
|
||||
echo "$response" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
vms = [v for v in data.get('data', []) if v.get('type') == 'qemu' and str(v.get('vmid')) == '$vmid']
|
||||
print('true' if vms else 'false')
|
||||
" 2>/dev/null || echo "false"
|
||||
}
|
||||
|
||||
# Create VM via API
|
||||
create_vm() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
log_step "Creating VM: $VM_NAME (ID: $VMID)..."
|
||||
|
||||
# First, verify ISO exists in storage
|
||||
local iso_volid="${STORAGE_POOL}:iso/${ISO_FILE}"
|
||||
log_info "Using ISO: $iso_volid"
|
||||
|
||||
# Strategy: Create VM with minimal config, then add hardware via separate API calls
|
||||
log_info "Step 1: Creating VM skeleton..."
|
||||
local create_response=$(curl -k -s -X POST \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "vmid=$VMID" \
|
||||
-d "name=$VM_NAME" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu" 2>&1)
|
||||
|
||||
if echo "$create_response" | grep -q '"errors"'; then
|
||||
log_error "Failed to create VM skeleton:"
|
||||
echo "$create_response" | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d.get('errors', {}), indent=2))" 2>/dev/null || echo "$create_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "✓ VM skeleton created"
|
||||
sleep 1
|
||||
|
||||
# Step 2: Configure basic VM settings
|
||||
log_info "Step 2: Configuring CPU and memory..."
|
||||
curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "cores=$CORES" \
|
||||
-d "memory=$MEMORY" \
|
||||
-d "ostype=l26" \
|
||||
-d "agent=1" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$VMID/config" > /dev/null 2>&1
|
||||
|
||||
# Step 3: Add disk
|
||||
log_info "Step 3: Adding disk..."
|
||||
local disk_response=$(curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "scsi0=${STORAGE_POOL}:${DISK_SIZE}" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$VMID/config" 2>&1)
|
||||
|
||||
if echo "$disk_response" | grep -q '"errors"'; then
|
||||
log_warn "Disk configuration warning (continuing anyway)"
|
||||
fi
|
||||
|
||||
# Step 4: Add ISO
|
||||
log_info "Step 4: Adding ISO..."
|
||||
local iso_response=$(curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "ide2=$iso_volid" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$VMID/config" 2>&1)
|
||||
|
||||
if echo "$iso_response" | grep -q '"errors"'; then
|
||||
log_warn "ISO configuration warning (continuing anyway)"
|
||||
fi
|
||||
|
||||
# Step 5: Add network (DHCP configuration)
|
||||
log_info "Step 5: Adding network (DHCP)..."
|
||||
local net_response=$(curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "net0=model=virtio,bridge=vmbr0" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$VMID/config" 2>&1)
|
||||
|
||||
if echo "$net_response" | grep -q '"errors"'; then
|
||||
log_warn "Network configuration warning (may need manual configuration)"
|
||||
fi
|
||||
|
||||
# Step 6: Set boot order
|
||||
log_info "Step 6: Configuring boot order..."
|
||||
curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "boot=order=ide2" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$VMID/config" > /dev/null 2>&1
|
||||
|
||||
# Step 7: Add tags
|
||||
log_info "Step 7: Adding tags..."
|
||||
curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "tags=mail;security;gateway" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$VMID/config" > /dev/null 2>&1
|
||||
|
||||
# Verify VM config file was created
|
||||
sleep 2
|
||||
local verify_response=$(curl -k -s -H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$VMID/config" 2>&1)
|
||||
|
||||
if echo "$verify_response" | grep -q '"errors"'; then
|
||||
log_error "VM $VM_NAME was not created properly. Config file missing."
|
||||
log_error "Response: $verify_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "✓ VM $VM_NAME created successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Start VM
|
||||
start_vm() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
log_info "Starting VM $VMID..."
|
||||
local start_response=$(curl -k -s -X POST \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$VMID/status/start")
|
||||
|
||||
if echo "$start_response" | grep -q '"error"'; then
|
||||
log_warn "Failed to start VM $VMID: $start_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "✓ VM $VMID started"
|
||||
return 0
|
||||
}
|
||||
|
||||
main() {
|
||||
log_header "Create Proxmox Mail Gateway VM"
|
||||
echo ""
|
||||
|
||||
if [ -z "$PVE_PASSWORD" ]; then
|
||||
log_error "PVE_ROOT_PASS not set in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check/download ISO file
|
||||
check_iso
|
||||
echo ""
|
||||
|
||||
# Authenticate
|
||||
log_step "Authenticating with Proxmox..."
|
||||
auth=$(get_ticket)
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
log_info "✓ Authentication successful"
|
||||
echo ""
|
||||
|
||||
# Check if VM already exists
|
||||
if [ "$(vm_exists "$auth" "$VMID")" = "true" ]; then
|
||||
log_warn "VM $VM_NAME (ID: $VMID) already exists. Skipping creation..."
|
||||
log_info "To recreate, delete the VM first via Proxmox Web UI or API"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if ISO already uploaded
|
||||
if [ "$(iso_exists "$auth")" = "true" ]; then
|
||||
log_info "✓ ISO already exists in Proxmox storage"
|
||||
else
|
||||
# Upload ISO
|
||||
if ! upload_iso "$auth"; then
|
||||
log_error "Failed to upload ISO"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Create VM
|
||||
log_step "Creating VM..."
|
||||
echo ""
|
||||
|
||||
if create_vm "$auth"; then
|
||||
# Start VM
|
||||
start_vm "$auth"
|
||||
echo ""
|
||||
|
||||
log_header "VM Creation Complete"
|
||||
echo ""
|
||||
log_info "Proxmox Mail Gateway VM has been created and started!"
|
||||
echo ""
|
||||
log_info "VM Details:"
|
||||
echo " - Name: $VM_NAME"
|
||||
echo " - ID: $VMID"
|
||||
echo " - Cores: $CORES"
|
||||
echo " - Memory: ${MEMORY}MB"
|
||||
echo " - Disk: $DISK_SIZE"
|
||||
echo " - Network: DHCP (vmbr0)"
|
||||
echo ""
|
||||
log_info "Next steps:"
|
||||
echo " 1. Access VM console via Proxmox Web UI: $PROXMOX_URL"
|
||||
echo " 2. Complete Proxmox Mail Gateway installation via console"
|
||||
echo " 3. Configure PMG after installation completes"
|
||||
echo ""
|
||||
else
|
||||
log_error "Failed to create VM $VM_NAME"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
94
scripts/vm-management/create/create-proxmox-template.sh
Executable file
94
scripts/vm-management/create/create-proxmox-template.sh
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
source ~/.bashrc
|
||||
# Create Proxmox Cloud-Init Template from Ubuntu Cloud Image
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source <(grep -v '^#' .env | grep -v '^$' | sed 's/#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep '=')
|
||||
set +a
|
||||
fi
|
||||
|
||||
PVE_USERNAME="${PVE_USERNAME:-root@pam}"
|
||||
PVE_PASSWORD="${PVE_ROOT_PASS:-}"
|
||||
PROXMOX_URL="${PROXMOX_URL:-https://192.168.1.206:8006}"
|
||||
PROXMOX_NODE="${PROXMOX_NODE:-pve}"
|
||||
STORAGE="${STORAGE:-local-lvm}"
|
||||
|
||||
CLOUD_IMAGE="${1:-./downloads/ubuntu-24.04-server-cloudimg-amd64.img}"
|
||||
TEMPLATE_NAME="${2:-ubuntu-24.04-cloudinit}"
|
||||
TEMPLATE_ID="${3:-9000}"
|
||||
|
||||
main() {
|
||||
echo "========================================="
|
||||
echo "Create Proxmox Cloud-Init Template"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
if [ ! -f "$CLOUD_IMAGE" ]; then
|
||||
log_error "Cloud image not found: $CLOUD_IMAGE"
|
||||
log_info "Download it first: ./scripts/download-ubuntu-cloud-image.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$PVE_PASSWORD" ]; then
|
||||
log_error "PVE_ROOT_PASS not set in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_step "Creating template from: $CLOUD_IMAGE"
|
||||
log_info "Template name: $TEMPLATE_NAME"
|
||||
log_info "Template ID: $TEMPLATE_ID"
|
||||
log_info "Storage: $STORAGE"
|
||||
echo ""
|
||||
|
||||
log_info "This script provides instructions for manual template creation."
|
||||
log_info "Proxmox Web UI method is more reliable for template creation."
|
||||
echo ""
|
||||
|
||||
log_step "Manual Steps (Recommended):"
|
||||
echo ""
|
||||
echo "1. Upload Cloud Image to Proxmox:"
|
||||
echo " - Proxmox Web UI → Datacenter → $PROXMOX_NODE → Storage"
|
||||
echo " - Select storage → Content → Upload"
|
||||
echo " - Upload: $CLOUD_IMAGE"
|
||||
echo ""
|
||||
echo "2. Create VM from Cloud Image:"
|
||||
echo " - Create VM (ID: $TEMPLATE_ID)"
|
||||
echo " - Import disk from uploaded image"
|
||||
echo " - Configure Cloud-Init settings"
|
||||
echo ""
|
||||
echo "3. Convert to Template:"
|
||||
echo " - Right-click VM → Convert to Template"
|
||||
echo ""
|
||||
echo "4. Use Template:"
|
||||
echo " - Clone template to create new VMs"
|
||||
echo " - Configure Cloud-Init on clone"
|
||||
echo ""
|
||||
|
||||
log_info "See: docs/proxmox-ubuntu-images.md for detailed instructions"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
90
scripts/vm-management/create/create-template-quick.sh
Executable file
90
scripts/vm-management/create/create-template-quick.sh
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
source ~/.bashrc
|
||||
# Quick Template Creation Guide
|
||||
# This provides step-by-step instructions for creating the template
|
||||
|
||||
set -e
|
||||
|
||||
cat <<'EOF'
|
||||
========================================
|
||||
Ubuntu Cloud-Init Template Creation
|
||||
========================================
|
||||
|
||||
This guide will help you create a Ubuntu Cloud-Init template in Proxmox.
|
||||
|
||||
STEP 1: Download Ubuntu Cloud Image
|
||||
------------------------------------
|
||||
Run this command to download the image:
|
||||
|
||||
wget https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img
|
||||
|
||||
Or use the script:
|
||||
./scripts/download-ubuntu-cloud-image.sh 24.04
|
||||
|
||||
STEP 2: Upload to Proxmox
|
||||
--------------------------
|
||||
1. Open Proxmox Web UI: https://192.168.1.206:8006
|
||||
2. Go to: Datacenter → pve → Storage → local
|
||||
3. Click "Upload" button
|
||||
4. Select the downloaded .img file
|
||||
5. Wait for upload to complete (may take a few minutes)
|
||||
|
||||
STEP 3: Create VM from Image
|
||||
------------------------------
|
||||
1. Click "Create VM" (top right)
|
||||
2. General:
|
||||
- VM ID: 9000
|
||||
- Name: ubuntu-24.04-cloudinit
|
||||
- Click "Next"
|
||||
3. OS:
|
||||
- Select "Do not use any media"
|
||||
- Click "Next"
|
||||
4. System:
|
||||
- Keep defaults
|
||||
- Click "Next"
|
||||
5. Disks:
|
||||
- Delete the default disk
|
||||
- Click "Add" → "Hard Disk"
|
||||
- Storage: local
|
||||
- Import from: Select the uploaded .img file
|
||||
- Disk size: 20GB (minimum)
|
||||
- Click "Add"
|
||||
- Click "Next"
|
||||
6. CPU:
|
||||
- Cores: 2
|
||||
- Click "Next"
|
||||
7. Memory:
|
||||
- Memory: 2048 MB
|
||||
- Click "Next"
|
||||
8. Network:
|
||||
- Bridge: vmbr0
|
||||
- Model: VirtIO
|
||||
- Click "Next"
|
||||
9. Confirm:
|
||||
- Review settings
|
||||
- Click "Finish"
|
||||
|
||||
STEP 4: Configure Cloud-Init
|
||||
-----------------------------
|
||||
1. Select the VM (9000)
|
||||
2. Go to "Options" tab
|
||||
3. Click "Cloud-Init"
|
||||
4. Configure:
|
||||
- User: ubuntu
|
||||
- Password: (leave empty, use SSH keys)
|
||||
- SSH Public Keys: Paste your public key
|
||||
- Click "OK"
|
||||
|
||||
STEP 5: Convert to Template
|
||||
----------------------------
|
||||
1. Right-click on VM 9000
|
||||
2. Select "Convert to Template"
|
||||
3. Confirm
|
||||
|
||||
Done! Template is ready.
|
||||
|
||||
Now you can run:
|
||||
./scripts/recreate-vms-from-template.sh
|
||||
|
||||
EOF
|
||||
|
||||
221
scripts/vm-management/create/create-template-via-api.sh
Executable file
221
scripts/vm-management/create/create-template-via-api.sh
Executable file
@@ -0,0 +1,221 @@
|
||||
#!/bin/bash
|
||||
source ~/.bashrc
|
||||
# Create Ubuntu Cloud-Init Template via Proxmox API
|
||||
# This attempts to automate template creation as much as possible
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_header() {
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
echo -e "${CYAN}$1${NC}"
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
}
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source <(grep -v '^#' .env | grep -v '^$' | sed 's/#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep '=')
|
||||
set +a
|
||||
fi
|
||||
|
||||
PVE_USERNAME="${PVE_USERNAME:-root@pam}"
|
||||
PVE_PASSWORD="${PVE_ROOT_PASS:-}"
|
||||
PROXMOX_URL="https://192.168.1.206:8006"
|
||||
PROXMOX_NODE="pve"
|
||||
STORAGE="${STORAGE:-local}"
|
||||
TEMPLATE_ID=9000
|
||||
TEMPLATE_NAME="ubuntu-24.04-cloudinit"
|
||||
CLOUD_IMAGE="ubuntu-24.04-server-cloudimg-amd64.img"
|
||||
IMAGE_PATH="./downloads/${CLOUD_IMAGE}"
|
||||
|
||||
# Get authentication ticket
|
||||
get_ticket() {
|
||||
local response=$(curl -k -s -d "username=$PVE_USERNAME&password=$PVE_PASSWORD" \
|
||||
"$PROXMOX_URL/api2/json/access/ticket")
|
||||
|
||||
local ticket=$(echo "$response" | grep -o '"ticket":"[^"]*' | cut -d'"' -f4)
|
||||
local csrf=$(echo "$response" | grep -o '"CSRFPreventionToken":"[^"]*' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$ticket" ] || [ -z "$csrf" ]; then
|
||||
log_error "Failed to authenticate with Proxmox"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$ticket|$csrf"
|
||||
}
|
||||
|
||||
# Check if image is uploaded
|
||||
check_image_uploaded() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
local response=$(curl -k -s \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/storage/$STORAGE/content")
|
||||
|
||||
if echo "$response" | grep -q "$CLOUD_IMAGE"; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Upload image to Proxmox
|
||||
upload_image() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
log_step "Uploading cloud image to Proxmox..."
|
||||
|
||||
if [ ! -f "$IMAGE_PATH" ]; then
|
||||
log_error "Cloud image not found: $IMAGE_PATH"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_warn "Image upload via API is complex. Please upload manually:"
|
||||
log_info "1. Proxmox Web UI → Storage → $STORAGE → Upload"
|
||||
log_info "2. Select file: $IMAGE_PATH"
|
||||
log_info "3. Wait for upload to complete"
|
||||
echo ""
|
||||
read -p "Press Enter after image is uploaded..."
|
||||
|
||||
if check_image_uploaded "$auth"; then
|
||||
log_info "✓ Image uploaded"
|
||||
return 0
|
||||
else
|
||||
log_warn "Image upload not detected. Please verify manually."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Create VM from uploaded image
|
||||
create_vm_from_image() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
log_step "Creating VM $TEMPLATE_ID from cloud image..."
|
||||
|
||||
# Check if VM already exists
|
||||
local existing=$(curl -k -s \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$TEMPLATE_ID/config" 2>&1)
|
||||
|
||||
if echo "$existing" | grep -q '"name"'; then
|
||||
log_warn "VM $TEMPLATE_ID already exists"
|
||||
read -p "Delete and recreate? (y/N): " confirm
|
||||
if [ "$confirm" != "y" ]; then
|
||||
return 0
|
||||
fi
|
||||
# Delete existing VM
|
||||
curl -k -s -X DELETE \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$TEMPLATE_ID" > /dev/null 2>&1
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
log_warn "VM creation from image requires manual steps in Proxmox Web UI:"
|
||||
echo ""
|
||||
log_info "1. Create VM:"
|
||||
log_info " • Click 'Create VM'"
|
||||
log_info " • VM ID: $TEMPLATE_ID"
|
||||
log_info " • Name: $TEMPLATE_NAME"
|
||||
log_info " • OS: 'Do not use any media'"
|
||||
log_info " • Delete default disk"
|
||||
log_info " • Add disk: Import from $CLOUD_IMAGE"
|
||||
log_info " • CPU: 2, Memory: 2048MB"
|
||||
log_info " • Network: vmbr0, VirtIO"
|
||||
echo ""
|
||||
log_info "2. Configure Cloud-Init:"
|
||||
log_info " • Options → Cloud-Init"
|
||||
log_info " • User: ubuntu"
|
||||
log_info " • SSH Public Key: $(cat ~/.ssh/id_rsa.pub 2>/dev/null | head -1 || echo 'Your SSH key')"
|
||||
echo ""
|
||||
log_info "3. Convert to Template:"
|
||||
log_info " • Right-click VM → Convert to Template"
|
||||
echo ""
|
||||
read -p "Press Enter after template is created..."
|
||||
|
||||
# Verify template exists
|
||||
local template_check=$(curl -k -s \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$TEMPLATE_ID/config" 2>&1)
|
||||
|
||||
if echo "$template_check" | grep -q '"template".*1'; then
|
||||
log_info "✓ Template created successfully"
|
||||
return 0
|
||||
else
|
||||
log_warn "Template verification failed. Please check manually."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
log_header "Create Ubuntu Cloud-Init Template"
|
||||
echo ""
|
||||
|
||||
if [ -z "$PVE_PASSWORD" ]; then
|
||||
log_error "PVE_ROOT_PASS not set in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$IMAGE_PATH" ]; then
|
||||
log_error "Cloud image not found: $IMAGE_PATH"
|
||||
log_info "Download it first: ./scripts/download-ubuntu-cloud-image.sh 24.04"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Authenticate
|
||||
auth=$(get_ticket)
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 1: Upload image
|
||||
if ! check_image_uploaded "$auth"; then
|
||||
upload_image "$auth"
|
||||
else
|
||||
log_info "✓ Image already uploaded"
|
||||
fi
|
||||
|
||||
# Step 2: Create VM and convert to template
|
||||
create_vm_from_image "$auth"
|
||||
|
||||
log_header "Template Creation Complete!"
|
||||
echo ""
|
||||
log_info "Template $TEMPLATE_ID is ready"
|
||||
log_info "You can now run: ./scripts/recreate-vms-from-template.sh"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
722
scripts/vm-management/create/create-vm-from-image.sh
Executable file
722
scripts/vm-management/create/create-vm-from-image.sh
Executable file
@@ -0,0 +1,722 @@
|
||||
#!/bin/bash
|
||||
source ~/.bashrc
|
||||
# Create Proxmox VM from QCOW2/RAW Image - Comprehensive Automation Script
|
||||
#
|
||||
# This script automates the complete workflow for creating a VM from any disk image
|
||||
# in Proxmox VE using the qm command-line interface.
|
||||
#
|
||||
# Reference: https://pve.proxmox.com/pve-docs/qm.1.html
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/create-vm-from-image.sh --vmid 9000 --name "ubuntu-24.04" \
|
||||
# --image /path/to/image.img --storage local-lvm
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
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 -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# Default values
|
||||
VMID=""
|
||||
VMNAME=""
|
||||
IMAGE=""
|
||||
STORAGE="local-lvm"
|
||||
MEMORY=4096
|
||||
CORES=2
|
||||
BRIDGE="vmbr0"
|
||||
VLAN_TAG=""
|
||||
ENABLE_CLOUD_INIT=false
|
||||
ENABLE_UEFI=false
|
||||
ENABLE_TEMPLATE=false
|
||||
ENABLE_SERIAL=false
|
||||
CIUSER=""
|
||||
CIPASSWORD=""
|
||||
SSHKEY=""
|
||||
IPCONFIG=""
|
||||
NAMESERVER=""
|
||||
SEARCHDOMAIN=""
|
||||
CPU_TYPE="host"
|
||||
ENABLE_AGENT=true
|
||||
IOTHREAD=true
|
||||
CACHE_MODE="none"
|
||||
ENABLE_DISCARD=false
|
||||
BALLOON=0
|
||||
DESCRIPTION=""
|
||||
TAGS=""
|
||||
NODE=""
|
||||
DRY_RUN=false
|
||||
|
||||
# Load environment variables from .env if available
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source <(grep -v '^#' .env | grep -v '^$' | sed 's/#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep '=')
|
||||
set +a
|
||||
fi
|
||||
|
||||
# Parse command line arguments
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--vmid)
|
||||
VMID="$2"
|
||||
shift 2
|
||||
;;
|
||||
--name)
|
||||
VMNAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
--image)
|
||||
IMAGE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--storage)
|
||||
STORAGE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--memory)
|
||||
MEMORY="$2"
|
||||
shift 2
|
||||
;;
|
||||
--cores)
|
||||
CORES="$2"
|
||||
shift 2
|
||||
;;
|
||||
--bridge)
|
||||
BRIDGE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--vlan)
|
||||
VLAN_TAG="$2"
|
||||
shift 2
|
||||
;;
|
||||
--cloud-init)
|
||||
ENABLE_CLOUD_INIT=true
|
||||
shift
|
||||
;;
|
||||
--uefi)
|
||||
ENABLE_UEFI=true
|
||||
shift
|
||||
;;
|
||||
--template)
|
||||
ENABLE_TEMPLATE=true
|
||||
shift
|
||||
;;
|
||||
--serial)
|
||||
ENABLE_SERIAL=true
|
||||
shift
|
||||
;;
|
||||
--ciuser)
|
||||
CIUSER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--cipassword)
|
||||
CIPASSWORD="$2"
|
||||
shift 2
|
||||
;;
|
||||
--sshkey)
|
||||
SSHKEY="$2"
|
||||
shift 2
|
||||
;;
|
||||
--sshkey-file)
|
||||
if [ -f "$2" ]; then
|
||||
SSHKEY="$(cat "$2")"
|
||||
else
|
||||
log_error "SSH key file not found: $2"
|
||||
exit 1
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
--ipconfig)
|
||||
IPCONFIG="$2"
|
||||
shift 2
|
||||
;;
|
||||
--nameserver)
|
||||
NAMESERVER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--searchdomain)
|
||||
SEARCHDOMAIN="$2"
|
||||
shift 2
|
||||
;;
|
||||
--cpu)
|
||||
CPU_TYPE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-agent)
|
||||
ENABLE_AGENT=false
|
||||
shift
|
||||
;;
|
||||
--no-iothread)
|
||||
IOTHREAD=false
|
||||
shift
|
||||
;;
|
||||
--cache)
|
||||
CACHE_MODE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--discard)
|
||||
ENABLE_DISCARD=true
|
||||
shift
|
||||
;;
|
||||
--balloon)
|
||||
BALLOON="$2"
|
||||
shift 2
|
||||
;;
|
||||
--description)
|
||||
DESCRIPTION="$2"
|
||||
shift 2
|
||||
;;
|
||||
--tags)
|
||||
TAGS="$2"
|
||||
shift 2
|
||||
;;
|
||||
--node)
|
||||
NODE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Show help message
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Create Proxmox VM from QCOW2/RAW Image
|
||||
|
||||
Usage: $0 [OPTIONS]
|
||||
|
||||
Required Options:
|
||||
--vmid ID VM ID (e.g., 9000)
|
||||
--name NAME VM name (e.g., "ubuntu-24.04-cloudinit")
|
||||
--image PATH Full path to image file
|
||||
|
||||
Optional Options:
|
||||
--storage STORAGE Storage pool (default: local-lvm)
|
||||
--memory MB Memory in MB (default: 4096)
|
||||
--cores NUM CPU cores (default: 2)
|
||||
--bridge BRIDGE Network bridge (default: vmbr0)
|
||||
--vlan TAG VLAN tag number
|
||||
|
||||
Cloud-Init Options:
|
||||
--cloud-init Enable Cloud-Init support
|
||||
--ciuser USER Cloud-Init username
|
||||
--cipassword PASS Cloud-Init password (not recommended)
|
||||
--sshkey KEY SSH public key (or use --sshkey-file)
|
||||
--sshkey-file FILE Read SSH key from file
|
||||
--ipconfig CONFIG IP configuration (e.g., "ip=192.168.1.100/24,gw=192.168.1.1")
|
||||
--nameserver DNS DNS servers (space-separated)
|
||||
--searchdomain DOMAIN Search domains
|
||||
|
||||
VM Configuration:
|
||||
--uefi Enable UEFI/OVMF (recommended for modern images)
|
||||
--cpu TYPE CPU type (default: host, options: host, kvm64, etc.)
|
||||
--no-agent Disable QEMU Guest Agent
|
||||
--no-iothread Disable IO thread
|
||||
--cache MODE Disk cache mode (none, writeback, writethrough)
|
||||
--discard Enable discard (for thin provisioning)
|
||||
--balloon MB Memory balloon size in MB
|
||||
|
||||
Other Options:
|
||||
--template Convert to template after creation
|
||||
--serial Enable serial console
|
||||
--description TEXT VM description
|
||||
--tags TAGS Tags (comma-separated, e.g., "dev,web")
|
||||
--node NODE Target Proxmox node
|
||||
--dry-run Show commands without executing
|
||||
--help Show this help message
|
||||
|
||||
Examples:
|
||||
# Basic VM creation
|
||||
$0 --vmid 9000 --name "ubuntu-24.04" \\
|
||||
--image /var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img
|
||||
|
||||
# Full cloud-init VM
|
||||
$0 --vmid 9000 --name "ubuntu-24.04-cloudinit" \\
|
||||
--image /var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img \\
|
||||
--storage local-lvm --memory 4096 --cores 2 \\
|
||||
--cloud-init --uefi --serial \\
|
||||
--ciuser ubuntu --sshkey-file ~/.ssh/id_rsa.pub \\
|
||||
--ipconfig "ip=192.168.1.100/24,gw=192.168.1.1"
|
||||
|
||||
# Create and convert to template
|
||||
$0 --vmid 9000 --name "ubuntu-template" \\
|
||||
--image /var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img \\
|
||||
--cloud-init --uefi --template \\
|
||||
--ciuser ubuntu --sshkey-file ~/.ssh/id_rsa.pub
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Validate required arguments
|
||||
validate_args() {
|
||||
if [ -z "$VMID" ]; then
|
||||
log_error "VMID is required. Use --vmid option."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$VMNAME" ]; then
|
||||
log_error "VM name is required. Use --name option."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$IMAGE" ]; then
|
||||
log_error "Image path is required. Use --image option."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$IMAGE" ]; then
|
||||
log_error "Image file not found: $IMAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate VMID is numeric
|
||||
if ! [[ "$VMID" =~ ^[0-9]+$ ]]; then
|
||||
log_error "VMID must be numeric: $VMID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if VMID already exists
|
||||
if qm list | grep -q "^\s*$VMID\s"; then
|
||||
log_error "VM with ID $VMID already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate storage exists
|
||||
if ! pvesm status | grep -q "^$STORAGE\s"; then
|
||||
log_warn "Storage '$STORAGE' not found in pvesm status"
|
||||
log_info "Available storage:"
|
||||
pvesm status
|
||||
log_warn "Continuing anyway..."
|
||||
fi
|
||||
}
|
||||
|
||||
# Validate image
|
||||
validate_image() {
|
||||
log_step "Validating image: $IMAGE"
|
||||
|
||||
# Check image format
|
||||
if ! command -v qemu-img &> /dev/null; then
|
||||
log_warn "qemu-img not found, skipping image validation"
|
||||
return
|
||||
fi
|
||||
|
||||
local image_info
|
||||
image_info=$(qemu-img info "$IMAGE" 2>&1)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
log_error "Failed to read image: $IMAGE"
|
||||
log_error "$image_info"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Image format: $(echo "$image_info" | grep "file format" | awk '{print $3}')"
|
||||
log_info "Virtual size: $(echo "$image_info" | grep "virtual size" | awk -F'[()]' '{print $2}')"
|
||||
}
|
||||
|
||||
# Create VM shell
|
||||
create_vm_shell() {
|
||||
log_step "Creating VM shell (ID: $VMID, Name: $VMNAME)"
|
||||
|
||||
local cmd="qm create $VMID --name \"$VMNAME\" --memory $MEMORY --cores $CORES"
|
||||
|
||||
# Add node if specified
|
||||
if [ -n "$NODE" ]; then
|
||||
cmd="$cmd --target $NODE"
|
||||
fi
|
||||
|
||||
# Configure network
|
||||
if [ -n "$VLAN_TAG" ]; then
|
||||
cmd="$cmd --net0 virtio,bridge=$BRIDGE,tag=$VLAN_TAG"
|
||||
else
|
||||
cmd="$cmd --net0 virtio,bridge=$BRIDGE"
|
||||
fi
|
||||
|
||||
# Configure CPU
|
||||
cmd="$cmd --cpu $CPU_TYPE"
|
||||
|
||||
# Enable agent
|
||||
if [ "$ENABLE_AGENT" = true ]; then
|
||||
cmd="$cmd --agent 1"
|
||||
fi
|
||||
|
||||
# Add description if provided
|
||||
if [ -n "$DESCRIPTION" ]; then
|
||||
cmd="$cmd --description \"$DESCRIPTION\""
|
||||
fi
|
||||
|
||||
# Add tags if provided
|
||||
if [ -n "$TAGS" ]; then
|
||||
cmd="$cmd --tags $TAGS"
|
||||
fi
|
||||
|
||||
log_info "Command: $cmd"
|
||||
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
log_info "✓ VM shell created"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
}
|
||||
|
||||
# Import disk
|
||||
import_disk() {
|
||||
log_step "Importing disk from image: $IMAGE"
|
||||
|
||||
local cmd="qm importdisk $VMID \"$IMAGE\" $STORAGE"
|
||||
|
||||
log_info "Command: $cmd"
|
||||
log_info "This may take several minutes depending on image size..."
|
||||
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
log_info "✓ Disk imported"
|
||||
|
||||
# Get the volume name (usually vm-<VMID>-disk-0)
|
||||
local volume_name="vm-${VMID}-disk-0"
|
||||
log_info "Imported volume: $volume_name"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
}
|
||||
|
||||
# Attach disk
|
||||
attach_disk() {
|
||||
log_step "Attaching imported disk"
|
||||
|
||||
local volume_name="vm-${VMID}-disk-0"
|
||||
local cmd="qm set $VMID --scsihw virtio-scsi-pci --scsi0 ${STORAGE}:${volume_name}"
|
||||
|
||||
# Add IO thread if enabled
|
||||
if [ "$IOTHREAD" = true ]; then
|
||||
cmd="$cmd --iothread 1"
|
||||
fi
|
||||
|
||||
# Add cache mode
|
||||
cmd="$cmd --cache $CACHE_MODE"
|
||||
|
||||
# Add discard if enabled
|
||||
if [ "$ENABLE_DISCARD" = true ]; then
|
||||
cmd="$cmd --discard on"
|
||||
fi
|
||||
|
||||
log_info "Command: $cmd"
|
||||
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
log_info "✓ Disk attached"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
}
|
||||
|
||||
# Configure boot
|
||||
configure_boot() {
|
||||
log_step "Configuring boot settings"
|
||||
|
||||
local cmd="qm set $VMID --boot order=scsi0"
|
||||
|
||||
# Configure BIOS/UEFI
|
||||
if [ "$ENABLE_UEFI" = true ]; then
|
||||
cmd="$cmd --bios ovmf --efidisk0 ${STORAGE}:1,format=raw"
|
||||
log_info "UEFI/OVMF enabled"
|
||||
else
|
||||
cmd="$cmd --bios seabios"
|
||||
log_info "BIOS (SeaBIOS) enabled"
|
||||
fi
|
||||
|
||||
log_info "Command: $cmd"
|
||||
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
log_info "✓ Boot configured"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
}
|
||||
|
||||
# Configure Cloud-Init
|
||||
configure_cloud_init() {
|
||||
log_step "Configuring Cloud-Init"
|
||||
|
||||
# Add Cloud-Init drive
|
||||
local cmd="qm set $VMID --ide2 ${STORAGE}:cloudinit"
|
||||
|
||||
# Enable serial console if requested
|
||||
if [ "$ENABLE_SERIAL" = true ] || [ "$ENABLE_CLOUD_INIT" = true ]; then
|
||||
cmd="$cmd --serial0 socket --vga serial0"
|
||||
fi
|
||||
|
||||
log_info "Command: $cmd"
|
||||
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
|
||||
# Configure Cloud-Init user
|
||||
if [ -n "$CIUSER" ]; then
|
||||
cmd="qm set $VMID --ciuser $CIUSER"
|
||||
log_info "Command: $cmd"
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configure password (if provided, but not recommended)
|
||||
if [ -n "$CIPASSWORD" ]; then
|
||||
cmd="qm set $VMID --cipassword \"$CIPASSWORD\""
|
||||
log_warn "Setting password via Cloud-Init (not recommended, use SSH keys instead)"
|
||||
log_info "Command: $cmd"
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configure SSH key
|
||||
if [ -n "$SSHKEY" ]; then
|
||||
cmd="qm set $VMID --sshkey \"$SSHKEY\""
|
||||
log_info "Command: $cmd"
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
log_info "✓ SSH key configured"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configure IP
|
||||
if [ -n "$IPCONFIG" ]; then
|
||||
cmd="qm set $VMID --ipconfig0 $IPCONFIG"
|
||||
log_info "Command: $cmd"
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configure DNS
|
||||
if [ -n "$NAMESERVER" ]; then
|
||||
cmd="qm set $VMID --nameserver \"$NAMESERVER\""
|
||||
log_info "Command: $cmd"
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configure search domain
|
||||
if [ -n "$SEARCHDOMAIN" ]; then
|
||||
cmd="qm set $VMID --searchdomain \"$SEARCHDOMAIN\""
|
||||
log_info "Command: $cmd"
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
log_info "✓ Cloud-Init configured"
|
||||
fi
|
||||
}
|
||||
|
||||
# Configure memory balloon
|
||||
configure_balloon() {
|
||||
if [ "$BALLOON" -gt 0 ]; then
|
||||
log_step "Configuring memory balloon: ${BALLOON}MB"
|
||||
local cmd="qm set $VMID --balloon $BALLOON"
|
||||
|
||||
log_info "Command: $cmd"
|
||||
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
log_info "✓ Memory balloon configured"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Start VM
|
||||
start_vm() {
|
||||
log_step "Starting VM"
|
||||
|
||||
local cmd="qm start $VMID"
|
||||
|
||||
log_info "Command: $cmd"
|
||||
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
eval "$cmd"
|
||||
log_info "✓ VM started"
|
||||
|
||||
# Show status
|
||||
sleep 2
|
||||
qm status $VMID
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: $cmd"
|
||||
fi
|
||||
}
|
||||
|
||||
# Convert to template
|
||||
convert_to_template() {
|
||||
if [ "$ENABLE_TEMPLATE" = false ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log_step "Converting VM to template"
|
||||
|
||||
log_warn "VM must be shut down before converting to template"
|
||||
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
# Check if VM is running
|
||||
local status
|
||||
status=$(qm status $VMID 2>&1 | grep "status:" | awk '{print $2}')
|
||||
|
||||
if [ "$status" = "running" ]; then
|
||||
log_info "VM is running. Shutting down..."
|
||||
qm shutdown $VMID
|
||||
|
||||
log_info "Waiting for shutdown (this may take a minute)..."
|
||||
local max_wait=60
|
||||
local waited=0
|
||||
while [ $waited -lt $max_wait ]; do
|
||||
status=$(qm status $VMID 2>&1 | grep "status:" | awk '{print $2}')
|
||||
if [ "$status" != "running" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
waited=$((waited + 2))
|
||||
echo -n "."
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Convert to template
|
||||
qm template $VMID
|
||||
log_info "✓ VM converted to template"
|
||||
else
|
||||
log_info "[DRY RUN] Would execute: qm shutdown $VMID && qm template $VMID"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main function
|
||||
main() {
|
||||
echo "========================================="
|
||||
echo "Create Proxmox VM from Image"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
log_warn "DRY RUN MODE - No changes will be made"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
validate_args
|
||||
validate_image
|
||||
|
||||
echo ""
|
||||
log_info "VM Configuration:"
|
||||
log_info " VMID: $VMID"
|
||||
log_info " Name: $VMNAME"
|
||||
log_info " Image: $IMAGE"
|
||||
log_info " Storage: $STORAGE"
|
||||
log_info " Memory: ${MEMORY}MB"
|
||||
log_info " Cores: $CORES"
|
||||
log_info " Bridge: $BRIDGE"
|
||||
[ -n "$VLAN_TAG" ] && log_info " VLAN: $VLAN_TAG"
|
||||
[ "$ENABLE_CLOUD_INIT" = true ] && log_info " Cloud-Init: Enabled"
|
||||
[ "$ENABLE_UEFI" = true ] && log_info " UEFI: Enabled"
|
||||
[ "$ENABLE_TEMPLATE" = true ] && log_info " Convert to Template: Yes"
|
||||
echo ""
|
||||
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
read -p "Continue with VM creation? (y/N): " -n 1 -r
|
||||
echo ""
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "Aborted by user"
|
||||
exit 0
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
create_vm_shell
|
||||
import_disk
|
||||
attach_disk
|
||||
configure_boot
|
||||
|
||||
if [ "$ENABLE_CLOUD_INIT" = true ]; then
|
||||
configure_cloud_init
|
||||
fi
|
||||
|
||||
configure_balloon
|
||||
|
||||
if [ "$ENABLE_TEMPLATE" = false ]; then
|
||||
start_vm
|
||||
else
|
||||
log_info "Skipping VM start (will be converted to template)"
|
||||
fi
|
||||
|
||||
convert_to_template
|
||||
|
||||
echo ""
|
||||
log_info "========================================="
|
||||
log_info "VM Creation Complete!"
|
||||
log_info "========================================="
|
||||
|
||||
if [ "$ENABLE_TEMPLATE" = false ] && [ "$DRY_RUN" = false ]; then
|
||||
echo ""
|
||||
log_info "VM Status:"
|
||||
qm status $VMID
|
||||
echo ""
|
||||
log_info "View VM console: qm terminal $VMID"
|
||||
log_info "View VM config: qm config $VMID"
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
|
||||
519
scripts/vm-management/create/create-vm-template.sh
Executable file
519
scripts/vm-management/create/create-vm-template.sh
Executable file
@@ -0,0 +1,519 @@
|
||||
#!/bin/bash
|
||||
source ~/.bashrc
|
||||
# Create Proxmox Cloud-Init Template with Best Practices
|
||||
#
|
||||
# This script creates an optimized cloud-init template for Proxmox VE
|
||||
# following best practices for template management and cloud-init configuration.
|
||||
#
|
||||
# Reference: https://pve.proxmox.com/pve-docs/qm.1.html
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Logging functions
|
||||
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 -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# Default values
|
||||
VMID=""
|
||||
TEMPLATE_NAME=""
|
||||
IMAGE=""
|
||||
STORAGE="local-lvm"
|
||||
MEMORY=2048
|
||||
CORES=2
|
||||
BRIDGE="vmbr0"
|
||||
CIUSER="ubuntu"
|
||||
SSHKEY=""
|
||||
SSHKEY_FILE=""
|
||||
IPCONFIG="ip=dhcp"
|
||||
NAMESERVER=""
|
||||
SEARCHDOMAIN=""
|
||||
DESCRIPTION=""
|
||||
TAGS="template,cloud-init"
|
||||
NODE=""
|
||||
SKIP_VERIFICATION=false
|
||||
OPTIMIZE_TEMPLATE=true
|
||||
|
||||
# Load environment variables from .env if available
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source <(grep -v '^#' .env | grep -v '^$' | sed 's/#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep '=')
|
||||
set +a
|
||||
fi
|
||||
|
||||
# Parse command line arguments
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--vmid)
|
||||
VMID="$2"
|
||||
shift 2
|
||||
;;
|
||||
--name)
|
||||
TEMPLATE_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
--image)
|
||||
IMAGE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--storage)
|
||||
STORAGE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--memory)
|
||||
MEMORY="$2"
|
||||
shift 2
|
||||
;;
|
||||
--cores)
|
||||
CORES="$2"
|
||||
shift 2
|
||||
;;
|
||||
--bridge)
|
||||
BRIDGE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--ciuser)
|
||||
CIUSER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--sshkey)
|
||||
SSHKEY="$2"
|
||||
shift 2
|
||||
;;
|
||||
--sshkey-file)
|
||||
SSHKEY_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--ipconfig)
|
||||
IPCONFIG="$2"
|
||||
shift 2
|
||||
;;
|
||||
--nameserver)
|
||||
NAMESERVER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--searchdomain)
|
||||
SEARCHDOMAIN="$2"
|
||||
shift 2
|
||||
;;
|
||||
--description)
|
||||
DESCRIPTION="$2"
|
||||
shift 2
|
||||
;;
|
||||
--tags)
|
||||
TAGS="$2"
|
||||
shift 2
|
||||
;;
|
||||
--node)
|
||||
NODE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-verification)
|
||||
SKIP_VERIFICATION=true
|
||||
shift
|
||||
;;
|
||||
--no-optimize)
|
||||
OPTIMIZE_TEMPLATE=false
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Show help message
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Create Proxmox Cloud-Init Template with Best Practices
|
||||
|
||||
Usage: $0 [OPTIONS]
|
||||
|
||||
Required Options:
|
||||
--vmid ID VM ID (e.g., 9000)
|
||||
--name NAME Template name (e.g., "ubuntu-24.04-cloudinit")
|
||||
--image PATH Full path to cloud image file
|
||||
|
||||
Optional Options:
|
||||
--storage STORAGE Storage pool (default: local-lvm)
|
||||
--memory MB Memory in MB (default: 2048, minimal for template)
|
||||
--cores NUM CPU cores (default: 2)
|
||||
--bridge BRIDGE Network bridge (default: vmbr0)
|
||||
|
||||
Cloud-Init Configuration:
|
||||
--ciuser USER Cloud-Init username (default: ubuntu)
|
||||
--sshkey KEY SSH public key (or use --sshkey-file)
|
||||
--sshkey-file FILE Read SSH key from file
|
||||
--ipconfig CONFIG IP configuration (default: ip=dhcp)
|
||||
--nameserver DNS DNS servers (space-separated)
|
||||
--searchdomain DOMAIN Search domains
|
||||
|
||||
Template Options:
|
||||
--description TEXT Template description
|
||||
--tags TAGS Tags (comma-separated, default: "template,cloud-init")
|
||||
--node NODE Target Proxmox node
|
||||
--skip-verification Skip template verification after creation
|
||||
--no-optimize Skip template optimization steps
|
||||
|
||||
Examples:
|
||||
# Create template from Ubuntu cloud image
|
||||
$0 --vmid 9000 --name "ubuntu-24.04-cloudinit" \\
|
||||
--image /var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img \\
|
||||
--sshkey-file ~/.ssh/id_rsa.pub
|
||||
|
||||
# Create template with custom configuration
|
||||
$0 --vmid 9000 --name "ubuntu-24.04-cloudinit" \\
|
||||
--image /var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img \\
|
||||
--storage local-lvm --memory 2048 --cores 2 \\
|
||||
--ciuser ubuntu --sshkey-file ~/.ssh/id_rsa.pub \\
|
||||
--description "Ubuntu 24.04 LTS Cloud-Init Template"
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Validate required arguments
|
||||
validate_args() {
|
||||
if [ -z "$VMID" ]; then
|
||||
log_error "VMID is required. Use --vmid option."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$TEMPLATE_NAME" ]; then
|
||||
log_error "Template name is required. Use --name option."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$IMAGE" ]; then
|
||||
log_error "Image path is required. Use --image option."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$IMAGE" ]; then
|
||||
log_error "Image file not found: $IMAGE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate VMID is numeric
|
||||
if ! [[ "$VMID" =~ ^[0-9]+$ ]]; then
|
||||
log_error "VMID must be numeric: $VMID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if VMID already exists
|
||||
if qm list | grep -q "^\s*$VMID\s"; then
|
||||
log_error "VM with ID $VMID already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Load SSH key from file if specified
|
||||
if [ -n "$SSHKEY_FILE" ]; then
|
||||
if [ ! -f "$SSHKEY_FILE" ]; then
|
||||
log_error "SSH key file not found: $SSHKEY_FILE"
|
||||
exit 1
|
||||
fi
|
||||
SSHKEY="$(cat "$SSHKEY_FILE")"
|
||||
log_info "Loaded SSH key from: $SSHKEY_FILE"
|
||||
fi
|
||||
|
||||
# Validate SSH key format if provided
|
||||
if [ -n "$SSHKEY" ]; then
|
||||
if ! echo "$SSHKEY" | grep -qE "^ssh-(rsa|ed25519|ecdsa)"; then
|
||||
log_warn "SSH key format may be invalid (should start with ssh-rsa, ssh-ed25519, or ecdsa)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Validate template after creation
|
||||
verify_template() {
|
||||
log_step "Verifying template configuration"
|
||||
|
||||
local config
|
||||
config=$(qm config $VMID 2>&1)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
log_error "Failed to read template configuration"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local errors=0
|
||||
|
||||
# Check Cloud-Init is configured
|
||||
if ! echo "$config" | grep -q "ide2.*cloudinit"; then
|
||||
log_warn "Cloud-Init drive not found in template"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
# Check serial console is enabled
|
||||
if ! echo "$config" | grep -q "serial0.*socket"; then
|
||||
log_warn "Serial console not enabled (recommended for cloud-init)"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
# Check SSH key is configured
|
||||
if [ -n "$SSHKEY" ]; then
|
||||
if ! echo "$config" | grep -q "sshkey"; then
|
||||
log_warn "SSH key not found in template configuration"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check UEFI is enabled
|
||||
if ! echo "$config" | grep -q "bios.*ovmf"; then
|
||||
log_warn "UEFI not enabled (recommended for modern images)"
|
||||
fi
|
||||
|
||||
if [ $errors -eq 0 ]; then
|
||||
log_info "✓ Template configuration verified"
|
||||
return 0
|
||||
else
|
||||
log_warn "Template has $errors configuration warnings"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Clone template for testing
|
||||
test_template_clone() {
|
||||
if [ "$SKIP_VERIFICATION" = true ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
log_step "Testing template by creating a temporary clone"
|
||||
|
||||
local test_vmid=$((VMID + 1000)) # Use a different VMID range
|
||||
local test_name="${TEMPLATE_NAME}-test-$$"
|
||||
|
||||
# Find available VMID
|
||||
while qm list | grep -q "^\s*$test_vmid\s"; do
|
||||
test_vmid=$((test_vmid + 1))
|
||||
done
|
||||
|
||||
log_info "Creating test clone: VMID $test_vmid"
|
||||
|
||||
# Create linked clone
|
||||
if ! qm clone $VMID $test_vmid --name "$test_name" > /dev/null 2>&1; then
|
||||
log_error "Failed to create test clone"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "✓ Test clone created successfully (VMID: $test_vmid)"
|
||||
|
||||
# Clean up test clone
|
||||
read -p "Delete test clone $test_vmid? (Y/n): " -n 1 -r
|
||||
echo ""
|
||||
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
||||
log_info "Deleting test clone..."
|
||||
qm destroy $test_vmid --purge
|
||||
log_info "✓ Test clone deleted"
|
||||
else
|
||||
log_info "Test clone preserved. Manual cleanup required: qm destroy $test_vmid --purge"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Create template using the main script
|
||||
create_template() {
|
||||
log_step "Creating template using create-vm-from-image.sh"
|
||||
|
||||
# Build command
|
||||
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local create_script="${script_dir}/create-vm-from-image.sh"
|
||||
|
||||
if [ ! -f "$create_script" ]; then
|
||||
log_error "create-vm-from-image.sh not found at: $create_script"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local cmd="$create_script"
|
||||
cmd="$cmd --vmid $VMID"
|
||||
cmd="$cmd --name \"$TEMPLATE_NAME\""
|
||||
cmd="$cmd --image \"$IMAGE\""
|
||||
cmd="$cmd --storage $STORAGE"
|
||||
cmd="$cmd --memory $MEMORY"
|
||||
cmd="$cmd --cores $CORES"
|
||||
cmd="$cmd --bridge $BRIDGE"
|
||||
cmd="$cmd --cloud-init"
|
||||
cmd="$cmd --uefi"
|
||||
cmd="$cmd --serial"
|
||||
cmd="$cmd --template"
|
||||
cmd="$cmd --cpu host"
|
||||
cmd="$cmd --cache none"
|
||||
cmd="$cmd --discard"
|
||||
|
||||
# Add node if specified
|
||||
if [ -n "$NODE" ]; then
|
||||
cmd="$cmd --node $NODE"
|
||||
fi
|
||||
|
||||
# Add Cloud-Init configuration
|
||||
if [ -n "$CIUSER" ]; then
|
||||
cmd="$cmd --ciuser $CIUSER"
|
||||
fi
|
||||
|
||||
if [ -n "$SSHKEY" ]; then
|
||||
cmd="$cmd --sshkey \"$SSHKEY\""
|
||||
fi
|
||||
|
||||
if [ -n "$IPCONFIG" ]; then
|
||||
cmd="$cmd --ipconfig \"$IPCONFIG\""
|
||||
fi
|
||||
|
||||
if [ -n "$NAMESERVER" ]; then
|
||||
cmd="$cmd --nameserver \"$NAMESERVER\""
|
||||
fi
|
||||
|
||||
if [ -n "$SEARCHDOMAIN" ]; then
|
||||
cmd="$cmd --searchdomain \"$SEARCHDOMAIN\""
|
||||
fi
|
||||
|
||||
if [ -n "$DESCRIPTION" ]; then
|
||||
cmd="$cmd --description \"$DESCRIPTION\""
|
||||
fi
|
||||
|
||||
if [ -n "$TAGS" ]; then
|
||||
cmd="$cmd --tags \"$TAGS\""
|
||||
fi
|
||||
|
||||
log_info "Executing: $cmd"
|
||||
echo ""
|
||||
|
||||
# Execute the main script
|
||||
eval "$cmd"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
log_error "Failed to create template"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Add template metadata
|
||||
add_template_metadata() {
|
||||
log_step "Adding template metadata"
|
||||
|
||||
local metadata_desc
|
||||
if [ -n "$DESCRIPTION" ]; then
|
||||
metadata_desc="$DESCRIPTION"
|
||||
else
|
||||
metadata_desc="Cloud-Init Template - Created $(date +%Y-%m-%d)"
|
||||
fi
|
||||
|
||||
# Update description
|
||||
qm set $VMID --description "$metadata_desc"
|
||||
|
||||
# Ensure tags include template
|
||||
if [[ ! "$TAGS" =~ template ]]; then
|
||||
TAGS="template,$TAGS"
|
||||
fi
|
||||
|
||||
# Update tags
|
||||
qm set $VMID --tags "$TAGS"
|
||||
|
||||
log_info "✓ Template metadata added"
|
||||
}
|
||||
|
||||
# Main function
|
||||
main() {
|
||||
echo "========================================="
|
||||
echo "Create Proxmox Cloud-Init Template"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
parse_args "$@"
|
||||
|
||||
validate_args
|
||||
|
||||
echo ""
|
||||
log_info "Template Configuration:"
|
||||
log_info " VMID: $VMID"
|
||||
log_info " Name: $TEMPLATE_NAME"
|
||||
log_info " Image: $IMAGE"
|
||||
log_info " Storage: $STORAGE"
|
||||
log_info " Memory: ${MEMORY}MB (template minimal)"
|
||||
log_info " Cores: $CORES"
|
||||
log_info " Bridge: $BRIDGE"
|
||||
log_info " Cloud-Init User: $CIUSER"
|
||||
[ -n "$SSHKEY" ] && log_info " SSH Key: Configured"
|
||||
[ -n "$DESCRIPTION" ] && log_info " Description: $DESCRIPTION"
|
||||
[ -n "$TAGS" ] && log_info " Tags: $TAGS"
|
||||
echo ""
|
||||
|
||||
read -p "Continue with template creation? (y/N): " -n 1 -r
|
||||
echo ""
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_info "Aborted by user"
|
||||
exit 0
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Create template
|
||||
create_template
|
||||
|
||||
# Add metadata
|
||||
add_template_metadata
|
||||
|
||||
# Verify template
|
||||
if [ "$SKIP_VERIFICATION" = false ]; then
|
||||
echo ""
|
||||
verify_template
|
||||
fi
|
||||
|
||||
# Test clone if verification enabled
|
||||
if [ "$SKIP_VERIFICATION" = false ]; then
|
||||
echo ""
|
||||
read -p "Test template by creating a temporary clone? (Y/n): " -n 1 -r
|
||||
echo ""
|
||||
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
||||
test_template_clone
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "========================================="
|
||||
log_info "Template Creation Complete!"
|
||||
log_info "========================================="
|
||||
echo ""
|
||||
log_info "Template Details:"
|
||||
qm config $VMID | head -20
|
||||
echo ""
|
||||
log_info "Clone template with:"
|
||||
log_info " qm clone $VMID <new-vmid> --name \"<name>\""
|
||||
echo ""
|
||||
log_info "Full clone:"
|
||||
log_info " qm clone $VMID <new-vmid> --full --name \"<name>\""
|
||||
echo ""
|
||||
log_info "After cloning, configure Cloud-Init:"
|
||||
log_info " qm set <new-vmid> --ciuser $CIUSER"
|
||||
log_info " qm set <new-vmid> --sshkey \"<ssh-key>\""
|
||||
log_info " qm set <new-vmid> --ipconfig0 ip=<ip>/24,gw=<gateway>"
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
|
||||
386
scripts/vm-management/create/create-vms-from-iso.sh
Executable file
386
scripts/vm-management/create/create-vms-from-iso.sh
Executable file
@@ -0,0 +1,386 @@
|
||||
#!/bin/bash
|
||||
source ~/.bashrc
|
||||
# Create All Service VMs via Proxmox API using ISO
|
||||
# Uploads ISO and creates all VMs automatically
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
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 -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
log_header() {
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
echo -e "${CYAN}$1${NC}"
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
}
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source <(grep -v '^#' .env | grep -v '^$' | sed 's/#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep '=')
|
||||
set +a
|
||||
else
|
||||
log_error ".env file not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PVE_USERNAME="${PVE_USERNAME:-root@pam}"
|
||||
PVE_PASSWORD="${PVE_ROOT_PASS:-}"
|
||||
PROXMOX_HOST="${1:-192.168.1.206}"
|
||||
PROXMOX_URL="https://${PROXMOX_HOST}:8006"
|
||||
PROXMOX_NODE="${2:-pve}"
|
||||
ISO_FILE="${ISO_FILE:-ubuntu-24.04.3-live-server-amd64.iso}"
|
||||
ISO_PATH="${ISO_PATH:-./${ISO_FILE}}"
|
||||
|
||||
# Get authentication ticket
|
||||
get_ticket() {
|
||||
local response=$(curl -k -s -d "username=$PVE_USERNAME&password=$PVE_PASSWORD" \
|
||||
"$PROXMOX_URL/api2/json/access/ticket")
|
||||
|
||||
local ticket=$(echo "$response" | grep -o '"ticket":"[^"]*' | cut -d'"' -f4)
|
||||
local csrf=$(echo "$response" | grep -o '"CSRFPreventionToken":"[^"]*' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$ticket" ] || [ -z "$csrf" ]; then
|
||||
log_error "Failed to authenticate with Proxmox"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$ticket|$csrf"
|
||||
}
|
||||
|
||||
# Check if ISO exists locally
|
||||
check_iso() {
|
||||
if [ ! -f "$ISO_PATH" ]; then
|
||||
log_error "ISO file not found: $ISO_PATH"
|
||||
log_info "Looking for ISO in project root..."
|
||||
ISO_PATH="./ubuntu-24.04.3-live-server-amd64.iso"
|
||||
if [ ! -f "$ISO_PATH" ]; then
|
||||
log_error "ISO file not found. Please ensure ubuntu-24.04.3-live-server-amd64.iso is in the project root."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
log_info "Found ISO: $ISO_PATH"
|
||||
ISO_SIZE=$(du -h "$ISO_PATH" | cut -f1)
|
||||
log_info "ISO size: $ISO_SIZE"
|
||||
}
|
||||
|
||||
# Check if ISO already exists in Proxmox
|
||||
iso_exists() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
local response=$(curl -k -s -H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/storage/local/content")
|
||||
|
||||
echo "$response" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
isos = [f for f in data.get('data', []) if f.get('content') == 'iso' and '$ISO_FILE' in f.get('volid', '')]
|
||||
print('true' if isos else 'false')
|
||||
" 2>/dev/null || echo "false"
|
||||
}
|
||||
|
||||
# Upload ISO to Proxmox
|
||||
upload_iso() {
|
||||
local auth=$1
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
log_step "Uploading ISO to Proxmox..."
|
||||
log_warn "This may take several minutes depending on ISO size and network speed..."
|
||||
|
||||
# Upload ISO using multipart form
|
||||
local result=$(curl -k -X POST \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-F "content=iso" \
|
||||
-F "filename=@$ISO_PATH" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/storage/local/upload" 2>&1)
|
||||
|
||||
if echo "$result" | grep -q "error"; then
|
||||
log_error "ISO upload failed: $result"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "✓ ISO uploaded successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check if VM exists
|
||||
vm_exists() {
|
||||
local auth=$1
|
||||
local vmid=$2
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
local response=$(curl -k -s -H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/cluster/resources?type=vm")
|
||||
|
||||
echo "$response" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
vms = [v for v in data.get('data', []) if v.get('type') == 'qemu' and str(v.get('vmid')) == '$vmid']
|
||||
print('true' if vms else 'false')
|
||||
" 2>/dev/null || echo "false"
|
||||
}
|
||||
|
||||
# Create VM via API
|
||||
create_vm() {
|
||||
local auth=$1
|
||||
local vmid=$2
|
||||
local name=$3
|
||||
local cores=$4
|
||||
local memory=$5
|
||||
local disk_size=$6
|
||||
local ip_address=$7
|
||||
local gateway=$8
|
||||
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
log_step "Creating VM: $name (ID: $vmid)..."
|
||||
|
||||
# First, verify ISO exists in storage
|
||||
local iso_volid="local:iso/${ISO_FILE}"
|
||||
log_info "Using ISO: $iso_volid"
|
||||
|
||||
# Create VM with proper API format
|
||||
# Note: Proxmox API requires specific parameter format
|
||||
log_info "API Call: POST $PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu"
|
||||
log_info "Parameters: vmid=$vmid, name=$name, cores=$cores, memory=$memory"
|
||||
|
||||
# Strategy: Create VM with minimal config, then add hardware via separate API calls
|
||||
log_info "Step 1: Creating VM skeleton..."
|
||||
local create_response=$(curl -k -s -X POST \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "vmid=$vmid" \
|
||||
-d "name=$name" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu" 2>&1)
|
||||
|
||||
if echo "$create_response" | grep -q '"errors"'; then
|
||||
log_error "Failed to create VM skeleton:"
|
||||
echo "$create_response" | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d.get('errors', {}), indent=2))" 2>/dev/null || echo "$create_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "✓ VM skeleton created"
|
||||
sleep 1
|
||||
|
||||
# Step 2: Configure basic VM settings
|
||||
log_info "Step 2: Configuring CPU and memory..."
|
||||
curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "cores=$cores" \
|
||||
-d "memory=$memory" \
|
||||
-d "ostype=l26" \
|
||||
-d "agent=1" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$vmid/config" > /dev/null 2>&1
|
||||
|
||||
# Step 3: Add disk (simplest format)
|
||||
log_info "Step 3: Adding disk..."
|
||||
local disk_response=$(curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "scsi0=local:${disk_size}" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$vmid/config" 2>&1)
|
||||
|
||||
if echo "$disk_response" | grep -q '"errors"'; then
|
||||
log_warn "Disk configuration warning (continuing anyway)"
|
||||
fi
|
||||
|
||||
# Step 4: Add ISO
|
||||
log_info "Step 4: Adding ISO..."
|
||||
local iso_response=$(curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "ide2=$iso_volid" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$vmid/config" 2>&1)
|
||||
|
||||
if echo "$iso_response" | grep -q '"errors"'; then
|
||||
log_warn "ISO configuration warning (continuing anyway)"
|
||||
fi
|
||||
|
||||
# Step 5: Add network (try simplest format)
|
||||
log_info "Step 5: Adding network..."
|
||||
local net_response=$(curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "net0=bridge=vmbr0" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$vmid/config" 2>&1)
|
||||
|
||||
if echo "$net_response" | grep -q '"errors"'; then
|
||||
log_warn "Network configuration warning (may need manual configuration)"
|
||||
fi
|
||||
|
||||
# Step 6: Set boot order
|
||||
log_info "Step 6: Configuring boot order..."
|
||||
curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "boot=order=ide2" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$vmid/config" > /dev/null 2>&1
|
||||
|
||||
# Verify VM config file was created
|
||||
sleep 2
|
||||
local verify_response=$(curl -k -s -H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$vmid/config" 2>&1)
|
||||
|
||||
if echo "$verify_response" | grep -q '"errors"'; then
|
||||
log_error "VM $name was not created properly. Config file missing."
|
||||
log_error "Response: $verify_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "✓ VM $name created successfully"
|
||||
|
||||
# Configure Cloud-Init if IP is provided
|
||||
if [ -n "$ip_address" ] && [ -n "$gateway" ]; then
|
||||
log_info "Configuring Cloud-Init for $name..."
|
||||
local config_response=$(curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "ipconfig0=ip=${ip_address}/24,gw=${gateway}" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$vmid/config" 2>&1)
|
||||
|
||||
if echo "$config_response" | grep -q '"errors"'; then
|
||||
log_warn "Cloud-Init configuration may have failed (VM will use DHCP)"
|
||||
echo "$config_response" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('errors', {}).get('errors', 'Unknown error'))" 2>/dev/null || echo "$config_response"
|
||||
else
|
||||
log_info "✓ Network configured for $name"
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Start VM
|
||||
start_vm() {
|
||||
local auth=$1
|
||||
local vmid=$2
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
log_info "Starting VM $vmid..."
|
||||
local start_response=$(curl -k -s -X POST \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$vmid/status/start")
|
||||
|
||||
if echo "$start_response" | grep -q '"error"'; then
|
||||
log_warn "Failed to start VM $vmid: $start_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "✓ VM $vmid started"
|
||||
return 0
|
||||
}
|
||||
|
||||
# VM configurations
|
||||
declare -A VMS=(
|
||||
["100"]="cloudflare-tunnel:2:4096:40:192.168.1.60:192.168.1.254"
|
||||
["101"]="k3s-master:4:8192:80:192.168.1.188:192.168.1.254"
|
||||
["102"]="git-server:4:8192:100:192.168.1.121:192.168.1.254"
|
||||
["103"]="observability:4:8192:200:192.168.1.82:192.168.1.254"
|
||||
)
|
||||
|
||||
main() {
|
||||
log_header "Create All Service VMs via Proxmox API"
|
||||
echo ""
|
||||
|
||||
if [ -z "$PVE_PASSWORD" ]; then
|
||||
log_error "PVE_ROOT_PASS not set in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check ISO file
|
||||
check_iso
|
||||
echo ""
|
||||
|
||||
# Authenticate
|
||||
log_step "Authenticating with Proxmox..."
|
||||
auth=$(get_ticket)
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
log_info "✓ Authentication successful"
|
||||
echo ""
|
||||
|
||||
# Check if ISO already uploaded
|
||||
if [ "$(iso_exists "$auth")" = "true" ]; then
|
||||
log_info "✓ ISO already exists in Proxmox storage"
|
||||
else
|
||||
# Upload ISO
|
||||
if ! upload_iso "$auth"; then
|
||||
log_error "Failed to upload ISO"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Create VMs
|
||||
log_step "Creating VMs..."
|
||||
echo ""
|
||||
|
||||
for vmid in "${!VMS[@]}"; do
|
||||
IFS=':' read -r name cores memory disk_size ip_address gateway <<< "${VMS[$vmid]}"
|
||||
|
||||
# Check if VM already exists
|
||||
if [ "$(vm_exists "$auth" "$vmid")" = "true" ]; then
|
||||
log_warn "VM $name (ID: $vmid) already exists. Skipping..."
|
||||
continue
|
||||
fi
|
||||
|
||||
# Create VM
|
||||
if create_vm "$auth" "$vmid" "$name" "$cores" "$memory" "$disk_size" "$ip_address" "$gateway"; then
|
||||
# Start VM
|
||||
start_vm "$auth" "$vmid"
|
||||
echo ""
|
||||
else
|
||||
log_error "Failed to create VM $name"
|
||||
fi
|
||||
done
|
||||
|
||||
log_header "VM Creation Complete"
|
||||
echo ""
|
||||
log_info "All VMs have been created and started!"
|
||||
echo ""
|
||||
log_info "Next steps:"
|
||||
echo " 1. Access each VM console via Proxmox Web UI: $PROXMOX_URL"
|
||||
echo " 2. Complete Ubuntu installation on each VM"
|
||||
echo " 3. After OS installation, run setup scripts:"
|
||||
echo " - scripts/setup-cloudflare-tunnel.sh (on 192.168.1.60)"
|
||||
echo " - scripts/setup-k3s.sh (on 192.168.1.188)"
|
||||
echo " - scripts/setup-git-server.sh (on 192.168.1.121)"
|
||||
echo " - scripts/setup-observability.sh (on 192.168.1.82)"
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
257
scripts/vm-management/create/create-vms-from-template.sh
Executable file
257
scripts/vm-management/create/create-vms-from-template.sh
Executable file
@@ -0,0 +1,257 @@
|
||||
#!/bin/bash
|
||||
source ~/.bashrc
|
||||
# Create VMs from Cloud-Init Template with Automated Setup Scripts
|
||||
# This script creates VMs from a Cloud-Init template and applies service-specific install scripts
|
||||
|
||||
set -e
|
||||
|
||||
# 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_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source <(grep -v '^#' .env | grep -v '^$' | sed 's/#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep '=')
|
||||
set +a
|
||||
fi
|
||||
|
||||
PVE_USERNAME="${PVE_USERNAME:-root@pam}"
|
||||
PVE_PASSWORD="${PVE_ROOT_PASS:-}"
|
||||
PROXMOX_URL="https://192.168.1.206:8006"
|
||||
PROXMOX_NODE="pve"
|
||||
STORAGE="${STORAGE:-local-lvm}"
|
||||
TEMPLATE_NAME="${TEMPLATE_NAME:-ubuntu-24.04-cloudinit}"
|
||||
|
||||
# VM Configuration
|
||||
declare -A VMS=(
|
||||
[100]="cloudflare-tunnel:2:4096:40G:192.168.1.60:192.168.1.1:setup-cloudflare-tunnel.sh"
|
||||
[101]="k3s-master:4:8192:80G:192.168.1.188:192.168.1.1:setup-k3s.sh"
|
||||
[102]="git-server:2:4096:100G:192.168.1.121:192.168.1.1:setup-git-server.sh"
|
||||
[103]="observability:4:8192:200G:192.168.1.82:192.168.1.1:setup-observability.sh"
|
||||
)
|
||||
|
||||
# Get authentication ticket
|
||||
get_ticket() {
|
||||
local response=$(curl -k -s -d "username=$PVE_USERNAME&password=$PVE_PASSWORD" \
|
||||
"$PROXMOX_URL/api2/json/access/ticket")
|
||||
|
||||
local ticket=$(echo "$response" | grep -o '"ticket":"[^"]*' | cut -d'"' -f4)
|
||||
local csrf=$(echo "$response" | grep -o '"CSRFPreventionToken":"[^"]*' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$ticket" ] || [ -z "$csrf" ]; then
|
||||
log_error "Failed to authenticate with Proxmox"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$ticket|$csrf"
|
||||
}
|
||||
|
||||
# Read install script and create Cloud-Init user-data
|
||||
create_cloud_init_user_data() {
|
||||
local script_path=$1
|
||||
local vm_name=$2
|
||||
local ip_address=$3
|
||||
local gateway=$4
|
||||
|
||||
if [ ! -f "$script_path" ]; then
|
||||
log_error "Install script not found: $script_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local script_content=$(cat "$script_path" | base64 -w 0)
|
||||
|
||||
cat <<EOF
|
||||
#cloud-config
|
||||
hostname: ${vm_name}
|
||||
manage_etc_hosts: true
|
||||
|
||||
users:
|
||||
- name: ubuntu
|
||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||
shell: /bin/bash
|
||||
ssh_authorized_keys:
|
||||
- ${SSH_PUBLIC_KEY:-}
|
||||
|
||||
# Network configuration
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
addresses:
|
||||
- ${ip_address}/24
|
||||
gateway4: ${gateway}
|
||||
nameservers:
|
||||
addresses:
|
||||
- 8.8.8.8
|
||||
- 8.8.4.4
|
||||
|
||||
# Write install script
|
||||
write_files:
|
||||
- path: /tmp/install-service.sh
|
||||
content: !!binary |
|
||||
${script_content}
|
||||
owner: root:root
|
||||
permissions: '0755'
|
||||
encoding: b64
|
||||
|
||||
# Run install script on first boot
|
||||
runcmd:
|
||||
- /tmp/install-service.sh
|
||||
- rm -f /tmp/install-service.sh
|
||||
|
||||
# Update packages
|
||||
package_update: true
|
||||
package_upgrade: true
|
||||
|
||||
# Install common packages
|
||||
packages:
|
||||
- curl
|
||||
- wget
|
||||
- git
|
||||
- vim
|
||||
- net-tools
|
||||
EOF
|
||||
}
|
||||
|
||||
# Create VM from template
|
||||
create_vm_from_template() {
|
||||
local auth=$1
|
||||
local vmid=$2
|
||||
local name=$3
|
||||
local cores=$4
|
||||
local memory=$5
|
||||
local disk_size=$6
|
||||
local ip_address=$7
|
||||
local gateway=$8
|
||||
local install_script=$9
|
||||
|
||||
local ticket=$(echo "$auth" | cut -d'|' -f1)
|
||||
local csrf=$(echo "$auth" | cut -d'|' -f2)
|
||||
|
||||
log_step "Creating VM $vmid: $name"
|
||||
|
||||
# Create Cloud-Init user-data
|
||||
local user_data_file="/tmp/cloud-init-${vmid}.yaml"
|
||||
create_cloud_init_user_data "scripts/${install_script}" "$name" "$ip_address" "$gateway" > "$user_data_file"
|
||||
|
||||
log_info "Cloud-Init user-data created: $user_data_file"
|
||||
|
||||
# Upload user-data to Proxmox (requires manual step or SCP)
|
||||
log_warn "Note: Cloud-Init user-data needs to be uploaded to Proxmox storage"
|
||||
log_info "You can:"
|
||||
log_info " 1. Upload $user_data_file to Proxmox storage manually"
|
||||
log_info " 2. Or use cicustom parameter in API call"
|
||||
|
||||
# Clone template to create VM
|
||||
log_info "Cloning template $TEMPLATE_NAME to VM $vmid..."
|
||||
|
||||
local clone_response=$(curl -k -s -X POST \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "newid=$vmid" \
|
||||
-d "name=$name" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$TEMPLATE_NAME/clone")
|
||||
|
||||
if echo "$clone_response" | grep -q '"errors"'; then
|
||||
log_error "Failed to clone template: $clone_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Template cloned successfully"
|
||||
|
||||
# Wait for clone to complete
|
||||
sleep 5
|
||||
|
||||
# Configure VM
|
||||
log_info "Configuring VM..."
|
||||
|
||||
# Set CPU, memory, disk
|
||||
curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "cores=$cores" \
|
||||
-d "memory=$memory" \
|
||||
-d "net0=virtio,bridge=vmbr0" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$vmid/config" > /dev/null
|
||||
|
||||
# Configure Cloud-Init
|
||||
curl -k -s -X PUT \
|
||||
-H "Cookie: PVEAuthCookie=$ticket" \
|
||||
-H "CSRFPreventionToken: $csrf" \
|
||||
-d "ipconfig0=ip=${ip_address}/24,gw=${gateway}" \
|
||||
-d "ciuser=ubuntu" \
|
||||
"$PROXMOX_URL/api2/json/nodes/$PROXMOX_NODE/qemu/$vmid/config" > /dev/null
|
||||
|
||||
log_info "✓ VM $vmid configured"
|
||||
|
||||
# Cleanup
|
||||
rm -f "$user_data_file"
|
||||
}
|
||||
|
||||
main() {
|
||||
echo "========================================="
|
||||
echo "Create VMs from Cloud-Init Template"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
if [ -z "$PVE_PASSWORD" ]; then
|
||||
log_error "PVE_ROOT_PASS not set in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$TEMPLATE_NAME" ]; then
|
||||
log_error "TEMPLATE_NAME not set. Create template first."
|
||||
log_info "See: scripts/create-proxmox-template.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Authenticate
|
||||
auth=$(get_ticket)
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_step "Creating VMs from template: $TEMPLATE_NAME"
|
||||
echo ""
|
||||
|
||||
# Create each VM
|
||||
for vmid in 100 101 102 103; do
|
||||
IFS=':' read -r name cores memory disk_size ip_address gateway install_script <<< "${VMS[$vmid]}"
|
||||
|
||||
if create_vm_from_template "$auth" "$vmid" "$name" "$cores" "$memory" "$disk_size" "$ip_address" "$gateway" "$install_script"; then
|
||||
log_info "✓ VM $vmid ($name) created successfully"
|
||||
else
|
||||
log_error "✗ Failed to create VM $vmid"
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
|
||||
log_info "========================================="
|
||||
log_info "VM Creation Complete"
|
||||
log_info "========================================="
|
||||
echo ""
|
||||
log_warn "Next steps:"
|
||||
echo " 1. Start each VM"
|
||||
echo " 2. VM will boot and run install script automatically"
|
||||
echo " 3. Check VM console for installation progress"
|
||||
echo " 4. SSH to VM after installation completes"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
135
scripts/vm-management/create/create-vms-via-ssh.sh
Executable file
135
scripts/vm-management/create/create-vms-via-ssh.sh
Executable file
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
source ~/.bashrc
|
||||
# Create VMs via SSH using qm command (more reliable than API)
|
||||
# Requires SSH access to Proxmox host
|
||||
|
||||
set -e
|
||||
|
||||
# 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_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${BLUE}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# Load environment variables
|
||||
if [ -f .env ]; then
|
||||
set -a
|
||||
source <(grep -v '^#' .env | grep -v '^$' | sed 's/#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep '=')
|
||||
set +a
|
||||
fi
|
||||
|
||||
PROXMOX_HOST="${1:-192.168.1.206}"
|
||||
PROXMOX_USER="${2:-root}"
|
||||
ISO_FILE="ubuntu-24.04.3-live-server-amd64.iso"
|
||||
|
||||
# VM configurations
|
||||
declare -A VMS=(
|
||||
["100"]="cloudflare-tunnel:2:4096:40:192.168.1.60:192.168.1.254"
|
||||
["101"]="k3s-master:4:8192:80:192.168.1.188:192.168.1.254"
|
||||
["102"]="git-server:4:8192:100:192.168.1.121:192.168.1.254"
|
||||
["103"]="observability:4:8192:200:192.168.1.82:192.168.1.254"
|
||||
)
|
||||
|
||||
create_vm_ssh() {
|
||||
local vmid=$1
|
||||
local name=$2
|
||||
local cores=$3
|
||||
local memory=$4
|
||||
local disk_size=$5
|
||||
local ip_address=$6
|
||||
local gateway=$7
|
||||
|
||||
log_step "Creating VM: $name (ID: $vmid) via SSH..."
|
||||
|
||||
ssh "$PROXMOX_USER@$PROXMOX_HOST" <<EOF
|
||||
# Create VM
|
||||
qm create $vmid --name $name --cores $cores --memory $memory --ostype l26
|
||||
|
||||
# Add network
|
||||
qm set $vmid --net0 virtio,bridge=vmbr0
|
||||
|
||||
# Add disk
|
||||
qm set $vmid --scsi0 local:${disk_size},format=raw
|
||||
|
||||
# Add ISO
|
||||
qm set $vmid --ide2 local:iso/${ISO_FILE},media=cdrom
|
||||
|
||||
# Configure boot order
|
||||
qm set $vmid --boot c --bootdisk scsi0
|
||||
|
||||
# Enable QEMU agent
|
||||
qm set $vmid --agent 1
|
||||
|
||||
# Configure Cloud-Init (if IP provided)
|
||||
if [ -n "$ip_address" ] && [ -n "$gateway" ]; then
|
||||
qm set $vmid --ipconfig0 ip=${ip_address}/24,gw=${gateway}
|
||||
fi
|
||||
|
||||
# Start VM
|
||||
qm start $vmid
|
||||
EOF
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
log_info "✓ VM $name created and started"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to create VM $name"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
echo "========================================="
|
||||
echo "Create VMs via SSH (qm command)"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
log_info "This script requires SSH access to Proxmox host"
|
||||
log_info "Host: $PROXMOX_HOST"
|
||||
log_info "User: $PROXMOX_USER"
|
||||
echo ""
|
||||
|
||||
read -p "Do you have SSH access configured? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_error "SSH access required. Please configure SSH keys first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test SSH connection
|
||||
if ! ssh -o ConnectTimeout=5 "$PROXMOX_USER@$PROXMOX_HOST" "echo 'SSH connection successful'" 2>/dev/null; then
|
||||
log_error "Cannot connect to Proxmox host via SSH"
|
||||
log_info "Please configure SSH access or use Proxmox Web UI instead"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "✓ SSH connection successful"
|
||||
echo ""
|
||||
|
||||
# Create VMs
|
||||
for vmid in "${!VMS[@]}"; do
|
||||
IFS=':' read -r name cores memory disk_size ip_address gateway <<< "${VMS[$vmid]}"
|
||||
create_vm_ssh "$vmid" "$name" "$cores" "$memory" "$disk_size" "$ip_address" "$gateway"
|
||||
echo ""
|
||||
done
|
||||
|
||||
log_info "========================================="
|
||||
log_info "VM Creation Complete!"
|
||||
log_info "========================================="
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user