192 lines
6.1 KiB
Bash
Executable File
192 lines
6.1 KiB
Bash
Executable File
#!/bin/sh
|
||
set -eu
|
||
|
||
VERSION="${1:-${RELEASE_VERSION:-}}"
|
||
if [ -z "$VERSION" ]; then
|
||
echo "Usage: $0 <version> [staging|production]"
|
||
echo "Example: $0 v0.1.5 production"
|
||
echo " $0 abc1234 staging"
|
||
exit 1
|
||
fi
|
||
|
||
# 环境参数:staging 或 production(默认 production)
|
||
BUILD_ENV="${2:-production}"
|
||
case "$BUILD_ENV" in
|
||
staging) NGINX_CONF_FILE="infra/docker/nginx-staging.conf" ;;
|
||
*) NGINX_CONF_FILE="infra/docker/nginx-production.conf" ;;
|
||
esac
|
||
echo "Build environment: $BUILD_ENV → nginx config: $NGINX_CONF_FILE"
|
||
|
||
# 可选:只构建指定镜像(api|worker|web),空则全部构建
|
||
BUILD_ONLY="${BUILD_ONLY:-}"
|
||
if [ -n "$BUILD_ONLY" ]; then
|
||
echo "Build mode: only $BUILD_ONLY"
|
||
fi
|
||
|
||
ROOT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
|
||
cd "$ROOT_DIR"
|
||
|
||
if docker ps --format "{{.Names}}" | grep -Eq "^(xiaoxia-(api|web|worker|postgres|redis)-production|gitea)$"; then
|
||
if [ "${ALLOW_SHARED_PRODUCTION_BUILD_HOST:-false}" != "true" ]; then
|
||
echo "Refusing to build runtime images on a host that is running production services."
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# ---- Registry 配置 ----
|
||
REGISTRY="${REGISTRY:-git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas}"
|
||
CACHE_REGISTRY="${CACHE_REGISTRY:-git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas}"
|
||
# 主缓存 tag:develop 分支构建时写入,所有分支读取
|
||
CACHE_TAG_PRIMARY="${CACHE_TAG:-develop}"
|
||
|
||
API_IMAGE="xiaoxia-saas-api:$VERSION"
|
||
WORKER_IMAGE="xiaoxia-saas-worker:$VERSION"
|
||
WEB_IMAGE="xiaoxia-saas-web:$VERSION"
|
||
API_LATEST="xiaoxia-saas-api:dev"
|
||
WORKER_LATEST="xiaoxia-saas-worker:dev"
|
||
|
||
# Registry 上的完整镜像名
|
||
REGISTRY_API="${REGISTRY}/xiaoxia-saas-api:$VERSION"
|
||
REGISTRY_WORKER="${REGISTRY}/xiaoxia-saas-worker:$VERSION"
|
||
REGISTRY_WEB="${REGISTRY}/xiaoxia-saas-web:$VERSION"
|
||
|
||
USE_CACHE=0
|
||
USE_PUSH=0
|
||
CACHE_WRITE=0
|
||
|
||
# 检查 buildx 和 Registry 认证
|
||
if docker buildx version >/dev/null 2>&1; then
|
||
if [ -n "${REGISTRY_TOKEN:-}" ]; then
|
||
printf "%s" "${REGISTRY_TOKEN}" | docker login git.xiaoxiajianji.com -u xiaoxia --password-stdin 2>/dev/null && USE_CACHE=1 && USE_PUSH=1
|
||
fi
|
||
docker buildx use default 2>/dev/null || true
|
||
fi
|
||
|
||
# ---- 缓存读写策略(按分支隔离)----
|
||
# 默认只读不写,防止 feature 分支污染主缓存
|
||
# 只有 develop/main 分支才写回缓存
|
||
BRANCH_NAME="${GITHUB_REF_NAME:-${CI_COMMIT_BRANCH:-unknown}}"
|
||
case "$BRANCH_NAME" in
|
||
develop|main) CACHE_WRITE=1 ; echo "Cache mode: read+write (branch: $BRANCH_NAME)" ;;
|
||
*) CACHE_WRITE=0 ; echo "Cache mode: read-only (branch: $BRANCH_NAME)" ;;
|
||
esac
|
||
|
||
# ---- 构建函数:使用 buildx 直接 push 到 registry ----
|
||
# 不再使用 --load → docker tag → docker push 的串行方式
|
||
# buildx --push 直接从 buildkit 推送,省去序列化导入 daemon 的开销
|
||
build_with_cache() {
|
||
# usage: build_with_cache <image_name> <dockerfile> [extra_args...]
|
||
IMG_NAME="$1"
|
||
DOCKERFILE="$2"
|
||
shift 2
|
||
EXTRA_ARGS="$*"
|
||
|
||
REGISTRY_IMG="${REGISTRY}/xiaoxia-saas-${IMG_NAME}:$VERSION"
|
||
CACHE_FROM="type=registry,ref=${CACHE_REGISTRY}/${IMG_NAME}-cache:${CACHE_TAG_PRIMARY},ignore-error=true"
|
||
|
||
if [ "$CACHE_WRITE" -eq 1 ]; then
|
||
CACHE_TO="type=registry,ref=${CACHE_REGISTRY}/${IMG_NAME}-cache:${CACHE_TAG_PRIMARY},mode=max"
|
||
echo " cache: read+write from ${CACHE_REGISTRY}/${IMG_NAME}-cache:${CACHE_TAG_PRIMARY}"
|
||
else
|
||
CACHE_TO=""
|
||
echo " cache: read-only from ${CACHE_REGISTRY}/${IMG_NAME}-cache:${CACHE_TAG_PRIMARY}"
|
||
fi
|
||
|
||
if [ "$USE_CACHE" -eq 1 ] && [ "$USE_PUSH" -eq 1 ]; then
|
||
if [ -n "$CACHE_TO" ]; then
|
||
docker buildx build \
|
||
$EXTRA_ARGS \
|
||
--cache-from "$CACHE_FROM" \
|
||
--cache-to "$CACHE_TO" \
|
||
-f "$DOCKERFILE" \
|
||
-t "$REGISTRY_IMG" \
|
||
--push \
|
||
.
|
||
else
|
||
docker buildx build \
|
||
$EXTRA_ARGS \
|
||
--cache-from "$CACHE_FROM" \
|
||
-f "$DOCKERFILE" \
|
||
-t "$REGISTRY_IMG" \
|
||
--push \
|
||
.
|
||
fi
|
||
echo " ✅ Pushed: $REGISTRY_IMG"
|
||
else
|
||
# 无push权限时,本地构建用于测试
|
||
docker build --pull=false $EXTRA_ARGS -f "$DOCKERFILE" -t "xiaoxia-saas-${IMG_NAME}:$VERSION" .
|
||
echo " ✅ Built locally: xiaoxia-saas-${IMG_NAME}:$VERSION"
|
||
fi
|
||
}
|
||
|
||
should_build() {
|
||
# usage: should_build <image_name>
|
||
# 返回0表示需要构建,1表示跳过
|
||
local img="$1"
|
||
if [ -z "$BUILD_ONLY" ]; then
|
||
return 0 # 全部构建
|
||
fi
|
||
if [ "$BUILD_ONLY" = "$img" ]; then
|
||
return 0
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
if should_build "api"; then
|
||
echo ""
|
||
echo "=== Building API image ==="
|
||
build_with_cache "api" "infra/docker/api.Dockerfile" \
|
||
"--build-arg APP_VERSION=$VERSION"
|
||
|
||
# 本地也打一个 :dev 标签方便本地引用(如果有本地镜像的话)
|
||
if [ "$USE_PUSH" -eq 0 ]; then
|
||
docker tag "$API_IMAGE" "$API_LATEST" 2>/dev/null || true
|
||
fi
|
||
fi
|
||
|
||
if should_build "worker"; then
|
||
echo ""
|
||
echo "=== Building Worker image ==="
|
||
build_with_cache "worker" "infra/docker/worker.Dockerfile" \
|
||
"--build-arg APP_VERSION=$VERSION"
|
||
|
||
if [ "$USE_PUSH" -eq 0 ]; then
|
||
docker tag "$WORKER_IMAGE" "$WORKER_LATEST" 2>/dev/null || true
|
||
fi
|
||
fi
|
||
|
||
if should_build "web"; then
|
||
echo ""
|
||
echo "=== Building Web image ==="
|
||
# 先构建前端产物(使用持久化 npm 缓存卷)
|
||
NPM_CACHE_VOLUME="xiaoxia-npm-cache"
|
||
if ! docker volume inspect "$NPM_CACHE_VOLUME" >/dev/null 2>&1; then
|
||
docker volume create "$NPM_CACHE_VOLUME" >/dev/null
|
||
echo " Created npm cache volume: $NPM_CACHE_VOLUME"
|
||
fi
|
||
|
||
docker run --rm \
|
||
-v "$PWD:/workspace" \
|
||
-v "$NPM_CACHE_VOLUME:/workspace/apps/web/node_modules" \
|
||
-w /workspace/apps/web \
|
||
docker.m.daocloud.io/library/node:20 \
|
||
sh -lc "npm ci && npm run build"
|
||
|
||
test -f apps/web/dist/index.html
|
||
|
||
build_with_cache "web" "infra/docker/web-artifact.Dockerfile" \
|
||
"--build-arg NGINX_CONF=$NGINX_CONF_FILE"
|
||
fi
|
||
|
||
echo ""
|
||
echo "=== Build complete ==="
|
||
if [ "$USE_PUSH" -eq 1 ]; then
|
||
echo "Images pushed to $REGISTRY:"
|
||
should_build "api" && echo " - $REGISTRY_API"
|
||
should_build "worker" && echo " - $REGISTRY_WORKER"
|
||
should_build "web" && echo " - $REGISTRY_WEB"
|
||
else
|
||
echo "Images built locally (registry push skipped)"
|
||
docker images | grep "xiaoxia-saas.*:$VERSION" || true
|
||
fi
|