#!/bin/bash
################################################################################
# Script Name: update_pbs_fingerprint.sh
#
# Description:
# This script updates the fingerprint of a Proxmox Backup Server (PBS) datastore
# in Proxmox Virtual Environment (Proxmox VE). The fingerprint is refreshed daily
# due to the PBS using ACME certificates, which causes the Proxmox VE datastore
# to fail validation unless updated. The script fetches the latest PBS certificate
# fingerprint, updates the corresponding storage configuration in Proxmox VE,
# and verifies the update was successful.
#
# Environment:
# - Proxmox VE (PVE) Host: The server where the Proxmox VE instance is running.
# - Proxmox Backup Server (PBS): The server where the Proxmox Backup Server instance is running.
#
# Environment Variables:
# - PBS_HOST: The hostname or IP address of the Proxmox Backup Server (PBS).
# - PVE_HOST: The hostname or IP address of the Proxmox VE server.
# - PVE_API_TOKEN: The API token used to authenticate against the Proxmox VE API.
# - PVE_STORAGE_ID: The ID of the storage in Proxmox VE that points to the PBS datastore.
#
# Prerequisites:
# 1. Ensure that you have a valid API token created on the Proxmox VE server. The token must
# have sufficient privileges to update storage configurations.
# 2. The API token format is "user@realm!tokenname=API-Token-ID". Replace this
# with your actual API token.
# 3. The Proxmox Backup Server must be configured to use an ACME certificate or any other
# mechanism that refreshes its certificate periodically.
# 4. OpenSSL, curl, and jq must be installed on the system running this script.
#
# How it Works:
# 1. The script connects to the PBS server over port 8007 and fetches the current certificate
# fingerprint using OpenSSL.
# 2. It checks the current storage configuration and fingerprint in Proxmox VE.
# 3. If the fingerprint needs updating, it sends a PUT request to the Proxmox VE API.
# 4. The script then verifies the update by querying the Proxmox VE API and comparing the
# returned fingerprint with the one we just set.
# 5. If the update is successful and verified, the script outputs a success message;
# otherwise, it reports an error.
#
# Execution:
# This script is intended to run on the Proxmox VE server, but it can be executed from any
# server that has access to both the PBS and PVE hosts, provided that the API token is valid
# and the required commands (openssl, curl, jq) are available.
#
# Note:
# No restart of Proxmox VE services is required after updating the fingerprint.
# The new fingerprint takes effect immediately.
################################################################################
# Default debug mode off
DEBUG=0
# Function to log information
log_info() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO]: $1"
}
# Function to log warnings
log_warn() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [WARN]: $1"
}
# Function to log errors
log_error() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR]: $1" >&2
}
# Function to log debug information
log_debug() {
if [ $DEBUG -eq 1 ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') [DEBUG]: $1"
fi
}
# Function to check if a command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Parse command-line arguments
while getopts "d" opt; do
case $opt in
d)
DEBUG=1
log_info "Debug mode enabled."
;;
*)
log_error "Invalid option: -$OPTARG"
exit 1
;;
esac
done
# Check for required commands
for cmd in openssl curl jq; do
if ! command_exists "$cmd"; then
log_error "Required command '$cmd' not found. Please install it and try again."
exit 1
fi
done
# Set variables
PBS_HOST="pbs.com"
PVE_HOST="pve.com"
PVE_API_TOKEN="pbs@pam!update-pbs-fingerprint=xxxxxxxxxxxxxxxxxxxxxxxx"
PVE_STORAGE_ID="pbs"
# Validate environment variables
for var in PBS_HOST PVE_HOST PVE_API_TOKEN PVE_STORAGE_ID; do
if [ -z "${!var}" ]; then
log_error "$var is not set or is empty"
exit 1
fi
done
# Function to make API calls and display results
make_api_call() {
local method=$1
local endpoint=$2
local data=$3
local response
log_debug "Making $method request to $endpoint"
response=$(curl -s -X $method "https://${PVE_HOST}:8006/api2/json$endpoint" \
-H "Authorization: PVEAPIToken=${PVE_API_TOKEN}" \
-H "Content-Type: application/x-www-form-urlencoded" \
${data:+-d "$data"})
log_debug "Response: $response"
echo "$response" | jq .
}
# Fetch the new fingerprint
NEW_FINGERPRINT=$(echo | openssl s_client -connect ${PBS_HOST}:8007 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256 | cut
-d '=' -f 2)
# Check OpenSSL connection and fingerprint extraction
if [ $? -ne 0 ] || [ -z "$NEW_FINGERPRINT" ]; then
log_error "Unable to connect to PBS or extract fingerprint from the certificate"
exit 1
fi
# Validate fingerprint format (assuming SHA256 fingerprint format)
if ! [[ $NEW_FINGERPRINT =~ ^([0-9A-F]{2}:){31}[0-9A-F]{2}$ ]]; then
log_error "Extracted fingerprint does not match expected format"
exit 1
fi
log_info "New fingerprint: ${NEW_FINGERPRINT}"
# Check current storage configuration
log_info "Checking current storage configuration..."
CURRENT_CONFIG=$(make_api_call GET "/storage/${PVE_STORAGE_ID}")
log_debug "Current config response: $CURRENT_CONFIG"
CURRENT_FINGERPRINT=$(echo "$CURRENT_CONFIG" | jq -r '.data.fingerprint // empty' 2>/dev/null)
if [ $? -ne 0 ]; then
log_error "Error parsing current configuration JSON. Raw response:"
echo "$CURRENT_CONFIG"
exit 1
fi
log_info "Current fingerprint: ${CURRENT_FINGERPRINT}"
if [ "$CURRENT_FINGERPRINT" = "$NEW_FINGERPRINT" ]; then
log_info "Fingerprint is already up to date. No changes needed."
exit 0
fi
# Attempt to update the fingerprint
log_info "Attempting to update the fingerprint..."
UPDATE_RESPONSE=$(make_api_call PUT "/storage/${PVE_STORAGE_ID}" "fingerprint=${NEW_FINGERPRINT}")
log_debug "Update response: $UPDATE_RESPONSE"
# Check if the update was successful
if echo "$UPDATE_RESPONSE" | grep -q '"data":null'; then
log_error "Failed to update the fingerprint. API returned null data."
exit 1
fi
# Verify the fingerprint update
log_info "Verifying the fingerprint update..."
VERIFY_CONFIG=$(make_api_call GET "/storage/${PVE_STORAGE_ID}")
log_debug "Verify config response: $VERIFY_CONFIG"
UPDATED_FINGERPRINT=$(echo "$VERIFY_CONFIG" | jq -r '.data.fingerprint // empty' 2>/dev/null)
if [ $? -ne 0 ]; then
log_error "Error parsing verification configuration JSON. Raw response:"
echo "$VERIFY_CONFIG"
exit 1
fi
log_info "Updated fingerprint: ${UPDATED_FINGERPRINT}"
if [ "$UPDATED_FINGERPRINT" = "$NEW_FINGERPRINT" ]; then
log_info "Fingerprint successfully updated and verified."
else
log_error "Fingerprint update failed. Current fingerprint does not match the new fingerprint."
log_error "Current fingerprint: ${UPDATED_FINGERPRINT}"
log_error "Expected fingerprint: ${NEW_FINGERPRINT}"
exit 1
fi
log_info "Script execution completed successfully."