Files
geo/deploy/scripts/package.sh
T

236 lines
9.0 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# package.sh — 在【有网络的开发机】上运行
# 构建所有镜像(支持跨平台),打包成可离线部署的压缩包
#
# 用法:
# bash package.sh [IMAGE_TAG] [PLATFORM]
#
# 示例:
# bash package.sh latest linux/amd64 # M1/M2 Mac 打包给 x86 服务器(默认)
# bash package.sh v1.0.0 linux/arm64 # 打包给 ARM 服务器(如树莓派、飞腾)
# bash package.sh latest linux/amd64,linux/arm64 # 暂不支持(save 不支持 multi-arch
#
# 产物:
# geo-rankly-deploy-<image-tag>-<date>-<arch>.tar.gz
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
DEPLOY_DIR="${REPO_ROOT}/deploy"
IMAGE_TAG="${1:-latest}"
PLATFORM="${2:-linux/amd64}"
LOCAL_PREFIX="${LOCAL_PREFIX:-geo-rankly}"
SOURCE_IMAGE_REGISTRY="${SOURCE_IMAGE_REGISTRY:-}"
OUTPUT_DIR="${PACKAGE_OUTPUT_DIR:-${REPO_ROOT}}"
DATE_TAG="$(date +%Y%m%d)"
# 将 linux/amd64 → amd64,用于包名
ARCH_LABEL="${PLATFORM##*/}"
SAFE_IMAGE_TAG="$(printf '%s' "${IMAGE_TAG}" | tr '/:@' '---' | tr -c 'A-Za-z0-9_.-' '-')"
PACKAGE_NAME="geo-rankly-deploy-${SAFE_IMAGE_TAG}-${DATE_TAG}-${ARCH_LABEL}"
WORK_DIR="/tmp/${PACKAGE_NAME}"
PACKAGE_PATH="${OUTPUT_DIR}/${PACKAGE_NAME}.tar.gz"
echo "==> 目标平台: ${PLATFORM} | 镜像 tag: ${IMAGE_TAG}"
echo "==> 输出目录: ${OUTPUT_DIR}"
echo ""
# ─── 确保 buildx builder 支持目标平台 ────────────────────────────────────────
echo "==> [0/5] 检查 buildx builder"
BUILDER_NAME="geo-cross-builder"
if ! docker buildx inspect "${BUILDER_NAME}" &>/dev/null; then
echo " 创建多平台 builder: ${BUILDER_NAME}"
docker buildx create \
--name "${BUILDER_NAME}" \
--driver docker-container \
--driver-opt image=moby/buildkit:latest \
--use
else
docker buildx use "${BUILDER_NAME}"
echo " 使用已有 builder: ${BUILDER_NAME}"
fi
# 确保 builder 已启动(首次使用时需要)
docker buildx inspect --bootstrap "${BUILDER_NAME}" > /dev/null
# ─── 准备自定义镜像 ────────────────────────────────────────────────────────────
echo "==> [1/5] 准备自定义镜像 (platform: ${PLATFORM}, tag: ${IMAGE_TAG})"
cd "${REPO_ROOT}"
if [[ -n "${SOURCE_IMAGE_REGISTRY}" ]]; then
echo " pulling from ${SOURCE_IMAGE_REGISTRY}"
for svc in tenant-api browser-fetch worker-generate kol-assist-worker ops-api scheduler migrate frontend ops-web; do
echo " pulling ${SOURCE_IMAGE_REGISTRY}/${svc}:${IMAGE_TAG}"
docker pull --platform "${PLATFORM}" "${SOURCE_IMAGE_REGISTRY}/${svc}:${IMAGE_TAG}"
docker tag "${SOURCE_IMAGE_REGISTRY}/${svc}:${IMAGE_TAG}" "${LOCAL_PREFIX}/${svc}:${IMAGE_TAG}"
done
else
# 注意:--load 仅支持单平台;交叉编译时 buildx 使用 QEMU 或 Go 交叉编译
_build() {
local target="$1" tag_suffix="$2"
shift 2
echo " building ${LOCAL_PREFIX}/${tag_suffix}:${IMAGE_TAG}"
docker buildx build \
--platform "${PLATFORM}" \
--target "${target}" \
"$@" \
--tag "${LOCAL_PREFIX}/${tag_suffix}:${IMAGE_TAG}" \
--load \
-f server/Dockerfile \
server/
}
_build runtime tenant-api --build-arg SERVICE=tenant-api
_build browser-fetch browser-fetch --build-arg SERVICE=browser-fetch
_build runtime worker-generate --build-arg SERVICE=worker-generate
_build runtime kol-assist-worker --build-arg SERVICE=kol-assist-worker
_build runtime ops-api --build-arg SERVICE=ops-api
_build runtime scheduler --build-arg SERVICE=scheduler
_build migrate migrate # migrate stage 不需要 SERVICE arg
echo " building ${LOCAL_PREFIX}/frontend:${IMAGE_TAG}"
docker buildx build \
--platform "${PLATFORM}" \
--tag "${LOCAL_PREFIX}/frontend:${IMAGE_TAG}" \
--load \
-f Dockerfile.frontend \
.
echo " building ${LOCAL_PREFIX}/ops-web:${IMAGE_TAG}"
docker buildx build \
--platform "${PLATFORM}" \
--tag "${LOCAL_PREFIX}/ops-web:${IMAGE_TAG}" \
--load \
-f Dockerfile.ops-web \
.
fi
# ─── 拉取基础设施镜像(指定平台) ─────────────────────────────────────────────
echo "==> [2/5] 拉取基础设施镜像 (platform: ${PLATFORM})"
INFRA_IMAGES=(
"postgres:16-alpine"
2026-05-03 15:03:59 +08:00
"edoburu/pgbouncer:v1.24.1-p1"
"rabbitmq:3.13-management-alpine"
"redis:7-alpine"
"qdrant/qdrant:v1.13.2"
"minio/minio:RELEASE.2025-03-12T18-04-18Z"
"minio/mc:latest"
)
for img in "${INFRA_IMAGES[@]}"; do
echo " pulling ${img}"
# --platform 确保拉取目标架构,覆盖本机可能已缓存的 arm64 版本
docker pull --platform "${PLATFORM}" "${img}"
done
# ─── 保存所有镜像 ───────────────────────────────────────────────────────────
echo "==> [3/5] 保存所有镜像到 images.tar"
mkdir -p "${WORK_DIR}"
ALL_IMAGES=(
"${LOCAL_PREFIX}/tenant-api:${IMAGE_TAG}"
"${LOCAL_PREFIX}/browser-fetch:${IMAGE_TAG}"
"${LOCAL_PREFIX}/worker-generate:${IMAGE_TAG}"
"${LOCAL_PREFIX}/kol-assist-worker:${IMAGE_TAG}"
"${LOCAL_PREFIX}/scheduler:${IMAGE_TAG}"
"${LOCAL_PREFIX}/ops-api:${IMAGE_TAG}"
"${LOCAL_PREFIX}/migrate:${IMAGE_TAG}"
"${LOCAL_PREFIX}/frontend:${IMAGE_TAG}"
"${LOCAL_PREFIX}/ops-web:${IMAGE_TAG}"
"postgres:16-alpine"
2026-05-03 15:03:59 +08:00
"edoburu/pgbouncer:v1.24.1-p1"
"rabbitmq:3.13-management-alpine"
"redis:7-alpine"
"qdrant/qdrant:v1.13.2"
"minio/minio:RELEASE.2025-03-12T18-04-18Z"
"minio/mc:latest"
)
docker save "${ALL_IMAGES[@]}" -o "${WORK_DIR}/images.tar"
# 验证镜像架构
echo " 验证镜像架构:"
for img in "${LOCAL_PREFIX}/tenant-api:${IMAGE_TAG}" "postgres:16-alpine"; do
arch="$(docker inspect --format '{{.Architecture}}' "${img}" 2>/dev/null || echo unknown)"
echo " ${img}${arch}"
done
echo " 镜像包大小: $(du -sh "${WORK_DIR}/images.tar" | cut -f1)"
# ─── 打包部署文件 ───────────────────────────────────────────────────────────
echo "==> [4/5] 打包部署文件"
cp "${DEPLOY_DIR}/docker-compose.yaml" "${WORK_DIR}/"
cp "${DEPLOY_DIR}/docker-compose.offline.yaml" "${WORK_DIR}/"
cp "${DEPLOY_DIR}/docker-compose.aliyun-oss.yaml" "${WORK_DIR}/"
cp "${DEPLOY_DIR}/config.yaml" "${WORK_DIR}/"
cp "${DEPLOY_DIR}/ops-config.yaml" "${WORK_DIR}/"
cp "${DEPLOY_DIR}/prompts.yml" "${WORK_DIR}/"
cp "${DEPLOY_DIR}/.env.example" "${WORK_DIR}/"
cp "${DEPLOY_DIR}/config.local.yaml.example" "${WORK_DIR}/"
cp "${DEPLOY_DIR}/ops-config.local.yaml.example" "${WORK_DIR}/"
cp "${DEPLOY_DIR}/scripts/load-and-start.sh" "${WORK_DIR}/deploy.sh"
cp -R "${DEPLOY_DIR}/k3s" "${WORK_DIR}/k3s"
cp -R "${DEPLOY_DIR}/k3s-aliyun-oss" "${WORK_DIR}/k3s-aliyun-oss"
if [[ -f "${DEPLOY_DIR}/OFFLINE_PACKAGE.md" ]]; then
cp "${DEPLOY_DIR}/OFFLINE_PACKAGE.md" "${WORK_DIR}/README.md"
fi
if [[ -f "${WORK_DIR}/k3s/kustomization.yaml" ]]; then
awk -v tag="${IMAGE_TAG}" '
$1 == "newTag:" && $2 == "latest" { print " newTag: " tag; next }
{ print }
' "${WORK_DIR}/k3s/kustomization.yaml" > "${WORK_DIR}/k3s/kustomization.yaml.tmp"
mv "${WORK_DIR}/k3s/kustomization.yaml.tmp" "${WORK_DIR}/k3s/kustomization.yaml"
fi
chmod +x "${WORK_DIR}/deploy.sh"
# 写入元数据,供 load 脚本读取
cat > "${WORK_DIR}/.package-meta" <<EOF
IMAGE_TAG=${IMAGE_TAG}
LOCAL_PREFIX=${LOCAL_PREFIX}
PLATFORM=${PLATFORM}
BUILT_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
BUILT_ON=$(uname -m)
EOF
cat > "${WORK_DIR}/MANIFEST.txt" <<EOF
geo-rankly offline deployment package
Image tag: ${IMAGE_TAG}
Platform: ${PLATFORM}
Local image prefix: ${LOCAL_PREFIX}
Built at: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
Start:
bash deploy.sh
Files:
images.tar
docker-compose.yaml
docker-compose.offline.yaml
docker-compose.aliyun-oss.yaml
.env.example
config.local.yaml.example
ops-config.local.yaml.example
deploy.sh
EOF
# ─── 压缩 ──────────────────────────────────────────────────────────────────
echo "==> [5/5] 压缩打包"
mkdir -p "${OUTPUT_DIR}"
cd /tmp
tar -czf "${PACKAGE_PATH}" "${PACKAGE_NAME}/"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "${PACKAGE_PATH}" > "${PACKAGE_PATH}.sha256"
else
shasum -a 256 "${PACKAGE_PATH}" > "${PACKAGE_PATH}.sha256"
fi
rm -rf "${WORK_DIR}"
echo ""
echo "✅ 打包完成: ${PACKAGE_PATH}"
echo " 校验文件: ${PACKAGE_PATH}.sha256"
echo " 平台: ${PLATFORM} | 大小: $(du -sh "${PACKAGE_PATH}" | cut -f1)"
echo ""
echo "传输到目标服务器后执行:"
echo " tar xzf ${PACKAGE_NAME}.tar.gz"
echo " cd ${PACKAGE_NAME}"
echo " bash deploy.sh"