Document Docker Compose deployment workflow
This commit is contained in:
parent
8ad5b06c18
commit
6f730be0cd
20
.dockerignore
Normal file
20
.dockerignore
Normal file
@ -0,0 +1,20 @@
|
||||
.git
|
||||
.idea
|
||||
.nx
|
||||
.turbo
|
||||
.devenv*
|
||||
.direnv
|
||||
.DS_Store
|
||||
|
||||
.env
|
||||
*.log
|
||||
|
||||
node_modules
|
||||
**/node_modules
|
||||
|
||||
dist
|
||||
apps/web/dist
|
||||
apps/api/bin
|
||||
apps/api/tmp
|
||||
apps/api/data
|
||||
coverage
|
||||
80
Dockerfile
Normal file
80
Dockerfile
Normal file
@ -0,0 +1,80 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
ARG GO_VERSION=1.26.3
|
||||
ARG NODE_VERSION=22
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS api-builder
|
||||
ARG TARGETOS=linux
|
||||
ARG TARGETARCH=amd64
|
||||
ARG GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
ENV GOPROXY=$GOPROXY
|
||||
|
||||
RUN apk add --no-cache ca-certificates git
|
||||
WORKDIR /src
|
||||
|
||||
COPY go.work go.work.sum ./
|
||||
COPY apps/api/go.mod apps/api/go.sum apps/api/
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
cd apps/api && \
|
||||
for attempt in 1 2 3; do \
|
||||
go mod download && exit 0; \
|
||||
echo "go mod download failed, retrying (${attempt}/3)" >&2; \
|
||||
sleep $((attempt * 3)); \
|
||||
done; \
|
||||
go mod download
|
||||
|
||||
COPY apps/api apps/api
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
cd apps/api && \
|
||||
CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -trimpath -ldflags="-s -w" -o /out/easyai-ai-gateway ./cmd/gateway && \
|
||||
CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -trimpath -ldflags="-s -w" -o /out/easyai-ai-gateway-migrate ./cmd/migrate
|
||||
|
||||
FROM alpine:3.22 AS api
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata wget && \
|
||||
adduser -D -H -u 10001 appuser
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=api-builder /out/easyai-ai-gateway /app/easyai-ai-gateway
|
||||
COPY --from=api-builder /out/easyai-ai-gateway-migrate /app/easyai-ai-gateway-migrate
|
||||
COPY apps/api/migrations /app/migrations
|
||||
|
||||
RUN mkdir -p /app/data/static/generated /app/data/static/uploaded && \
|
||||
chown -R appuser:appuser /app
|
||||
|
||||
USER appuser
|
||||
EXPOSE 8088
|
||||
ENV APP_ENV=production \
|
||||
HTTP_ADDR=:8088 \
|
||||
AI_GATEWAY_GENERATED_STORAGE_DIR=/app/data/static/generated \
|
||||
AI_GATEWAY_UPLOADED_STORAGE_DIR=/app/data/static/uploaded
|
||||
|
||||
CMD ["/app/easyai-ai-gateway"]
|
||||
|
||||
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-alpine AS web-builder
|
||||
|
||||
WORKDIR /src
|
||||
RUN npm install -g pnpm@10.18.1
|
||||
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml nx.json ./
|
||||
COPY apps/web/package.json apps/web/package.json
|
||||
COPY packages/contracts/package.json packages/contracts/package.json
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
COPY packages packages
|
||||
COPY apps/web apps/web
|
||||
|
||||
ARG VITE_GATEWAY_API_BASE_URL=/gateway-api
|
||||
ENV VITE_GATEWAY_API_BASE_URL=$VITE_GATEWAY_API_BASE_URL
|
||||
RUN pnpm --filter @easyai-ai-gateway/web build
|
||||
|
||||
FROM nginx:1.27-alpine AS web
|
||||
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=web-builder /src/apps/web/dist /usr/share/nginx/html
|
||||
|
||||
EXPOSE 80
|
||||
54
README.md
54
README.md
@ -43,6 +43,60 @@ pnpm dev
|
||||
|
||||
后端热更新可通过 `GO_WATCH_SHUTDOWN_GRACE_MS` 和 `GO_WATCH_RESTART_DELAY_MS` 调整旧进程退出等待时间与重启间隔。
|
||||
|
||||
## Docker Compose 一键部署
|
||||
|
||||
仓库内提供了面向 `linux/amd64` 的 Docker Compose 构建和部署脚本,会自动构建 API/Web 镜像、启动 PostgreSQL、执行数据库迁移,并验证 API 与 Web 是否可访问:
|
||||
|
||||
```bash
|
||||
scripts/deploy-compose.sh
|
||||
```
|
||||
|
||||
部署成功后默认访问地址:
|
||||
|
||||
- Web: `http://127.0.0.1:5178`
|
||||
- API: `http://127.0.0.1:8088/healthz`
|
||||
- Web 反代 API: `http://127.0.0.1:5178/gateway-api/healthz`
|
||||
|
||||
常用覆盖项:
|
||||
|
||||
```bash
|
||||
AI_GATEWAY_IMAGE_TAG=2026.05.23-1 scripts/deploy-compose.sh
|
||||
AI_GATEWAY_IMAGE_TAG=2026.05.23-1 AI_GATEWAY_PUSH=1 scripts/deploy-compose.sh
|
||||
AI_GATEWAY_IMAGE_TAG=2026.05.23-1 scripts/deploy-compose.sh push
|
||||
AI_GATEWAY_WEB_PORT=8080 AI_GATEWAY_API_PORT=18088 scripts/deploy-compose.sh
|
||||
AI_GATEWAY_GO_PROXY='https://proxy.golang.org,direct' scripts/deploy-compose.sh
|
||||
AI_GATEWAY_SKIP_BUILD=1 scripts/deploy-compose.sh
|
||||
scripts/deploy-compose.sh down
|
||||
scripts/deploy-compose.sh clean
|
||||
```
|
||||
|
||||
默认镜像地址为:
|
||||
|
||||
- API: `registry.cn-shanghai.aliyuncs.com/easyaigc/ai-gateway:latest`
|
||||
- Web: `registry.cn-shanghai.aliyuncs.com/easyaigc/ai-gateway-web:latest`
|
||||
|
||||
执行 `scripts/deploy-compose.sh push` 或设置 `AI_GATEWAY_PUSH=1` 时,会同时推送当前版本 tag 和 `latest`。当前版本 tag 优先使用 `AI_GATEWAY_IMAGE_TAG`;如果没有设置,则使用根 `package.json` 里的 `version`。
|
||||
|
||||
推送前需要先登录阿里云镜像仓库:
|
||||
|
||||
```bash
|
||||
docker login --username=<your-aliyun-account> registry.cn-shanghai.aliyuncs.com
|
||||
```
|
||||
|
||||
Web 容器的 Nginx 配置通过 bind mount 挂载自仓库文件 [docker/nginx.conf](docker/nginx.conf),可直接修改该文件调整静态资源和 `/gateway-api` 反向代理配置。修改后执行以下命令使配置生效:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml restart web
|
||||
```
|
||||
|
||||
Compose 默认使用独立容器数据库 `postgres:18-alpine`,数据卷会保留在 `postgres_data` 和 `api_data`。为避免本地开发 `.env` 中的 `localhost` 数据库地址污染容器部署,compose 使用 `AI_GATEWAY_COMPOSE_*` 变量作为容器部署专用覆盖,例如:
|
||||
|
||||
```bash
|
||||
AI_GATEWAY_COMPOSE_DATABASE_URL='postgresql://easyai:easyai2025@postgres:5432/easyai_ai_gateway?sslmode=disable' scripts/deploy-compose.sh
|
||||
```
|
||||
|
||||
数据库迁移仍通过 `migrator` 容器执行,但脚本使用 `docker compose run --rm migrator`,迁移成功后不会留下 `migrator-1 Exited` 容器。
|
||||
|
||||
## OpenAPI 文档
|
||||
|
||||
修改 `apps/api/internal/httpapi` 下的接口、请求或响应类型后,请重新执行:
|
||||
|
||||
114
docker-compose.yml
Normal file
114
docker-compose.yml
Normal file
@ -0,0 +1,114 @@
|
||||
name: ${COMPOSE_PROJECT_NAME:-easyai-ai-gateway}
|
||||
|
||||
x-api-environment: &api-environment
|
||||
APP_ENV: ${AI_GATEWAY_COMPOSE_APP_ENV:-production}
|
||||
HTTP_ADDR: :8088
|
||||
AI_GATEWAY_DATABASE_URL: ${AI_GATEWAY_COMPOSE_DATABASE_URL:-postgresql://easyai:easyai2025@postgres:5432/easyai_ai_gateway?sslmode=disable}
|
||||
CONFIG_JWT_SECRET: ${CONFIG_JWT_SECRET:-this is a very secret secret}
|
||||
IDENTITY_MODE: ${AI_GATEWAY_COMPOSE_IDENTITY_MODE:-hybrid}
|
||||
SERVER_MAIN_BASE_URL: ${AI_GATEWAY_COMPOSE_SERVER_MAIN_BASE_URL:-http://host.docker.internal:3000}
|
||||
SERVER_MAIN_INTERNAL_TOKEN: ${SERVER_MAIN_INTERNAL_TOKEN:-change-me}
|
||||
TASK_PROGRESS_CALLBACK_ENABLED: ${AI_GATEWAY_COMPOSE_TASK_PROGRESS_CALLBACK_ENABLED:-false}
|
||||
TASK_PROGRESS_CALLBACK_URL: ${AI_GATEWAY_COMPOSE_TASK_PROGRESS_CALLBACK_URL:-http://host.docker.internal:3000/internal/platform/task-progress-callbacks}
|
||||
TASK_PROGRESS_CALLBACK_TIMEOUT_MS: ${TASK_PROGRESS_CALLBACK_TIMEOUT_MS:-5000}
|
||||
TASK_PROGRESS_CALLBACK_MAX_ATTEMPTS: ${TASK_PROGRESS_CALLBACK_MAX_ATTEMPTS:-10}
|
||||
CORS_ALLOWED_ORIGIN: ${AI_GATEWAY_COMPOSE_CORS_ALLOWED_ORIGIN:-http://localhost:5178,http://127.0.0.1:5178}
|
||||
AI_GATEWAY_PUBLIC_BASE_URL: ${AI_GATEWAY_COMPOSE_PUBLIC_BASE_URL:-http://localhost:8088}
|
||||
AI_GATEWAY_GENERATED_STORAGE_DIR: /app/data/static/generated
|
||||
AI_GATEWAY_UPLOADED_STORAGE_DIR: /app/data/static/uploaded
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: ${AI_GATEWAY_POSTGRES_IMAGE:-postgres:18-alpine}
|
||||
platform: ${AI_GATEWAY_PLATFORM:-linux/amd64}
|
||||
environment:
|
||||
POSTGRES_DB: ${AI_GATEWAY_COMPOSE_DATABASE_NAME:-easyai_ai_gateway}
|
||||
POSTGRES_USER: ${AI_GATEWAY_COMPOSE_PG_USER:-easyai}
|
||||
POSTGRES_PASSWORD: ${AI_GATEWAY_COMPOSE_PG_PASSWORD:-easyai2025}
|
||||
ports:
|
||||
- "${AI_GATEWAY_DB_PORT:-54329}:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d \"$${POSTGRES_DB}\""]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 10s
|
||||
restart: unless-stopped
|
||||
|
||||
migrator:
|
||||
image: ${AI_GATEWAY_API_IMAGE:-${AI_GATEWAY_IMAGE_REGISTRY:-registry.cn-shanghai.aliyuncs.com/easyaigc}/ai-gateway:${AI_GATEWAY_IMAGE_TAG:-latest}}
|
||||
platform: ${AI_GATEWAY_PLATFORM:-linux/amd64}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: api
|
||||
args:
|
||||
GOPROXY: ${AI_GATEWAY_GO_PROXY:-https://goproxy.cn,direct}
|
||||
command: ["/app/easyai-ai-gateway-migrate"]
|
||||
environment: *api-environment
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: "no"
|
||||
|
||||
api:
|
||||
image: ${AI_GATEWAY_API_IMAGE:-${AI_GATEWAY_IMAGE_REGISTRY:-registry.cn-shanghai.aliyuncs.com/easyaigc}/ai-gateway:${AI_GATEWAY_IMAGE_TAG:-latest}}
|
||||
platform: ${AI_GATEWAY_PLATFORM:-linux/amd64}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: api
|
||||
args:
|
||||
GOPROXY: ${AI_GATEWAY_GO_PROXY:-https://goproxy.cn,direct}
|
||||
environment: *api-environment
|
||||
ports:
|
||||
- "${AI_GATEWAY_API_PORT:-8088}:8088"
|
||||
volumes:
|
||||
- api_data:/app/data
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8088/readyz | grep -q '\"ok\":true'"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 10s
|
||||
restart: unless-stopped
|
||||
|
||||
web:
|
||||
image: ${AI_GATEWAY_WEB_IMAGE:-${AI_GATEWAY_IMAGE_REGISTRY:-registry.cn-shanghai.aliyuncs.com/easyaigc}/ai-gateway-web:${AI_GATEWAY_IMAGE_TAG:-latest}}
|
||||
platform: ${AI_GATEWAY_PLATFORM:-linux/amd64}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: web
|
||||
args:
|
||||
VITE_GATEWAY_API_BASE_URL: ${AI_GATEWAY_WEB_API_BASE_URL:-/gateway-api}
|
||||
ports:
|
||||
- "${AI_GATEWAY_WEB_PORT:-5178}:80"
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./docker/nginx.conf
|
||||
target: /etc/nginx/conf.d/default.conf
|
||||
read_only: true
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/gateway-api/healthz | grep -q 'easyai-ai-gateway'"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 10s
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
api_data:
|
||||
29
docker/nginx.conf
Normal file
29
docker/nginx.conf
Normal file
@ -0,0 +1,29 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
client_max_body_size 200m;
|
||||
|
||||
location = /gateway-api {
|
||||
return 308 /gateway-api/;
|
||||
}
|
||||
|
||||
location /gateway-api/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Connection "";
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_pass http://api:8088/;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
283
scripts/deploy-compose.sh
Executable file
283
scripts/deploy-compose.sh
Executable file
@ -0,0 +1,283 @@
|
||||
#!/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:-latest}"
|
||||
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
|
||||
scripts/deploy-compose.sh deploy Same as default
|
||||
scripts/deploy-compose.sh push Build and push API/Web images
|
||||
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_PUSH=1
|
||||
AI_GATEWAY_SKIP_BUILD=1
|
||||
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
|
||||
}
|
||||
|
||||
package_version() {
|
||||
local version=""
|
||||
|
||||
version="$(
|
||||
sed -nE 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' "$PROJECT_ROOT/package.json" \
|
||||
| head -n 1
|
||||
)"
|
||||
if [[ -z "$version" ]]; then
|
||||
echo "[ai-gateway] cannot infer package version from package.json; set AI_GATEWAY_IMAGE_TAG" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$version"
|
||||
}
|
||||
|
||||
push_version_tag() {
|
||||
if [[ "$AI_GATEWAY_IMAGE_TAG" == "latest" ]]; then
|
||||
package_version
|
||||
else
|
||||
echo "$AI_GATEWAY_IMAGE_TAG"
|
||||
fi
|
||||
}
|
||||
|
||||
image_repository() {
|
||||
local image="$1"
|
||||
echo "${image%:*}"
|
||||
}
|
||||
|
||||
tag_release_images() {
|
||||
local version_tag="$1"
|
||||
local api_repo web_repo
|
||||
local api_version_image api_latest_image web_version_image web_latest_image
|
||||
|
||||
api_repo="$(image_repository "$AI_GATEWAY_API_IMAGE")"
|
||||
web_repo="$(image_repository "$AI_GATEWAY_WEB_IMAGE")"
|
||||
api_version_image="${api_repo}:${version_tag}"
|
||||
api_latest_image="${api_repo}:latest"
|
||||
web_version_image="${web_repo}:${version_tag}"
|
||||
web_latest_image="${web_repo}:latest"
|
||||
|
||||
docker image inspect "$AI_GATEWAY_API_IMAGE" >/dev/null 2>&1 || {
|
||||
echo "[ai-gateway] missing local API image: ${AI_GATEWAY_API_IMAGE}; build it first" >&2
|
||||
exit 1
|
||||
}
|
||||
docker image inspect "$AI_GATEWAY_WEB_IMAGE" >/dev/null 2>&1 || {
|
||||
echo "[ai-gateway] missing local Web image: ${AI_GATEWAY_WEB_IMAGE}; build it first" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
docker tag "$AI_GATEWAY_API_IMAGE" "$api_version_image"
|
||||
docker tag "$AI_GATEWAY_API_IMAGE" "$api_latest_image"
|
||||
docker tag "$AI_GATEWAY_WEB_IMAGE" "$web_version_image"
|
||||
docker tag "$AI_GATEWAY_WEB_IMAGE" "$web_latest_image"
|
||||
|
||||
RELEASE_IMAGES=(
|
||||
"$api_version_image"
|
||||
"$api_latest_image"
|
||||
"$web_version_image"
|
||||
"$web_latest_image"
|
||||
)
|
||||
}
|
||||
|
||||
push_images() {
|
||||
local version_tag
|
||||
local image
|
||||
|
||||
version_tag="$(push_version_tag)"
|
||||
echo "[ai-gateway] release image tag: ${version_tag}"
|
||||
echo "[ai-gateway] latest tag will also be pushed"
|
||||
tag_release_images "$version_tag"
|
||||
|
||||
for image in "${RELEASE_IMAGES[@]}"; do
|
||||
echo "[ai-gateway] pushing image: ${image}"
|
||||
if ! docker push "$image"; then
|
||||
echo "[ai-gateway] failed to push image; login may be required:" >&2
|
||||
echo "[ai-gateway] docker login --username=<your-aliyun-account> registry.cn-shanghai.aliyuncs.com" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if [[ "${AI_GATEWAY_PUSH:-0}" == "1" ]]; then
|
||||
push_images
|
||||
fi
|
||||
|
||||
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}/healthz" "easyai-ai-gateway"
|
||||
wait_for_http "api readiness" "http://127.0.0.1:${api_port}/readyz" '"ok":true'
|
||||
wait_for_http "web reverse proxy" "http://127.0.0.1:${web_port}/gateway-api/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}/healthz"
|
||||
}
|
||||
|
||||
case "$ACTION" in
|
||||
deploy|up)
|
||||
deploy
|
||||
;;
|
||||
push)
|
||||
cd "$PROJECT_ROOT"
|
||||
build_images
|
||||
push_images
|
||||
;;
|
||||
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
|
||||
Loading…
Reference in New Issue
Block a user