#!/usr/bin/env bash
|
# ============================================================
|
# 宁夏万通新材 —— 前端部署脚本(macOS / Linux 版)
|
# 对应仓库里的 WTXC.bat,只适用于本分支(万通新材)。
|
#
|
# ./WTXC.sh # 构建 + 上传 + 解压(等价于 WTXC.bat)
|
# ./WTXC.sh --dry-run # 只构建和本地检查,不连服务器、不上传
|
# ./WTXC.sh --skip-build # 不构建,直接上传当前目录已有的 dist.zip
|
#
|
# 流程与 WTXC.bat 完全一致:pnpm build(带 VITE_COMPANY=WTXC)→ 生成 dist.zip
|
# → scp 上传到 <远端目录>/.dist.zip.uploading → 校验大小 → 改名
|
# → unzip -t 校验 → unzip -o 解压 → 删除远端 zip
|
#
|
# 部署目标:root@42.63.71.140:/home/vue/dist
|
#
|
# 环境变量覆盖:DEPLOY_PASSWORD、DEPLOY_HOST、DEPLOY_DIR、DEPLOY_PORT
|
# (改密码或临时指到别的目录时用,避免把密码写进命令行历史)
|
#
|
# 说明:WTXC.bat 走的是 bin\sshpass.exe(Windows 专用)。macOS 上没有 sshpass
|
# 时本脚本自动改用系统自带的 expect 送密码,因此无需额外安装;若已装
|
# sshpass(brew install hudochenkov/sshpass/sshpass)则优先使用它。
|
# ============================================================
|
set -euo pipefail
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
cd "$SCRIPT_DIR"
|
|
# ------------------------------------------------------------------
|
# 1. 参数与目标配置(对应 WTXC.bat 顶部的 Configuration 段)
|
# ------------------------------------------------------------------
|
SKIP_BUILD=0
|
DRY_RUN=0
|
|
for arg in "$@"; do
|
case "$arg" in
|
--skip-build) SKIP_BUILD=1 ;;
|
--dry-run) DRY_RUN=1 ;;
|
-h | --help)
|
# 打印文件头部的注释说明(第一行 shebang 之后到 set 之前)
|
awk 'NR == 1 { next } /^set / { exit } { print }' "${BASH_SOURCE[0]}"
|
exit 0
|
;;
|
*)
|
echo "[ERROR] 未知参数:${arg}(可用:--skip-build / --dry-run / -h)" >&2
|
exit 1
|
;;
|
esac
|
done
|
|
COMPANY="WTXC"
|
EXPECT_BRANCH="dev_宁夏_万通新材料"
|
REMOTE_USER="root"
|
REMOTE_HOST="42.63.71.140"
|
PORT="22"
|
PASSWORD="Nxwtxc123."
|
REMOTE_DIR="/home/vue/dist"
|
ZIP_FILE="dist.zip"
|
|
# 允许用环境变量覆盖
|
REMOTE_HOST="${DEPLOY_HOST:-$REMOTE_HOST}"
|
REMOTE_DIR="${DEPLOY_DIR:-$REMOTE_DIR}"
|
PASSWORD="${DEPLOY_PASSWORD:-$PASSWORD}"
|
PORT="${DEPLOY_PORT:-$PORT}"
|
|
SSH_OPTS=(-o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o NumberOfPasswordPrompts=1)
|
|
# ------------------------------------------------------------------
|
# 2. 前置检查:只能在本客户分支上构建部署
|
# 包里写死了万通的名称/Logo/接口地址,用别的分支构建会把别客户的前端传上来。
|
# ------------------------------------------------------------------
|
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')"
|
if [ -n "$CURRENT_BRANCH" ] && [ "$CURRENT_BRANCH" != "$EXPECT_BRANCH" ]; then
|
if [ "${ALLOW_BRANCH_MISMATCH:-0}" != "1" ]; then
|
echo "[ERROR] 当前分支是 ${CURRENT_BRANCH},不是万通分支 ${EXPECT_BRANCH}。" >&2
|
echo " 本脚本会把构建结果部署到万通生产服务器,请先切回万通分支:" >&2
|
echo " git checkout ${EXPECT_BRANCH}" >&2
|
echo " 确实要在该分支上部署时,可加 ALLOW_BRANCH_MISMATCH=1 跳过此校验。" >&2
|
exit 1
|
fi
|
echo "[WARN] 分支不是 ${EXPECT_BRANCH}(当前 ${CURRENT_BRANCH}),按 ALLOW_BRANCH_MISMATCH=1 继续。" >&2
|
fi
|
|
echo
|
echo "============================================================"
|
echo " Vue Dist Auto Deploy (万通新材 / macOS)"
|
echo "============================================================"
|
echo
|
echo "Company : $COMPANY"
|
echo "Server : $REMOTE_USER@$REMOTE_HOST:$PORT"
|
echo "Remote Dir : $REMOTE_DIR"
|
echo "Package : $ZIP_FILE"
|
if [ -n "$(git status --porcelain 2>/dev/null || true)" ]; then
|
echo "Git : ⚠ 工作区有未提交改动(部署的是工作区内容,不是提交内容)"
|
else
|
echo "Git : ${CURRENT_BRANCH} @ $(git rev-parse --short HEAD 2>/dev/null || echo '-')"
|
fi
|
echo
|
|
# ------------------------------------------------------------------
|
# 3. 依赖检查(对应 WTXC.bat 的 [1/7]~[3/7])
|
# ------------------------------------------------------------------
|
echo "[1/4] 检查 pnpm / node ..."
|
command -v pnpm >/dev/null 2>&1 || {
|
echo "[ERROR] 找不到 pnpm,请先 npm install -g pnpm" >&2
|
exit 1
|
}
|
command -v node >/dev/null 2>&1 || {
|
echo "[ERROR] 找不到 node" >&2
|
exit 1
|
}
|
echo " pnpm $(pnpm --version) / node $(node --version)"
|
|
if [ ! -d node_modules ]; then
|
echo "[INFO] node_modules 不存在,执行 pnpm install ..."
|
pnpm install
|
else
|
echo " node_modules 已存在"
|
fi
|
|
# ------------------------------------------------------------------
|
# 4. 构建(对应 WTXC.bat 的 [4/7])
|
# 注意:archiver 插件是在 closeBundle 里异步写 dist.zip,
|
# 所以构建完要等它落地;同时先删掉旧 zip,避免把旧包当新包传上去。
|
# 默认每次重新构建(WTXC.bat 也是如此),只有显式 --skip-build 才复用旧包。
|
# ------------------------------------------------------------------
|
if [ "$SKIP_BUILD" -eq 0 ]; then
|
echo
|
echo "[2/4] 构建中(VITE_COMPANY=${COMPANY},pnpm run build)..."
|
rm -f "$ZIP_FILE"
|
VITE_COMPANY="$COMPANY" pnpm run build
|
for _ in $(seq 1 30); do
|
[ -f "$ZIP_FILE" ] && break
|
sleep 1
|
done
|
else
|
echo
|
echo "[2/4] 跳过构建(--skip-build),复用已有的 ${ZIP_FILE}"
|
echo "[WARN] 请确认该包就是用 VITE_COMPANY=${COMPANY} 构建的:" >&2
|
echo " 包里已写死客户的名称/Logo/接口地址,用错包会把别客户的前端传到本服务器。" >&2
|
fi
|
|
if [ ! -f "$ZIP_FILE" ]; then
|
echo "[ERROR] 构建结束但没有生成 $ZIP_FILE" >&2
|
exit 1
|
fi
|
|
ZIP_BYTES="$(wc -c < "$ZIP_FILE" | tr -d ' ')"
|
echo "[OK] 打包完成:${ZIP_FILE}(${ZIP_BYTES} 字节)"
|
|
if [ "$DRY_RUN" -eq 1 ]; then
|
echo "[INFO] --dry-run:本地检查通过,未连服务器、未上传。"
|
exit 0
|
fi
|
|
# ------------------------------------------------------------------
|
# 5. 远端操作(对应 WTXC.bat 的 [6/7][7/7])
|
# ------------------------------------------------------------------
|
EXPECT_HELPER="$(mktemp -t wtxc-sshpass)"
|
cleanup() { rm -f "$EXPECT_HELPER"; }
|
trap cleanup EXIT
|
|
cat > "$EXPECT_HELPER" <<'EXPECT'
|
#!/usr/bin/expect -f
|
# 用法: sshpass-helper <password> <command...>
|
set timeout 3600
|
set pw [lindex $argv 0]
|
set cmd [lrange $argv 1 end]
|
eval spawn -noecho $cmd
|
expect {
|
-re "(?i)are you sure you want to continue connecting" { send "yes\r"; exp_continue }
|
-re "(?i)password:" { send "$pw\r"; exp_continue }
|
-re "(?i)permission denied" { exit 5 }
|
# 超时必须显式退出,否则 expect 会挂在后面的 wait 上不返回
|
timeout { puts stderr "SSH 超时"; exit 9 }
|
eof
|
}
|
catch wait result
|
exit [lindex $result 3]
|
EXPECT
|
chmod +x "$EXPECT_HELPER"
|
|
HAS_SSHPASS=0
|
command -v sshpass >/dev/null 2>&1 && HAS_SSHPASS=1
|
if [ "$HAS_SSHPASS" -eq 0 ] && ! command -v expect >/dev/null 2>&1; then
|
echo "[ERROR] 既没有 sshpass 也没有 expect,无法在脚本里自动送密码。" >&2
|
echo " 可执行:brew install hudochenkov/sshpass/sshpass,或手工 scp 上传。" >&2
|
exit 1
|
fi
|
|
# 在远端执行命令,返回其退出码;stdout 由调用方捕获(失败时输出打到 stderr)
|
remote_run() {
|
local out rc=0
|
if [ "$HAS_SSHPASS" -eq 1 ]; then
|
out="$(SSHPASS="$PASSWORD" sshpass -e ssh -p "$PORT" "${SSH_OPTS[@]}" "$REMOTE_USER@$REMOTE_HOST" "$1" 2>&1)" || rc=$?
|
else
|
out="$("$EXPECT_HELPER" "$PASSWORD" ssh -p "$PORT" "${SSH_OPTS[@]}" "$REMOTE_USER@$REMOTE_HOST" "$1" 2>&1)" || rc=$?
|
fi
|
if [ "$rc" -ne 0 ]; then
|
echo "[ERROR] 远端命令执行失败(exit $rc):$1" >&2
|
[ -n "$out" ] && printf '%s\n' "$out" >&2
|
fi
|
printf '%s' "$out"
|
return "$rc"
|
}
|
|
# 上传一个文件:remote_copy <本地文件> <远端绝对路径>(保留进度条)
|
remote_copy() {
|
if [ "$HAS_SSHPASS" -eq 1 ]; then
|
SSHPASS="$PASSWORD" sshpass -e scp -P "$PORT" "${SSH_OPTS[@]}" "$1" "$REMOTE_USER@$REMOTE_HOST:$2"
|
else
|
"$EXPECT_HELPER" "$PASSWORD" scp -P "$PORT" "${SSH_OPTS[@]}" "$1" "$REMOTE_USER@$REMOTE_HOST:$2"
|
fi
|
}
|
|
echo
|
echo "[3/4] 连接服务器 ..."
|
remote_run "echo SSH_OK" >/dev/null
|
echo "[OK] SSH 连接成功"
|
|
remote_run "mkdir -p '$REMOTE_DIR'" >/dev/null
|
echo "[OK] 远端目录就绪"
|
|
REMOTE_TMP="$REMOTE_DIR/.dist.zip.uploading"
|
echo
|
echo "[4/4] 上传并解压 ..."
|
remote_run "rm -f '$REMOTE_TMP'" >/dev/null
|
remote_copy "$ZIP_FILE" "$REMOTE_TMP"
|
echo "[OK] 上传完成"
|
|
# 远端命令的输出里会混有 expect 回显的密码提示,命令本身的 stdout 总在最后,
|
# 取最后一个字段即为所需数值(stat 的 stderr 也会走到这里,故再做一次空值判断)。
|
REMOTE_BYTES="$(remote_run "stat -c %s '$REMOTE_TMP'" | tr -d '\r' | awk 'END { print $NF }')"
|
REMOTE_BYTES="$(printf '%s' "$REMOTE_BYTES" | tr -cd '0-9')"
|
if [ -z "$REMOTE_BYTES" ] || [ "$REMOTE_BYTES" != "$ZIP_BYTES" ]; then
|
echo "[ERROR] 远端文件大小($REMOTE_BYTES)与本地($ZIP_BYTES)不一致,已中止;远端临时文件保留在 $REMOTE_TMP" >&2
|
exit 1
|
fi
|
echo "[OK] 大小校验一致($REMOTE_BYTES 字节)"
|
|
remote_run "mv -f '$REMOTE_TMP' '$REMOTE_DIR/$ZIP_FILE'" >/dev/null
|
echo "[OK] 包已就位"
|
|
remote_run "command -v unzip" >/dev/null || {
|
echo "[ERROR] 远端没有 unzip:CentOS 用 yum install -y unzip / Ubuntu 用 apt install -y unzip" >&2
|
exit 1
|
}
|
|
remote_run "cd '$REMOTE_DIR' && unzip -tq '$ZIP_FILE'" >/dev/null
|
echo "[OK] 远端 zip 校验通过"
|
|
remote_run "cd '$REMOTE_DIR' && unzip -oq '$ZIP_FILE'" >/dev/null
|
echo "[OK] 解压完成"
|
|
remote_run "rm -f '$REMOTE_DIR/$ZIP_FILE'" >/dev/null || echo "[WARN] 远端 zip 删除失败(不影响使用)"
|
|
echo
|
echo "============================================================"
|
echo " DEPLOY SUCCESS"
|
echo "============================================================"
|
echo "Company : $COMPANY"
|
echo "Server : $REMOTE_HOST:$PORT"
|
echo "Remote Dir : $REMOTE_DIR"
|
echo "Package : ${ZIP_FILE}(${ZIP_BYTES} 字节)"
|
echo "提示:前端是静态资源,浏览器 Ctrl/Cmd+Shift+R 强刷后再验证。"
|
echo
|