Files
web_hghl_h5/deploy_prod.sh
T
谢宇宁andClaude Opus 5.5 61d0a11dfa Initial commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 17:51:31 +07:00

123 lines
5.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 正式服:构建 → Linux 运行包 + MD5 → 跳板机 running.sh(对齐 hkdj-web)。
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
cd "$SCRIPT_DIR"
JUMP_HOST="${DEPLOY_JUMP_HOST:-server@34.92.204.175}"
JUMP_DIR="${DEPLOY_JUMP_DIR:-/home/server/web_hjsq/h5}"
SCP_PORT="${DEPLOY_SCP_PORT:-22889}"
SSH_PORT="${DEPLOY_SSH_PORT:-22889}"
REMOTE_SCRIPT="${DEPLOY_REMOTE_SCRIPT:-rsupdate.sh}"
SSH_IDENTITY="${DEPLOY_SSH_IDENTITY:-$HOME/.ssh/id_ed25519}"
TARGET_ARCH="${DEPLOY_TARGET_ARCH:-x64}"
TARGET_LIBC="${DEPLOY_TARGET_LIBC:-glibc}"
PACKAGE_NAME=web_hjsq_h5.tar.gz
MD5_NAME=web_hjsq_h5.md5
PACKAGE="$SCRIPT_DIR/$PACKAGE_NAME"
MD5_FILE="$SCRIPT_DIR/$MD5_NAME"
RELEASE="$SCRIPT_DIR/dist/production-linux-$TARGET_ARCH-$TARGET_LIBC"
PACKAGE_ONLY=0
fail() { printf '错误:%s\n' "$*" >&2; exit 1; }
step() { printf '\n▸ %s\n' "$*"; }
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 '默认跳板:%s 端口 %s,远程脚本 %s\n' "$JUMP_HOST" "$SCP_PORT" "$REMOTE_SCRIPT"
printf '可选:DEPLOY_JUMP_HOST / DEPLOY_JUMP_DIR / DEPLOY_SCP_PORT / DEPLOY_SSH_PORT\n'
printf ' DEPLOY_REMOTE_SCRIPT / DEPLOY_SSH_IDENTITY / DEPLOY_TARGET_ARCH / DEPLOY_TARGET_LIBC\n'
exit 0 ;;
*) fail "不支持的参数 $argument;使用 --help 查看用法" ;;
esac
done
[[ "$JUMP_HOST" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.@-]*$ ]] || fail 'DEPLOY_JUMP_HOST 格式无效'
[[ "$JUMP_DIR" =~ ^/[a-zA-Z0-9_-]+(/[a-zA-Z0-9_-]+)*/?$ ]] || fail 'DEPLOY_JUMP_DIR 必须是普通绝对路径'
JUMP_DIR="${JUMP_DIR%/}"
for port in "$SCP_PORT" "$SSH_PORT"; do
[[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ] || fail 'SCP/SSH 端口必须在 1–65535 内'
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
for tool in node pnpm tar; do
command -v "$tool" >/dev/null || fail "缺少命令 $tool"
done
node -e 'const [a,b]=process.versions.node.split(".").map(Number); if(a<20 || a===20 && b<9) process.exit(1)' || fail '需要 Node.js >=20.9,推荐 22'
command -v md5sum >/dev/null || command -v md5 >/dev/null || fail '缺少 md5sum 或 macOS md5'
for file in config/production.env package.json scripts/prepare-production-runtime.ts; do
[ -s "$file" ] || fail "缺少 $file"
done
[ -x node_modules/.bin/tsx ] || fail '请先执行 pnpm install'
if [ "${DEPLOY_DRY_RUN:-0}" = 1 ]; then
printf 'DRY RUN:正式环境;Linux %s/%s\n' "$TARGET_ARCH" "$TARGET_LIBC"
node --input-type=module -e 'import {readFileSync} from "node:fs"; import {parse} from "dotenv"; const c=parse(readFileSync("config/production.env")); console.log(` PORT=${c.PORT} API=${c.API_PROXY_TARGET||c.NEXT_PUBLIC_API_BASE_URL}`)'
printf ' pnpm build → bundle → bundle:linux -- %s %s\n' "$TARGET_ARCH" "$TARGET_LIBC"
printf ' 生成 %s + %s\n' "$PACKAGE_NAME" "$MD5_NAME"
if [ "$PACKAGE_ONLY" = 1 ]; then
printf ' --package-only:生成后结束,不上传。\n'
else
printf ' scp -P %s → %s:%s/\n' "$SCP_PORT" "$JUMP_HOST" "$JUMP_DIR"
printf ' ssh -p %s %s bash %s/%s\n' "$SSH_PORT" "$JUMP_HOST" "$JUMP_DIR" "$REMOTE_SCRIPT"
fi
exit 0
fi
if [ "$PACKAGE_ONLY" = 0 ]; then
for tool in ssh scp tee; do command -v "$tool" >/dev/null || fail "缺少命令 $tool"; done
[ -f "$SSH_IDENTITY" ] || fail "缺少 SSH 私钥:$SSH_IDENTITY"
fi
mkdir -p "$SCRIPT_DIR/dist"
DEPLOY_LOCK="$SCRIPT_DIR/dist/.production-deploy.lock"
mkdir "$DEPLOY_LOCK" 2>/dev/null || fail "已有正式发布进程或残留锁:${DEPLOY_LOCK}"
trap 'rmdir "$DEPLOY_LOCK" 2>/dev/null || true' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
step '构建并整理正式环境 standalone'
pnpm run build
pnpm run bundle
step "准备 Linux $TARGET_ARCH/$TARGET_LIBC 运行包"
pnpm run bundle:linux -- "$TARGET_ARCH" "$TARGET_LIBC"
for file in start.mjs server.js .next/BUILD_ID config/production.env runtime-target.json ecosystem.config.cjs; 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"
COPYFILE_DISABLE=1 COPY_EXTENDED_ATTRIBUTES_DISABLE=1 tar \
--no-xattrs \
--exclude='._*' --exclude='.DS_Store' --exclude='.env*' \
--exclude='*.local.env' --exclude='*.log' \
-czf "$PACKAGE" -C "$RELEASE" .
chmod 600 "$PACKAGE"
if command -v md5sum >/dev/null; then
md5sum "$PACKAGE" | awk '{print $1}' > "$MD5_FILE"
else
md5 -q "$PACKAGE" > "$MD5_FILE"
fi
tar -tzf "$PACKAGE" >/dev/null
printf '运行包已生成:%s;校验:%s\n' "$PACKAGE_NAME" "$MD5_NAME"
if [ "$PACKAGE_ONLY" = 1 ]; then exit 0; fi
SSH_OPTS=(-i "$SSH_IDENTITY" -o IdentitiesOnly=yes -o BatchMode=yes)
step "上传到跳板机 $JUMP_HOST:$JUMP_DIR/"
scp -P "$SCP_PORT" "${SSH_OPTS[@]}" "$PACKAGE" "$JUMP_HOST:$JUMP_DIR/"
step "执行正式服 $REMOTE_SCRIPT"
RUN_LOG=$(mktemp "$SCRIPT_DIR/dist/production-running.XXXXXX")
if ! ssh -p "$SSH_PORT" "${SSH_OPTS[@]}" "$JUMP_HOST" "bash $JUMP_DIR/$REMOTE_SCRIPT" 2>&1 | tee "$RUN_LOG"; then
fail "正式服 $REMOTE_SCRIPT 执行失败;日志:${RUN_LOG}"
fi
printf '\n正式服发布完成;日志:%s\n' "$RUN_LOG"