#!/bin/bash # create-proxmox-cluster.sh # Creates a Proxmox cluster between two instances set -euo pipefail # Load environment variables SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "${SCRIPT_DIR}/../.env" ]; then set -a source <(grep -v '^#' "${SCRIPT_DIR}/../.env" | grep -v '^$' | sed 's/^/export /') set +a fi # Colors GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Configuration CLUSTER_NAME="${CLUSTER_NAME:-sankofa-cluster}" NODE1_IP="192.168.11.10" NODE1_NAME="ML110-01" NODE1_TOKEN="${PROXMOX_TOKEN_ML110_01:-}" NODE2_IP="192.168.11.11" NODE2_NAME="R630-01" NODE2_TOKEN="${PROXMOX_TOKEN_R630_01:-}" log() { echo -e "${GREEN}[INFO]${NC} $1" } error() { echo -e "${RED}[ERROR]${NC} $1" >&2 exit 1 } warn() { echo -e "${YELLOW}[WARN]${NC} $1" } info() { echo -e "${BLUE}[INFO]${NC} $1" } check_prerequisites() { if [ -z "$NODE1_TOKEN" ] || [ -z "$NODE2_TOKEN" ]; then error "Proxmox API tokens not found in .env file" fi log "Prerequisites check passed" } check_cluster_status() { log "Checking current cluster status..." local node1_cluster=$(curl -k -s -H "Authorization: PVEAPIToken ${NODE1_TOKEN}" \ "https://${NODE1_IP}:8006/api2/json/cluster/config/nodes" 2>/dev/null | jq -r '.data // null') local node2_cluster=$(curl -k -s -H "Authorization: PVEAPIToken ${NODE2_TOKEN}" \ "https://${NODE2_IP}:8006/api2/json/cluster/config/nodes" 2>/dev/null | jq -r '.data // null') if [ "$node1_cluster" != "null" ] && [ -n "$node1_cluster" ]; then warn "Node 1 is already in a cluster" echo "$node1_cluster" | jq '.' return 1 fi if [ "$node2_cluster" != "null" ] && [ -n "$node2_cluster" ]; then warn "Node 2 is already in a cluster" echo "$node2_cluster" | jq '.' return 1 fi log "Both nodes are standalone - ready for clustering" return 0 } create_cluster_on_node1() { log "Creating cluster '${CLUSTER_NAME}' on ${NODE1_NAME}..." # Create cluster via API local response=$(curl -k -s -X POST \ -H "Authorization: PVEAPIToken ${NODE1_TOKEN}" \ -H "Content-Type: application/json" \ "https://${NODE1_IP}:8006/api2/json/cluster/config" \ -d "{\"clustername\":\"${CLUSTER_NAME}\",\"link0\":\"${NODE1_IP}\"}" 2>/dev/null) local success=$(echo "$response" | jq -r '.data // null') if [ "$success" != "null" ] && [ -n "$success" ]; then log "✓ Cluster created on ${NODE1_NAME}" return 0 else local error_msg=$(echo "$response" | jq -r '.errors[0].message // "Unknown error"') error "Failed to create cluster: ${error_msg}" fi } get_cluster_fingerprint() { log "Getting cluster fingerprint from ${NODE1_NAME}..." local fingerprint=$(curl -k -s -H "Authorization: PVEAPIToken ${NODE1_TOKEN}" \ "https://${NODE1_IP}:8006/api2/json/cluster/config/totem" 2>/dev/null | \ jq -r '.data.fingerprint // empty') if [ -n "$fingerprint" ]; then echo "$fingerprint" return 0 else warn "Could not get cluster fingerprint" return 1 fi } add_node2_to_cluster() { log "Adding ${NODE2_NAME} to cluster..." # Get cluster fingerprint local fingerprint=$(get_cluster_fingerprint) if [ -z "$fingerprint" ]; then warn "Fingerprint not available, trying without it" fi # Add node to cluster local response=$(curl -k -s -X POST \ -H "Authorization: PVEAPIToken ${NODE2_TOKEN}" \ -H "Content-Type: application/json" \ "https://${NODE2_IP}:8006/api2/json/cluster/config/nodes" \ -d "{\"hostname\":\"${NODE1_NAME}\",\"nodeid\":1,\"votes\":1,\"link0\":\"${NODE1_IP}\"}" 2>/dev/null) local success=$(echo "$response" | jq -r '.data // null') if [ "$success" != "null" ] && [ -n "$success" ]; then log "✓ ${NODE2_NAME} added to cluster" return 0 else local error_msg=$(echo "$response" | jq -r '.errors[0].message // "Unknown error"') warn "API method failed: ${error_msg}" warn "Cluster creation may require SSH access or manual setup" return 1 fi } verify_cluster() { log "Verifying cluster status..." sleep 5 # Wait for cluster to stabilize local node1_nodes=$(curl -k -s -H "Authorization: PVEAPIToken ${NODE1_TOKEN}" \ "https://${NODE1_IP}:8006/api2/json/cluster/config/nodes" 2>/dev/null | \ jq -r '.data | length // 0') local node2_nodes=$(curl -k -s -H "Authorization: PVEAPIToken ${NODE2_TOKEN}" \ "https://${NODE2_IP}:8006/api2/json/cluster/config/nodes" 2>/dev/null | \ jq -r '.data | length // 0') if [ "$node1_nodes" -ge 2 ] && [ "$node2_nodes" -ge 2 ]; then log "✓ Cluster verified - both nodes see 2+ members" return 0 else warn "Cluster verification incomplete" warn "Node 1 sees ${node1_nodes} members" warn "Node 2 sees ${node2_nodes} members" return 1 fi } main() { echo "" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ Proxmox Cluster Creation ║" echo "╚══════════════════════════════════════════════════════════════╝" echo "" check_prerequisites echo "" if ! check_cluster_status; then error "One or both nodes are already in a cluster" fi echo "" info "Cluster Configuration:" echo " Cluster Name: ${CLUSTER_NAME}" echo " Node 1: ${NODE1_NAME} (${NODE1_IP})" echo " Node 2: ${NODE2_NAME} (${NODE2_IP})" echo "" read -p "Create cluster? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then log "Cluster creation cancelled" exit 0 fi echo "" # Try API-based cluster creation if create_cluster_on_node1; then log "Cluster created on ${NODE1_NAME}" else error "Failed to create cluster via API" fi echo "" # Try to add second node if add_node2_to_cluster; then log "Node 2 added to cluster" else warn "Could not add node 2 via API" warn "You may need to add it manually via SSH or web UI" fi echo "" # Verify cluster verify_cluster echo "" log "Cluster creation process complete!" echo "" info "Next steps:" info "1. Verify cluster: Check both nodes in Proxmox web UI" info "2. Test cluster: Create a VM and verify it's visible on both nodes" info "3. Configure quorum: For 2-node cluster, set expected votes: pvecm expected 2" } main "$@"