#!/usr/bin/env bash # app 开发环境:构建开发镜像 → 推送 → 远端拉取重启。 # # 业务五服务(gateway/home/api/mcp/timer)打成单镜像 starpivot-app,推到私有仓库后远端拉取重启。 # 仅做镜像部署:服务器上的 docker-compose.yml / .env / confs/ 由你自行维护 # (照着仓库里的 env/env.example、confs/*.yaml.example 在服务器上放好真实文件),本脚本不上传任何配置。 # # 用法(在 deploy/app/ 下): # ./dev-deploy.sh along # 发布到阿龙测试环境 # ./dev-deploy.sh # 不带参数 → 交互选择环境(目前只有 along 一个,无 TTY 时也用它) # # 参数只用于选环境;开发版镜像始终使用固定 tag dev-latest,每次构建覆盖同一 tag。 set -euo pipefail # ⚠️ 2026-09-13:云雁(yunyan)/灵谱(lingpu) 的部署目标已从本脚本移除——本仓库的部署目标只剩阿龙。 # 移除的是「往哪台服务器部署」,**镜像仓库没动**:prod-build.sh 仍可选 yunyan/lingpu 推镜像。 # 要恢复:git log 里找回对应的 env_profile/region_profile 段即可(含 IP 与各自的 SSH 私钥路径)。 SVC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # deploy/app REPO_ROOT="$(cd "$SVC_DIR/../.." && pwd)" # 仓库根 BUILD_CONTEXT="$REPO_ROOT/apps/services" # Go 构建上下文 DOCKERFILE="$BUILD_CONTEXT/Dockerfile.voitrans" # 各公司镜像仓库地址/账号统一放这里,换仓库只改 deploy/registry-profiles.sh source "$REPO_ROOT/deploy/registry-profiles.sh" blue() { printf "\033[34m%s\033[0m\n" "$*"; } green() { printf "\033[32m%s\033[0m\n" "$*"; } red() { printf "\033[31m%s\033[0m\n" "$*"; } IMAGE_NAME="starpivot-app" # 业务单镜像名 # ╔══════════════════════════════════════════════════════════════════════╗ # ║ 各测试环境部署目标 / 私有仓库凭据 —— 每环境一整套 仓库/HOST/KEY/远端目录。 ║ # ║ 写死在此(本文件纳入 git,私有仓库内可接受)。 ║ # ║ ⚠️ 公开仓库前务必清空 DEV_HOST / REGISTRY_PASS ║ # ║ 新增环境:在 env_profile 加一段 case,并把环境名加进 ENVS 数组。 ║ # ╚══════════════════════════════════════════════════════════════════════╝ ENVS=(along) # 可选测试环境(目前只剩阿龙;云雁/灵谱已于 2026-09-13 移除,见文件头说明) DEFAULT_ENV="along" # 无参数且无 TTY(CI/管道)时的默认环境 env_profile() { DEV_PORT=22 # 各环境通用默认 DEV_USER=root case "$DEV_ENV" in along) ENV_DESC="阿龙测试环境 · 阿里云" registry_profile along # 镜像仓库:就在这台机上(registry.ymaikj.com,账号 admin) DEV_HOST="8.133.166.29" # ← 阿龙测试服务器 IP DEV_KEY="${DEV_KEY:-/Users/liwei1dao/liwei/密钥/along/releae-guangzhou.pem}" # 本机访问该服务器的 SSH 私钥(2026-08-18 由 ~/.ssh/along.pem 迁到此处;文件名 releae 是原文件笔误) DEV_DIR="/home/work/starpivot/app" ;; *) red "✗ 未知环境: ${DEV_ENV}(应为 ${ENVS[*]// /|})"; exit 1 ;; esac } # 取某环境的目标主机(供交互菜单展示):子 shell 里跑,不污染当前环境变量 env_host_of() { ( DEV_ENV="$1" env_profile >/dev/null 2>&1; printf '%s' "${DEV_HOST:-}" ); } usage() { cat >&2 <&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 env_profile green "✓ 目标环境: ${DEV_ENV}(${ENV_DESC})" PLATFORM="${PLATFORM:-linux/amd64}" TAG="dev-latest" # 开发版固定 tag,每次构建覆盖同一 dev-latest FULL_IMAGE="$REGISTRY/$IMAGE_NAME:$TAG" [ -f "$DOCKERFILE" ] || { red "✗ 缺少 $DOCKERFILE"; exit 1; } : "${DEV_HOST:?请在 dev-deploy.sh 的 env_profile 中为环境 $DEV_ENV 填 DEV_HOST(服务器 IP)}" # ── 远程 SSH ── _ssh() { ssh -p "$DEV_PORT" -i "${DEV_KEY/#\~/$HOME}" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new "$DEV_USER@$DEV_HOST" "$@"; } _scp() { scp -P "$DEV_PORT" -i "${DEV_KEY/#\~/$HOME}" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new "$@"; } _dest() { echo "$DEV_USER@$DEV_HOST:$1"; } # 1) 构建并推送镜像 blue ">>> 构建镜像 $FULL_IMAGE (context=$BUILD_CONTEXT)" echo "$REGISTRY_PASS" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin docker build --network=host --platform "$PLATFORM" -f "$DOCKERFILE" -t "$FULL_IMAGE" "$BUILD_CONTEXT" docker push "$FULL_IMAGE" # 2) 同步「模板 + compose」到服务器(只传模板,绝不覆盖你维护的真实 .env / confs/*.yaml) blue ">>> 同步模板与 compose → $DEV_USER@$DEV_HOST:$DEV_DIR" _ssh "mkdir -p '$DEV_DIR/confs' '$DEV_DIR/log'" _scp "$SVC_DIR/docker-compose.yml" "$(_dest "$DEV_DIR/docker-compose.yml")" _scp "$SVC_DIR"/confs/*.yaml.example "$(_dest "$DEV_DIR/confs/")" _scp "$SVC_DIR/env/env.example" "$(_dest "$DEV_DIR/env.example")" # ip2region 离线 IP 库(静态数据非凭据,home 的 sys.ipinfo 启动必需,缺了会 panic)。 # 约 46MB 且几乎从不变,只在远端缺失或大小对不上时才传 —— 到测试机的上行只有几十 KB/s, # 无条件重传会让每次发 app 白白多花十几分钟(2026-09-08 实测 13 分钟,占整轮部署的八成)。 # 比大小而不是只判存在:上一次传到一半被中断会留下个非空的残文件,`test -s` 认它是好的, # 于是永远补不上,而 home 读到截断的库会在启动时 panic。 XDB_LOCAL_SIZES="$(cd "$REPO_ROOT/apps/services/sys/ipinfo" && wc -c < ip2region_v4.xdb)/$(cd "$REPO_ROOT/apps/services/sys/ipinfo" && wc -c < ip2region_v6.xdb)" XDB_REMOTE_SIZES="$(_ssh "cd '$DEV_DIR/confs' 2>/dev/null && echo \"\$(wc -c < ip2region_v4.xdb 2>/dev/null)/\$(wc -c < ip2region_v6.xdb 2>/dev/null)\"" 2>/dev/null || true)" if [ "$(echo "$XDB_LOCAL_SIZES" | tr -d ' ')" = "$(echo "$XDB_REMOTE_SIZES" | tr -d ' ')" ]; then echo " ip2region 离线 IP 库已是最新($XDB_LOCAL_SIZES 字节),跳过上传" else blue ">>> 同步 ip2region 离线 IP 库(约 46MB,仅在远端缺失/大小不符时传)" _scp "$REPO_ROOT"/apps/services/sys/ipinfo/ip2region_v*.xdb "$(_dest "$DEV_DIR/confs/")" fi # 3) 远端拉取并重启(用你在服务器维护的真实 .env / confs/*.yaml) # REGISTRY/TAG 通过 inline 环境变量传给 compose(优先级高于 --env-file),确保拉到本次镜像。 blue ">>> 远端拉取并重启" _ssh "echo '$REGISTRY_PASS' | docker login '$REGISTRY' -u '$REGISTRY_USER' --password-stdin >/dev/null" _ssh "docker network inspect 1panel-network >/dev/null 2>&1 || docker network create 1panel-network" if ! _ssh "test -f '$DEV_DIR/.env'"; then red "⚠ 服务器缺少真实 .env —— 模板已同步,请登录服务器按模板填好后重跑本脚本:" echo " ssh -i $DEV_KEY $DEV_USER@$DEV_HOST" echo " cd $DEV_DIR && cp env.example .env # 编辑 .env 填真实值,然后重跑 ./dev-deploy.sh" exit 0 fi _ssh "bash -lc 'for f in gateway api home mcp timer; do if [ ! -f \"$DEV_DIR/confs/\$f.yaml\" ] && [ -f \"$DEV_DIR/confs/\$f.yaml.example\" ]; then cp \"$DEV_DIR/confs/\$f.yaml.example\" \"$DEV_DIR/confs/\$f.yaml\"; fi; done'" # compose 里的 service 名由 starpivot-app 改成通用的 app 后,服务器上按旧 service 名建的容器 # 既不属于新 service、又占着容器名 starpivot-app,直接 up 会报 "container name is already in use"。 # 它反正要被 --force-recreate 重建,这里先按 compose 标签认一下,不是本 service 的同名容器就删掉。 _ssh "old=\$(docker inspect -f '{{index .Config.Labels \"com.docker.compose.service\"}}' starpivot-app 2>/dev/null || true); if [ -n \"\$old\" ] && [ \"\$old\" != 'app' ]; then echo \"→ 移除旧 service '\$old' 留下的同名容器 starpivot-app\"; docker rm -f starpivot-app >/dev/null 2>&1 || true; fi" # --remove-orphans:按旧 service 名建的容器在本项目里已成孤儿,一并清掉。 _ssh "cd '$DEV_DIR' && REGISTRY='$REGISTRY' IMAGE_NAME='$IMAGE_NAME' TAG='$TAG' docker compose --env-file .env pull && REGISTRY='$REGISTRY' IMAGE_NAME='$IMAGE_NAME' TAG='$TAG' docker compose --env-file .env up -d --force-recreate --remove-orphans && docker image prune -f" green "✓ app dev[$DEV_ENV] 已发布: $FULL_IMAGE → $DEV_HOST:$DEV_DIR"