mirror of
https://github.com/uprightbass360/AzerothCore-RealmMaster.git
synced 2026-01-13 17:09:09 +00:00
424 lines
11 KiB
Bash
424 lines
11 KiB
Bash
#!/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)
|
||
warn() {
|
||
printf '%b\n' "${YELLOW}⚠️ $*${NC}"
|
||
}
|
||
|
||
# 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
|