包含本地/远程部署脚本、AI Agent 服务、Dashboard 与 QA 验证。 Co-authored-by: Cursor <cursoragent@cursor.com>
89 lines
3.1 KiB
Bash
Executable File
89 lines
3.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# qa/verify-local.sh — Stage A 健康检查
|
|
# 退出码:0 全过;1 任一失败
|
|
set -uo pipefail
|
|
|
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
LOCAL_DIR="${PROJECT_ROOT}/deploy/local"
|
|
ENV_FILE="${LOCAL_DIR}/.env"
|
|
COMPOSE=(docker compose -f "${LOCAL_DIR}/src/deployments/cli/community/docker-compose.yml" -f "${LOCAL_DIR}/docker-compose.override.yml" --env-file "${ENV_FILE}")
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
ok() { printf '[OK] %s\n' "$*"; PASS=$((PASS+1)); }
|
|
bad() { printf '[FAIL] %s\n' "$*"; FAIL=$((FAIL+1)); }
|
|
info() { printf '[..] %s\n' "$*"; }
|
|
|
|
# 1) docker compose ps healthy
|
|
info "1/6 docker compose ps — all services healthy"
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
bad "docker not installed"
|
|
elif [[ ! -f "${ENV_FILE}" ]]; then
|
|
bad ".env missing — run 'cp deploy/local/.env.template deploy/local/.env' first"
|
|
else
|
|
PS_OUTPUT="$("${COMPOSE[@]}" ps --format json 2>/dev/null || true)"
|
|
if [[ -z "${PS_OUTPUT}" ]]; then
|
|
bad "docker compose ps returned nothing — stack not started?"
|
|
else
|
|
UNHEALTHY=$(printf '%s\n' "${PS_OUTPUT}" | grep -Ei '"State":"(exited|dead|restarting)"|"Health":"unhealthy"' || true)
|
|
if [[ -n "${UNHEALTHY}" ]]; then
|
|
bad "some containers not healthy: ${UNHEALTHY}"
|
|
else
|
|
ok "compose ps clean"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# 2) curl :8090 — 端口可能被本机其他进程占,必须验响应来源是 Plane
|
|
info "2/6 curl http://localhost:8090 (verify content is Plane, not random listener)"
|
|
WEB_BODY="$(curl -fsS --max-time 5 http://localhost:8090 2>/dev/null || true)"
|
|
if [[ -z "${WEB_BODY}" ]]; then
|
|
bad "GET http://localhost:8090 did not respond"
|
|
elif printf '%s' "${WEB_BODY}" | grep -qiE '<title>[^<]*plane|/plane-|makeplane'; then
|
|
ok "Plane web on :8090"
|
|
else
|
|
bad "port 8090 responds but content is NOT Plane"
|
|
fi
|
|
|
|
# 3) curl :8100/healthz
|
|
info "3/6 curl http://localhost:8100/healthz"
|
|
HEALTH_BODY="$(curl -fsS --max-time 5 http://localhost:8100/healthz 2>/dev/null || true)"
|
|
if [[ "${HEALTH_BODY}" == *'"status"'*'"ok"'* ]]; then
|
|
ok "AI Agent healthz returned ok"
|
|
else
|
|
bad "AI Agent /healthz body unexpected: ${HEALTH_BODY:-<empty>}"
|
|
fi
|
|
|
|
# 4) .env perm 600
|
|
info "4/6 .env permissions == 600"
|
|
if [[ -f "${ENV_FILE}" ]]; then
|
|
PERM="$(stat -f '%A' "${ENV_FILE}" 2>/dev/null || stat -c '%a' "${ENV_FILE}" 2>/dev/null || echo "?")"
|
|
if [[ "${PERM}" == "600" ]]; then
|
|
ok ".env perm 600"
|
|
else
|
|
bad ".env perm is ${PERM} (need 600) — run: chmod 600 ${ENV_FILE}"
|
|
fi
|
|
else
|
|
bad ".env missing"
|
|
fi
|
|
|
|
# 5) no :latest images
|
|
info "5/6 docker compose images — no :latest"
|
|
if command -v docker >/dev/null 2>&1 && [[ -f "${ENV_FILE}" ]]; then
|
|
IMG_OUT="$("${COMPOSE[@]}" images 2>/dev/null || true)"
|
|
if [[ -z "${IMG_OUT}" ]]; then
|
|
bad "compose images returned nothing"
|
|
elif printf '%s\n' "${IMG_OUT}" | awk 'NR>1 {print $3}' | grep -qx 'latest'; then
|
|
bad "found :latest tag in compose images"
|
|
else
|
|
ok "all images pinned (no :latest)"
|
|
fi
|
|
else
|
|
bad "skipped (docker missing or .env absent)"
|
|
fi
|
|
|
|
# 6) summary
|
|
info "6/6 summary"
|
|
printf '\n=== verify-local.sh result: PASS=%d FAIL=%d ===\n' "${PASS}" "${FAIL}"
|
|
[[ "${FAIL}" -eq 0 ]]
|