Menu
Public repository / master
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