Files
bootstrap/tools/health/stack-health.sh
2026-02-22 17:52:23 +00:00

195 lines
6.4 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# stack-health.sh — Check health of all configured Mosaic stack services
#
# Usage: stack-health.sh [-f format] [-s service] [-q]
#
# Checks connectivity to all services configured in credentials.json.
# For each service, makes a lightweight API call and reports status.
#
# Options:
# -f format Output format: table (default), json
# -s service Check only a specific service
# -q Quiet — exit code only (0=all healthy, 1=any unhealthy)
# -h Show this help
set -euo pipefail
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
source "$MOSAIC_HOME/tools/_lib/credentials.sh"
FORMAT="table"
SINGLE_SERVICE=""
QUIET=false
CRED_FILE="${MOSAIC_CREDENTIALS_FILE:-$HOME/src/jarvis-brain/credentials.json}"
while getopts "f:s:qh" opt; do
case $opt in
f) FORMAT="$OPTARG" ;;
s) SINGLE_SERVICE="$OPTARG" ;;
q) QUIET=true ;;
h) head -15 "$0" | grep "^#" | sed 's/^# \?//'; exit 0 ;;
*) echo "Usage: $0 [-f format] [-s service] [-q]" >&2; exit 1 ;;
esac
done
if [[ ! -f "$CRED_FILE" ]]; then
echo "Error: Credentials file not found: $CRED_FILE" >&2
exit 1
fi
# Colors (disabled if not a terminal or quiet mode)
if [[ -t 1 ]] && [[ "$QUIET" == "false" ]]; then
GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[0;33m' RESET='\033[0m'
else
GREEN='' RED='' YELLOW='' RESET=''
fi
TOTAL=0
HEALTHY=0
RESULTS="[]"
check_service() {
local name="$1"
local display_name="$2"
local url="$3"
local endpoint="$4"
local auth_header="$5"
local insecure="${6:-false}"
TOTAL=$((TOTAL + 1))
local curl_args=(-s -o /dev/null -w "%{http_code} %{time_total}" --connect-timeout 5 --max-time 10)
[[ -n "$auth_header" ]] && curl_args+=(-H "$auth_header")
[[ "$insecure" == "true" ]] && curl_args+=(-k)
local result
result=$(curl "${curl_args[@]}" "${url}${endpoint}" 2>/dev/null) || result="000 0.000"
local http_code response_time status_text
http_code=$(echo "$result" | awk '{print $1}')
response_time=$(echo "$result" | awk '{print $2}')
if [[ "$http_code" -ge 200 && "$http_code" -lt 400 ]]; then
status_text="UP"
HEALTHY=$((HEALTHY + 1))
elif [[ "$http_code" == "000" ]]; then
status_text="DOWN"
elif [[ "$http_code" == "401" || "$http_code" == "403" ]]; then
# Auth error but service is reachable
status_text="AUTH_ERR"
HEALTHY=$((HEALTHY + 1)) # Service is up, just auth issue
else
status_text="ERROR"
fi
# Append to JSON results
RESULTS=$(echo "$RESULTS" | jq --arg n "$name" --arg d "$display_name" \
--arg u "$url" --arg s "$status_text" --arg c "$http_code" --arg t "$response_time" \
'. + [{name: $n, display_name: $d, url: $u, status: $s, http_code: ($c | tonumber), response_time: $t}]')
if [[ "$QUIET" == "false" && "$FORMAT" == "table" ]]; then
local color="$GREEN"
[[ "$status_text" == "DOWN" || "$status_text" == "ERROR" ]] && color="$RED"
[[ "$status_text" == "AUTH_ERR" ]] && color="$YELLOW"
printf " %-22s %-35s ${color}%-8s${RESET} %ss\n" \
"$display_name" "$url" "$status_text" "$response_time"
fi
}
# Discover and check services
[[ "$QUIET" == "false" && "$FORMAT" == "table" ]] && {
echo ""
echo " SERVICE URL STATUS RESPONSE"
echo " ---------------------- ----------------------------------- -------- --------"
}
# Portainer
if [[ -z "$SINGLE_SERVICE" || "$SINGLE_SERVICE" == "portainer" ]]; then
portainer_url=$(jq -r '.portainer.url // empty' "$CRED_FILE")
portainer_key=$(jq -r '.portainer.api_key // empty' "$CRED_FILE")
if [[ -n "$portainer_url" ]]; then
check_service "portainer" "Portainer" "$portainer_url" "/api/system/status" \
"X-API-Key: $portainer_key" "true"
fi
fi
# Coolify
if [[ -z "$SINGLE_SERVICE" || "$SINGLE_SERVICE" == "coolify" ]]; then
coolify_url=$(jq -r '.coolify.url // empty' "$CRED_FILE")
coolify_token=$(jq -r '.coolify.app_token // empty' "$CRED_FILE")
if [[ -n "$coolify_url" ]]; then
check_service "coolify" "Coolify" "$coolify_url" "/api/v1/teams" \
"Authorization: Bearer $coolify_token" "false"
fi
fi
# Authentik
if [[ -z "$SINGLE_SERVICE" || "$SINGLE_SERVICE" == "authentik" ]]; then
authentik_url=$(jq -r '.authentik.url // empty' "$CRED_FILE")
if [[ -n "$authentik_url" ]]; then
check_service "authentik" "Authentik" "$authentik_url" "/-/health/ready/" "" "true"
fi
fi
# GLPI
if [[ -z "$SINGLE_SERVICE" || "$SINGLE_SERVICE" == "glpi" ]]; then
glpi_url=$(jq -r '.glpi.url // empty' "$CRED_FILE")
if [[ -n "$glpi_url" ]]; then
check_service "glpi" "GLPI" "$glpi_url" "/" "" "true"
fi
fi
# Gitea instances
if [[ -z "$SINGLE_SERVICE" || "$SINGLE_SERVICE" == "gitea" ]]; then
for instance in mosaicstack usc; do
gitea_url=$(jq -r ".gitea.${instance}.url // empty" "$CRED_FILE")
if [[ -n "$gitea_url" ]]; then
display="Gitea (${instance})"
check_service "gitea-${instance}" "$display" "$gitea_url" "/api/v1/version" "" "true"
fi
done
fi
# GitHub
if [[ -z "$SINGLE_SERVICE" || "$SINGLE_SERVICE" == "github" ]]; then
github_token=$(jq -r '.github.token // empty' "$CRED_FILE")
if [[ -n "$github_token" ]]; then
check_service "github" "GitHub" "https://api.github.com" "/rate_limit" \
"Authorization: Bearer $github_token" "false"
fi
fi
# Woodpecker
if [[ -z "$SINGLE_SERVICE" || "$SINGLE_SERVICE" == "woodpecker" ]]; then
woodpecker_url=$(jq -r '.woodpecker.url // empty' "$CRED_FILE")
woodpecker_token=$(jq -r '.woodpecker.token // empty' "$CRED_FILE")
if [[ -n "$woodpecker_url" && -n "$woodpecker_token" ]]; then
check_service "woodpecker" "Woodpecker CI" "$woodpecker_url" "/api/user" \
"Authorization: Bearer $woodpecker_token" "true"
elif [[ "$QUIET" == "false" && "$FORMAT" == "table" ]]; then
printf " %-22s %-35s ${YELLOW}%-8s${RESET} %s\n" \
"Woodpecker CI" "—" "NOTOKEN" "—"
fi
fi
# Output
if [[ "$FORMAT" == "json" ]]; then
jq -n --argjson results "$RESULTS" --argjson total "$TOTAL" --argjson healthy "$HEALTHY" \
'{total: $total, healthy: $healthy, results: $results}'
exit 0
fi
if [[ "$QUIET" == "false" && "$FORMAT" == "table" ]]; then
echo ""
UNHEALTHY=$((TOTAL - HEALTHY))
if [[ "$UNHEALTHY" -eq 0 ]]; then
echo -e " ${GREEN}All $TOTAL services healthy${RESET}"
else
echo -e " ${RED}$UNHEALTHY/$TOTAL services unhealthy${RESET}"
fi
echo ""
fi
# Exit code: 0 if all healthy, 1 if any unhealthy
[[ "$HEALTHY" -eq "$TOTAL" ]]