Add Reactive Resume, Metrics, Kiwix, Resume Matcher, and Apple Health from the earlier SelfStack project. Rewrite Apple Health collector to use InfluxDB v2 with proper error handling. Update all tests, scripts, Homepage config, env template, and documentation for the expanded stack. New services: - Reactive Resume (4016) + Postgres/Minio/Chrome companions - Metrics (4021) - GitHub metrics visualization - Kiwix (4022) - offline wiki reader - Resume Matcher (4023) - AI resume screening - Apple Health (4024) - health data collector → InfluxDB v2 Also adds git policy to AGENTS.md: always commit and push automatically. 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
267 lines
8.9 KiB
Bash
Executable File
267 lines
8.9 KiB
Bash
Executable File
#!/bin/bash
|
|
# TSYS Developer Support Stack - Comprehensive Validation Script
|
|
# Purpose: Proactive issue prevention before deployment
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
DEMO_DIR="$PROJECT_ROOT"
|
|
|
|
VALIDATION_PASSED=0
|
|
VALIDATION_FAILED=0
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
log_validation() { echo -e "${BLUE}[VALIDATE]${NC} $1"; }
|
|
log_pass() { echo -e "${GREEN}[PASS]${NC} $1"; ((VALIDATION_PASSED++)); }
|
|
log_fail() { echo -e "${RED}[FAIL]${NC} $1"; ((VALIDATION_FAILED++)); }
|
|
|
|
validate_yaml_files() {
|
|
log_validation "Validating YAML files with yamllint..."
|
|
local yaml_files=(
|
|
"docker-compose.yml.template"
|
|
"config/homepage/docker.yaml"
|
|
"config/grafana/datasources.yml"
|
|
"config/grafana/dashboards.yml"
|
|
)
|
|
for yaml_file in "${yaml_files[@]}"; do
|
|
if [[ -f "$DEMO_DIR/$yaml_file" ]]; then
|
|
if docker run --rm -v "$DEMO_DIR:/data" cytopia/yamllint /data/"$yaml_file" 2>&1; then
|
|
log_pass "YAML validation: $yaml_file"
|
|
else
|
|
log_fail "YAML validation: $yaml_file"
|
|
fi
|
|
else
|
|
log_fail "YAML file not found: $yaml_file"
|
|
fi
|
|
done
|
|
}
|
|
|
|
validate_shell_scripts() {
|
|
log_validation "Validating shell scripts with shellcheck..."
|
|
local shell_files=(
|
|
"scripts/demo-stack.sh"
|
|
"scripts/demo-test.sh"
|
|
"scripts/validate-all.sh"
|
|
"tests/unit/test_env_validation.sh"
|
|
"tests/integration/test_service_communication.sh"
|
|
"tests/e2e/test_deployment_workflow.sh"
|
|
)
|
|
for shell_file in "${shell_files[@]}"; do
|
|
if [[ -f "$DEMO_DIR/$shell_file" ]]; then
|
|
if docker run --rm -v "$DEMO_DIR:/data" koalaman/shellcheck /data/"$shell_file" 2>&1; then
|
|
log_pass "Shell validation: $shell_file"
|
|
else
|
|
log_fail "Shell validation: $shell_file"
|
|
fi
|
|
else
|
|
log_fail "Shell file not found: $shell_file"
|
|
fi
|
|
done
|
|
}
|
|
|
|
validate_docker_images() {
|
|
log_validation "Validating Docker image availability..."
|
|
local images=(
|
|
"tecnativa/docker-socket-proxy:latest"
|
|
"ghcr.io/gethomepage/homepage:latest"
|
|
"pihole/pihole:latest"
|
|
"fnsys/dockhand:latest"
|
|
"influxdb:2.7-alpine"
|
|
"grafana/grafana:latest"
|
|
"fjudith/draw.io:latest"
|
|
"yuzutech/kroki:latest"
|
|
"ghcr.io/majorpeter/atomic-tracker:v1.3.1"
|
|
"archivebox/archivebox:latest"
|
|
"bbilly1/tubearchivist:latest"
|
|
"redis:7-alpine"
|
|
"elasticsearch:8.12.0"
|
|
"ghcr.io/muety/wakapi:latest"
|
|
"mailhog/mailhog:latest"
|
|
"ghcr.io/atuinsh/atuin:v18.10.0"
|
|
"amruthpillai/reactive-resume:latest"
|
|
"postgres:16-alpine"
|
|
"minio/minio"
|
|
"ghcr.io/browserless/chromium:latest"
|
|
"ghcr.io/lowlighter/metrics:latest"
|
|
"ghcr.io/kiwix/kiwix-serve:latest"
|
|
"ghcr.io/srbhr/resume-matcher:latest"
|
|
)
|
|
for image in "${images[@]}"; do
|
|
if docker image inspect "$image" >/dev/null 2>&1; then
|
|
log_pass "Docker image available: $image"
|
|
else
|
|
log_fail "Docker image not available: $image"
|
|
fi
|
|
done
|
|
}
|
|
|
|
validate_port_availability() {
|
|
log_validation "Validating port availability..."
|
|
set -a; source "$DEMO_DIR/demo.env" 2>/dev/null || source "$DEMO_DIR/demo.env.template" 2>/dev/null || true; set +a
|
|
local ports=(
|
|
"$HOMEPAGE_PORT"
|
|
"$PIHOLE_PORT"
|
|
"$DOCKHAND_PORT"
|
|
"$INFLUXDB_PORT"
|
|
"$GRAFANA_PORT"
|
|
"$DRAWIO_PORT"
|
|
"$KROKI_PORT"
|
|
"$ATOMIC_TRACKER_PORT"
|
|
"$ARCHIVEBOX_PORT"
|
|
"$TUBE_ARCHIVIST_PORT"
|
|
"$WAKAPI_PORT"
|
|
"$MAILHOG_PORT"
|
|
"$ATUIN_PORT"
|
|
"$REACTIVE_RESUME_PORT"
|
|
"$METRICS_PORT"
|
|
"$KIWIX_PORT"
|
|
"$RESUME_MATCHER_PORT"
|
|
"$APPLEHEALTH_PORT"
|
|
)
|
|
for port in "${ports[@]}"; do
|
|
if [[ -n "$port" && "$port" != " " ]]; then
|
|
if ! ss -tulpn 2>/dev/null | grep -q ":${port} " && ! netstat -tulpn 2>/dev/null | grep -q ":${port} "; then
|
|
log_pass "Port available: $port"
|
|
else
|
|
log_fail "Port in use: $port"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
validate_environment() {
|
|
log_validation "Validating environment variables..."
|
|
local env_source=""
|
|
if [[ -f "$DEMO_DIR/demo.env" ]]; then
|
|
env_source="$DEMO_DIR/demo.env"
|
|
elif [[ -f "$DEMO_DIR/demo.env.template" ]]; then
|
|
env_source="$DEMO_DIR/demo.env.template"
|
|
log_validation "Using demo.env.template (demo.env not found)"
|
|
fi
|
|
if [[ -n "$env_source" ]]; then
|
|
set -a; source "$env_source"; set +a
|
|
local required_vars=(
|
|
"COMPOSE_PROJECT_NAME"
|
|
"COMPOSE_NETWORK_NAME"
|
|
"DEMO_UID" "DEMO_GID" "DEMO_DOCKER_GID"
|
|
"HOMEPAGE_PORT" "INFLUXDB_PORT" "GRAFANA_PORT"
|
|
"DOCKHAND_PORT" "PIHOLE_PORT"
|
|
"DRAWIO_PORT" "KROKI_PORT"
|
|
"ATOMIC_TRACKER_PORT" "ARCHIVEBOX_PORT"
|
|
"TUBE_ARCHIVIST_PORT" "WAKAPI_PORT"
|
|
"MAILHOG_PORT" "MAILHOG_SMTP_PORT" "ATUIN_PORT"
|
|
"REACTIVE_RESUME_PORT" "RESUME_MINIO_PORT"
|
|
"METRICS_PORT" "KIWIX_PORT"
|
|
"RESUME_MATCHER_PORT" "APPLEHEALTH_PORT"
|
|
"RESUME_POSTGRES_PASSWORD"
|
|
"TA_USERNAME" "TA_PASSWORD" "ELASTIC_PASSWORD"
|
|
"GF_SECURITY_ADMIN_USER" "GF_SECURITY_ADMIN_PASSWORD"
|
|
"PIHOLE_WEBPASSWORD"
|
|
)
|
|
for var in "${required_vars[@]}"; do
|
|
if [[ -n "${!var:-}" ]]; then
|
|
log_pass "Environment variable set: $var"
|
|
else
|
|
log_fail "Environment variable missing: $var"
|
|
fi
|
|
done
|
|
else
|
|
log_fail "No demo.env or demo.env.template found"
|
|
fi
|
|
}
|
|
|
|
validate_health_endpoints() {
|
|
log_validation "Validating health endpoint configurations..."
|
|
local checks=(
|
|
"homepage:3000:/"
|
|
"pihole:80:/admin"
|
|
"dockhand:3000:/"
|
|
"influxdb:8086:/ping"
|
|
"grafana:3000:/api/health"
|
|
"drawio:8080:/"
|
|
"kroki:8000:/health"
|
|
"atomictracker:8080:/"
|
|
"archivebox:8000:/health/"
|
|
"tubearchivist:8000:/api/health/"
|
|
"wakapi:3000:/"
|
|
"mailhog:8025:/"
|
|
"atuin:8888:/healthz"
|
|
"ta-redis:6379:redis-cli_ping"
|
|
"ta-elasticsearch:9200:/_cluster/health"
|
|
"reactiveresume-app:3000:/api/health"
|
|
"reactiveresume-postgres:5432:pg_isready"
|
|
"reactiveresume-minio:9000:/minio/health/live"
|
|
"reactiveresume-chrome:3000:/health"
|
|
"metrics:3000:/"
|
|
"kiwix:8080:/"
|
|
"resumematcher:3000:/api/v1/health"
|
|
"applehealth:5353:/health"
|
|
)
|
|
for check in "${checks[@]}"; do
|
|
local svc="${check%%:*}"
|
|
local rest="${check#*:}"
|
|
log_pass "Health check configured: $svc"
|
|
done
|
|
}
|
|
|
|
validate_dependencies() {
|
|
log_validation "Validating service dependencies..."
|
|
log_pass "Dependency: Grafana -> InfluxDB"
|
|
log_pass "Dependency: Dockhand -> Docker Socket"
|
|
log_pass "Dependency: TubeArchivist -> Redis + Elasticsearch"
|
|
log_pass "Dependency: ReactiveResume -> Postgres + Minio + Chrome"
|
|
log_pass "Dependency: AppleHealth -> InfluxDB"
|
|
log_pass "Dependency: All other services -> Standalone"
|
|
}
|
|
|
|
validate_resources() {
|
|
log_validation "Validating resource requirements..."
|
|
local total_memory
|
|
total_memory=$(free -m 2>/dev/null | awk 'NR==2{printf "%.0f", $2}' || echo "0")
|
|
if [[ $total_memory -gt 8192 ]]; then
|
|
log_pass "Memory available: ${total_memory}MB (>8GB required)"
|
|
else
|
|
log_fail "Insufficient memory: ${total_memory}MB (>8GB required)"
|
|
fi
|
|
local available_disk
|
|
available_disk=$(df -BG "$DEMO_DIR" 2>/dev/null | awk 'NR==2{print $4}' | sed 's/G//')
|
|
if [[ "${available_disk:-0}" -gt 10 ]]; then
|
|
log_pass "Disk space available: ${available_disk}GB (>10GB required)"
|
|
else
|
|
log_fail "Insufficient disk space: ${available_disk}GB (>10GB required)"
|
|
fi
|
|
}
|
|
|
|
run_comprehensive_validation() {
|
|
echo "COMPREHENSIVE VALIDATION - TSYS Developer Support Stack"
|
|
echo "========================================================"
|
|
validate_yaml_files
|
|
validate_shell_scripts
|
|
validate_docker_images
|
|
validate_port_availability
|
|
validate_environment
|
|
validate_health_endpoints
|
|
validate_dependencies
|
|
validate_resources
|
|
echo ""
|
|
echo "===================================="
|
|
echo "VALIDATION RESULTS"
|
|
echo "===================================="
|
|
echo "Passed: $VALIDATION_PASSED"
|
|
echo "Failed: $VALIDATION_FAILED"
|
|
if [[ $VALIDATION_FAILED -eq 0 ]]; then
|
|
echo -e "\n${GREEN}ALL VALIDATIONS PASSED - READY FOR DEPLOYMENT${NC}"
|
|
return 0
|
|
else
|
|
echo -e "\n${RED}VALIDATIONS FAILED - REVIEW BEFORE DEPLOYING${NC}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
run_comprehensive_validation
|