#!/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