#!/bin/bash # 91短剧正式服:构建 → 测试 → Linux standalone + MD5 → 可选触发 rsupdate.sh。 set -euo pipefail SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) cd "$SCRIPT_DIR" source "$SCRIPT_DIR/scripts/deploy-common.sh" JUMP_HOST="${DEPLOY_JUMP_HOST:-server@34.92.204.175}" # 压缩包上传完成后,在同一目录执行服务器现有的 rsupdate.sh。 JUMP_DIR="${DEPLOY_JUMP_DIR:-/home/server/web_91dj/h5}" # valid_path 限制目录为安全字符;命令文本与手动执行一致,兼容 SSH 命令白名单。 REMOTE_UPDATE_COMMAND="cd ${JUMP_DIR%/} && bash rsupdate.sh" SCP_PORT="${DEPLOY_SCP_PORT:-22889}" SSH_PORT="${DEPLOY_SSH_PORT:-22889}" TARGET_ARCH="${DEPLOY_TARGET_ARCH:-x64}" TARGET_LIBC="${DEPLOY_TARGET_LIBC:-glibc}" PACKAGE_NAME="${DEPLOY_PACKAGE_NAME:-web_91dj_h5.tar.gz}" MD5_NAME="${PACKAGE_NAME%.tar.gz}.md5" RELEASE="$SCRIPT_DIR/dist/production-linux-$TARGET_ARCH-$TARGET_LIBC" PACKAGE_ONLY=0 for argument in "$@"; do case "$argument" in --dry-run) DEPLOY_DRY_RUN=1 ;; --package-only) PACKAGE_ONLY=1 ;; -h|--help) printf '用法:%s [--dry-run] [--package-only]\n' "$0" printf '仅打包:--package-only。仅预览:--dry-run(不构建、不联网)。\n' printf '上传目标:%s:%s/;上传成功后在该目录执行 bash rsupdate.sh。\n' "$JUMP_HOST" "${JUMP_DIR%/}" printf '可覆盖 DEPLOY_JUMP_HOST、DEPLOY_JUMP_DIR、DEPLOY_SCP_PORT(%s)、DEPLOY_SSH_PORT(%s)、DEPLOY_PACKAGE_NAME(%s)、DEPLOY_TARGET_ARCH(x64/arm64)、DEPLOY_TARGET_LIBC(glibc/musl)。\n' "$SCP_PORT" "$SSH_PORT" "$PACKAGE_NAME" exit 0 ;; *) fail "不支持的参数 $argument" ;; esac done case "$TARGET_ARCH" in x64|arm64) ;; *) fail 'DEPLOY_TARGET_ARCH 仅支持 x64 或 arm64' ;; esac case "$TARGET_LIBC" in glibc|musl) ;; *) fail 'DEPLOY_TARGET_LIBC 仅支持 glibc 或 musl' ;; esac [[ "$PACKAGE_NAME" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]*\.tar\.gz$ ]] || fail 'DEPLOY_PACKAGE_NAME 必须是 .tar.gz 文件名' if [ "$PACKAGE_ONLY" = 0 ] && [ "${DEPLOY_DRY_RUN:-0}" != 1 ]; then valid_host "$JUMP_HOST" DEPLOY_JUMP_HOST; valid_path "$JUMP_DIR" DEPLOY_JUMP_DIR valid_port "$SCP_PORT" DEPLOY_SCP_PORT; valid_port "$SSH_PORT" DEPLOY_SSH_PORT need ssh; need scp; need tee fi check_local production if [ "${DEPLOY_DRY_RUN:-0}" = 1 ]; then printf 'DRY RUN:正式环境;Linux %s/%s;不构建、不连接服务器。\n' "$TARGET_ARCH" "$TARGET_LIBC"; public_profile production printf ' npm run check → npm run bundle → npm run bundle:linux -- %s %s\n' "$TARGET_ARCH" "$TARGET_LIBC" printf ' 保留 standalone 裁剪依赖,仅安装 lockfile 中目标 Linux 的 Sharp/libvips。\n' printf ' 生成 %s + %s(纯 MD5 hash)。\n' "$PACKAGE_NAME" "$MD5_NAME" if [ "$PACKAGE_ONLY" = 1 ]; then printf ' 仅生成本地包。\n' else printf ' scp -P %s %s %s %s:%s/\n' "$SCP_PORT" "$PACKAGE_NAME" "$MD5_NAME" "$JUMP_HOST" "${JUMP_DIR%/}" printf ' ssh -p %s %s "%s"\n' "$SSH_PORT" "$JUMP_HOST" "$REMOTE_UPDATE_COMMAND" fi exit 0 fi need tar command -v md5sum >/dev/null || command -v md5 >/dev/null || fail '缺少 md5sum 或 macOS md5' lock_build step '构建正式环境并运行回归测试' npm run check step '整理正式环境 standalone 和目标 Linux 原生依赖' npm run bundle npm run bundle:linux -- "$TARGET_ARCH" "$TARGET_LIBC" for file in start.mjs server.js .next/BUILD_ID config/production.env runtime-target.json; do [ -s "$RELEASE/$file" ] || fail "正式包缺少 $file" done for directory in node_modules .next/static public; do [ -d "$RELEASE/$directory" ] || fail "正式包缺少 $directory"; done step "生成 $PACKAGE_NAME" TEMP_PACKAGE=$(mktemp "$SCRIPT_DIR/dist/.production-archive.XXXXXX") if ! COPYFILE_DISABLE=1 COPY_EXTENDED_ATTRIBUTES_DISABLE=1 tar --no-xattrs \ --exclude='._*' --exclude='.DS_Store' --exclude='.env*' --exclude='*.local.env' --exclude='*.log' \ -czf "$TEMP_PACKAGE" -C "$RELEASE" .; then rm -f "$TEMP_PACKAGE"; fail '压缩失败' fi tar -tzf "$TEMP_PACKAGE" >/dev/null mv -f "$TEMP_PACKAGE" "$PACKAGE_NAME" chmod 600 "$PACKAGE_NAME" if command -v md5sum >/dev/null; then md5sum "$PACKAGE_NAME" | awk '{print $1}' > "$MD5_NAME" else md5 -q "$PACKAGE_NAME" > "$MD5_NAME"; fi printf '正式包:%s/%s;校验:%s/%s\n' "$SCRIPT_DIR" "$PACKAGE_NAME" "$SCRIPT_DIR" "$MD5_NAME" if [ "$PACKAGE_ONLY" = 1 ]; then exit 0; fi step "上传到 $JUMP_HOST:$JUMP_DIR/" scp -P "$SCP_PORT" "$PACKAGE_NAME" "$MD5_NAME" "$JUMP_HOST:${JUMP_DIR%/}/" step '执行正式服 rsupdate.sh' printf '远端命令:%s\n' "$REMOTE_UPDATE_COMMAND" RUN_LOG=$(mktemp "$SCRIPT_DIR/dist/production-rsupdate.XXXXXX") if ! ssh -p "$SSH_PORT" "$JUMP_HOST" "$REMOTE_UPDATE_COMMAND" 2>&1 | tee "$RUN_LOG"; then fail "rsupdate.sh 执行失败,日志:$RUN_LOG"; fi if ! node - "$RUN_LOG" <<'NODE' const output=require('node:fs').readFileSync(process.argv[2],'utf8').replace(/\x1b\[[0-?]*[ -/]*[@-~]/g,''); if(/\b(?:BACKOFF|FATAL|ERROR)\b|\bspawn\s+error\b/i.test(output))process.exit(1); NODE then fail "远端输出包含失败状态,日志:$RUN_LOG"; fi printf '正式服 rsupdate.sh 执行完成;请核对远端服务状态。日志:%s\n' "$RUN_LOG"