Resolved multiple critical discovery system issues: ## Discovery System Fixes - Fixed console commands not being discovered on first run - Implemented fallback discovery for empty caches - Added context-aware caching with separate cache keys - Fixed object serialization preventing __PHP_Incomplete_Class ## Cache System Improvements - Smart caching that only caches meaningful results - Separate caches for different execution contexts (console, web, test) - Proper array serialization/deserialization for cache compatibility - Cache hit logging for debugging and monitoring ## Object Serialization Fixes - Fixed DiscoveredAttribute serialization with proper string conversion - Sanitized additional data to prevent object reference issues - Added fallback for corrupted cache entries ## Performance & Reliability - All 69 console commands properly discovered and cached - 534 total discovery items successfully cached and restored - No more __PHP_Incomplete_Class cache corruption - Improved error handling and graceful fallbacks ## Testing & Quality - Fixed code style issues across discovery components - Enhanced logging for better debugging capabilities - Improved cache validation and error recovery Ready for production deployment with stable discovery system. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
463 lines
13 KiB
Bash
Executable File
463 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Health Check Script for Custom PHP Framework
|
|
# Verifies application health after deployment
|
|
# Usage: ./health-check.sh [environment] [options]
|
|
|
|
set -euo pipefail
|
|
|
|
# Script configuration
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../../" && pwd)"
|
|
DEPLOYMENT_DIR="${PROJECT_ROOT}/deployment"
|
|
APPLICATIONS_DIR="${DEPLOYMENT_DIR}/applications"
|
|
|
|
# Default settings
|
|
DEFAULT_ENV="staging"
|
|
VERBOSE=false
|
|
MAX_RETRIES=10
|
|
RETRY_INTERVAL=30
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Logging functions
|
|
log() {
|
|
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $1${NC}"
|
|
}
|
|
|
|
warn() {
|
|
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARN: $1${NC}"
|
|
}
|
|
|
|
error() {
|
|
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
|
|
}
|
|
|
|
debug() {
|
|
if [ "$VERBOSE" = true ]; then
|
|
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] DEBUG: $1${NC}"
|
|
fi
|
|
}
|
|
|
|
success() {
|
|
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] SUCCESS: $1${NC}"
|
|
}
|
|
|
|
# Usage information
|
|
show_usage() {
|
|
cat << EOF
|
|
Usage: $0 [environment] [options]
|
|
|
|
Environment:
|
|
staging Check staging environment (default)
|
|
production Check production environment
|
|
|
|
Options:
|
|
--verbose Enable verbose output
|
|
--max-retries N Maximum number of health check retries (default: 10)
|
|
--retry-interval N Seconds between retries (default: 30)
|
|
-h, --help Show this help message
|
|
|
|
Examples:
|
|
$0 staging # Check staging environment
|
|
$0 production --verbose # Check production with detailed output
|
|
$0 staging --max-retries 5 # Check with custom retry limit
|
|
|
|
EOF
|
|
}
|
|
|
|
# Parse command line arguments
|
|
parse_arguments() {
|
|
local environment=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
staging|production)
|
|
environment="$1"
|
|
shift
|
|
;;
|
|
--verbose)
|
|
VERBOSE=true
|
|
shift
|
|
;;
|
|
--max-retries)
|
|
MAX_RETRIES="$2"
|
|
shift 2
|
|
;;
|
|
--retry-interval)
|
|
RETRY_INTERVAL="$2"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
show_usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
error "Unknown argument: $1"
|
|
show_usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Set environment, defaulting to staging
|
|
CHECK_ENV="${environment:-$DEFAULT_ENV}"
|
|
}
|
|
|
|
# Load environment configuration
|
|
load_environment() {
|
|
local env_file="${APPLICATIONS_DIR}/environments/.env.${CHECK_ENV}"
|
|
|
|
if [[ ! -f "$env_file" ]]; then
|
|
error "Environment file not found: $env_file"
|
|
exit 1
|
|
fi
|
|
|
|
debug "Loading environment from: $env_file"
|
|
set -a
|
|
source "$env_file"
|
|
set +a
|
|
}
|
|
|
|
# Check Docker container health
|
|
check_container_health() {
|
|
log "Checking Docker container health"
|
|
|
|
local compose_files="-f ${PROJECT_ROOT}/docker-compose.yml -f ${APPLICATIONS_DIR}/docker-compose.${CHECK_ENV}.yml"
|
|
local env_file="--env-file ${APPLICATIONS_DIR}/environments/.env.${CHECK_ENV}"
|
|
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# Get container status
|
|
local containers
|
|
containers=$(docker-compose $compose_files $env_file ps --services)
|
|
|
|
local all_healthy=true
|
|
|
|
while IFS= read -r service; do
|
|
if [[ -n "$service" ]]; then
|
|
local container_status
|
|
container_status=$(docker-compose $compose_files $env_file ps "$service" | tail -n +3 | awk '{print $4}')
|
|
|
|
case "$container_status" in
|
|
"Up"|"Up (healthy)")
|
|
success "Container $service: $container_status"
|
|
;;
|
|
*)
|
|
error "Container $service: $container_status"
|
|
all_healthy=false
|
|
;;
|
|
esac
|
|
fi
|
|
done <<< "$containers"
|
|
|
|
if [ "$all_healthy" = false ]; then
|
|
return 1
|
|
fi
|
|
|
|
debug "All containers are healthy"
|
|
return 0
|
|
}
|
|
|
|
# Check HTTP endpoints
|
|
check_http_endpoints() {
|
|
log "Checking HTTP endpoints"
|
|
|
|
# Determine base URL based on environment
|
|
local base_url
|
|
if [ "$CHECK_ENV" = "production" ]; then
|
|
base_url="https://michaelschiemer.de"
|
|
else
|
|
base_url="https://localhost:${APP_SSL_PORT:-443}"
|
|
fi
|
|
|
|
# Test endpoints
|
|
local endpoints=(
|
|
"/"
|
|
"/health"
|
|
"/api/health"
|
|
)
|
|
|
|
local all_ok=true
|
|
|
|
for endpoint in "${endpoints[@]}"; do
|
|
local url="${base_url}${endpoint}"
|
|
debug "Testing endpoint: $url"
|
|
|
|
local http_code
|
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" -k \
|
|
-H "User-Agent: Mozilla/5.0 (HealthCheck)" \
|
|
--max-time 30 \
|
|
"$url" || echo "000")
|
|
|
|
case "$http_code" in
|
|
200|301|302)
|
|
success "Endpoint $endpoint: HTTP $http_code"
|
|
;;
|
|
000)
|
|
error "Endpoint $endpoint: Connection failed"
|
|
all_ok=false
|
|
;;
|
|
*)
|
|
error "Endpoint $endpoint: HTTP $http_code"
|
|
all_ok=false
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ "$all_ok" = false ]; then
|
|
return 1
|
|
fi
|
|
|
|
debug "All HTTP endpoints are responding correctly"
|
|
return 0
|
|
}
|
|
|
|
# Check database connectivity
|
|
check_database() {
|
|
log "Checking database connectivity"
|
|
|
|
local compose_files="-f ${PROJECT_ROOT}/docker-compose.yml -f ${APPLICATIONS_DIR}/docker-compose.${CHECK_ENV}.yml"
|
|
local env_file="--env-file ${APPLICATIONS_DIR}/environments/.env.${CHECK_ENV}"
|
|
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# Test database connection through application
|
|
local db_check
|
|
if db_check=$(docker-compose $compose_files $env_file exec -T php \
|
|
php console.php db:ping 2>&1); then
|
|
success "Database connection: OK"
|
|
debug "Database response: $db_check"
|
|
return 0
|
|
else
|
|
error "Database connection: FAILED"
|
|
error "Database error: $db_check"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Check Redis connectivity
|
|
check_redis() {
|
|
log "Checking Redis connectivity"
|
|
|
|
local compose_files="-f ${PROJECT_ROOT}/docker-compose.yml -f ${APPLICATIONS_DIR}/docker-compose.${CHECK_ENV}.yml"
|
|
local env_file="--env-file ${APPLICATIONS_DIR}/environments/.env.${CHECK_ENV}"
|
|
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# Test Redis connection
|
|
local redis_check
|
|
if redis_check=$(docker-compose $compose_files $env_file exec -T redis \
|
|
redis-cli ping 2>&1); then
|
|
if [[ "$redis_check" == *"PONG"* ]]; then
|
|
success "Redis connection: OK"
|
|
debug "Redis response: $redis_check"
|
|
return 0
|
|
else
|
|
error "Redis connection: Unexpected response - $redis_check"
|
|
return 1
|
|
fi
|
|
else
|
|
error "Redis connection: FAILED"
|
|
error "Redis error: $redis_check"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Check queue worker status
|
|
check_queue_worker() {
|
|
log "Checking queue worker status"
|
|
|
|
local compose_files="-f ${PROJECT_ROOT}/docker-compose.yml -f ${APPLICATIONS_DIR}/docker-compose.${CHECK_ENV}.yml"
|
|
local env_file="--env-file ${APPLICATIONS_DIR}/environments/.env.${CHECK_ENV}"
|
|
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# Check if worker container is running
|
|
local worker_status
|
|
worker_status=$(docker-compose $compose_files $env_file ps queue-worker | tail -n +3 | awk '{print $4}')
|
|
|
|
case "$worker_status" in
|
|
"Up")
|
|
success "Queue worker: Running"
|
|
|
|
# Check worker process inside container
|
|
local worker_process
|
|
if worker_process=$(docker-compose $compose_files $env_file exec -T queue-worker \
|
|
ps aux | grep -v grep | grep worker 2>&1); then
|
|
debug "Worker process found: $worker_process"
|
|
return 0
|
|
else
|
|
warn "Queue worker container running but no worker process found"
|
|
return 1
|
|
fi
|
|
;;
|
|
*)
|
|
error "Queue worker: $worker_status"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Check application performance
|
|
check_performance() {
|
|
log "Checking application performance"
|
|
|
|
local base_url
|
|
if [ "$CHECK_ENV" = "production" ]; then
|
|
base_url="https://michaelschiemer.de"
|
|
else
|
|
base_url="https://localhost:${APP_SSL_PORT:-443}"
|
|
fi
|
|
|
|
# Test response times
|
|
local response_time
|
|
response_time=$(curl -s -o /dev/null -w "%{time_total}" -k \
|
|
-H "User-Agent: Mozilla/5.0 (HealthCheck)" \
|
|
--max-time 30 \
|
|
"${base_url}/" || echo "timeout")
|
|
|
|
if [[ "$response_time" == "timeout" ]]; then
|
|
error "Performance check: Request timed out"
|
|
return 1
|
|
fi
|
|
|
|
local response_ms
|
|
response_ms=$(echo "$response_time * 1000" | bc -l | cut -d. -f1)
|
|
|
|
# Performance thresholds (milliseconds)
|
|
local warning_threshold=2000
|
|
local error_threshold=5000
|
|
|
|
if [ "$response_ms" -lt "$warning_threshold" ]; then
|
|
success "Performance: ${response_ms}ms (Good)"
|
|
return 0
|
|
elif [ "$response_ms" -lt "$error_threshold" ]; then
|
|
warn "Performance: ${response_ms}ms (Slow)"
|
|
return 0
|
|
else
|
|
error "Performance: ${response_ms}ms (Too slow)"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Check SSL certificate
|
|
check_ssl() {
|
|
if [ "$CHECK_ENV" != "production" ]; then
|
|
debug "Skipping SSL check for non-production environment"
|
|
return 0
|
|
fi
|
|
|
|
log "Checking SSL certificate"
|
|
|
|
local domain="michaelschiemer.de"
|
|
local ssl_info
|
|
|
|
if ssl_info=$(echo | openssl s_client -connect "${domain}:443" -servername "$domain" 2>/dev/null | \
|
|
openssl x509 -noout -dates 2>/dev/null); then
|
|
|
|
local expiry_date
|
|
expiry_date=$(echo "$ssl_info" | grep "notAfter" | cut -d= -f2)
|
|
|
|
local expiry_timestamp
|
|
expiry_timestamp=$(date -d "$expiry_date" +%s 2>/dev/null || echo "0")
|
|
|
|
local current_timestamp
|
|
current_timestamp=$(date +%s)
|
|
|
|
local days_until_expiry
|
|
days_until_expiry=$(( (expiry_timestamp - current_timestamp) / 86400 ))
|
|
|
|
if [ "$days_until_expiry" -gt 30 ]; then
|
|
success "SSL certificate: Valid (expires in $days_until_expiry days)"
|
|
return 0
|
|
elif [ "$days_until_expiry" -gt 7 ]; then
|
|
warn "SSL certificate: Expires soon (in $days_until_expiry days)"
|
|
return 0
|
|
else
|
|
error "SSL certificate: Critical expiry (in $days_until_expiry days)"
|
|
return 1
|
|
fi
|
|
else
|
|
error "SSL certificate: Could not retrieve certificate information"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Comprehensive health check with retries
|
|
run_health_checks() {
|
|
log "Starting comprehensive health check for $CHECK_ENV environment"
|
|
|
|
local checks=(
|
|
"check_container_health"
|
|
"check_database"
|
|
"check_redis"
|
|
"check_queue_worker"
|
|
"check_http_endpoints"
|
|
"check_performance"
|
|
"check_ssl"
|
|
)
|
|
|
|
local attempt=1
|
|
local all_passed=false
|
|
|
|
while [ $attempt -le $MAX_RETRIES ] && [ "$all_passed" = false ]; do
|
|
log "Health check attempt $attempt of $MAX_RETRIES"
|
|
|
|
local failed_checks=()
|
|
|
|
for check in "${checks[@]}"; do
|
|
debug "Running check: $check"
|
|
if ! $check; then
|
|
failed_checks+=("$check")
|
|
fi
|
|
done
|
|
|
|
if [ ${#failed_checks[@]} -eq 0 ]; then
|
|
all_passed=true
|
|
success "All health checks passed!"
|
|
else
|
|
warn "Failed checks: ${failed_checks[*]}"
|
|
|
|
if [ $attempt -lt $MAX_RETRIES ]; then
|
|
log "Waiting $RETRY_INTERVAL seconds before retry..."
|
|
sleep $RETRY_INTERVAL
|
|
fi
|
|
fi
|
|
|
|
((attempt++))
|
|
done
|
|
|
|
if [ "$all_passed" = false ]; then
|
|
error "Health checks failed after $MAX_RETRIES attempts"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Main function
|
|
main() {
|
|
log "Starting health check for $CHECK_ENV environment"
|
|
|
|
load_environment
|
|
|
|
if run_health_checks; then
|
|
success "Health check completed successfully for $CHECK_ENV environment"
|
|
log "Application is healthy and ready to serve traffic"
|
|
return 0
|
|
else
|
|
error "Health check failed for $CHECK_ENV environment"
|
|
error "Please review the application status and fix any issues"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Parse arguments and run main function
|
|
parse_arguments "$@"
|
|
main |