feat: rename rails/ to tools/ and add service tool suites
Rename the `rails/` directory to `tools/` for agent discoverability — agents frequently failed to locate helper scripts due to the non-intuitive directory name. Add backward-compat symlink `rails/ → tools/`. New tool suites: - Authentik: auth-token, user-list, user-create, group-list, app-list, flow-list, admin-status (8 scripts) - Coolify: team-list, project-list, service-list, service-status, deploy, env-set (7 scripts) - Woodpecker: pipeline-list, pipeline-status, pipeline-trigger (3 stubs) - GLPI: session-init, computer-list, ticket-list, ticket-create, user-list (6 scripts) - Health: stack-health.sh — stack-wide connectivity check Infrastructure: - Shared credential loader at tools/_lib/credentials.sh - install.sh creates symlink + chmod on tool scripts - All ~253 rails/ path references updated across 68+ files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
194
tools/health/stack-health.sh
Executable file
194
tools/health/stack-health.sh
Executable file
@@ -0,0 +1,194 @@
|
||||
#!/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" ]]
|
||||
Reference in New Issue
Block a user