AkurAI Build
Menu

Public repository / 8d7067ab5b862b25460ebbdbf35b265ac7e4f995

AkurAI-Framework / scripts/validate.sh

Latest change 6dcfea4319fde02bfdcdbd5c872f5ee323b2cf65 - Checkpoint WIP + add AkurAI Build CI/CD pipeline by Ólafur Búi Ólafsson

#!/usr/bin/env bash
# scripts/validate.sh — Post-deploy validation for AkurAI-Framework
# 
# Canonical validation for ALL Framework-based apps. Other repos can source
# this at their own top (override DOMAIN, PORT, APP_NAME, SKIP_LOGIN).
# Exit 0 = healthy.
set -euo pipefail

: "${DOMAIN:=akurai-framework.olibuijr.com}"
: "${PORT:=8094}"
: "${APP_NAME:=akurai-framework}"
: "${SKIP_LOGIN:=}"         # set to 1 for apps without interactive auth
: "${OIDC_AUTH:=}"          # set to 1 for apps with OIDC (VPN, Drive, etc.)
RED='\033[0;31m'; GRN='\033[0;32m'; NC='\033[0m'
pass=0; fail=0
pass_() { printf "  ${GRN}PASS${NC} %s\n" "$*"; ((++pass)); }
fail_() { printf "  ${RED}FAIL${NC} %s\n" "$*"; ((++fail)); }

echo "=== Post-deploy validation: ${APP_NAME} ==="

# 1. Systemd
if systemctl is-active --quiet "${APP_NAME}.service" 2>/dev/null; then
  pass_ "systemd active"
else
  fail_ "systemd not active"
fi

# 2. Loopback + public health
curl -fsS --max-time 5 "http://127.0.0.1:${PORT}/api/health" >/dev/null 2>&1 \
  && pass_ "loopback health" || fail_ "loopback health"

curl -fsS --max-time 10 "https://${DOMAIN}/api/health" >/dev/null 2>&1 \
  && pass_ "public health" || fail_ "public health"

# 3. Health JSON schema — verify required keys
HEALTH=$(curl -fsS --max-time 5 "https://${DOMAIN}/api/health" 2>/dev/null || echo '{}')
echo "$HEALTH" | python3 -c "
import json,sys
d=json.load(sys.stdin)
for k in ['app','version','status']:
    assert k in d, f'missing key: {k}'
assert d['status'] in ('ok','degraded','down'), f\"bad status: {d.get('status')}\"
" 2>/dev/null && pass_ "health JSON schema valid" || fail_ "health JSON schema invalid"

# 4. Health app name matches APP_NAME
APP=$(echo "$HEALTH" | python3 -c "import json,sys; print(json.load(sys.stdin).get('app',''))" 2>/dev/null)
[ "$APP" = "${APP_NAME}" ] && pass_ "health app='${APP_NAME}'" || fail_ "health app='$APP' (expected '${APP_NAME}')"

# 5. Landing page
LANDING=$(curl -fsS --max-time 10 "https://${DOMAIN}/" 2>/dev/null || echo "")
grep -q 'AkurAI' <<<"$LANDING" && pass_ "landing page" || fail_ "landing page"

# 6. Login flow (Framework local auth)
if [ -z "$SKIP_LOGIN" ]; then
  # 6a. GET /login returns login form
  LOGIN=$(curl -fsS --max-time 5 -c /tmp/${APP_NAME}_cookies "https://${DOMAIN}/login" 2>/dev/null || echo "")
  [ -n "$LOGIN" ] && echo "$LOGIN" | grep -qi 'login\|password\|demo\|innskrá\|session' && pass_ "/login form serves" || fail_ "/login form content"

  # 6b. POST /login with demo creds → 303 + session cookie
  POST_RESULT=$(curl -sS -D - --max-time 5 \
    -d "username=demo&password=akurai" \
    -c /tmp/${APP_NAME}_cookies \
    "https://${DOMAIN}/login" 2>/dev/null || echo "")
  HTTP_CODE=$(echo "$POST_RESULT" | head -1 | awk '{print $2}')
  HAS_COOKIE=$(echo "$POST_RESULT" | grep -i 'set-cookie.*session=' || echo "")
  if [ "${HTTP_CODE:-000}" = "303" ] && [ -n "$HAS_COOKIE" ]; then
    pass_ "POST /login → 303 + Set-Cookie"
  else
    fail_ "POST /login → ${HTTP_CODE:-no response} cookie=$([ -n "$HAS_COOKIE" ] && echo yes || echo no)"
  fi

  # 6c. Protected route with session cookie
  curl -fsS --max-time 5 -b /tmp/${APP_NAME}_cookies "https://${DOMAIN}/account" > /dev/null 2>&1 && pass_ "authenticated request succeeds" || fail_ "authenticated request fails"

  # 6d. Logout
  LOGOUT_CODE=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 5 \
    -X POST -b /tmp/${APP_NAME}_cookies "https://${DOMAIN}/logout" 2>/dev/null)
  [ "${LOGOUT_CODE:-000}" = "303" ] && pass_ "POST /logout → 303" || fail_ "POST /logout → ${LOGOUT_CODE}"

  rm -f /tmp/${APP_NAME}_cookies
fi

# 7. OIDC login flow (for OIDC-based apps like VPN, Drive)
if [ -n "$OIDC_AUTH" ]; then
  # 7a. GET /login → 302 redirect to auth.olibuijr.com/authorize
  LOGIN_REDIR=$(curl -sS -o /dev/null -w '%{http_code}:%{redirect_url}' --max-time 5 "https://${DOMAIN}/login" 2>/dev/null || echo "000:")
  LOGIN_STATUS="${LOGIN_REDIR%%:*}"
  LOGIN_URL="${LOGIN_REDIR#*:}"
  if [ "$LOGIN_STATUS" = "302" ] && echo "$LOGIN_URL" | grep -q 'auth.olibuijr.com/authorize'; then
    pass_ "OIDC /login → 302 to IDP authorize"
  else
    fail_ "OIDC /login → ${LOGIN_STATUS} (expected 302 to IDP)"
  fi

  # 7b. Login URL contains client_id, redirect_uri, state
  if echo "$LOGIN_URL" | grep -q 'client_id=' && echo "$LOGIN_URL" | grep -q 'redirect_uri=' && echo "$LOGIN_URL" | grep -q 'state='; then
    pass_ "authorize URL has client_id, redirect_uri, state"
  else
    fail_ "authorize URL missing required params"
  fi

  # 7c. Protected route without auth redirects to /login
  DASH_CODE=$(curl -sS -o /dev/null -w '%{http_code}:%{redirect_url}' --max-time 5 "https://${DOMAIN}/dashboard" 2>/dev/null || echo "000:")
  DASH_STATUS="${DASH_CODE%%:*}"
  DASH_URL="${DASH_CODE#*:}"
  if [ "$DASH_STATUS" = "302" ] && echo "$DASH_URL" | grep -q '/login$'; then
    pass_ "/dashboard → 302 to /login (no auth)"
  else
    fail_ "/dashboard → ${DASH_STATUS} (expected 302 to /login)"
  fi
fi

echo "━━━━━━━━━━━━━━━━━━━━━━━━"
echo -e "${GRN}Pass: $pass${NC}  ${RED}Fail: $fail${NC}"
[ "$fail" -eq 0 ] || exit 1