#!/bin/bash #============================================================================== # Momentry Core — Maintenance & Check Script # Usage: bash check_momentry.sh [--production] [--json] # # Checks: # 1. Version & build info (vs latest tag) # 2. All 4 core services (PostgreSQL, Redis, MongoDB, Qdrant) # 3. Binary health (API endpoint) # 4. Pipeline completeness (scripts, models, processors, tools) # 5. Python dependencies & environment # 6. API smoke tests # 7. Resource usage (CPU, memory, disk) #============================================================================== set -euo pipefail PROJECT_DIR="$(cd "$(dirname "$0")/../.." && pwd)" COLOR=true JSON=false PRODUCTION=false # ─── Color helpers ─── if [ "$COLOR" = true ]; then R='\033[0;31m'; G='\033[0;32m'; Y='\033[1;33m'; B='\033[0;34m'; C='\033[0;36m'; N='\033[0m' else R=''; G=''; Y=''; B=''; C=''; N='' fi ok() { echo -e " ${G}✓${N} $1"; } fail() { echo -e " ${R}✗${N} $1"; FAILURES+=("$1"); } info() { echo -e " ${B}→${N} $1"; } warn() { echo -e " ${Y}⚠${N} $1"; } header(){ echo -e "\n${C}─── $1 ───${N}"; } FAILURES=() CHECKS_TOTAL=0 CHECKS_PASS=0 # ─── Parse args ─── while [ $# -gt 0 ]; do case "$1" in --production) PRODUCTION=true; shift ;; --json) JSON=true; shift ;; --no-color) COLOR=false; shift ;; --help) head -20 "$0"; exit 0 ;; *) echo "Unknown: $1"; exit 1 ;; esac done if $PRODUCTION; then API_BASE="http://127.0.0.1:3002" SCHEMA="public" else API_BASE="http://127.0.0.1:3003" SCHEMA="dev" fi PG_BIN="${PG_BIN:-$HOME/pgsql/18.3/bin}" DB_NAME="${DB_NAME:-momentry}" API_KEY="${MOMENTRY_API_KEY:-muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69}" PYTHON_BIN="${MOMENTRY_PYTHON_PATH:-/opt/homebrew/bin/python3.11}" NOW=$(date '+%Y-%m-%d %H:%M:%S') # JSON output accumulator JSON_PARTS="[" add_json() { local name="$1" status="$2" detail="$3" JSON_PARTS+="{\"check\":\"$name\",\"status\":\"$status\",\"detail\":\"$detail\"}," } emit_json() { JSON_PARTS="${JSON_PARTS%,}]" echo "$JSON_PARTS" | python3 -m json.tool 2>/dev/null || echo "$JSON_PARTS" } run_check() { local name="$1" local status="$2" shift 2 local output output=$("$@" 2>&1) || true local rc=$? if [ "$status" = "optional" ]; then [ $rc -eq 0 ] && ok "$name" || warn "$name — $output" add_json "$name" "$([ $rc -eq 0 ] && echo 'ok' || echo 'warn')" "$([ $rc -eq 0 ] && echo 'pass' || echo "$output")" else CHECKS_TOTAL=$((CHECKS_TOTAL + 1)) if [ $rc -eq 0 ]; then ok "$name" CHECKS_PASS=$((CHECKS_PASS + 1)) add_json "$name" "ok" "pass" else fail "$name — $output" add_json "$name" "fail" "$output" fi fi } cd "$PROJECT_DIR" echo -e "${C}========================================${N}" echo -e "${C} Momentry Core — Maintenance Check${N}" echo -e "${C} Date: $NOW${N}" echo -e "${C} Target: $API_BASE (schema: $SCHEMA)${N}" echo -e "${C}========================================${N}" # ═══════════════════════════════════════════════════════════════ # Check 1: Version & Build # ═══════════════════════════════════════════════════════════════ header "Check 1/8 — Version & Build" HEALTH=$(curl -sf "$API_BASE/health" 2>/dev/null || echo '{"status":"error"}') CURRENT_VER=$(echo "$HEALTH" | python3 -c "import json,sys;print(json.load(sys.stdin).get('version','?'))" 2>/dev/null) CURRENT_HASH=$(echo "$HEALTH" | python3 -c "import json,sys;print(json.load(sys.stdin).get('build_git_hash','?'))" 2>/dev/null) CURRENT_TS=$(echo "$HEALTH" | python3 -c "import json,sys;print(json.load(sys.stdin).get('build_timestamp','?'))" 2>/dev/null) run_check "API server reachable" "critical" bash -c "curl -sf '$API_BASE/health' > /dev/null" run_check "Version: $CURRENT_VER" "critical" bash -c "echo '$CURRENT_VER' | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'" # Compare with latest git tag LATEST_TAG=$(git tag --sort=-v:refname 2>/dev/null | head -1 || echo "none") if [ "$LATEST_TAG" != "none" ]; then if [ "v$CURRENT_VER" = "$LATEST_TAG" ]; then ok "Latest tag: $LATEST_TAG (match)" else warn "Latest tag: $LATEST_TAG (running v$CURRENT_VER, latest is $LATEST_TAG)" fi fi echo " Build: $CURRENT_HASH" echo " Timestamp: $CURRENT_TS" echo " Uptime: $(echo "$HEALTH" | python3 -c "import json,sys;u=json.load(sys.stdin).get('uptime_ms',0);print(f'{u/1000:.0f}s')" 2>/dev/null)" # ═══════════════════════════════════════════════════════════════ # Check 2: Core Services # ═══════════════════════════════════════════════════════════════ header "Check 2/8 — Core Services" run_check "PostgreSQL" "critical" bash -c "'$PG_BIN/pg_isready' -q" run_check "Redis" "critical" bash -c "redis-cli ping 2>/dev/null | grep -q PONG" run_check "MongoDB" "critical" bash -c "mongosh --quiet --eval 'db.adminCommand(\"ping\")' 2>/dev/null | grep -q ok" run_check "Qdrant" "critical" bash -c "curl -sf http://localhost:6333/healthz > /dev/null" # Database query test run_check "Database query (videos)" "critical" bash -c \ "'$PG_BIN/psql' -U accusys -d '$DB_NAME' -c 'SELECT COUNT(*) FROM ${SCHEMA}.videos' > /dev/null 2>&1" # ═══════════════════════════════════════════════════════════════ # Check 3: Server Health # ═══════════════════════════════════════════════════════════════ header "Check 3/8 — Server Health" SCHEMA_CHECK=$(curl -sf "$API_BASE/health/detailed" 2>/dev/null | python3 -c " import json,sys;d=json.load(sys.stdin).get('schema',{}) r=d.get('required',[]);a=d.get('applied',[]) required_set={(m['filename'],m['checksum']) for m in r} applied_set={(m['filename'],m['checksum']) for m in a} missing=required_set-applied_set print(f'{len(r)}|{len(a)}|{d.get(\"ok\")}|{\"|\".join(sorted([m[\"filename\"] for m in r if m[\"filename\"] not in {x[0] for x in applied_set}]) if missing else [])}') " 2>/dev/null || echo "0|0|False|") SCHEMA_OK=$(echo "$SCHEMA_CHECK" | cut -d'|' -f3) SCHEMA_REQUIRED=$(echo "$SCHEMA_CHECK" | cut -d'|' -f1) SCHEMA_APPLIED=$(echo "$SCHEMA_CHECK" | cut -d'|' -f2) SCHEMA_MISSING=$(echo "$SCHEMA_CHECK" | cut -d'|' -f4-) run_check "Schema: $SCHEMA_APPLIED/$SCHEMA_REQUIRED migrations" "critical" bash -c "[ '$SCHEMA_OK' = 'True' ]" [ -n "$SCHEMA_MISSING" ] && warn " Missing: $SCHEMA_MISSING" run_check "Health endpoint" "critical" bash -c \ "echo '$HEALTH' | python3 -c 'import json,sys;d=json.load(sys.stdin);exit(0 if d.get(\"status\")==\"ok\" else 1)'" DETAILED=$(curl -sf "$API_BASE/health/detailed" 2>/dev/null || echo '{}') # Services in health detail echo "$DETAILED" | python3 -c " import json,sys d=json.load(sys.stdin) s=d.get('services',{}) for svc in ['postgres','redis','mongodb','qdrant']: st=s.get(svc,{}).get('status','?') la=s.get(svc,{}).get('latency_ms','?') print(f' {svc}: status={st} latency={la}ms')" 2>/dev/null # ═══════════════════════════════════════════════════════════════ # Check 4: Pipeline Completeness # ═══════════════════════════════════════════════════════════════ header "Check 4/8 — Pipeline Completeness" PL=$(echo "$DETAILED" | python3 -c " import json,sys d=json.load(sys.stdin) p=d.get('pipeline',{}) print(json.dumps(p))" 2>/dev/null) # Scripts SCRIPTS_READY=$(echo "$PL" | python3 -c "import json,sys;print(json.load(sys.stdin).get('scripts_ready',False))" 2>/dev/null) SCRIPTS_COUNT=$(echo "$PL" | python3 -c "import json,sys;print(json.load(sys.stdin).get('scripts_count',0))" 2>/dev/null) run_check "Scripts directory ready" "critical" bash -c "[ '$SCRIPTS_READY' = 'True' ]" run_check "Processor script count: $SCRIPTS_COUNT" "critical" bash -c "[ $SCRIPTS_COUNT -gt 10 ]" # Models MODELS_READY=$(echo "$PL" | python3 -c "import json,sys;print(json.load(sys.stdin).get('models_ready',False))" 2>/dev/null) MODELS_COUNT=$(echo "$PL" | python3 -c "import json,sys;print(json.load(sys.stdin).get('models_count',0))" 2>/dev/null) run_check "Models directory ready" "optional" bash -c "[ '$MODELS_READY' = 'True' ]" echo " Models: $MODELS_COUNT files" # Processor inventory PROC=$(echo "$PL" | python3 -c "import json,sys;d=json.load(sys.stdin).get('processors',{});print(' '.join([k for k in d if d[k]]))" 2>/dev/null) PROC_MISSING=$(echo "$PL" | python3 -c "import json,sys;d=json.load(sys.stdin).get('processors',{});print(' '.join([k for k in d if not d[k]]))" 2>/dev/null) ALL_PROC_COUNT=$(echo "$PL" | python3 -c "import json,sys;d=json.load(sys.stdin).get('processors',{});print(sum(1 for k in d if d[k] and k != 'total_py_files'))" 2>/dev/null) EXPECTED_PROCS=(asr yolo face pose ocr cut caption scene story asrx probe visual_chunk) EXPECTED_COUNT=${#EXPECTED_PROCS[@]} run_check "Processors: $ALL_PROC_COUNT/$EXPECTED_COUNT available" "critical" bash -c "[ $ALL_PROC_COUNT -eq $EXPECTED_COUNT ] 2>/dev/null" for p in "${EXPECTED_PROCS[@]}"; do STATUS=$(echo "$PL" | python3 -c "import json,sys;print(json.load(sys.stdin).get('processors',{}).get('$p',False))" 2>/dev/null) run_check " processor: $p" "optional" bash -c "[ '$STATUS' = 'True' ]" done # Tools FFMPEG=$(echo "$PL" | python3 -c "import json,sys;print(json.load(sys.stdin).get('ffmpeg',False))" 2>/dev/null) run_check "ffmpeg" "critical" bash -c "[ '$FFMPEG' = 'True' ]" command -v ffprobe &>/dev/null && ok "ffprobe" || warn "ffprobe" # Script integrity (SHA256 checksum) CHECKSUMS_FILE="$PROJECT_DIR/scripts/checksums.sha256" if [ -f "$CHECKSUMS_FILE" ]; then CS_TOTAL=0; CS_PASS=0; CS_FAIL=0 while IFS= read -r line; do [ -z "$line" ] && continue EXPECTED_HASH=$(echo "$line" | awk '{print $1}') FILE_PATH=$(echo "$line" | awk '{print $2}') FULL_PATH="$PROJECT_DIR/scripts/$FILE_PATH" CS_TOTAL=$((CS_TOTAL + 1)) if [ -f "$FULL_PATH" ]; then ACTUAL_HASH=$(shasum -a 256 "$FULL_PATH" 2>/dev/null | awk '{print $1}') [ "$ACTUAL_HASH" = "$EXPECTED_HASH" ] && CS_PASS=$((CS_PASS + 1)) || CS_FAIL=$((CS_FAIL + 1)) else CS_FAIL=$((CS_FAIL + 1)) fi done < "$CHECKSUMS_FILE" run_check "Script integrity: $CS_PASS/$CS_TOTAL checksums match" "critical" bash -c "[ $CS_FAIL -eq 0 ]" [ $CS_FAIL -gt 0 ] && warn " $CS_FAIL scripts have hash mismatches" else warn "checksums.sha256 not found — cannot verify script integrity" fi # Inference services EMBEDDING=$(echo "$PL" | python3 -c "import json,sys;print(json.load(sys.stdin).get('embedding_server',{}).get('status','error'))" 2>/dev/null) LLM=$(echo "$PL" | python3 -c "import json,sys;print(json.load(sys.stdin).get('llm',{}).get('status','error'))" 2>/dev/null) run_check "Embedding server (port 11436)" "optional" bash -c "[ '$EMBEDDING' = 'ok' ]" run_check "LLM server (port 8082)" "optional" bash -c "[ '$LLM' = 'ok' ]" # ═══════════════════════════════════════════════════════════════ # Check 5: Python Environment # ═══════════════════════════════════════════════════════════════ header "Check 5/8 — Python Environment" run_check "Python 3.11" "critical" bash -c "[ -f '$PYTHON_BIN' ]" run_check "Python version" "critical" bash -c \ "'$PYTHON_BIN' --version 2>&1 | grep -q '3.11'" # Python deps echo " Python packages" for pkg in PyPDF2 docx openpyxl pptx; do run_check "$pkg" "critical" bash -c "'$PYTHON_BIN' -c 'import $pkg' 2>/dev/null" done # ═══════════════════════════════════════════════════════════════ # Check 6: API Smoke Tests # ═══════════════════════════════════════════════════════════════ header "Check 6/8 — API Smoke Tests" run_check "GET /api/v1/videos" "critical" bash -c \ "curl -sf -o /dev/null -w '%{http_code}' -H 'X-API-Key: $API_KEY' '$API_BASE/api/v1/videos?page=1&page_size=1' 2>/dev/null | grep -qE '^(200|201)'" run_check "GET /api/v1/identities" "critical" bash -c \ "curl -sf -o /dev/null -w '%{http_code}' -H 'X-API-Key: $API_KEY' '$API_BASE/api/v1/identities?page=1' 2>/dev/null | grep -qE '^(200|201)'" run_check "GET /health" "critical" bash -c \ "curl -sf -o /dev/null -w '%{http_code}' '$API_BASE/health' 2>/dev/null | grep -qE '^(200|201)'" run_check "GET /health/detailed" "critical" bash -c \ "curl -sf -o /dev/null -w '%{http_code}' '$API_BASE/health/detailed' 2>/dev/null | grep -qE '^(200|201)'" # Search (POST) run_check "POST /api/v1/search" "optional" bash -c \ "curl -sf -o /dev/null -w '%{http_code}' -X POST '$API_BASE/api/v1/search' \ -H 'Content-Type: application/json' -H 'X-API-Key: $API_KEY' \ -d '{\"query\":\"test\",\"limit\":1}' 2>/dev/null | grep -qE '^(200|201)'" # ═══════════════════════════════════════════════════════════════ # Check 7: Watcher # ═══════════════════════════════════════════════════════════════ header "Check 7/8 — Watcher" WATCH_DIR="${MOMENTRY_SFTP_ROOT:-$PROJECT_DIR/storage/watch}" run_check "Watcher directory exists" "critical" bash -c "[ -d '$WATCH_DIR' ]" # Check server logs for [WATCHER] activity LOG_FILE="$PROJECT_DIR/playground_boot.log" WATCHER_IN_LOGS=false if [ -f "$LOG_FILE" ] && grep -q "\[WATCHER\]" "$LOG_FILE" 2>/dev/null; then WATCHER_IN_LOGS=true fi if $WATCHER_IN_LOGS; then ok "Watcher activity confirmed in server logs" elif curl -sf "$API_BASE/health" &>/dev/null; then # Server is running — since watcher auto-starts, it should be active info "Watcher auto-starts with server — check logs for [WATCHER] messages" ok "Watcher (server is running → watcher is running)" else warn "Watcher status unknown (server not running)" fi # Verify the binary contains watcher code (grep for "Watcher" string in binary) if strings "$PROJECT_DIR/target/debug/momentry_playground" 2>/dev/null | grep -q "Starting File Watcher"; then ok "Watcher compiled into binary" elif strings "$PROJECT_DIR/target/release/momentry" 2>/dev/null | grep -q "Starting File Watcher"; then ok "Watcher compiled into production binary" else # Fallback: check if the source file exists (watcher is always compiled in) grep -q "run_watcher" "$PROJECT_DIR/src/watcher/watcher.rs" 2>/dev/null && \ ok "Watcher code found in source" || \ warn "Watcher source not found" fi # ═══════════════════════════════════════════════════════════════ # Check 8: Resource Usage # ═══════════════════════════════════════════════════════════════ header "Check 8/8 — Resource Usage" # CPU CPU_USED=$(ps -A -o %cpu | awk '{s+=$1}END{printf "%.1f", s}' 2>/dev/null || echo "?") run_check "CPU load: ${CPU_USED}%" "optional" bash -c "echo '$CPU_USED' | python3 -c 'import sys;exit(0 if float(sys.stdin.read().strip()) < 500 else 1)' 2>/dev/null || [ '$CPU_USED' = '?' ]" # Memory MEM_TOTAL=$(vm_stat 2>/dev/null | head -1 | awk '{print $NF}' | sed 's/\.//' || echo "0") MEM_WIRED=$(vm_stat 2>/dev/null | grep "wired" | awk '{print $NF}' | sed 's/\.//' || echo "0") MEM_ACTIVE=$(vm_stat 2>/dev/null | grep "active" | head -1 | awk '{print $NF}' | sed 's/\.//' || echo "0") MEM_PCT=$(echo "scale=1; ($MEM_WIRED + $MEM_ACTIVE) * 100 / $MEM_TOTAL" | bc 2>/dev/null || echo "?") echo " Memory: ${MEM_PCT}% used" # Disk DISK_USAGE=$(df -h / 2>/dev/null | awk 'NR==2 {print $5}' | tr -d '%' || echo "?") run_check "Disk usage: ${DISK_USAGE}%" "critical" bash -c "[ ${DISK_USAGE:-0} -lt 90 ] 2>/dev/null" # Momentry process memory MOMENTRY_PID=$(pgrep -f "momentry.*server" 2>/dev/null | head -1 || echo "") if [ -n "$MOMENTRY_PID" ]; then MOMENTRY_MEM=$(ps -o rss= -p "$MOMENTRY_PID" 2>/dev/null | awk '{printf "%.0f MB", $1/1024}' || echo "?") echo " Momentry RSS: $MOMENTRY_MEM" fi # ═══════════════════════════════════════════════════════════════ # Summary # ═══════════════════════════════════════════════════════════════ PASS_PCT=$([ $CHECKS_TOTAL -gt 0 ] && echo "scale=0; $CHECKS_PASS * 100 / $CHECKS_TOTAL" | bc 2>/dev/null || echo 0) echo "" echo -e "${C}========================================${N}" echo -e "${C} Check Complete${N}" echo -e "${C}========================================${N}" echo " ${G}$CHECKS_PASS${N}/$CHECKS_TOTAL checks passed (${PASS_PCT}%)" echo " ${R}${#FAILURES[@]}${N} failures" echo "" if [ ${#FAILURES[@]} -eq 0 ]; then echo -e "${G} System is healthy and complete.${N}" else echo -e "${Y} Issues found:${N}" for f in "${FAILURES[@]}"; do echo -e " ${R}✗${N} $f"; done fi if $JSON; then emit_json fi exit $([ ${#FAILURES[@]} -eq 0 ] && echo 0 || echo 1)