Files
easyai-ai-gateway/scripts/deploy-compose.sh
T
easyai dae5d16a58 refactor(release): 改为 Agent 双阶段人工发布
删除 Gitea Actions、Tag/Main 自动流水线和旧 Runner 配置,取消 Git 操作与发布授权的绑定。\n\n新增本地镜像发布、固定生产部署助手、digest manifest、迁移安全检查、simulation 冒烟及显式回滚流程。\n\n验证:pnpm lint、pnpm test、pnpm build、Go 全量测试、ShellCheck、Compose 配置、人工发布测试和 linux/amd64 完整栈冒烟。
2026-07-22 15:13:40 +08:00

196 lines
5.7 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-easyai-ai-gateway}"
AI_GATEWAY_PLATFORM="${AI_GATEWAY_PLATFORM:-linux/amd64}"
AI_GATEWAY_IMAGE_REGISTRY="${AI_GATEWAY_IMAGE_REGISTRY:-registry.cn-shanghai.aliyuncs.com/easyaigc}"
AI_GATEWAY_IMAGE_TAG="${AI_GATEWAY_IMAGE_TAG:-local}"
AI_GATEWAY_API_IMAGE="${AI_GATEWAY_API_IMAGE:-${AI_GATEWAY_IMAGE_REGISTRY}/ai-gateway:${AI_GATEWAY_IMAGE_TAG}}"
AI_GATEWAY_WEB_IMAGE="${AI_GATEWAY_WEB_IMAGE:-${AI_GATEWAY_IMAGE_REGISTRY}/ai-gateway-web:${AI_GATEWAY_IMAGE_TAG}}"
ACTION="${1:-deploy}"
export COMPOSE_PROJECT_NAME
export AI_GATEWAY_PLATFORM
export AI_GATEWAY_API_IMAGE
export AI_GATEWAY_WEB_IMAGE
export DOCKER_DEFAULT_PLATFORM="${DOCKER_DEFAULT_PLATFORM:-$AI_GATEWAY_PLATFORM}"
compose=(docker compose -f "$COMPOSE_FILE")
usage() {
cat <<'EOF'
Usage:
scripts/deploy-compose.sh Build, migrate, start, and verify locally
scripts/deploy-compose.sh deploy Same as default
scripts/deploy-compose.sh down Stop containers, keep volumes
scripts/deploy-compose.sh clean Stop containers and remove volumes
Useful environment overrides:
AI_GATEWAY_PLATFORM=linux/amd64
AI_GATEWAY_IMAGE_TAG=2026.05.23-1
AI_GATEWAY_API_IMAGE=registry.cn-shanghai.aliyuncs.com/easyaigc/ai-gateway:2026.05.23-1
AI_GATEWAY_WEB_IMAGE=registry.cn-shanghai.aliyuncs.com/easyaigc/ai-gateway-web:2026.05.23-1
AI_GATEWAY_WEB_PORT=5178
AI_GATEWAY_API_PORT=8088
AI_GATEWAY_DB_PORT=54329
AI_GATEWAY_SKIP_BUILD=1
Production publishing is intentionally unavailable here. Use:
scripts/publish-release-images.sh --components auto
EOF
}
fail_with_logs() {
echo "[ai-gateway] deployment failed; recent container state follows" >&2
"${compose[@]}" ps >&2 || true
"${compose[@]}" logs --tail=160 postgres migrator api web >&2 || true
exit 1
}
remove_stale_migrator() {
"${compose[@]}" rm -sf migrator >/dev/null 2>&1 || true
}
wait_for_service_healthy() {
local service="$1"
local container_id=""
local status=""
for _ in $(seq 1 60); do
container_id="$("${compose[@]}" ps -q "$service" 2>/dev/null || true)"
if [[ -n "$container_id" ]]; then
status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id" 2>/dev/null || true)"
if [[ "$status" == "healthy" || "$status" == "running" ]]; then
echo "[ai-gateway] ${service} is ${status}"
return 0
fi
if [[ "$status" == "exited" || "$status" == "dead" ]]; then
echo "[ai-gateway] ${service} stopped while waiting for health" >&2
fail_with_logs
fi
fi
sleep 2
done
echo "[ai-gateway] timed out waiting for ${service} to become healthy" >&2
fail_with_logs
}
wait_for_http() {
local label="$1"
local url="$2"
local expected="${3:-}"
local body=""
for _ in $(seq 1 60); do
if body="$(curl -fsS --max-time 5 "$url" 2>/dev/null)"; then
if [[ -z "$expected" || "$body" == *"$expected"* ]]; then
echo "[ai-gateway] verified ${label}: ${url}"
return 0
fi
fi
sleep 2
done
echo "[ai-gateway] timed out waiting for ${label}: ${url}" >&2
[[ -n "$body" ]] && echo "$body" >&2
fail_with_logs
}
published_port() {
local service="$1"
local private_port="$2"
local fallback="$3"
local endpoint=""
endpoint="$("${compose[@]}" port "$service" "$private_port" 2>/dev/null | tail -n 1 || true)"
if [[ -n "$endpoint" ]]; then
echo "${endpoint##*:}"
else
echo "$fallback"
fi
}
build_images() {
if [[ "${AI_GATEWAY_SKIP_BUILD:-0}" != "1" ]]; then
echo "[ai-gateway] building images"
echo "[ai-gateway] api image: ${AI_GATEWAY_API_IMAGE}"
echo "[ai-gateway] web image: ${AI_GATEWAY_WEB_IMAGE}"
"${compose[@]}" build --pull
else
echo "[ai-gateway] skipping image build"
fi
}
deploy() {
cd "$PROJECT_ROOT"
command -v docker >/dev/null 2>&1 || {
echo "[ai-gateway] docker is required" >&2
exit 1
}
docker compose version >/dev/null 2>&1 || {
echo "[ai-gateway] docker compose v2 is required" >&2
exit 1
}
command -v curl >/dev/null 2>&1 || {
echo "[ai-gateway] curl is required for deployment verification" >&2
exit 1
}
echo "[ai-gateway] compose project: ${COMPOSE_PROJECT_NAME}"
echo "[ai-gateway] target platform: ${AI_GATEWAY_PLATFORM}"
echo "[ai-gateway] image tag: ${AI_GATEWAY_IMAGE_TAG}"
build_images
echo "[ai-gateway] starting postgres"
"${compose[@]}" up -d postgres
wait_for_service_healthy postgres
echo "[ai-gateway] running database migrations"
remove_stale_migrator
"${compose[@]}" run --rm migrator
remove_stale_migrator
echo "[ai-gateway] starting api and web"
"${compose[@]}" up -d api web
local api_port
local web_port
api_port="$(published_port api 8088 "${AI_GATEWAY_API_PORT:-8088}")"
web_port="$(published_port web 80 "${AI_GATEWAY_WEB_PORT:-5178}")"
wait_for_http "api health" "http://127.0.0.1:${api_port}/api/v1/healthz" "easyai-ai-gateway"
wait_for_http "api readiness" "http://127.0.0.1:${api_port}/api/v1/readyz" '"ok":true'
wait_for_http "web reverse proxy" "http://127.0.0.1:${web_port}/api/v1/healthz" "easyai-ai-gateway"
wait_for_http "web app" "http://127.0.0.1:${web_port}/" "EasyAI AI Gateway"
echo "[ai-gateway] deployment succeeded"
echo "[ai-gateway] Web: http://127.0.0.1:${web_port}"
echo "[ai-gateway] API: http://127.0.0.1:${api_port}/api/v1/healthz"
}
case "$ACTION" in
deploy|up)
deploy
;;
down)
cd "$PROJECT_ROOT"
"${compose[@]}" down --remove-orphans
;;
clean)
cd "$PROJECT_ROOT"
"${compose[@]}" down -v --remove-orphans
;;
-h|--help|help)
usage
;;
*)
usage >&2
exit 2
;;
esac