Update cloud-init.func

This commit is contained in:
CanbiZ 2025-12-01 12:51:51 +01:00
parent 255316b811
commit 40b9867653

View File

@ -1,30 +1,93 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright (c) 2021-2025 community-scripts ORG
# Author: community-scripts ORG
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/branch/main/LICENSE
# Revision: 1
# ============================================================================== # ==============================================================================
# Cloud-Init Library - Universal Helper for all Proxmox VM Scripts # CLOUD-INIT.FUNC - VM CLOUD-INIT CONFIGURATION LIBRARY
# ============================================================================== # ==============================================================================
# Author: community-scripts ORG #
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # Universal helper library for Cloud-Init configuration in Proxmox VMs.
# Provides functions for:
#
# - Native Proxmox Cloud-Init setup (user, password, network, SSH keys)
# - Interactive configuration dialogs (whiptail)
# - IP address retrieval via qemu-guest-agent
# - Cloud-Init status monitoring and waiting
# #
# Usage: # Usage:
# 1. Source this library in your VM script: # source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/cloud-init.func)
# source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/cloud-init-lib.sh) # setup_cloud_init "$VMID" "$STORAGE" "$HN" "yes"
#
# 2. Call setup_cloud_init with parameters:
# setup_cloud_init "$VMID" "$STORAGE" "$HN" "$USE_CLOUD_INIT"
# #
# Compatible with: Debian, Ubuntu, and all Cloud-Init enabled distributions # Compatible with: Debian, Ubuntu, and all Cloud-Init enabled distributions
# ============================================================================== # ==============================================================================
# Configuration defaults (can be overridden before sourcing) # ==============================================================================
# SECTION 1: CONFIGURATION DEFAULTS
# ==============================================================================
# These can be overridden before sourcing this library
CLOUDINIT_DEFAULT_USER="${CLOUDINIT_DEFAULT_USER:-root}" CLOUDINIT_DEFAULT_USER="${CLOUDINIT_DEFAULT_USER:-root}"
CLOUDINIT_DNS_SERVERS="${CLOUDINIT_DNS_SERVERS:-1.1.1.1 8.8.8.8}" CLOUDINIT_DNS_SERVERS="${CLOUDINIT_DNS_SERVERS:-1.1.1.1 8.8.8.8}"
CLOUDINIT_SEARCH_DOMAIN="${CLOUDINIT_SEARCH_DOMAIN:-local}" CLOUDINIT_SEARCH_DOMAIN="${CLOUDINIT_SEARCH_DOMAIN:-local}"
CLOUDINIT_SSH_KEYS="${CLOUDINIT_SSH_KEYS:-/root/.ssh/authorized_keys}" CLOUDINIT_SSH_KEYS="${CLOUDINIT_SSH_KEYS:-/root/.ssh/authorized_keys}"
# ============================================================================== # ==============================================================================
# Main Setup Function - Configures Proxmox Native Cloud-Init # SECTION 2: HELPER FUNCTIONS
# ============================================================================== # ==============================================================================
# ------------------------------------------------------------------------------
# _ci_msg - Internal message helper with fallback
# ------------------------------------------------------------------------------
function _ci_msg_info() { msg_info "$1" 2>/dev/null || echo "[INFO] $1"; }
function _ci_msg_ok() { msg_ok "$1" 2>/dev/null || echo "[OK] $1"; }
function _ci_msg_warn() { msg_warn "$1" 2>/dev/null || echo "[WARN] $1"; }
function _ci_msg_error() { msg_error "$1" 2>/dev/null || echo "[ERROR] $1"; }
# ------------------------------------------------------------------------------
# validate_ip_cidr - Validate IP address in CIDR format
# Usage: validate_ip_cidr "192.168.1.100/24" && echo "Valid"
# Returns: 0 if valid, 1 if invalid
# ------------------------------------------------------------------------------
function validate_ip_cidr() {
local ip_cidr="$1"
# Match: 0-255.0-255.0-255.0-255/0-32
if [[ "$ip_cidr" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then
# Validate each octet is 0-255
local ip="${ip_cidr%/*}"
IFS='.' read -ra octets <<<"$ip"
for octet in "${octets[@]}"; do
((octet > 255)) && return 1
done
return 0
fi
return 1
}
# ------------------------------------------------------------------------------
# validate_ip - Validate plain IP address (no CIDR)
# Usage: validate_ip "192.168.1.1" && echo "Valid"
# ------------------------------------------------------------------------------
function validate_ip() {
local ip="$1"
if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
IFS='.' read -ra octets <<<"$ip"
for octet in "${octets[@]}"; do
((octet > 255)) && return 1
done
return 0
fi
return 1
}
# ==============================================================================
# SECTION 3: MAIN CLOUD-INIT FUNCTIONS
# ==============================================================================
# ------------------------------------------------------------------------------
# setup_cloud_init - Configures Proxmox Native Cloud-Init
# ------------------------------------------------------------------------------
# Parameters: # Parameters:
# $1 - VMID (required) # $1 - VMID (required)
# $2 - Storage name (required) # $2 - Storage name (required)
@ -55,7 +118,19 @@ function setup_cloud_init() {
return 0 return 0
fi fi
msg_info "Configuring Cloud-Init" 2>/dev/null || echo "[INFO] Configuring Cloud-Init" # Validate static IP if provided
if [ "$network_mode" = "static" ]; then
if [ -n "$static_ip" ] && ! validate_ip_cidr "$static_ip"; then
_ci_msg_error "Invalid static IP format: $static_ip (expected: x.x.x.x/xx)"
return 1
fi
if [ -n "$gateway" ] && ! validate_ip "$gateway"; then
_ci_msg_error "Invalid gateway IP format: $gateway"
return 1
fi
fi
_ci_msg_info "Configuring Cloud-Init"
# Create Cloud-Init drive (try ide2 first, then scsi1 as fallback) # Create Cloud-Init drive (try ide2 first, then scsi1 as fallback)
if ! qm set "$vmid" --ide2 "${storage}:cloudinit" >/dev/null 2>&1; then if ! qm set "$vmid" --ide2 "${storage}:cloudinit" >/dev/null 2>&1; then
@ -90,12 +165,16 @@ function setup_cloud_init() {
# Enable package upgrades on first boot (if supported by Proxmox version) # Enable package upgrades on first boot (if supported by Proxmox version)
qm set "$vmid" --ciupgrade 1 >/dev/null 2>&1 || true qm set "$vmid" --ciupgrade 1 >/dev/null 2>&1 || true
# Save credentials to file # Save credentials to file (with restrictive permissions)
local cred_file="/tmp/${hostname}-${vmid}-cloud-init-credentials.txt" local cred_file="/tmp/${hostname}-${vmid}-cloud-init-credentials.txt"
umask 077
cat >"$cred_file" <<EOF cat >"$cred_file" <<EOF
======================================== ╔══════════════════════════════════════════════════════════════════╗
║ ⚠️ SECURITY WARNING: DELETE THIS FILE AFTER NOTING CREDENTIALS ║
╚══════════════════════════════════════════════════════════════════╝
Cloud-Init Credentials Cloud-Init Credentials
======================================== ────────────────────────────────────────
VM ID: ${vmid} VM ID: ${vmid}
Hostname: ${hostname} Hostname: ${hostname}
Created: $(date) Created: $(date)
@ -106,19 +185,24 @@ Password: ${cipassword}
Network: ${network_mode}$([ "$network_mode" = "static" ] && echo " (IP: ${static_ip}, GW: ${gateway})" || echo " (DHCP)") Network: ${network_mode}$([ "$network_mode" = "static" ] && echo " (IP: ${static_ip}, GW: ${gateway})" || echo " (DHCP)")
DNS: ${nameservers} DNS: ${nameservers}
======================================== ────────────────────────────────────────
SSH Access (if keys configured): SSH Access (if keys configured):
ssh ${ciuser}@<vm-ip> ssh ${ciuser}@<vm-ip>
Proxmox UI Configuration: Proxmox UI Configuration:
VM ${vmid} > Cloud-Init > Edit VM ${vmid} > Cloud-Init > Edit
- User, Password, SSH Keys - User, Password, SSH Keys
- Network (IP Config) - Network (IP Config)
- DNS, Search Domain - DNS, Search Domain
========================================
EOF
msg_ok "Cloud-Init configured (User: ${ciuser})" 2>/dev/null || echo "[OK] Cloud-Init configured (User: ${ciuser})" ────────────────────────────────────────
🗑️ To delete this file:
rm -f ${cred_file}
────────────────────────────────────────
EOF
chmod 600 "$cred_file"
_ci_msg_ok "Cloud-Init configured (User: ${ciuser})"
# Export for use in calling script (DO NOT display password here - will be shown in summary) # Export for use in calling script (DO NOT display password here - will be shown in summary)
export CLOUDINIT_USER="$ciuser" export CLOUDINIT_USER="$ciuser"
@ -129,8 +213,12 @@ EOF
} }
# ============================================================================== # ==============================================================================
# Interactive Cloud-Init Configuration (Whiptail/Dialog) # SECTION 4: INTERACTIVE CONFIGURATION
# ============================================================================== # ==============================================================================
# ------------------------------------------------------------------------------
# configure_cloud_init_interactive - Whiptail dialog for Cloud-Init setup
# ------------------------------------------------------------------------------
# Prompts user for Cloud-Init configuration choices # Prompts user for Cloud-Init configuration choices
# Returns configuration via exported variables: # Returns configuration via exported variables:
# - CLOUDINIT_ENABLE (yes/no) # - CLOUDINIT_ENABLE (yes/no)
@ -139,7 +227,7 @@ EOF
# - CLOUDINIT_IP (if static) # - CLOUDINIT_IP (if static)
# - CLOUDINIT_GW (if static) # - CLOUDINIT_GW (if static)
# - CLOUDINIT_DNS # - CLOUDINIT_DNS
# ============================================================================== # ------------------------------------------------------------------------------
function configure_cloud_init_interactive() { function configure_cloud_init_interactive() {
local default_user="${1:-root}" local default_user="${1:-root}"
@ -174,24 +262,42 @@ function configure_cloud_init_interactive() {
else else
export CLOUDINIT_NETWORK_MODE="static" export CLOUDINIT_NETWORK_MODE="static"
# Static IP # Static IP with validation
while true; do
if CLOUDINIT_IP=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox \ if CLOUDINIT_IP=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox \
"Static IP Address (CIDR format)\nExample: 192.168.1.100/24" 9 58 "" --title "IP ADDRESS" 3>&1 1>&2 2>&3); then "Static IP Address (CIDR format)\nExample: 192.168.1.100/24" 9 58 "" --title "IP ADDRESS" 3>&1 1>&2 2>&3); then
if validate_ip_cidr "$CLOUDINIT_IP"; then
export CLOUDINIT_IP export CLOUDINIT_IP
break
else else
echo "Error: Static IP required for static network mode" whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID IP" \
export CLOUDINIT_NETWORK_MODE="dhcp" --msgbox "Invalid IP format: $CLOUDINIT_IP\n\nPlease use CIDR format: x.x.x.x/xx\nExample: 192.168.1.100/24" 10 50
fi fi
else
_ci_msg_warn "Static IP required, falling back to DHCP"
export CLOUDINIT_NETWORK_MODE="dhcp"
break
fi
done
# Gateway # Gateway with validation
if [ "$CLOUDINIT_NETWORK_MODE" = "static" ]; then if [ "$CLOUDINIT_NETWORK_MODE" = "static" ]; then
while true; do
if CLOUDINIT_GW=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox \ if CLOUDINIT_GW=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox \
"Gateway IP Address\nExample: 192.168.1.1" 8 58 "" --title "GATEWAY" 3>&1 1>&2 2>&3); then "Gateway IP Address\nExample: 192.168.1.1" 8 58 "" --title "GATEWAY" 3>&1 1>&2 2>&3); then
if validate_ip "$CLOUDINIT_GW"; then
export CLOUDINIT_GW export CLOUDINIT_GW
break
else else
echo "Error: Gateway required for static network mode" whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID GATEWAY" \
export CLOUDINIT_NETWORK_MODE="dhcp" --msgbox "Invalid gateway format: $CLOUDINIT_GW\n\nPlease use format: x.x.x.x\nExample: 192.168.1.1" 10 50
fi fi
else
_ci_msg_warn "Gateway required, falling back to DHCP"
export CLOUDINIT_NETWORK_MODE="dhcp"
break
fi
done
fi fi
fi fi
@ -207,8 +313,12 @@ function configure_cloud_init_interactive() {
} }
# ============================================================================== # ==============================================================================
# Display Cloud-Init Summary Information # SECTION 5: UTILITY FUNCTIONS
# ============================================================================== # ==============================================================================
# ------------------------------------------------------------------------------
# display_cloud_init_info - Show Cloud-Init summary after setup
# ------------------------------------------------------------------------------
function display_cloud_init_info() { function display_cloud_init_info() {
local vmid="$1" local vmid="$1"
local hostname="${2:-}" local hostname="${2:-}"
@ -219,48 +329,64 @@ function display_cloud_init_info() {
echo -e "${TAB:- }${DGN:-}User: ${BGN:-}${CLOUDINIT_USER:-root}${CL:-}" echo -e "${TAB:- }${DGN:-}User: ${BGN:-}${CLOUDINIT_USER:-root}${CL:-}"
echo -e "${TAB:- }${DGN:-}Password: ${BGN:-}${CLOUDINIT_PASSWORD}${CL:-}" echo -e "${TAB:- }${DGN:-}Password: ${BGN:-}${CLOUDINIT_PASSWORD}${CL:-}"
echo -e "${TAB:- }${DGN:-}Credentials: ${BL:-}${CLOUDINIT_CRED_FILE}${CL:-}" echo -e "${TAB:- }${DGN:-}Credentials: ${BL:-}${CLOUDINIT_CRED_FILE}${CL:-}"
echo -e "${TAB:- }${YW:-}💡 You can configure Cloud-Init settings in Proxmox UI:${CL:-}" echo -e "${TAB:- }${RD:-}⚠️ Delete credentials file after noting password!${CL:-}"
echo -e "${TAB:- }${YW:-} VM ${vmid} > Cloud-Init > Edit (User, Password, SSH Keys, Network)${CL:-}" echo -e "${TAB:- }${YW:-}💡 Configure in Proxmox UI: VM ${vmid} > Cloud-Init${CL:-}"
else else
echo "" echo ""
echo "[INFO] Cloud-Init Configuration:" echo "[INFO] Cloud-Init Configuration:"
echo " User: ${CLOUDINIT_USER:-root}" echo " User: ${CLOUDINIT_USER:-root}"
echo " Password: ${CLOUDINIT_PASSWORD}" echo " Password: ${CLOUDINIT_PASSWORD}"
echo " Credentials: ${CLOUDINIT_CRED_FILE}" echo " Credentials: ${CLOUDINIT_CRED_FILE}"
echo " You can configure Cloud-Init settings in Proxmox UI:" echo " ⚠️ Delete credentials file after noting password!"
echo " VM ${vmid} > Cloud-Init > Edit" echo " Configure in Proxmox UI: VM ${vmid} > Cloud-Init"
fi fi
fi fi
} }
# ============================================================================== # ------------------------------------------------------------------------------
# Check if VM has Cloud-Init configured # cleanup_cloud_init_credentials - Remove credentials file
# ============================================================================== # ------------------------------------------------------------------------------
# Usage: cleanup_cloud_init_credentials
# Call this after user has noted/saved the credentials
# ------------------------------------------------------------------------------
function cleanup_cloud_init_credentials() {
if [ -n "$CLOUDINIT_CRED_FILE" ] && [ -f "$CLOUDINIT_CRED_FILE" ]; then
rm -f "$CLOUDINIT_CRED_FILE"
_ci_msg_ok "Credentials file removed: $CLOUDINIT_CRED_FILE"
unset CLOUDINIT_CRED_FILE
return 0
fi
return 1
}
# ------------------------------------------------------------------------------
# has_cloud_init - Check if VM has Cloud-Init configured
# ------------------------------------------------------------------------------
function has_cloud_init() { function has_cloud_init() {
local vmid="$1" local vmid="$1"
qm config "$vmid" 2>/dev/null | grep -qE "(ide2|scsi1):.*cloudinit" qm config "$vmid" 2>/dev/null | grep -qE "(ide2|scsi1):.*cloudinit"
} }
# ============================================================================== # ------------------------------------------------------------------------------
# Regenerate Cloud-Init configuration # regenerate_cloud_init - Regenerate Cloud-Init configuration
# ============================================================================== # ------------------------------------------------------------------------------
function regenerate_cloud_init() { function regenerate_cloud_init() {
local vmid="$1" local vmid="$1"
if has_cloud_init "$vmid"; then if has_cloud_init "$vmid"; then
msg_info "Regenerating Cloud-Init configuration" 2>/dev/null || echo "[INFO] Regenerating Cloud-Init" _ci_msg_info "Regenerating Cloud-Init configuration"
qm cloudinit update "$vmid" >/dev/null 2>&1 || true qm cloudinit update "$vmid" >/dev/null 2>&1 || true
msg_ok "Cloud-Init configuration regenerated" 2>/dev/null || echo "[OK] Cloud-Init regenerated" _ci_msg_ok "Cloud-Init configuration regenerated"
return 0 return 0
else else
echo "Warning: VM $vmid does not have Cloud-Init configured" _ci_msg_warn "VM $vmid does not have Cloud-Init configured"
return 1 return 1
fi fi
} }
# ============================================================================== # ------------------------------------------------------------------------------
# Get VM IP address via qemu-guest-agent # get_vm_ip - Get VM IP address via qemu-guest-agent
# ============================================================================== # ------------------------------------------------------------------------------
function get_vm_ip() { function get_vm_ip() {
local vmid="$1" local vmid="$1"
local timeout="${2:-30}" local timeout="${2:-30}"
@ -282,9 +408,9 @@ function get_vm_ip() {
return 1 return 1
} }
# ============================================================================== # ------------------------------------------------------------------------------
# Wait for Cloud-Init to complete (requires SSH access) # wait_for_cloud_init - Wait for Cloud-Init to complete (requires SSH access)
# ============================================================================== # ------------------------------------------------------------------------------
function wait_for_cloud_init() { function wait_for_cloud_init() {
local vmid="$1" local vmid="$1"
local timeout="${2:-300}" local timeout="${2:-300}"
@ -296,40 +422,45 @@ function wait_for_cloud_init() {
fi fi
if [ -z "$vm_ip" ]; then if [ -z "$vm_ip" ]; then
echo "Warning: Unable to determine VM IP address" _ci_msg_warn "Unable to determine VM IP address"
return 1 return 1
fi fi
msg_info "Waiting for Cloud-Init to complete on ${vm_ip}" 2>/dev/null || echo "[INFO] Waiting for Cloud-Init on ${vm_ip}" _ci_msg_info "Waiting for Cloud-Init to complete on ${vm_ip}"
local elapsed=0 local elapsed=0
while [ $elapsed -lt $timeout ]; do while [ $elapsed -lt $timeout ]; do
if timeout 10 ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ if timeout 10 ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
"${CLOUDINIT_USER:-root}@${vm_ip}" "cloud-init status --wait" 2>/dev/null; then "${CLOUDINIT_USER:-root}@${vm_ip}" "cloud-init status --wait" 2>/dev/null; then
msg_ok "Cloud-Init completed successfully" 2>/dev/null || echo "[OK] Cloud-Init completed" _ci_msg_ok "Cloud-Init completed successfully"
return 0 return 0
fi fi
sleep 10 sleep 10
elapsed=$((elapsed + 10)) elapsed=$((elapsed + 10))
done done
echo "Warning: Cloud-Init did not complete within ${timeout}s" _ci_msg_warn "Cloud-Init did not complete within ${timeout}s"
return 1 return 1
} }
# ============================================================================== # ==============================================================================
# Export all functions for use in other scripts # SECTION 6: EXPORTS
# ============================================================================== # ==============================================================================
# Export all functions for use in other scripts
export -f setup_cloud_init 2>/dev/null || true export -f setup_cloud_init 2>/dev/null || true
export -f configure_cloud_init_interactive 2>/dev/null || true export -f configure_cloud_init_interactive 2>/dev/null || true
export -f display_cloud_init_info 2>/dev/null || true export -f display_cloud_init_info 2>/dev/null || true
export -f cleanup_cloud_init_credentials 2>/dev/null || true
export -f has_cloud_init 2>/dev/null || true export -f has_cloud_init 2>/dev/null || true
export -f regenerate_cloud_init 2>/dev/null || true export -f regenerate_cloud_init 2>/dev/null || true
export -f get_vm_ip 2>/dev/null || true export -f get_vm_ip 2>/dev/null || true
export -f wait_for_cloud_init 2>/dev/null || true export -f wait_for_cloud_init 2>/dev/null || true
export -f validate_ip_cidr 2>/dev/null || true
export -f validate_ip 2>/dev/null || true
# ============================================================================== # ==============================================================================
# Quick Start Examples # SECTION 7: EXAMPLES & DOCUMENTATION
# ============================================================================== # ==============================================================================
: <<'EXAMPLES' : <<'EXAMPLES'
@ -361,4 +492,14 @@ if [ "$START_VM" = "yes" ]; then
wait_for_cloud_init "$VMID" 300 wait_for_cloud_init "$VMID" 300
fi fi
# Example 7: Cleanup credentials file after user has noted password
display_cloud_init_info "$VMID" "$HN"
read -p "Have you saved the credentials? (y/N): " -r
[[ $REPLY =~ ^[Yy]$ ]] && cleanup_cloud_init_credentials
# Example 8: Validate IP before using
if validate_ip_cidr "192.168.1.100/24"; then
echo "Valid IP/CIDR"
fi
EXAMPLES EXAMPLES