#!/usr/bin/env bash # 一键发布测试(dev)环境 —— 把 app / console / admin 依次发到同一家公司的同一台测试机。 # # 本脚本不重复任何部署逻辑,只是按顺序调用各服务自己的 deploy/<服务>/dev-deploy.sh <环境>, # 再把结果汇总成一张表。所以「构建什么镜像、推到哪个仓库、远端怎么重启」的规则仍然只有一处 # (各服务的 dev-deploy.sh + deploy/registry-profiles.sh),改那边这里自动跟随。 # # 用法(在 deploy/ 下): # ./dev-deploy-all.sh # 交互选公司(环境),发布全部三个服务 # ./dev-deploy-all.sh along # 发布全部服务到阿龙测试环境 # ./dev-deploy-all.sh along -y -k # 不确认、失败也继续发后面的服务 # # 发布顺序固定 app → console → admin:admin 的页面要连 console,console 又依赖业务库, # 让后端先就位,刷新页面时不至于打到还没起来的后端。 # # ⚠️ macOS 自带 bash 3.2 不认多字节字符边界,`"$rc)"` 会被当成变量名 `rc)` 而在 set -u 下 # 报 unbound variable。所以本文件里凡是变量后面紧跟中文标点的,一律写成 ${rc}。 set -euo pipefail # ⚠️ 本仓库的部署目标只剩阿龙一家:2026-09-13 移除了云雁/灵谱的服务器,2026-09-23 连它们的 # 镜像仓库档案(registry.voitrans.net / registry.lingpu.net)也一并删除。需要找回查 git 历史。 # 要恢复:git log 里找回对应的 env_profile/region_profile 段即可(含 IP 与各自的 SSH 私钥路径)。 DEPLOY_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 仓库的 deploy/ source "$DEPLOY_ROOT/registry-profiles.sh" # 公司 → 仓库地址/账号 blue() { printf "\033[34m%s\033[0m\n" "$*"; } green() { printf "\033[32m%s\033[0m\n" "$*"; } yellow() { printf "\033[33m%s\033[0m\n" "$*"; } red() { printf "\033[31m%s\033[0m\n" "$*"; } # 可选公司(测试环境)。名字与各服务 dev-deploy.sh 的 ENVS 一致,脚本启动时会交叉校验, # 一边加了新环境另一边忘了加时会直接报错,不会发到一半才炸。 ENVS=(along) DEFAULT_ENV="along" # 无参数且无 TTY(CI/管道)时用它 ALL_SVCS=(app console admin) # 默认发布顺序 usage() { cat >&2 </dev/null 2>&1 printf " %d) %-8s → %-6s 仓库 %s\n" "$i" "$e" "${COMPANY_DESC:-}" "${REGISTRY:-}" >&2 ) i=$((i+1)) done printf " 请输入序号 [1-%d]: " "${#ENVS[@]}" >&2 read -r sel || { red "✗ 已取消"; exit 1; } case "$sel" in ''|*[!0-9]*) red "✗ 无效序号: $sel"; exit 1 ;; esac { [ "$sel" -ge 1 ] && [ "$sel" -le "${#ENVS[@]}" ]; } || { red "✗ 序号超出范围"; exit 1; } DEV_ENV="${ENVS[$((sel-1))]}" else DEV_ENV="$DEFAULT_ENV" fi fi # 服务列表一律按 ALL_SVCS 的顺序重排并去重:命令行写成 `admin app` 也照样先发后端, # 顺序是依赖关系决定的,不该由参数书写顺序决定。 if [ "${#SVCS[@]}" -gt 0 ]; then ORDERED=() for s in "${ALL_SVCS[@]}"; do for picked in "${SVCS[@]}"; do if [ "$s" = "$picked" ]; then ORDERED+=("$s"); break; fi done done SVCS=("${ORDERED[@]}") else SVCS=("${ALL_SVCS[@]}") fi registry_profile "$DEV_ENV" || exit 1 # ── 前置校验:脚本在、可执行、且该服务确实认识这个环境 ── # 各服务的 ENVS 是各自 dev-deploy.sh 里的数组,这里读出来比对,避免「顶层能选、子脚本不认」 # 的漂移在推完镜像之后才暴露。 sub_envs() { sed -n 's/^ENVS=(\([^)]*\)).*/\1/p' "$DEPLOY_ROOT/$1/dev-deploy.sh"; } sub_host() { # 仅用于确认页展示,解析不出来就留空 sed -n "/^ $2)/,/;;/p" "$DEPLOY_ROOT/$1/dev-deploy.sh" | sed -n 's/.*DEV_HOST="\([^"]*\)".*/\1/p' | head -1 } for svc in "${SVCS[@]}"; do script="$DEPLOY_ROOT/$svc/dev-deploy.sh" [ -f "$script" ] || { red "✗ 找不到 $script"; exit 1; } found=0 for e in $(sub_envs "$svc"); do [ "$e" = "$DEV_ENV" ] && found=1 && break; done [ "$found" = 1 ] || { red "✗ $svc/dev-deploy.sh 的 ENVS 里没有环境 ${DEV_ENV},请先在该脚本的 env_profile 补一段"; exit 1; } done # Docker 得先能用。子脚本是「docker login → build」的顺序,login 走凭据存储不碰 daemon, # 所以 daemon 没起时会先打出一句 Login Succeeded 再失败,看着像是登录出了问题——在这里先拦一道。 if [ "$DRY_RUN" != 1 ]; then command -v docker >/dev/null 2>&1 || { red "✗ 没找到 docker 命令,请先装 Docker"; exit 1; } if ! docker info >/dev/null 2>&1; then red "✗ Docker 守护进程没在运行,无法构建镜像" echo " macOS: open -a Docker (等托盘鲸鱼图标停止转动后重跑本脚本)" exit 1 fi fi # ── 确认 ── blue "═══ 一键发布测试环境 ═══" printf " 公司/环境 : %s(%s)\n" "$DEV_ENV" "${COMPANY_DESC:-}" printf " 镜像仓库 : %s(账号 %s)\n" "${REGISTRY:-}" "${REGISTRY_USER:-}" printf " 服务顺序 : %s\n" "${SVCS[*]}" for svc in "${SVCS[@]}"; do printf " - %-8s → %s\n" "$svc" "$(sub_host "$svc" "$DEV_ENV" || true)" done printf " 镜像 tag : dev-latest(每次覆盖同一 tag)\n" if [ "$DRY_RUN" = 1 ]; then yellow " (dry-run:只打印命令,不构建、不连服务器)"; fi if [ "$DRY_RUN" != 1 ] && [ "$ASSUME_YES" != 1 ] && [ -t 0 ]; then printf " 确认发布?[y/N]: " read -r ok || { red "✗ 已取消"; exit 1; } case "$ok" in y|Y|yes|YES) ;; *) yellow "已取消"; exit 0 ;; esac fi # ── 依次发布,记录每个服务的结果与耗时 ── # 子脚本在「服务器还没有真实 .env」时会打印提示并以 0 退出(这是它的正常分支,不是失败), # 一键发布里这种情况必须显出来,否则一屏日志刷过去,你会以为三个服务都发上去了。 LOG_DIR="${TMPDIR:-/tmp}/starpivot-dev-deploy-$$" mkdir -p "$LOG_DIR" RESULTS=() FAILED=0 for svc in "${SVCS[@]}"; do blue "───────────────────────────────────────────────" blue ">>> [$svc] 发布到 $DEV_ENV" start=$(date +%s) log="$LOG_DIR/$svc.log" if [ "$DRY_RUN" = 1 ]; then echo " bash $DEPLOY_ROOT/$svc/dev-deploy.sh $DEV_ENV" : > "$log" rc=0 else set +e bash "$DEPLOY_ROOT/$svc/dev-deploy.sh" "$DEV_ENV" 2>&1 | tee "$log" rc=${PIPESTATUS[0]} set -e fi cost=$(( $(date +%s) - start )) if [ "$rc" -ne 0 ]; then RESULTS+=("$svc|失败(退出码 $rc)|${cost}s") FAILED=1 red "✗ [$svc] 发布失败(退出码 ${rc})" if [ "$KEEP_GOING" != 1 ]; then red " 已中止后续服务(要继续请加 -k/--keep-going)" break fi elif grep -q "缺少真实 .env" "$log"; then RESULTS+=("$svc|跳过(服务器缺 .env)|${cost}s") yellow "⚠ [$svc] 镜像已推送,但服务器缺 .env,未重启容器" else RESULTS+=("$svc|成功|${cost}s") green "✓ [$svc] 完成(${cost}s)" fi done # ── 汇总 ── blue "═══ 发布结果($DEV_ENV / ${COMPANY_DESC:-})═══" for r in "${RESULTS[@]}"; do svc="${r%%|*}"; rest="${r#*|}"; status="${rest%%|*}"; cost="${rest##*|}" case "$status" in 成功) printf " \033[32m✓\033[0m %-8s %-22s %s\n" "$svc" "$status" "$cost" ;; 跳过*) printf " \033[33m⚠\033[0m %-8s %-22s %s\n" "$svc" "$status" "$cost" ;; *) printf " \033[31m✗\033[0m %-8s %-22s %s\n" "$svc" "$status" "$cost" ;; esac done echo " 日志: $LOG_DIR" if [ "$FAILED" = 1 ]; then red "✗ 有服务发布失败" exit 1 fi green "✓ 全部完成"