Sync all scripts from website downloads — 352 scripts total

Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
This commit is contained in:
2026-05-25 03:31:08 +02:00
parent dbd6bf0324
commit a1a17e81a1
332 changed files with 174509 additions and 1106 deletions
+752
View File
@@ -0,0 +1,752 @@
#!/bin/bash
#############################################################
#### YUM/DNF Package Updates Exporter for Prometheus ####
#### Expose pending yum/dnf updates as Prometheus ####
#### metrics for RHEL, Rocky, Alma, and CentOS servers ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version: 3.8 ####
#### ####
#### Usage: ./yum-updates-exporter.sh ####
#############################################################
show_usage() {
cat <<EOF
Usage: $0 [OPTIONS]
Export pending YUM/DNF package updates as Prometheus metrics (v3.8).
MODES:
--textfile Write to node_exporter textfile collector (atomic write)
(default) Output metrics to stdout
OPTIONS:
-o, --output Output file path
-h, --help Show this help message
ENVIRONMENT VARIABLES:
NODE_EXPORTER_DIR Textfile collector directory (default: /var/lib/node_exporter)
AUTO_UPDATE Enable automatic package updates (default: 0)
AUTO_UPDATE_DELAY_DAYS Days to wait before auto-updating (default: 3)
USE_EXCLUSIONS Enable package exclusion list (default: 0)
EXCLUSION_FILE Path to exclusion list (default: /etc/yum/updates_exclude.conf)
CONFIG_FILE Optional config file (default: /etc/yum/yum_updates.conf)
DEBUG Enable debug output (default: 0)
METRICS:
- node_upgrades_pending Pending updates by repository origin
- node_upgrade_list Per-package upgrade details (name, version, repo)
- node_reboot_required Whether a reboot is required
- node_auto_updated_packages Packages updated in last auto-update
EXAMPLES:
sudo $0 # Output to stdout
sudo $0 --textfile # Write to textfile collector
sudo $0 -o /tmp/updates.prom # Write to custom file
AUTO_UPDATE=1 sudo $0 # Enable auto-updates
EOF
exit 0
}
OUTPUT_FILE=""
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help) show_usage ;;
--textfile) OUTPUT_FILE="${NODE_EXPORTER_DIR:-/var/lib/node_exporter}/yum_updates.prom"; shift ;;
-o|--output) OUTPUT_FILE="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
}
parse_args "$@"
set -o pipefail
#############################################
#### CONFIGURATION VARIABLES ####
#############################################
# Cron Configuration
readonly CRONTAB_USER="${CRONTAB_USER:-root}" # Cron User (default: root)
readonly CRON_NAME="${CRON_NAME:-yum_updates.sh}" # Cron Job Name (default: yum_updates.sh)
readonly UPDATE_INTERVAL="${UPDATE_INTERVAL:-0 0 * * * }" # Cron Update Interval (daily - default: 0 0 * * * )
# File Paths
readonly CONFIG_FILE="${CONFIG_FILE:-/etc/yum/yum_updates.conf}" # Config File Location
readonly EXCLUSION_FILE="${EXCLUSION_FILE:-/etc/yum/updates_exclude.conf}" # Exclusion List Location
readonly LOCK_DIR="${LOCK_DIR:-/var/run}" # Lock Directory
readonly LOG_FILE="${LOG_FILE:-/var/log/yum_updates.log}" # Log File Location
readonly TMP_DIR="${TMP_DIR:-/tmp}" # Temporary Directory
# Prometheus Configuration
readonly NODE_EXPORTER_DIR="${NODE_EXPORTER_DIR:-/var/lib/node_exporter}" # Node Exporter Directory
readonly PROMETHEUS_USER="${PROMETHEUS_USER:-prometheus}" # Prometheus User
# Script Configuration
readonly SCRIPT_PATH="$(readlink -f "$0")" # Script Path
readonly DEBUG="${DEBUG:-0}" # Debug Mode (0 Off/ 1 On)
readonly AUTO_UPDATE="${AUTO_UPDATE:-0}" # Auto Update Switch (default: 0 - off)
readonly AUTO_UPDATE_DELAY_DAYS="${AUTO_UPDATE_DELAY_DAYS:-3}" # Days to wait before auto-update (default: 3)
readonly USE_EXCLUSIONS="${USE_EXCLUSIONS:-0}" # Exclusion List Switch (default: 0 - off)
# Runtime Variables - set during script execution
LOCKFILE="" # Path to process lock file
TEMP_YUM_OUTPUT="" # Temporary file for package manager output
SKIP_REBOOT_CHECK=0 # Flag to skip reboot checking if tools unavailable
LAST_UPDATE_FILE="" # Path to file storing last update timestamp
LAST_UPDATED_PACKAGES_FILE="" # Path to file storing list of last auto-updated packages
# Command Paths - populated by find_commands function
# Stores full paths to required system commands
declare -A COMMANDS
# Exclusion List - populated by load_exclusions function
# Contains package patterns to exclude from updates/metrics
declare -a EXCLUSIONS
#############################################
#### UTILITY FUNCTIONS ####
#############################################
# Error handling function that logs error and exits with specified code
# Args: $1 = error message, $2 = exit code (defaults to 1)
handle_error() {
local err_msg="$1"
local exit_code="${2:-1}"
echo "ERROR: $err_msg" >&2
exit "$exit_code"
}
# Logging function that outputs to stdout and optionally to log file
# Args: $1 = log level (INFO, WARNING, ERROR, DEBUG), $2 = message
# DEBUG messages only shown when DEBUG=1
log() {
local level="$1"
local message="$2"
local log_entry="[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message"
# Show all levels except DEBUG (unless DEBUG mode is enabled)
if [[ "$level" != "DEBUG" || "$DEBUG" -eq 1 ]]; then
# Always write to stderr for console output
echo "$log_entry" >&2
# Always append to log file (regardless of stdout redirection)
if [[ -n "$LOG_FILE" ]]; then
echo "$log_entry" >> "$LOG_FILE" 2>/dev/null || true
fi
fi
}
# Rotate log file if it exceeds 10MB to prevent unlimited growth
# Moves current log to .old extension
rotate_log_if_needed() {
# Check if log file exists and is larger than 10MB (10485760 bytes)
if [[ -f "$LOG_FILE" && $(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0) -gt 10485760 ]]; then
mv "$LOG_FILE" "${LOG_FILE}.old" 2>/dev/null || true
fi
}
# Find command in PATH or fallback locations and verify it's executable
# Args: $1 = command name, $2+ = fallback directory paths to search
# Returns: full path to executable command
find_command() {
local command_name="$1"
local fallback_paths=("${@:2}")
local path
# First try to find command in PATH
path=$(command -v "$command_name" 2>/dev/null)
if [[ -z "$path" ]]; then
# If not found in PATH, search fallback directories
for fallback in "${fallback_paths[@]}"; do
if [[ -x "$fallback/$command_name" ]]; then
echo "$fallback/$command_name"
return 0
fi
done
handle_error "Could not find '$command_name' executable"
fi
# Verify the found command is executable
if [[ ! -x "$path" ]]; then
handle_error "Found '$command_name' at '$path' but it's not executable"
fi
echo "$path"
}
#############################################
#### INITIALIZATION FUNCTIONS ####
#############################################
# Load optional configuration file to override default variables, allows customization of script behavior without modifying the script
load_config() {
if [[ -f "$CONFIG_FILE" ]]; then
# shellcheck disable=SC1090
if ! source "$CONFIG_FILE"; then
log "WARNING" "Failed to load configuration from $CONFIG_FILE"
fi
fi
}
# Load package exclusion patterns from file to filter updates and prevent specific packages from being reported or updated
load_exclusions() {
EXCLUSIONS=()
if [[ "$USE_EXCLUSIONS" -eq 1 && -f "$EXCLUSION_FILE" ]]; then
log "INFO" "Loading package exclusions from $EXCLUSION_FILE"
while IFS= read -r line; do
# Skip empty lines and comments (lines starting with #)
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
# Remove leading/trailing whitespace from package names
line=$(echo "$line" | "${COMMANDS[sed]}" 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ -n "$line" ]] && EXCLUSIONS+=("$line")
done < "$EXCLUSION_FILE"
log "INFO" "Loaded ${#EXCLUSIONS[@]} package exclusions"
elif [[ "$USE_EXCLUSIONS" -eq 0 ]]; then
log "INFO" "Package exclusions disabled"
else
log "INFO" "No exclusion file found at $EXCLUSION_FILE"
fi
}
# Locate all required system commands and store their paths, and ensures all necessary tools are available before proceeding
find_commands() {
# Essential commands required for package parsing and metrics generation
local required_commands=(
"awk:/usr/bin"
"sed:/usr/bin"
"sort:/usr/bin"
"uniq:/usr/bin"
"xargs:/usr/bin"
)
# Find and validate each required command
for cmd_info in "${required_commands[@]}"; do
local cmd="${cmd_info%:*}"
local fallback="${cmd_info#*:}"
COMMANDS["$cmd"]=$(find_command "$cmd" "$fallback")
done
# Detect and locate the package manager (yum or dnf)
if command -v yum >/dev/null 2>&1; then
COMMANDS["pkg_mgr"]=$(find_command yum /usr/bin)
elif command -v dnf >/dev/null 2>&1; then
COMMANDS["pkg_mgr"]=$(find_command dnf /usr/bin)
else
handle_error "Neither yum nor dnf found"
fi
# Optional: Find needs-restarting for reboot requirement detection
if command -v needs-restarting >/dev/null 2>&1; then
COMMANDS["needs_restarting"]=$(command -v needs-restarting)
else
log "WARNING" "needs-restarting not found. Install yum-utils/dnf-utils to enable reboot checks"
SKIP_REBOOT_CHECK=1
fi
}
validate_environment() {
# Define validation patterns for environment variables (var_name:regex_pattern)
local validations=(
"CRONTAB_USER:^[a-z_][a-z0-9_-]*$"
"PROMETHEUS_USER:^[a-z_][a-z0-9_-]*$"
)
# Validate each environment variable against its pattern
for validation in "${validations[@]}"; do
local var="${validation%:*}" # Extract variable name
local pattern="${validation#*:}" # Extract regex pattern
local value="${!var}" # Get variable value via indirect expansion
# Check if value exists and doesn't match the pattern
if [[ -n "$value" && ! "$value" =~ $pattern ]]; then
handle_error "Invalid format for $var: $value"
fi
done
# Ensure required environment variables are set
[[ -z "$NODE_EXPORTER_DIR" || -z "$PROMETHEUS_USER" ]] &&
handle_error "Required environment variables not set"
# Verify TMP directory exists and is accessible
[[ ! -d "$TMP_DIR" ]] &&
handle_error "TMP directory does not exist or is not writable"
}
setup_locking() {
# Verify lock directory exists and is accessible
[[ ! -d "$LOCK_DIR" ]] &&
handle_error "Lock directory does not exist or is not writable"
# Clean up old lock files older than 60 minutes to prevent accumulation
find "$LOCK_DIR" -name "updates.*" -type f -mmin +60 -delete 2>/dev/null || true
# Create a unique temporary lock file in the lock directory
LOCKFILE=$(mktemp -p "$LOCK_DIR" updates.XXXXXX) ||
handle_error "Failed to create lock file"
# Open file descriptor 9 for the lock file and attempt to acquire exclusive lock
exec 9>"$LOCKFILE"
if ! flock -n 9; then
handle_error "Script is already running (unable to acquire lock)"
fi
}
# Create and configure the node exporter directory with proper permissions
setup_directories() {
# Create directory if it doesn't exist (only if running as root)
if [[ ! -d "$NODE_EXPORTER_DIR" ]]; then
if [[ "$(id -u)" = "0" ]]; then
mkdir -p "$NODE_EXPORTER_DIR" ||
handle_error "Failed to create $NODE_EXPORTER_DIR"
# Set ownership to prometheus user for security
chown "${PROMETHEUS_USER}" "$NODE_EXPORTER_DIR" 2>/dev/null ||
log "WARNING" "Failed to set ownership of $NODE_EXPORTER_DIR"
fi
fi
# Verify directory is writable before proceeding
[[ ! -w "$NODE_EXPORTER_DIR" ]] &&
handle_error "$NODE_EXPORTER_DIR is not writable"
}
# Install cron job to run script periodically and output metrics
setup_cron() {
# Only install cron if script exists and not already scheduled
if [[ -f "$SCRIPT_PATH" ]] && ! crontab -l 2>/dev/null | grep -q "$SCRIPT_PATH"; then
# Create cron entry that redirects metrics to prometheus file, logs to log file
local cron_entry="$UPDATE_INTERVAL $SCRIPT_PATH > $NODE_EXPORTER_DIR/updates.prom 2>> $LOG_FILE"
local existing_cron
existing_cron=$(crontab -u "$CRONTAB_USER" -l 2>/dev/null || echo '')
# Add new cron entry to existing crontab
if ! echo -e "${existing_cron}\n${cron_entry}" | crontab -u "$CRONTAB_USER" -; then
log "WARNING" "Failed to install cron job for user $CRONTAB_USER"
fi
fi
}
# Create temporary file for capturing yum command output
setup_temp_files() {
TEMP_YUM_OUTPUT=$(mktemp -p "$TMP_DIR" yum_output.XXXXXX) ||
handle_error "Failed to create temporary file"
}
# Set up update tracking file for delay functionality
setup_update_tracking() {
LAST_UPDATE_FILE="$TMP_DIR/yum_updates_last_check"
LAST_UPDATED_PACKAGES_FILE="$TMP_DIR/yum_updates_last_packages"
}
# Clean up lock files and temporary files on script exit
cleanup() {
# Release file lock and clean up lock file
[[ -n "$LOCKFILE" ]] && {
flock -u 9 2>/dev/null || true
exec 9>&- 2>/dev/null || true
rm -f "$LOCKFILE" 2>/dev/null || true
}
# Remove temporary yum output file
[[ -n "$TEMP_YUM_OUTPUT" ]] && rm -f "$TEMP_YUM_OUTPUT" 2>/dev/null || true
}
#############################################
#### METRICS COLLECTION FUNCTIONS ####
#############################################
# Check if package manager is already running any command (not just check-update)
# Prevents conflicts and ensures clean execution by waiting for completion
check_package_manager_running() {
local pkg_mgr_name
pkg_mgr_name=$(basename "${COMMANDS[pkg_mgr]}")
# Look for any existing package manager processes to avoid conflicts
if pgrep -x "$pkg_mgr_name" >/dev/null 2>&1; then
log "WARNING" "$pkg_mgr_name is already running"
# Wait up to 10 minutes (600 seconds) for the existing process to complete
local timeout=600
local elapsed=0
while pgrep -x "$pkg_mgr_name" >/dev/null 2>&1 && [[ $elapsed -lt $timeout ]]; do
log "INFO" "Waiting for existing $pkg_mgr_name process to complete..."
sleep 30
elapsed=$((elapsed + 30))
done
# If still running after timeout, abort to prevent system overload
if pgrep -x "$pkg_mgr_name" >/dev/null 2>&1; then
handle_error "$pkg_mgr_name is still running after ${timeout}s timeout"
fi
fi
}
# Check if package manager is already running check-update command
# Prevents conflicts and ensures clean execution by waiting for completion
check_yum_running() {
local pkg_mgr_name
pkg_mgr_name=$(basename "${COMMANDS[pkg_mgr]}")
# Look for existing check-update processes to avoid conflicts
if pgrep -f "${pkg_mgr_name}.*check-update" >/dev/null 2>&1; then
log "WARNING" "$pkg_mgr_name check-update is already running"
# Wait up to 5 minutes (300 seconds) for the existing process to complete
local timeout=300
local elapsed=0
while pgrep -f "${pkg_mgr_name}.*check-update" >/dev/null 2>&1 && [[ $elapsed -lt $timeout ]]; do
log "INFO" "Waiting for existing $pkg_mgr_name check-update to complete..."
sleep 10
elapsed=$((elapsed + 10))
done
# If still running after timeout, abort to prevent system overload
if pgrep -f "${pkg_mgr_name}.*check-update" >/dev/null 2>&1; then
handle_error "$pkg_mgr_name check-update is still running after ${timeout}s timeout"
fi
fi
}
run_package_check() {
local attempts=3
local attempt=1
# Check if yum/dnf is already running before we start
check_yum_running
while [[ $attempt -le $attempts ]]; do
# Redirect yum output to temp file, errors to log file to prevent metric pollution
if timeout 300 "${COMMANDS[pkg_mgr]}" -q check-update > "$TEMP_YUM_OUTPUT" 2>> "$LOG_FILE"; then
return 0
elif [[ $? -eq 124 ]]; then
log "WARNING" "Attempt $attempt: Package check timed out after 300 seconds"
((attempt++))
sleep 10
else
return 1
fi
done
return 1
}
apply_exclusions() {
local input="$1"
local result="$input"
if [[ "$USE_EXCLUSIONS" -eq 1 && ${#EXCLUSIONS[@]} -gt 0 ]]; then
for exclusion in "${EXCLUSIONS[@]}"; do
# Match package name followed by dot, dash, whitespace, or end of line
# This handles package formats like: kernel.x86_64, kernel-core.x86_64, etc.
result=$(echo "$result" | "${COMMANDS[sed]}" "/^${exclusion}\\([.-]\\|[[:space:]]\\|$\\)/d")
done
fi
echo "$result"
}
# Collect package upgrade counts grouped by repository origin, then generates Prometheus metrics showing available updates per repository
collect_upgrade_counts() {
local output raw_output
# Get package manager output and format into 3-column format
raw_output=$("${COMMANDS[pkg_mgr]}" -q check-update |
"${COMMANDS[xargs]}" -n3 |
"${COMMANDS[sed]}" '/Obsoleting Packages/q')
# Apply package exclusions, then count updates by repository origin
output=$(apply_exclusions "$raw_output" |
"${COMMANDS[sort]}" |
"${COMMANDS[uniq]}" -c |
"${COMMANDS[awk]}" '{print "node_upgrades_pending{origin=\""$2"\"} "$1}')
# Return zero metric if no updates found
if [[ -z "$output" ]]; then
log "WARNING" "No upgrade count data found"
echo 'node_upgrades_pending{origin=""} 0'
return 1
fi
echo "$output"
}
collect_upgrade_list() {
local output raw_output
raw_output=$("${COMMANDS[pkg_mgr]}" -q check-update |
"${COMMANDS[awk]}" '!seen[$0]++' |
"${COMMANDS[sed]}" '/Obsoleting Packages/q' |
"${COMMANDS[sed]}" '/@/d')
# Apply exclusions before processing with awk
output=$(apply_exclusions "$raw_output" |
"${COMMANDS[awk]}" 'NF>=3 && NR>1 {print "node_upgrade_list{pkgname=\"" $1 "\", uvers=\"" $2 "\", repo=\"" $3 "\"}", 1}')
if [[ -z "$output" ]]; then
echo 'node_upgrade_list{pkgname=""} 0'
return 1
fi
echo "$output"
}
check_reboot_required() {
if [[ "$SKIP_REBOOT_CHECK" -eq 1 || ! -x "${COMMANDS[needs_restarting]:-}" ]]; then
return 0
fi
echo '# HELP node_reboot_required Boolean indicator (0/1) if system requires reboot after updates.'
echo '# TYPE node_reboot_required gauge'
if timeout 60 "${COMMANDS[needs_restarting]}" -r >/dev/null 2>&1; then
echo 'node_reboot_required 0'
else
echo 'node_reboot_required 1'
fi
}
# Output metrics for last auto-updated packages
output_last_updated_packages() {
echo '# HELP node_auto_updated_packages Information about packages that were last auto-updated.'
echo '# TYPE node_auto_updated_packages gauge'
if [[ ! -f "$LAST_UPDATED_PACKAGES_FILE" ]]; then
echo 'node_auto_updated_packages{packages="none",timestamp="0"} 0'
return 0
fi
local timestamp packages
timestamp=$(grep "^timestamp:" "$LAST_UPDATED_PACKAGES_FILE" 2>/dev/null | cut -d: -f2- || echo "0")
packages=$(grep "^packages:" "$LAST_UPDATED_PACKAGES_FILE" 2>/dev/null | cut -d: -f2- || echo "none")
# Escape quotes in package names for Prometheus label
packages=$(echo "$packages" | "${COMMANDS[sed]}" 's/"/\\"/g')
# Output metric with package list and timestamp as labels
echo "node_auto_updated_packages{packages=\"$packages\",timestamp=\"$timestamp\"} 1"
}
#############################################
#### AUTO-UPDATE FUNCTIONS ####
#############################################
# Check if sufficient time has passed since last update check
check_update_delay() {
if [[ ! -f "$LAST_UPDATE_FILE" ]]; then
log "INFO" "No previous update timestamp found, proceeding with update"
return 0
fi
local last_update
last_update=$(cat "$LAST_UPDATE_FILE" 2>/dev/null || echo 0)
local current_time
current_time=$(date +%s)
local delay_seconds=$((AUTO_UPDATE_DELAY_DAYS * 86400))
local time_diff=$((current_time - last_update))
if [[ $time_diff -lt $delay_seconds ]]; then
local remaining_hours=$(((delay_seconds - time_diff) / 3600))
log "INFO" "Update delay active: $remaining_hours hours remaining before next auto-update"
return 1
fi
return 0
}
# Record timestamp of successful update for delay tracking
record_update_timestamp() {
date +%s > "$LAST_UPDATE_FILE" ||
log "WARNING" "Failed to record update timestamp"
}
# Record list of packages that were updated during auto-update
record_updated_packages() {
local updated_packages="$1"
local current_time
current_time=$(date +%s)
# Create a structured record with timestamp and package list
{
echo "timestamp:$current_time"
echo "packages:$updated_packages"
} > "$LAST_UPDATED_PACKAGES_FILE" ||
log "WARNING" "Failed to record updated packages list"
}
# Automatically update packages if AUTO_UPDATE is enabled, applies the same exclusions as metrics collection
perform_auto_update() {
if [[ "$AUTO_UPDATE" -ne 1 ]]; then
log "DEBUG" "Auto-update disabled, skipping"
return 0
fi
# Check if delay period has passed since last update
if ! check_update_delay; then
return 0
fi
log "INFO" "Starting automatic package updates"
# Verify root privileges are available for package updates
if [[ "$(id -u)" -ne 0 ]]; then
log "ERROR" "Auto-update requires root privileges"
return 1
fi
# Build update command with automatic confirmation
local update_command=("${COMMANDS[pkg_mgr]}" "-y" "update")
# Apply same exclusions used in metrics collection
if [[ "$USE_EXCLUSIONS" -eq 1 && ${#EXCLUSIONS[@]} -gt 0 ]]; then
log "INFO" "Applying exclusions to auto-update: ${EXCLUSIONS[*]}"
for exclusion in "${EXCLUSIONS[@]}"; do
update_command+=("--exclude=${exclusion}")
done
fi
log "INFO" "Executing: ${update_command[*]}"
# Create separate log file for update output
local update_log="${LOG_FILE%.log}_autoupdate.log"
local temp_update_output="${TMP_DIR}/update_output.tmp"
# Execute update with 30-minute timeout to prevent hanging, capture output
if timeout 1800 "${update_command[@]}" > "$temp_update_output" 2>&1; then
log "INFO" "Auto-update completed successfully"
# Extract updated package list from output
local updated_packages
updated_packages=$(grep -E "^Updated:|^Upgraded:" "$temp_update_output" | "${COMMANDS[sed]}" 's/^Updated://;s/^Upgraded://' | tr '\n' ',' | "${COMMANDS[sed]}" 's/,$//')
# Record successful update timestamp for delay tracking
record_update_timestamp
# Record the list of updated packages
record_updated_packages "$updated_packages"
# Also append to main update log
cat "$temp_update_output" >> "$update_log"
rm -f "$temp_update_output"
# Check if system reboot is required after updates
if [[ "$SKIP_REBOOT_CHECK" -eq 0 && -x "${COMMANDS[needs_restarting]:-}" ]]; then
if ! timeout 60 "${COMMANDS[needs_restarting]}" -r >/dev/null 2>&1; then
log "WARNING" "System requires reboot after updates"
fi
fi
return 0
else
local exit_code=$?
# Clean up temp file on failure
rm -f "$temp_update_output"
if [[ $exit_code -eq 124 ]]; then
log "ERROR" "Auto-update timed out after 30 minutes"
else
log "ERROR" "Auto-update failed with exit code $exit_code"
fi
return $exit_code
fi
}
#############################################
#### MAIN EXECUTION FLOW ####
#############################################
output_upgrade_metrics() {
local upgrades upgradelist
echo '# HELP node_upgrades_pending Count of pending package updates by repository origin.'
echo '# TYPE node_upgrades_pending gauge'
if upgrades=$(collect_upgrade_counts); then
echo "$upgrades"
else
log "ERROR" "Failed to collect upgrade counts"
echo 'node_upgrades_pending{origin=""} 0'
fi
echo '# HELP node_upgrade_list Detailed list of available package updates including version info.'
echo '# TYPE node_upgrade_list gauge'
if upgradelist=$(collect_upgrade_list); then
echo "$upgradelist"
else
log "ERROR" "Failed to collect upgrade list"
echo 'node_upgrade_list{pkgname=""} 0'
fi
}
# Main execution function that sets the environment, performs updates if enabled, and collects metrics
main() {
# Set up cleanup handlers for graceful shutdown
trap cleanup EXIT INT TERM PIPE
# Initialize script environment and configuration
rotate_log_if_needed
load_config
find_commands
# Check if package manager is already running before proceeding
check_package_manager_running
load_exclusions
validate_environment
setup_locking
setup_directories
setup_cron
setup_temp_files
setup_update_tracking
log "INFO" "Starting package update metrics collection"
# Execute automatic updates before collecting metrics (if enabled)
# This ensures metrics reflect post-update state
if [[ "$AUTO_UPDATE" -eq 1 ]]; then
perform_auto_update
fi
# Generate Prometheus metrics for package updates
output_upgrade_metrics
check_reboot_required
output_last_updated_packages
log "INFO" "Package update metrics collection completed"
}
#############################################
#### SCRIPT EXECUTION ####
#############################################
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
if [[ -n "$OUTPUT_FILE" ]]; then
output_dir="$(dirname "$OUTPUT_FILE")"
mkdir -p "$output_dir"
temp_file=$(mktemp "${output_dir}/.yum_updates_metrics.XXXXXX")
if ! main "$@" > "$temp_file" 2>/dev/null; then
rm -f "$temp_file"
echo "ERROR: Failed to generate metrics" >&2
exit 1
fi
file_lines=$(wc -l < "$temp_file" 2>/dev/null || echo 0)
if [[ "$file_lines" -lt 5 ]]; then
rm -f "$temp_file"
echo "ERROR: Metrics file too small ($file_lines lines), keeping previous" >&2
exit 1
fi
chmod 644 "$temp_file"
mv -f "$temp_file" "$OUTPUT_FILE"
echo "Metrics written to $OUTPUT_FILE ($file_lines lines)" >&2
else
main "$@"
fi
fi