- Add one-line installer (scripts/install.sh) with platform detection - Add doctor command (scripts/commands/doctor.sh) for environment diagnostics - Add shared libraries: dependencies, docker, platform, validation - Update README with quick-start installer instructions - Add AGENTS.md with codebase patterns for AI agent context Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
748 lines
20 KiB
Bash
748 lines
20 KiB
Bash
#!/bin/bash
|
||
# Validation functions for Mosaic Stack installer
|
||
# Post-install validation and health checks
|
||
|
||
# shellcheck source=lib/platform.sh
|
||
source "${BASH_SOURCE[0]%/*}/platform.sh"
|
||
|
||
# ============================================================================
|
||
# Validation Result Codes
|
||
# ============================================================================
|
||
|
||
readonly CHECK_PASS=0
|
||
readonly CHECK_WARN=1
|
||
readonly CHECK_FAIL=2
|
||
|
||
# ============================================================================
|
||
# Port Validation
|
||
# ============================================================================
|
||
|
||
# Check if a port is in use
|
||
check_port_in_use() {
|
||
local port="$1"
|
||
|
||
# Try ss first (most common on modern Linux)
|
||
if command -v ss &>/dev/null; then
|
||
ss -tuln 2>/dev/null | grep -q ":${port} "
|
||
return $?
|
||
fi
|
||
|
||
# Fall back to netstat
|
||
if command -v netstat &>/dev/null; then
|
||
netstat -tuln 2>/dev/null | grep -q ":${port} "
|
||
return $?
|
||
fi
|
||
|
||
# Fall back to lsof
|
||
if command -v lsof &>/dev/null; then
|
||
lsof -i ":$port" &>/dev/null
|
||
return $?
|
||
fi
|
||
|
||
# Can't check, assume port is free
|
||
return 1
|
||
}
|
||
|
||
# Get process using a port
|
||
get_process_on_port() {
|
||
local port="$1"
|
||
|
||
if command -v lsof &>/dev/null; then
|
||
lsof -i ":$port" -t 2>/dev/null | head -1
|
||
elif command -v ss &>/dev/null; then
|
||
ss -tulnp 2>/dev/null | grep ":${port} " | grep -oP 'pid=\K[0-9]+' | head -1
|
||
else
|
||
echo "unknown"
|
||
fi
|
||
}
|
||
|
||
# Validate port number
|
||
validate_port() {
|
||
local port="$1"
|
||
|
||
if [[ "$port" =~ ^[0-9]+$ ]] && [[ "$port" -ge 1 ]] && [[ "$port" -le 65535 ]]; then
|
||
return 0
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
# Check all configured ports
|
||
check_all_ports() {
|
||
local env_file="${1:-.env}"
|
||
local errors=0
|
||
local warnings=0
|
||
|
||
# Load env file if it exists
|
||
if [[ -f "$env_file" ]]; then
|
||
set -a
|
||
# shellcheck source=/dev/null
|
||
source "$env_file" 2>/dev/null || true
|
||
set +a
|
||
fi
|
||
|
||
# Default ports
|
||
declare -A default_ports=(
|
||
[WEB_PORT]=3000
|
||
[API_PORT]=3001
|
||
[POSTGRES_PORT]=5432
|
||
[VALKEY_PORT]=6379
|
||
[AUTHENTIK_PORT_HTTP]=9000
|
||
[AUTHENTIK_PORT_HTTPS]=9443
|
||
[OLLAMA_PORT]=11434
|
||
[TRAEFIK_HTTP_PORT]=80
|
||
[TRAEFIK_HTTPS_PORT]=443
|
||
[TRAEFIK_DASHBOARD_PORT]=8080
|
||
)
|
||
|
||
echo -e "${BOLD}Checking ports...${NC}"
|
||
echo ""
|
||
|
||
for port_var in "${!default_ports[@]}"; do
|
||
local port="${!port_var:-${default_ports[$port_var]}}"
|
||
|
||
if check_port_in_use "$port"; then
|
||
local process
|
||
process=$(get_process_on_port "$port")
|
||
echo -e "${WARN}⚠${NC} $port_var: Port $port is in use (PID: $process)"
|
||
((warnings++))
|
||
else
|
||
echo -e "${SUCCESS}✓${NC} $port_var: Port $port available"
|
||
fi
|
||
done
|
||
|
||
echo ""
|
||
|
||
if [[ $warnings -gt 0 ]]; then
|
||
return $CHECK_WARN
|
||
fi
|
||
return $CHECK_PASS
|
||
}
|
||
|
||
# ============================================================================
|
||
# Environment Validation
|
||
# ============================================================================
|
||
|
||
# Required environment variables
|
||
REQUIRED_ENV_VARS=(
|
||
"DATABASE_URL"
|
||
"JWT_SECRET"
|
||
"BETTER_AUTH_SECRET"
|
||
"ENCRYPTION_KEY"
|
||
)
|
||
|
||
# Optional but recommended environment variables
|
||
RECOMMENDED_ENV_VARS=(
|
||
"POSTGRES_PASSWORD"
|
||
"VALKEY_URL"
|
||
"NEXT_PUBLIC_API_URL"
|
||
"NEXT_PUBLIC_APP_URL"
|
||
)
|
||
|
||
# Check if env file exists
|
||
check_env_file() {
|
||
local env_file="${1:-.env}"
|
||
|
||
if [[ -f "$env_file" ]]; then
|
||
echo -e "${SUCCESS}✓${NC} .env file exists"
|
||
return 0
|
||
else
|
||
echo -e "${ERROR}✗${NC} .env file not found"
|
||
return $CHECK_FAIL
|
||
fi
|
||
}
|
||
|
||
# Check required environment variables
|
||
check_required_env() {
|
||
local env_file="${1:-.env}"
|
||
local errors=0
|
||
|
||
echo -e "${BOLD}Checking required environment variables...${NC}"
|
||
echo ""
|
||
|
||
# Load env file
|
||
if [[ -f "$env_file" ]]; then
|
||
set -a
|
||
# shellcheck source=/dev/null
|
||
source "$env_file" 2>/dev/null || true
|
||
set +a
|
||
fi
|
||
|
||
for var in "${REQUIRED_ENV_VARS[@]}"; do
|
||
local value="${!var:-}"
|
||
if [[ -z "$value" ]]; then
|
||
echo -e "${ERROR}✗${NC} $var: Not set"
|
||
((errors++))
|
||
elif is_placeholder "$value"; then
|
||
echo -e "${WARN}⚠${NC} $var: Contains placeholder value"
|
||
((errors++))
|
||
else
|
||
echo -e "${SUCCESS}✓${NC} $var: Set"
|
||
fi
|
||
done
|
||
|
||
echo ""
|
||
|
||
if [[ $errors -gt 0 ]]; then
|
||
return $CHECK_FAIL
|
||
fi
|
||
return $CHECK_PASS
|
||
}
|
||
|
||
# Check recommended environment variables
|
||
check_recommended_env() {
|
||
local env_file="${1:-.env}"
|
||
local warnings=0
|
||
|
||
echo -e "${BOLD}Checking recommended environment variables...${NC}"
|
||
echo ""
|
||
|
||
# Load env file
|
||
if [[ -f "$env_file" ]]; then
|
||
set -a
|
||
# shellcheck source=/dev/null
|
||
source "$env_file" 2>/dev/null || true
|
||
set +a
|
||
fi
|
||
|
||
for var in "${RECOMMENDED_ENV_VARS[@]}"; do
|
||
local value="${!var:-}"
|
||
if [[ -z "$value" ]]; then
|
||
echo -e "${WARN}⚠${NC} $var: Not set (using default)"
|
||
((warnings++))
|
||
elif is_placeholder "$value"; then
|
||
echo -e "${WARN}⚠${NC} $var: Contains placeholder value"
|
||
((warnings++))
|
||
else
|
||
echo -e "${SUCCESS}✓${NC} $var: Set"
|
||
fi
|
||
done
|
||
|
||
echo ""
|
||
|
||
if [[ $warnings -gt 0 ]]; then
|
||
return $CHECK_WARN
|
||
fi
|
||
return $CHECK_PASS
|
||
}
|
||
|
||
# Check if a value is a placeholder
|
||
is_placeholder() {
|
||
local value="$1"
|
||
|
||
if [[ -z "$value" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
# Common placeholder patterns
|
||
case "$value" in
|
||
*"REPLACE_WITH"*|*"CHANGE_ME"*|*"changeme"*|*"your-"*|*"example"*|*"placeholder"*|*"TODO"*|*"FIXME"*)
|
||
return 0
|
||
;;
|
||
*"xxx"*|*"<"*">"*|*"\${"*|*"$${"*)
|
||
return 0
|
||
;;
|
||
esac
|
||
|
||
return 1
|
||
}
|
||
|
||
# ============================================================================
|
||
# Secret Validation
|
||
# ============================================================================
|
||
|
||
# Minimum secret lengths
|
||
declare -A MIN_SECRET_LENGTHS=(
|
||
[JWT_SECRET]=32
|
||
[BETTER_AUTH_SECRET]=32
|
||
[ENCRYPTION_KEY]=64
|
||
[AUTHENTIK_SECRET_KEY]=50
|
||
[COORDINATOR_API_KEY]=32
|
||
[ORCHESTRATOR_API_KEY]=32
|
||
)
|
||
|
||
# Check secret strength
|
||
check_secrets() {
|
||
local env_file="${1:-.env}"
|
||
local errors=0
|
||
local warnings=0
|
||
|
||
echo -e "${BOLD}Checking secret strength...${NC}"
|
||
echo ""
|
||
|
||
# Load env file
|
||
if [[ -f "$env_file" ]]; then
|
||
set -a
|
||
# shellcheck source=/dev/null
|
||
source "$env_file" 2>/dev/null || true
|
||
set +a
|
||
fi
|
||
|
||
for secret_var in "${!MIN_SECRET_LENGTHS[@]}"; do
|
||
local value="${!secret_var:-}"
|
||
local min_len="${MIN_SECRET_LENGTHS[$secret_var]}"
|
||
|
||
if [[ -z "$value" ]]; then
|
||
echo -e "${WARN}⚠${NC} $secret_var: Not set"
|
||
((warnings++))
|
||
elif is_placeholder "$value"; then
|
||
echo -e "${ERROR}✗${NC} $secret_var: Contains placeholder (MUST change)"
|
||
((errors++))
|
||
elif [[ ${#value} -lt $min_len ]]; then
|
||
echo -e "${WARN}⚠${NC} $secret_var: Too short (${#value} chars, minimum $min_len)"
|
||
((warnings++))
|
||
else
|
||
echo -e "${SUCCESS}✓${NC} $secret_var: Strong (${#value} chars)"
|
||
fi
|
||
done
|
||
|
||
echo ""
|
||
|
||
if [[ $errors -gt 0 ]]; then
|
||
return $CHECK_FAIL
|
||
fi
|
||
if [[ $warnings -gt 0 ]]; then
|
||
return $CHECK_WARN
|
||
fi
|
||
return $CHECK_PASS
|
||
}
|
||
|
||
# ============================================================================
|
||
# Docker Validation
|
||
# ============================================================================
|
||
|
||
# Check Docker containers are running
|
||
check_docker_containers() {
|
||
local compose_file="${1:-docker-compose.yml}"
|
||
local errors=0
|
||
|
||
echo -e "${BOLD}Checking Docker containers...${NC}"
|
||
echo ""
|
||
|
||
# Expected container names
|
||
local containers=("mosaic-postgres" "mosaic-valkey" "mosaic-api" "mosaic-web")
|
||
|
||
for container in "${containers[@]}"; do
|
||
local status
|
||
status=$(docker inspect --format='{{.State.Status}}' "$container" 2>/dev/null || echo "not_found")
|
||
|
||
case "$status" in
|
||
running)
|
||
echo -e "${SUCCESS}✓${NC} $container: Running"
|
||
;;
|
||
exited)
|
||
echo -e "${ERROR}✗${NC} $container: Exited"
|
||
((errors++))
|
||
;;
|
||
not_found)
|
||
# Container might not be in current profile
|
||
echo -e "${MUTED}○${NC} $container: Not found (may not be in profile)"
|
||
;;
|
||
*)
|
||
echo -e "${WARN}⚠${NC} $container: $status"
|
||
((errors++))
|
||
;;
|
||
esac
|
||
done
|
||
|
||
echo ""
|
||
|
||
if [[ $errors -gt 0 ]]; then
|
||
return $CHECK_FAIL
|
||
fi
|
||
return $CHECK_PASS
|
||
}
|
||
|
||
# Check container health
|
||
check_container_health() {
|
||
local errors=0
|
||
|
||
echo -e "${BOLD}Checking container health...${NC}"
|
||
echo ""
|
||
|
||
# Get all mosaic containers
|
||
local containers
|
||
containers=$(docker ps --filter "name=mosaic-" --format "{{.Names}}" 2>/dev/null)
|
||
|
||
for container in $containers; do
|
||
local health
|
||
health=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "no_healthcheck")
|
||
|
||
case "$health" in
|
||
healthy)
|
||
echo -e "${SUCCESS}✓${NC} $container: Healthy"
|
||
;;
|
||
unhealthy)
|
||
echo -e "${ERROR}✗${NC} $container: Unhealthy"
|
||
((errors++))
|
||
;;
|
||
starting)
|
||
echo -e "${WARN}⚠${NC} $container: Starting..."
|
||
;;
|
||
no_healthcheck)
|
||
echo -e "${INFO}ℹ${NC} $container: No health check"
|
||
;;
|
||
*)
|
||
echo -e "${WARN}⚠${NC} $container: $health"
|
||
;;
|
||
esac
|
||
done
|
||
|
||
echo ""
|
||
|
||
if [[ $errors -gt 0 ]]; then
|
||
return $CHECK_FAIL
|
||
fi
|
||
return $CHECK_PASS
|
||
}
|
||
|
||
# ============================================================================
|
||
# Service Connectivity
|
||
# ============================================================================
|
||
|
||
# Check if a URL responds
|
||
check_url_responds() {
|
||
local url="$1"
|
||
local expected_status="${2:-200}"
|
||
local timeout="${3:-10}"
|
||
|
||
if command -v curl &>/dev/null; then
|
||
local status
|
||
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$timeout" "$url" 2>/dev/null)
|
||
|
||
if [[ "$status" == "$expected_status" ]]; then
|
||
return 0
|
||
fi
|
||
fi
|
||
|
||
return 1
|
||
}
|
||
|
||
# Check API health endpoint
|
||
check_api_health() {
|
||
local api_url="${1:-http://localhost:3001}"
|
||
|
||
echo -e "${BOLD}Checking API health...${NC}"
|
||
echo ""
|
||
|
||
if check_url_responds "${api_url}/health" 200 10; then
|
||
echo -e "${SUCCESS}✓${NC} API health check passed"
|
||
return $CHECK_PASS
|
||
else
|
||
echo -e "${ERROR}✗${NC} API health check failed"
|
||
return $CHECK_FAIL
|
||
fi
|
||
}
|
||
|
||
# Check Web frontend
|
||
check_web_health() {
|
||
local web_url="${1:-http://localhost:3000}"
|
||
|
||
echo -e "${BOLD}Checking Web frontend...${NC}"
|
||
echo ""
|
||
|
||
if check_url_responds "$web_url" 200 10; then
|
||
echo -e "${SUCCESS}✓${NC} Web frontend responding"
|
||
return $CHECK_PASS
|
||
else
|
||
echo -e "${WARN}⚠${NC} Web frontend not responding (may still be starting)"
|
||
return $CHECK_WARN
|
||
fi
|
||
}
|
||
|
||
# Check database connectivity
|
||
check_database_connection() {
|
||
local host="${1:-localhost}"
|
||
local port="${2:-5432}"
|
||
local user="${3:-mosaic}"
|
||
local database="${4:-mosaic}"
|
||
|
||
echo -e "${BOLD}Checking database connection...${NC}"
|
||
echo ""
|
||
|
||
# Try via Docker if postgres container exists
|
||
if docker exec mosaic-postgres pg_isready -U "$user" -d "$database" &>/dev/null; then
|
||
echo -e "${SUCCESS}✓${NC} Database connection successful"
|
||
return $CHECK_PASS
|
||
fi
|
||
|
||
# Try via psql if available
|
||
if command -v psql &>/dev/null; then
|
||
if PGPASSWORD="${POSTGRES_PASSWORD:-}" psql -h "$host" -p "$port" -U "$user" -d "$database" -c "SELECT 1" &>/dev/null; then
|
||
echo -e "${SUCCESS}✓${NC} Database connection successful"
|
||
return $CHECK_PASS
|
||
fi
|
||
fi
|
||
|
||
# Try via TCP
|
||
if command -v nc &>/dev/null; then
|
||
if nc -z "$host" "$port" 2>/dev/null; then
|
||
echo -e "${WARN}⚠${NC} Database port open but could not verify connection"
|
||
return $CHECK_WARN
|
||
fi
|
||
fi
|
||
|
||
echo -e "${ERROR}✗${NC} Database connection failed"
|
||
return $CHECK_FAIL
|
||
}
|
||
|
||
# Check Valkey/Redis connectivity
|
||
check_valkey_connection() {
|
||
local host="${1:-localhost}"
|
||
local port="${2:-6379}"
|
||
|
||
echo -e "${BOLD}Checking Valkey/Redis connection...${NC}"
|
||
echo ""
|
||
|
||
# Try via Docker if valkey container exists
|
||
if docker exec mosaic-valkey valkey-cli ping 2>/dev/null | grep -q PONG; then
|
||
echo -e "${SUCCESS}✓${NC} Valkey connection successful"
|
||
return $CHECK_PASS
|
||
fi
|
||
|
||
# Try via redis-cli if available
|
||
if command -v redis-cli &>/dev/null; then
|
||
if redis-cli -h "$host" -p "$port" ping 2>/dev/null | grep -q PONG; then
|
||
echo -e "${SUCCESS}✓${NC} Valkey/Redis connection successful"
|
||
return $CHECK_PASS
|
||
fi
|
||
fi
|
||
|
||
# Try via TCP
|
||
if command -v nc &>/dev/null; then
|
||
if nc -z "$host" "$port" 2>/dev/null; then
|
||
echo -e "${WARN}⚠${NC} Valkey port open but could not verify connection"
|
||
return $CHECK_WARN
|
||
fi
|
||
fi
|
||
|
||
echo -e "${ERROR}✗${NC} Valkey/Redis connection failed"
|
||
return $CHECK_FAIL
|
||
}
|
||
|
||
# ============================================================================
|
||
# System Requirements
|
||
# ============================================================================
|
||
|
||
# Check minimum system requirements
|
||
check_system_requirements() {
|
||
local min_ram="${1:-2048}"
|
||
local min_disk="${2:-10}"
|
||
local errors=0
|
||
local warnings=0
|
||
|
||
echo -e "${BOLD}Checking system requirements...${NC}"
|
||
echo ""
|
||
|
||
# RAM check
|
||
local ram
|
||
ram=$(get_total_ram)
|
||
if [[ "$ram" -lt "$min_ram" ]]; then
|
||
echo -e "${ERROR}✗${NC} RAM: ${ram}MB (minimum: ${min_ram}MB)"
|
||
((errors++))
|
||
else
|
||
echo -e "${SUCCESS}✓${NC} RAM: ${ram}MB"
|
||
fi
|
||
|
||
# Disk check
|
||
local disk
|
||
disk=$(get_available_disk "$HOME")
|
||
if [[ "$disk" -lt "$min_disk" ]]; then
|
||
echo -e "${WARN}⚠${NC} Disk: ${disk}GB available (recommended: ${min_disk}GB+)"
|
||
((warnings++))
|
||
else
|
||
echo -e "${SUCCESS}✓${NC} Disk: ${disk}GB available"
|
||
fi
|
||
|
||
# Docker disk (if using Docker)
|
||
if command -v docker &>/dev/null && docker info &>/dev/null; then
|
||
local docker_disk
|
||
docker_disk=$(docker system df --format "{{.Total}}" 2>/dev/null | head -1 || echo "unknown")
|
||
echo -e "${INFO}ℹ${NC} Docker storage: $docker_disk"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
if [[ $errors -gt 0 ]]; then
|
||
return $CHECK_FAIL
|
||
fi
|
||
if [[ $warnings -gt 0 ]]; then
|
||
return $CHECK_WARN
|
||
fi
|
||
return $CHECK_PASS
|
||
}
|
||
|
||
# ============================================================================
|
||
# File Permissions
|
||
# ============================================================================
|
||
|
||
# Check .env file permissions
|
||
check_env_permissions() {
|
||
local env_file="${1:-.env}"
|
||
|
||
echo -e "${BOLD}Checking file permissions...${NC}"
|
||
echo ""
|
||
|
||
if [[ ! -f "$env_file" ]]; then
|
||
echo -e "${WARN}⚠${NC} .env file not found"
|
||
return $CHECK_WARN
|
||
fi
|
||
|
||
local perms
|
||
perms=$(stat -c "%a" "$env_file" 2>/dev/null || stat -f "%OLp" "$env_file" 2>/dev/null)
|
||
|
||
# Check if world-readable
|
||
if [[ "$perms" =~ [0-7][0-7][4-7]$ ]]; then
|
||
echo -e "${WARN}⚠${NC} .env is world-readable (permissions: $perms)"
|
||
echo -e " ${INFO}Fix: chmod 600 $env_file${NC}"
|
||
return $CHECK_WARN
|
||
fi
|
||
|
||
echo -e "${SUCCESS}✓${NC} .env permissions: $perms"
|
||
return $CHECK_PASS
|
||
}
|
||
|
||
# ============================================================================
|
||
# Comprehensive Doctor Check
|
||
# ============================================================================
|
||
|
||
# Run all checks and report results
|
||
run_doctor() {
|
||
local env_file="${1:-.env}"
|
||
local compose_file="${2:-docker-compose.yml}"
|
||
local mode="${3:-docker}"
|
||
|
||
local errors=0
|
||
local warnings=0
|
||
|
||
echo ""
|
||
echo -e "${BOLD}════════════════════════════════════════════════════════════${NC}"
|
||
echo -e "${BOLD} Mosaic Stack Doctor${NC}"
|
||
echo -e "${BOLD}════════════════════════════════════════════════════════════${NC}"
|
||
echo ""
|
||
|
||
# System requirements
|
||
run_doctor_check "System Requirements" check_system_requirements 2048 10
|
||
collect_result $?
|
||
|
||
# Environment file
|
||
run_doctor_check "Environment File" check_env_file "$env_file"
|
||
collect_result $?
|
||
|
||
# Required environment variables
|
||
run_doctor_check "Required Variables" check_required_env "$env_file"
|
||
collect_result $?
|
||
|
||
# Secret strength
|
||
run_doctor_check "Secret Strength" check_secrets "$env_file"
|
||
collect_result $?
|
||
|
||
# File permissions
|
||
run_doctor_check "File Permissions" check_env_permissions "$env_file"
|
||
collect_result $?
|
||
|
||
if [[ "$mode" == "docker" ]]; then
|
||
# Docker containers
|
||
run_doctor_check "Docker Containers" check_docker_containers "$compose_file"
|
||
collect_result $?
|
||
|
||
# Container health
|
||
run_doctor_check "Container Health" check_container_health
|
||
collect_result $?
|
||
|
||
# Database connection
|
||
run_doctor_check "Database" check_database_connection
|
||
collect_result $?
|
||
|
||
# Valkey connection
|
||
run_doctor_check "Cache (Valkey)" check_valkey_connection
|
||
collect_result $?
|
||
|
||
# API health
|
||
run_doctor_check "API" check_api_health
|
||
collect_result $?
|
||
|
||
# Web frontend
|
||
run_doctor_check "Web Frontend" check_web_health
|
||
collect_result $?
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${BOLD}════════════════════════════════════════════════════════════${NC}"
|
||
|
||
# Summary
|
||
if [[ $errors -gt 0 ]]; then
|
||
echo -e "${ERROR}✗${NC} ${BOLD}Failed${NC}: $errors errors, $warnings warnings"
|
||
echo ""
|
||
echo "Fix the errors above and run doctor again."
|
||
return $CHECK_FAIL
|
||
elif [[ $warnings -gt 0 ]]; then
|
||
echo -e "${WARN}⚠${NC} ${BOLD}Warnings${NC}: $warnings warnings"
|
||
echo ""
|
||
echo "System is operational but some optimizations are recommended."
|
||
return $CHECK_WARN
|
||
else
|
||
echo -e "${SUCCESS}✓${NC} ${BOLD}All checks passed${NC}"
|
||
echo ""
|
||
echo "Mosaic Stack is healthy and ready to use."
|
||
return $CHECK_PASS
|
||
fi
|
||
}
|
||
|
||
# Helper to run a check and print result
|
||
run_doctor_check() {
|
||
local name="$1"
|
||
shift
|
||
|
||
echo -e "${BOLD}Checking: $name${NC}"
|
||
echo ""
|
||
|
||
"$@"
|
||
return $?
|
||
}
|
||
|
||
# Helper to collect check results
|
||
collect_result() {
|
||
local result=$1
|
||
|
||
case $result in
|
||
$CHECK_PASS) ;;
|
||
$CHECK_WARN) ((warnings++)) ;;
|
||
$CHECK_FAIL) ((errors++)) ;;
|
||
esac
|
||
}
|
||
|
||
# ============================================================================
|
||
# Quick Health Check
|
||
# ============================================================================
|
||
|
||
# Quick check for CI/CD or scripts
|
||
quick_health_check() {
|
||
local api_url="${1:-http://localhost:3001}"
|
||
|
||
check_url_responds "${api_url}/health" 200 5
|
||
}
|
||
|
||
# Wait for healthy state
|
||
wait_for_healthy() {
|
||
local timeout="${1:-120}"
|
||
local interval="${2:-5}"
|
||
|
||
echo -e "${INFO}ℹ${NC} Waiting for healthy state..."
|
||
|
||
local elapsed=0
|
||
while [[ $elapsed -lt $timeout ]]; do
|
||
if quick_health_check &>/dev/null; then
|
||
echo -e "${SUCCESS}✓${NC} System is healthy"
|
||
return 0
|
||
fi
|
||
|
||
sleep "$interval"
|
||
((elapsed += interval))
|
||
echo -n "."
|
||
done
|
||
|
||
echo ""
|
||
echo -e "${ERROR}✗${NC} Timeout waiting for healthy state"
|
||
return 1
|
||
}
|