You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
156 lines
8.7 KiB
156 lines
8.7 KiB
#!/usr/bin/env bash
|
|
# admin 生产环境:部署指定镜像版本到生产机,支持回滚。admin 为单机 Nuxt 管理前端,无区域之分。
|
|
#
|
|
# 镜像由 ./prod-build.sh 预先构建推送;本脚本只做「远端拉取 + 重启」。
|
|
# 仅做镜像部署:服务器上的 docker-compose.yml / .env 由你自行维护
|
|
# (照着仓库里的 env/env.example 在服务器上放好真实 .env),本脚本不上传任何真实配置。
|
|
#
|
|
# 用法(在 deploy/admin/ 下):
|
|
# ./prod-deploy.sh <tag> [环境] # 部署指定版本,如 ./prod-deploy.sh 0.1.1 lingpu
|
|
# ./prod-deploy.sh rollback [环境] # 回滚到上一次部署版本(与当前版本来回切换)
|
|
# 环境省略 → 交互选择;无 TTY 时用默认环境 yunyan。
|
|
# ⚠️ 环境必须与 prod-build.sh 构建时所选公司一致,否则拉不到镜像。
|
|
set -euo pipefail
|
|
|
|
SVC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
REPO_ROOT="$(cd "$SVC_DIR/../.." && pwd)"
|
|
# 各公司镜像仓库地址/账号统一放这里,换仓库只改 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" "$*"; }
|
|
|
|
# ╔══════════════════════════════════════════════════════════════════════╗
|
|
# ║ 各生产环境部署目标 —— 每环境一整套 仓库/镜像名/HOST/KEY/远端目录。 ║
|
|
# ║ 写死在此(本文件纳入 git,私有仓库内可接受)。 ║
|
|
# ║ ⚠️ 公开仓库前务必清空 DEPLOY_HOST / REGISTRY_PASS ║
|
|
# ║ 新增环境:在 env_profile 加一段 case,并把环境名加进 PROD_ENVS 数组。 ║
|
|
# ╚══════════════════════════════════════════════════════════════════════╝
|
|
DOCKER_NETWORK="1panel-network" # compose 外部网络名(与服务器一致)
|
|
# 容器名:必须与 docker-compose.yml 的 container_name 一字不差(本脚本会上传覆盖那份 compose)。
|
|
# 仅用于 up 冲突时清理同名遗留容器;不随环境变化。
|
|
CONTAINER_NAME="starpivot-admin"
|
|
PROD_ENVS=(yunyan lingpu) # 可选生产环境(交互菜单按此顺序展示)
|
|
DEFAULT_PROD_ENV="yunyan" # 无参数且无 TTY(CI/管道)时的默认环境
|
|
|
|
env_profile() {
|
|
DEPLOY_PORT=22 # 各环境通用默认
|
|
DEPLOY_USER=root
|
|
case "$DEPLOY_ENV" in
|
|
yunyan)
|
|
ENV_DESC="云雁生产环境 · 美国阿里云"
|
|
registry_profile yunyan || return 1 # 镜像仓库:云雁
|
|
DEPLOY_HOST="47.253.88.66" # ← 云雁生产服务器 IP
|
|
DEPLOY_KEY=/Users/liwei/Documents/key/yunyan-us.pem
|
|
DEPLOY_DIR="/home/work/yunyan-admin" # 远端部署目录(其中 docker-compose.yml/.env 由你自己维护)
|
|
;;
|
|
lingpu)
|
|
ENV_DESC="灵谱生产环境 · 广州阿里云 (lingpu-gz)"
|
|
registry_profile lingpu || return 1 # 镜像仓库:灵谱(registry.lingpu.net,实体在香港机)
|
|
DEPLOY_HOST="8.166.114.65" # ← 灵谱生产服务器 IP
|
|
DEPLOY_KEY=~/.ssh/lingpu-gz.pem # 私钥放 ~/.ssh:~/Documents 会被 macOS TCC 挡住读不出来
|
|
DEPLOY_DIR="/home/work/starpivot/admin"
|
|
;;
|
|
*) red "✗ 未知环境: ${DEPLOY_ENV}(应为 ${PROD_ENVS[*]// /|})"; exit 1 ;;
|
|
esac
|
|
IMAGE_NAME="$(prod_image_name "$DEPLOY_ENV" admin)" || return 1
|
|
}
|
|
# 取某环境的目标主机(供交互菜单展示):子 shell 里跑,不污染当前环境变量
|
|
env_host_of() { ( DEPLOY_ENV="$1" env_profile >/dev/null 2>&1; printf '%s' "${DEPLOY_HOST:-}" ); }
|
|
|
|
# ── 参数:<tag|rollback> [环境] ──
|
|
ARG1="${1:-}"
|
|
[ -n "$ARG1" ] || { red "用法: ./prod-deploy.sh <tag|rollback> [环境](如 0.1.1 lingpu)"; exit 1; }
|
|
if [ "$ARG1" = "rollback" ]; then MODE="rollback"; NEW_TAG=""; else MODE="deploy"; NEW_TAG="$ARG1"; fi
|
|
|
|
DEPLOY_ENV="${2:-}"
|
|
if [ -z "$DEPLOY_ENV" ]; then
|
|
if [ -t 0 ]; then
|
|
blue "选择要部署的生产环境:"
|
|
i=1
|
|
for e in "${PROD_ENVS[@]}"; do
|
|
printf " %d) %-8s → %s\n" "$i" "$e" "$(env_host_of "$e")" >&2
|
|
i=$((i+1))
|
|
done
|
|
printf " 请输入序号 [1-%d](回车用默认 %s): " "${#PROD_ENVS[@]}" "$DEFAULT_PROD_ENV" >&2
|
|
read -r sel || sel=""
|
|
if [ -z "$sel" ]; then
|
|
DEPLOY_ENV="$DEFAULT_PROD_ENV"
|
|
else
|
|
case "$sel" in ''|*[!0-9]*) red "✗ 无效序号: $sel"; exit 1 ;; esac
|
|
{ [ "$sel" -ge 1 ] && [ "$sel" -le "${#PROD_ENVS[@]}" ]; } || { red "✗ 序号超出范围"; exit 1; }
|
|
DEPLOY_ENV="${PROD_ENVS[$((sel-1))]}"
|
|
fi
|
|
else
|
|
DEPLOY_ENV="$DEFAULT_PROD_ENV"
|
|
fi
|
|
fi
|
|
env_profile || exit 1
|
|
|
|
: "${DEPLOY_HOST:?请在 prod-deploy.sh 的 env_profile($DEPLOY_ENV) 段填该环境 IP}"
|
|
|
|
green "✓ 目标环境: ${DEPLOY_ENV}(${ENV_DESC}) 镜像: $REGISTRY/$IMAGE_NAME 目标: $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_DIR"
|
|
# 生产环境改动不可逆,交互模式下再确认一次(无 TTY 的 CI/管道直接放行)。
|
|
if [ -t 0 ]; then
|
|
printf " 确认部署(%s%s)到【%s】? [y/N]: " "$MODE" "${NEW_TAG:+ $NEW_TAG}" "$ENV_DESC" >&2
|
|
read -r ok || ok=""
|
|
case "$ok" in y|Y|yes|YES) ;; *) red "已取消"; exit 1 ;; esac
|
|
fi
|
|
|
|
_ssh() { ssh -p "$DEPLOY_PORT" -i "${DEPLOY_KEY/#\~/$HOME}" -o StrictHostKeyChecking=accept-new "$DEPLOY_USER@$DEPLOY_HOST" "$@"; }
|
|
_scp() { scp -P "$DEPLOY_PORT" -i "${DEPLOY_KEY/#\~/$HOME}" -o StrictHostKeyChecking=accept-new "$@"; }
|
|
_dest() { echo "$DEPLOY_USER@$DEPLOY_HOST:$1"; }
|
|
|
|
# 同步「模板 + compose」(只传模板,绝不覆盖你维护的真实 .env)
|
|
blue ">>> 同步模板与 compose → $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_DIR"
|
|
_ssh "mkdir -p '$DEPLOY_DIR'"
|
|
_scp "$SVC_DIR/docker-compose.yml" "$(_dest "$DEPLOY_DIR/docker-compose.yml")"
|
|
_scp "$SVC_DIR/env/env.example" "$(_dest "$DEPLOY_DIR/env.example")"
|
|
|
|
# 远端拉取并重启(不上传真实配置):tag 历史存服务器上独立的 .deploy_tag,不动你维护的 .env。
|
|
blue ">>> 远端部署($MODE${NEW_TAG:+ $NEW_TAG}) → $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_DIR"
|
|
_ssh bash -s -- "$DEPLOY_DIR" "$MODE" "$NEW_TAG" "$REGISTRY/$IMAGE_NAME" "$REGISTRY" "$REGISTRY_USER" "$REGISTRY_PASS" "$DOCKER_NETWORK" "$CONTAINER_NAME" <<'REMOTE'
|
|
set -e
|
|
DIR="$1"; MODE="$2"; NEW_TAG="$3"; IMG_PREFIX="$4"; REG="$5"; REG_USER="$6"; REG_PASS="$7"; NET="$8"; CNAME="$9"
|
|
|
|
[ -f "$DIR/.env" ] || { echo "✗ 缺少 $DIR/.env(请照 env.example 在服务器填好真实 .env)"; exit 1; }
|
|
[ -f "$DIR/docker-compose.yml" ] || { echo "✗ 缺少 $DIR/docker-compose.yml"; exit 1; }
|
|
|
|
STATE="$DIR/.deploy_tag" # 当前部署 tag(独立文件,不动用户的 .env)
|
|
PREVF="$DIR/.deploy_tag.prev" # 上一次部署 tag
|
|
CUR_TAG="$([ -f "$STATE" ] && cat "$STATE" || true)"
|
|
CUR_PREV="$([ -f "$PREVF" ] && cat "$PREVF" || true)"
|
|
|
|
if [ "$MODE" = "rollback" ]; then
|
|
[ -n "$CUR_PREV" ] || { echo "没有上一次版本记录(.deploy_tag.prev),无法回滚"; exit 1; }
|
|
FINAL_TAG="$CUR_PREV"; FINAL_PREV="$CUR_TAG" # 与当前版本来回切换
|
|
else
|
|
FINAL_TAG="$NEW_TAG"
|
|
if [ -n "$CUR_TAG" ] && [ "$CUR_TAG" != "$NEW_TAG" ]; then FINAL_PREV="$CUR_TAG"; else FINAL_PREV="$CUR_PREV"; fi
|
|
fi
|
|
|
|
echo "$REG_PASS" | docker login "$REG" -u "$REG_USER" --password-stdin >/dev/null
|
|
docker network inspect "$NET" >/dev/null 2>&1 || docker network create "$NET"
|
|
docker pull "$IMG_PREFIX:$FINAL_TAG"
|
|
# 自动适配 compose 版本:优先 v2 插件(docker compose),回退 v1(docker-compose)
|
|
if docker compose version >/dev/null 2>&1; then DC="docker compose"; else DC="docker-compose"; fi
|
|
# .env 与 compose 同目录会被自动加载,无需 --env-file(老版本/ v1 不认该 flag);
|
|
# REGISTRY/TAG 用 inline 环境变量覆盖(优先级高于 .env),不改写用户的 .env
|
|
cd "$DIR"
|
|
# 先正常 up;若因历史遗留的同名容器(非本 compose 项目创建,compose 不会自动替换)冲突,
|
|
# 强制删掉该容器后重试一次。容器名与 docker-compose.yml 的 container_name 一致。
|
|
REGISTRY="$REG" TAG="$FINAL_TAG" $DC up -d --force-recreate || {
|
|
echo ">>> up 失败,尝试清理同名遗留容器($CNAME)后重试"
|
|
docker rm -f "$CNAME" 2>/dev/null || true
|
|
REGISTRY="$REG" TAG="$FINAL_TAG" $DC up -d --force-recreate
|
|
}
|
|
docker image prune -f
|
|
|
|
echo "$FINAL_TAG" > "$STATE"
|
|
[ -n "$FINAL_PREV" ] && echo "$FINAL_PREV" > "$PREVF" || true
|
|
echo "已部署版本 TAG=$FINAL_TAG (上一次=${FINAL_PREV:-无})"
|
|
REMOTE
|
|
|
|
green "✓ admin $MODE 完成"
|
|
|