Files
carlandCursor 0136c15274 Initial commit: Plane 自部署与 gptproto AI 集成
包含本地/远程部署脚本、AI Agent 服务、Dashboard 与 QA 验证。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 11:37:41 +08:00

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 ]]