Files
AzerothCore-RealmMaster/scripts/bash/lib/common.sh
2025-12-02 21:43:05 -05:00

424 lines
11 KiB
Bash
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
#
# Common utilities library for AzerothCore RealmMaster scripts
# This library provides shared functions for environment variable reading,
# logging, error handling, and other common operations.
#
# Usage: source /path/to/scripts/bash/lib/common.sh
# Prevent multiple sourcing
if [ -n "${_COMMON_LIB_LOADED:-}" ]; then
return 0
fi
_COMMON_LIB_LOADED=1
# =============================================================================
# COLOR DEFINITIONS (Standardized across all scripts)
# =============================================================================
BLUE='\033[0;34m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Legacy color names for backward compatibility
COLOR_BLUE="$BLUE"
COLOR_GREEN="$GREEN"
COLOR_YELLOW="$YELLOW"
COLOR_RED="$RED"
COLOR_CYAN="$CYAN"
COLOR_RESET="$NC"
# =============================================================================
# LOGGING FUNCTIONS (Standardized with emoji)
# =============================================================================
# Log informational messages (blue with info icon)
info() {
printf '%b\n' "${BLUE} $*${NC}"
}
# Log success messages (green with checkmark)
ok() {
printf '%b\n' "${GREEN}$*${NC}"
}
# Log general messages (green, no icon - for clean output)
log() {
printf '%b\n' "${GREEN}$*${NC}"
}
# Log warning messages (yellow with warning icon, to stderr for compatibility)
warn() {
printf '%b\n' "${YELLOW}⚠️ $*${NC}" >&2
}
# Log error messages (red with error icon, continues execution)
err() {
printf '%b\n' "${RED}$*${NC}" >&2
}
# Log fatal error and exit (red with error icon, exits with code 1)
fatal() {
printf '%b\n' "${RED}$*${NC}" >&2
exit 1
}
# =============================================================================
# ENVIRONMENT VARIABLE READING
# =============================================================================
# Read environment variable from .env file with fallback to default
# Handles various quote styles, comments, and whitespace
#
# Usage:
# read_env KEY [DEFAULT_VALUE]
# value=$(read_env "MYSQL_PASSWORD" "default_password")
#
# Features:
# - Reads from file specified by $ENV_PATH (or $DEFAULT_ENV_PATH)
# - Strips leading/trailing whitespace
# - Removes inline comments (everything after #)
# - Handles double quotes, single quotes, and unquoted values
# - Returns default value if key not found
# - Returns value from environment variable if already set
#
read_env() {
local key="$1"
local default="${2:-}"
local value=""
# Check if variable is already set in environment (takes precedence)
if [ -n "${!key:-}" ]; then
echo "${!key}"
return 0
fi
# Determine which .env file to use
local env_file="${ENV_PATH:-${DEFAULT_ENV_PATH:-}}"
# Read from .env file if it exists
if [ -f "$env_file" ]; then
# Extract value using grep and cut, handling various formats
value="$(grep -E "^${key}=" "$env_file" 2>/dev/null | tail -n1 | cut -d'=' -f2- | tr -d '\r')"
# Remove inline comments (everything after # that's not inside quotes)
# This is a simplified approach - doesn't handle quotes perfectly but works for most cases
value="$(echo "$value" | sed 's/[[:space:]]*#.*//' | sed 's/[[:space:]]*$//')"
# Strip quotes if present
if [[ "$value" == \"*\" && "$value" == *\" ]]; then
# Double quotes
value="${value:1:-1}"
elif [[ "$value" == \'*\' && "$value" == *\' ]]; then
# Single quotes
value="${value:1:-1}"
fi
fi
# Use default if still empty
if [ -z "${value:-}" ]; then
value="$default"
fi
printf '%s\n' "${value}"
}
# Read value from .env.template file (used during setup)
# This is similar to read_env but specifically for template files
#
# Usage:
# get_template_value KEY [TEMPLATE_FILE]
# value=$(get_template_value "MYSQL_PASSWORD")
#
get_template_value() {
local key="$1"
local template_file="${2:-${TEMPLATE_FILE:-${TEMPLATE_PATH:-.env.template}}}"
if [ ! -f "$template_file" ]; then
fatal "Template file not found: $template_file"
fi
# Extract value, handling variable expansion syntax like ${VAR:-default}
local value
local raw_line
raw_line=$(grep "^${key}=" "$template_file" 2>/dev/null | head -1)
if [ -z "$raw_line" ]; then
err "Key '$key' not found in template: $template_file"
return 1
fi
value="${raw_line#*=}"
value=$(echo "$value" | sed 's/^"\(.*\)"$/\1/')
# Handle ${VAR:-default} syntax by extracting the default value
if [[ "$value" =~ ^\$\{[^}]*:-([^}]*)\}$ ]]; then
value="${BASH_REMATCH[1]}"
fi
echo "$value"
}
# Update or add environment variable in .env file
# Creates file if it doesn't exist
#
# Usage:
# update_env_value KEY VALUE [ENV_FILE]
# update_env_value "MYSQL_PASSWORD" "new_password"
#
update_env_value() {
local key="$1"
local value="$2"
local env_file="${3:-${ENV_PATH:-${DEFAULT_ENV_PATH:-.env}}}"
[ -n "$env_file" ] || return 0
# Create file if it doesn't exist
if [ ! -f "$env_file" ]; then
printf '%s=%s\n' "$key" "$value" >> "$env_file"
return 0
fi
# Update existing or append new
if grep -q "^${key}=" "$env_file"; then
# Use platform-appropriate sed in-place editing
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s|^${key}=.*|${key}=${value}|" "$env_file"
else
sed -i "s|^${key}=.*|${key}=${value}|" "$env_file"
fi
else
printf '\n%s=%s\n' "$key" "$value" >> "$env_file"
fi
}
# =============================================================================
# VALIDATION & REQUIREMENTS
# =============================================================================
# Require command to be available in PATH, exit with error if not found
#
# Usage:
# require_cmd docker
# require_cmd python3 jq git
#
require_cmd() {
for cmd in "$@"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
fatal "Missing required command: $cmd"
fi
done
}
# Check if command exists (returns 0 if exists, 1 if not)
#
# Usage:
# if has_cmd docker; then
# echo "Docker is available"
# fi
#
has_cmd() {
command -v "$1" >/dev/null 2>&1
}
# =============================================================================
# MYSQL/DATABASE HELPERS
# =============================================================================
# Execute MySQL command in Docker container
# Reads MYSQL_PW and container name from environment
#
# Usage:
# mysql_exec DATABASE_NAME < script.sql
# echo "SELECT 1;" | mysql_exec acore_auth
#
mysql_exec() {
local db="$1"
local mysql_pw="${MYSQL_ROOT_PASSWORD:-${MYSQL_PW:-azerothcore}}"
local container="${MYSQL_CONTAINER:-ac-mysql}"
docker exec -i "$container" mysql -uroot -p"$mysql_pw" "$db"
}
# Execute MySQL query and return result
# Outputs in non-tabular format suitable for parsing
#
# Usage:
# count=$(mysql_query "acore_characters" "SELECT COUNT(*) FROM characters")
#
mysql_query() {
local db="$1"
local query="$2"
local mysql_pw="${MYSQL_ROOT_PASSWORD:-${MYSQL_PW:-azerothcore}}"
local container="${MYSQL_CONTAINER:-ac-mysql}"
docker exec "$container" mysql -uroot -p"$mysql_pw" -N -B "$db" -e "$query" 2>/dev/null
}
# Check if MySQL container is healthy and accepting connections
#
# Usage:
# if mysql_is_ready; then
# echo "MySQL is ready"
# fi
#
mysql_is_ready() {
local container="${MYSQL_CONTAINER:-ac-mysql}"
local mysql_pw="${MYSQL_ROOT_PASSWORD:-${MYSQL_PW:-azerothcore}}"
docker exec "$container" mysqladmin ping -uroot -p"$mysql_pw" >/dev/null 2>&1
}
# Wait for MySQL to be ready with timeout
#
# Usage:
# mysql_wait_ready 60 # Wait up to 60 seconds
#
mysql_wait_ready() {
local timeout="${1:-30}"
local elapsed=0
info "Waiting for MySQL to be ready..."
while [ $elapsed -lt $timeout ]; do
if mysql_is_ready; then
ok "MySQL is ready"
return 0
fi
sleep 2
elapsed=$((elapsed + 2))
done
err "MySQL did not become ready within ${timeout}s"
return 1
}
# =============================================================================
# FILE & DIRECTORY HELPERS
# =============================================================================
# Ensure directory exists and is writable
# Creates directory if needed and sets permissions
#
# Usage:
# ensure_writable_dir /path/to/directory
#
ensure_writable_dir() {
local dir="$1"
if [ ! -d "$dir" ]; then
mkdir -p "$dir" 2>/dev/null || {
err "Failed to create directory: $dir"
return 1
}
fi
if [ ! -w "$dir" ]; then
chmod u+w "$dir" 2>/dev/null || {
err "Directory not writable: $dir"
return 1
}
fi
return 0
}
# Create backup of file before modification
#
# Usage:
# backup_file /path/to/important.conf
# # Creates /path/to/important.conf.backup.TIMESTAMP
#
backup_file() {
local file="$1"
if [ ! -f "$file" ]; then
warn "File does not exist, skipping backup: $file"
return 0
fi
local backup="${file}.backup.$(date +%Y%m%d_%H%M%S)"
cp "$file" "$backup" || {
err "Failed to create backup: $backup"
return 1
}
info "Created backup: $backup"
return 0
}
# =============================================================================
# GIT HELPERS
# =============================================================================
# Configure git identity if not already set
#
# Usage:
# setup_git_config [USERNAME] [EMAIL]
#
setup_git_config() {
local git_user="${1:-${GIT_USERNAME:-AzerothCore RealmMaster}}"
local git_email="${2:-${GIT_EMAIL:-noreply@azerothcore.org}}"
if ! git config --global user.name >/dev/null 2>&1; then
info "Configuring git identity: $git_user <$git_email>"
git config --global user.name "$git_user" || true
git config --global user.email "$git_email" || true
fi
}
# =============================================================================
# ERROR HANDLING UTILITIES
# =============================================================================
# Retry command with exponential backoff
#
# Usage:
# retry 5 docker pull myimage:latest
# retry 3 2 mysql_query "acore_auth" "SELECT 1" # 3 retries with 2s initial delay
#
retry() {
local max_attempts="$1"
shift
local delay="${1:-1}"
# Check if delay is a number, if not treat it as part of the command
if ! [[ "$delay" =~ ^[0-9]+$ ]]; then
delay=1
else
shift
fi
local attempt=1
local exit_code=0
while [ $attempt -le "$max_attempts" ]; do
if "$@"; then
return 0
fi
exit_code=$?
if [ $attempt -lt "$max_attempts" ]; then
warn "Command failed (attempt $attempt/$max_attempts), retrying in ${delay}s..."
sleep "$delay"
delay=$((delay * 2)) # Exponential backoff
fi
attempt=$((attempt + 1))
done
err "Command failed after $max_attempts attempts"
return $exit_code
}
# =============================================================================
# INITIALIZATION
# =============================================================================
# Library loaded successfully
# Scripts can check for $_COMMON_LIB_LOADED to verify library is loaded