#!/usr/bin/env bash # proxmox-api -- Make authenticated Proxmox API calls # # Usage: proxmox-api METHOD PATH [CURL_ARGS...] # # Handles the ! in automation@pve!deploy safely (no bash history expansion). # Config: proxmox.yaml (host, api_token_id), env file (PROXMOX_TOKEN_SECRET) # # Outputs JSON response on stdout. set -euo pipefail # --------------------------------------------------------------------------- # Usage # --------------------------------------------------------------------------- usage() { cat <<'EOF' Usage: proxmox-api METHOD PATH [CURL_ARGS...] Make authenticated Proxmox API calls with proper token handling. Arguments: METHOD HTTP method (GET, POST, PUT, DELETE) PATH API path (e.g., /nodes/pve/qemu) CURL_ARGS Additional curl arguments (e.g., -d 'key=val') Options: --json Pretty-print JSON output --help, -h Show this help Environment: PROXMOX_TOKEN_SECRET API token secret (from env file) Config: proxmox.yaml Proxmox host + api_token_id (auto-discovered) Examples: proxmox-api GET /nodes/pve/qemu proxmox-api GET /nodes/pve/qemu/900/status/current --json proxmox-api POST /nodes/pve/qemu/900/status/stop proxmox-api GET /cluster/resources --json EOF exit 0 } if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then usage fi # --------------------------------------------------------------------------- # Parse arguments # --------------------------------------------------------------------------- PRETTY=false ARGS=() for arg in "$@"; do if [[ "$arg" == "--json" ]]; then PRETTY=true else ARGS+=("$arg") fi done if [[ ${#ARGS[@]} -lt 2 ]]; then echo "Error: METHOD and PATH required. Usage: proxmox-api METHOD PATH [CURL_ARGS...]" >&2 exit 1 fi METHOD="${ARGS[0]}" API_PATH="${ARGS[1]}" EXTRA_ARGS=("${ARGS[@]:2}") # --------------------------------------------------------------------------- # Load environment # --------------------------------------------------------------------------- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" load_env() { # Already exported (user sourced envs/*/env) [[ -n "${PROXMOX_TOKEN_SECRET:-}" ]] && return # Check envs/*/env in repo root local repo_root repo_root=$(cd "$SCRIPT_DIR/../.." && pwd) for env_file in "${repo_root}"/envs/*/env; do if [[ -f "$env_file" ]]; then # shellcheck disable=SC1091 source "$env_file" [[ -n "${PROXMOX_TOKEN_SECRET:-}" ]] && return fi done # Walk up from script dir (backward compat) local dir="$SCRIPT_DIR" while [[ "$dir" != "/" ]]; do if [[ -f "${dir}/env" ]]; then # shellcheck disable=SC1091 source "${dir}/env" return fi dir="$(dirname "$dir")" done } load_env if [[ -z "${PROXMOX_TOKEN_SECRET:-}" ]]; then echo "Error: PROXMOX_TOKEN_SECRET not set. Create an env file or export it." >&2 exit 1 fi # --------------------------------------------------------------------------- # Load proxmox.yaml # --------------------------------------------------------------------------- yaml_get() { local file="$1" key="$2" default="${3:-}" local val val=$(awk -F': ' -v k="$key" '$1 == k {print $2; exit}' "$file" 2>/dev/null) || val="" val="${val#\"}" ; val="${val%\"}" val="${val#\'}" ; val="${val%\'}" echo "${val:-$default}" } find_proxmox_yaml() { local repo_root repo_root=$(cd "$SCRIPT_DIR/../.." && pwd) # Check envs/*/proxmox.yaml in repo root for f in "${repo_root}"/envs/*/proxmox.yaml; do if [[ -f "$f" ]]; then echo "$f" return fi done # Walk up from script dir (backward compat) local search_dirs=( "${SCRIPT_DIR}/.." "${repo_root}" "$(pwd)" ) for dir in "${search_dirs[@]}"; do if [[ -f "${dir}/proxmox.yaml" ]]; then echo "${dir}/proxmox.yaml" return fi done echo "" } PROXMOX_YAML=$(find_proxmox_yaml) if [[ -z "$PROXMOX_YAML" ]]; then echo "Error: proxmox.yaml not found" >&2 exit 1 fi PVE_HOST=$(yaml_get "$PROXMOX_YAML" "host") PVE_TOKEN_ID=$(yaml_get "$PROXMOX_YAML" "api_token_id") if [[ -z "$PVE_HOST" ]]; then echo "Error: proxmox.yaml: 'host' is required" >&2 exit 1 fi if [[ -z "$PVE_TOKEN_ID" ]]; then echo "Error: proxmox.yaml: 'api_token_id' is required" >&2 exit 1 fi # --------------------------------------------------------------------------- # Make API call # --------------------------------------------------------------------------- # Build the full auth header in a variable (avoids ! expansion issues) AUTH_HEADER="Authorization: PVEAPIToken=${PVE_TOKEN_ID}=${PROXMOX_TOKEN_SECRET}" URL="https://${PVE_HOST}:8006/api2/json${API_PATH}" CURL_CMD=(curl -fsSk -X "$METHOD" -H "$AUTH_HEADER") if [[ ${#EXTRA_ARGS[@]} -gt 0 ]]; then CURL_CMD+=("${EXTRA_ARGS[@]}") fi CURL_CMD+=("$URL") RESPONSE=$("${CURL_CMD[@]}" 2>&1) || { echo "Error: API call failed: $METHOD $API_PATH" >&2 echo "$RESPONSE" >&2 exit 1 } if [[ "$PRETTY" == true ]]; then echo "$RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE" else echo "$RESPONSE" fi