#!/bin/bash # TSYS Developer Support Stack - Deep HTTP Validation # Purpose: post-deployment validation that services actually serve correct # HTTP responses (status code + body content), not merely that a TCP # port is open. A port being reachable does not prove a service works. # # Usage: ./scripts/validate-http.sh # Exit: 0 if all checks pass, 1 otherwise. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" ENV_FILE="$PROJECT_ROOT/demo.env" if [[ ! -f "$ENV_FILE" ]]; then echo "[ERROR] $ENV_FILE not found. Run ./scripts/demo-stack.sh deploy first." >&2 exit 1 fi # shellcheck source=/dev/null set -a; source "$ENV_FILE"; set +a RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' NC='\033[0m' PASS=0 FAIL=0 log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_pass() { echo -e "${GREEN}[PASS]${NC} $1"; PASS=$((PASS + 1)); } log_fail() { echo -e "${RED}[FAIL]${NC} $1"; FAIL=$((FAIL + 1)); } log_section() { echo -e "\n${BLUE}── $1 ──${NC}"; } # check_http NAME PORT PATH EXPECT_STATUS [CONTENT_SUBSTRING] # Follows redirects; HTTP status is the final response code. check_http() { local name="$1" port="$2" path="$3" expect="$4" content="${5:-}" local url="http://localhost:${port}${path}" local body status body=$(curl -sS -L --max-time 8 "$url" 2>/dev/null) || true status=$(curl -sS -L -o /dev/null -w '%{http_code}' --max-time 8 "$url" 2>/dev/null || echo "000") if [[ "$status" == "000" ]]; then log_fail "$name ($url) unreachable" return fi if [[ "$status" != "$expect" ]]; then log_fail "$name ($url) expected HTTP $expect, got $status" return fi if [[ -n "$content" ]]; then if ! echo "$body" | grep -qi "$content"; then log_fail "$name ($url) status $status but missing content \"$content\"" return fi fi log_pass "$name ($url) -> $status${content:+ \"$content\"}" } # Specialised check helpers for services whose "healthy" state is best expressed # as a JSON field or an XML marker rather than a plain string. check_influxdb() { # /health returns {"status":"pass", ...} when ready for queries check_http "InfluxDB" "$INFLUXDB_PORT" "/health" "200" '"status":"pass"' } check_grafana() { # Grafana pretty-prints its JSON, so match with optional whitespace. local url="http://localhost:${GRAFANA_PORT}/api/health" local body status status=$(curl -sS -L -o /dev/null -w '%{http_code}' --max-time 8 "$url" 2>/dev/null || echo "000") body=$(curl -sS -L --max-time 8 "$url" 2>/dev/null) || true if [[ "$status" == "200" ]] && echo "$body" | grep -qiE '"database"[[:space:]]*:[[:space:]]*"ok"'; then log_pass "Grafana ($url) -> 200 \"database\":\"ok\"" else log_fail "Grafana ($url) expected database:ok, got $status" fi } check_kiwix() { # 1. The welcome page must render. check_http "Kiwix UI" "$KIWIX_PORT" "/" "200" "welcome to kiwix server" # 2. A ZIM must actually be loaded: the OPDS catalog lists at least one entry. local url="http://localhost:${KIWIX_PORT}/catalog/v2/entries?count=1" local body body=$(curl -sS -L --max-time 8 "$url" 2>/dev/null) || true if echo "$body" | grep -q ""; then log_pass "Kiwix ZIM loaded (catalog has )" else log_fail "Kiwix ($url) no ZIM loaded (catalog feed missing )" fi } run_http_validation() { log_info "Deep HTTP validation of ${COMPOSE_PROJECT_NAME} services..." log_section "Infrastructure" check_http "Homepage" "$HOMEPAGE_PORT" "/" "200" check_http "Pi-hole" "$PIHOLE_PORT" "/admin/" "200" check_http "Dockhand" "$DOCKHAND_PORT" "/" "200" log_section "Monitoring & Observability" check_influxdb check_grafana check_http "Metrics" "$METRICS_PORT" "/" "200" check_http "AppleHealth" "$APPLEHEALTH_PORT" "/health" "200" '"status":"healthy"' log_section "Documentation & Diagramming" check_http "Draw.io" "$DRAWIO_PORT" "/" "200" check_http "Kroki" "$KROKI_PORT" "/" "200" check_kiwix log_section "Developer Tools" check_http "AtomicTracker" "$ATOMIC_TRACKER_PORT" "/" "200" check_http "ArchiveBox" "$ARCHIVEBOX_PORT" "/health/" "200" "OK" check_http "TubeArchivist" "$TUBE_ARCHIVIST_PORT" "/api/health/" "200" "OK" check_http "Wakapi" "$WAKAPI_PORT" "/" "200" check_http "MailHog" "$MAILHOG_PORT" "/" "200" check_http "Atuin" "$ATUIN_PORT" "/healthz" "200" '"status":"healthy"' log_section "Productivity" check_http "ReactiveResume" "$REACTIVE_RESUME_PORT" "/api/health" "200" check_http "ResumeMatcher" "$RESUME_MATCHER_PORT" "/api/v1/health" "200" '"status":"healthy"' echo "" echo "====================================" echo "HTTP VALIDATION RESULTS" echo "====================================" echo -e "Passed: ${GREEN}$PASS${NC}" echo -e "Failed: ${RED}$FAIL${NC}" if [[ $FAIL -eq 0 ]]; then echo -e "\n${GREEN}ALL HTTP CHECKS PASSED${NC}" return 0 else echo -e "\n${RED}HTTP VALIDATION FAILED${NC}" return 1 fi } run_http_validation