#!/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 lingpu # 发布到灵谱测试环境 # ./dev-deploy.sh yunyan # 发布到云雁测试环境 # ./dev-deploy.sh along # 发布到阿龙测试环境 # ./dev-deploy.sh # 不带参数 → 交互选择环境(无 TTY 时用默认环境 lingpu) # # 参数只用于选环境;开发版镜像始终使用固定 tag dev-latest,每次构建覆盖同一 tag。 set -euo pipefail 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=(lingpu yunyan along) # 可选测试环境(交互菜单按此顺序展示) DEFAULT_ENV="lingpu" # 无参数且无 TTY(CI/管道)时的默认环境 env_profile() { DEV_PORT=22 # 各环境通用默认 DEV_USER=root case "$DEV_ENV" in lingpu) ENV_DESC="灵谱 · 香港阿里云 (lingpu-hk)" registry_profile lingpu # 镜像仓库:灵谱 DEV_HOST="8.217.193.140" # ← 灵谱测试服务器 IP DEV_KEY=/Users/liwei/Documents/灵谱/lingpu-hk.pem # 本机访问该服务器的 SSH 私钥 DEV_DIR="/home/work/starpivot/app" # 远端部署目录(其中 docker-compose.yml/.env/confs 由你自己维护) ;; yunyan) ENV_DESC="云雁测试环境 · 广州阿里云" registry_profile yunyan # 镜像仓库:云雁(registry.voitrans.net,就在这台机上) DEV_HOST="8.138.86.223" # ← 云雁测试服务器 IP(2026-07 由腾讯云 124.222.232.218 迁来) DEV_KEY=~/.ssh/aliyun_guangzhou.pem # 本机访问该服务器的 SSH 私钥 DEV_DIR="/home/work/starpivot/app" ;; along) ENV_DESC="阿龙测试环境 · 阿里云" registry_profile along # 镜像仓库:就在这台机上(registry.ymaikj.com,账号 admin) DEV_HOST="8.133.166.29" # ← 阿龙测试服务器 IP DEV_KEY=~/.ssh/along.pem # 原件在 ~/Documents/阿龙/loginscre.pem,因 macOS TCC 挡文稿目录已拷贝到 ~/.ssh 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) _scp "$REPO_ROOT"/apps/services/sys/ipinfo/ip2region_v*.xdb "$(_dest "$DEV_DIR/confs/")" # 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'" _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 && docker image prune -f" green "✓ app dev[$DEV_ENV] 已发布: $FULL_IMAGE → $DEV_HOST:$DEV_DIR"