#!/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 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