Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# 测试服已有入口。deploy_test.sh 上传压缩包后执行:cd /server/web_sgdj/h5 && bash run.sh
|
||||
# 本文件供首次放到服务器,发布脚本不会覆盖远端 run.sh。
|
||||
set -Eeuo pipefail
|
||||
|
||||
APP_DIR=/server/web_sgdj/h5
|
||||
APP_NAME=web_sgdj_h5
|
||||
ARCHIVE="$APP_DIR/web_sgdj_h5.tar.gz"
|
||||
CURRENT="$APP_DIR/next"
|
||||
RELEASES="$APP_DIR/releases"
|
||||
NODE=/root/.nvm/versions/node/v22.22.3/bin/node
|
||||
PM2=/root/.nvm/versions/node/v22.22.3/bin/pm2
|
||||
NPM=/usr/bin/npm
|
||||
export PATH=/root/.nvm/versions/node/v22.22.3/bin:$PATH
|
||||
STAGING=''
|
||||
PREVIOUS=''
|
||||
ACTIVATED=0
|
||||
WAS_RUNNING=0
|
||||
|
||||
fail() { echo "发布失败:$*" >&2; exit 1; }
|
||||
cleanup() {
|
||||
result=$?
|
||||
trap - EXIT INT TERM
|
||||
if [ "$result" -ne 0 ] && [ "$ACTIVATED" -eq 1 ]; then
|
||||
echo '发布失败,正在恢复上一版本…' >&2
|
||||
"$PM2" delete "$APP_NAME" >/dev/null 2>&1 || true
|
||||
[ -d "$CURRENT" ] && mv "$CURRENT" "$RELEASES/failed.$(date +%Y%m%d%H%M%S)" || true
|
||||
if [ -n "$PREVIOUS" ] && [ -d "$PREVIOUS" ]; then
|
||||
mv "$PREVIOUS" "$CURRENT"
|
||||
[ "$WAS_RUNNING" -eq 1 ] && "$PM2" start "$CURRENT/start.mjs" --name "$APP_NAME" --interpreter "$NODE" >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
[ -n "$STAGING" ] && [ -d "$STAGING" ] && rm -rf "$STAGING"
|
||||
exit "$result"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
|
||||
[ -s "$ARCHIVE" ] || fail "未找到运行包:$ARCHIVE"
|
||||
for tool in tar "$NODE" "$NPM" "$PM2"; do command -v "$tool" >/dev/null 2>&1 || fail "缺少命令:$tool"; done
|
||||
"$NODE" -e 'const [a,b]=process.versions.node.split(".").map(Number);process.exit(a>20||a===20&&b>=9?0:1)' || fail '需要 Node.js >=20.9'
|
||||
mkdir -p "$RELEASES"
|
||||
while IFS= read -r entry; do
|
||||
case "$entry" in
|
||||
/*|../*|*/../*|*/..|*\\*) fail "运行包含非法路径:$entry" ;;
|
||||
esac
|
||||
done < <(/bin/tar -tzf "$ARCHIVE")
|
||||
STAGING=$(mktemp -d "$APP_DIR/.release.XXXXXX")
|
||||
/bin/tar -xzf "$ARCHIVE" -C "$STAGING" --no-same-owner
|
||||
for file in start.mjs server.js package.json package-lock.json scripts/next-env.mjs scripts/check-health.mjs .next-test/BUILD_ID config/test.env; do
|
||||
[ -s "$STAGING/$file" ] || fail "运行包缺少:$file"
|
||||
done
|
||||
[ -d "$STAGING/.next-test/static" ] && [ -d "$STAGING/public" ] || fail '运行包缺少 Next 静态资源'
|
||||
(cd "$STAGING" && "$NPM" ci --omit=dev --no-audit --no-fund)
|
||||
(cd "$STAGING" && "$NODE" -e 'for (const n of ["next","react","react-dom","dotenv"]) require.resolve(n)') >/dev/null 2>&1 || fail '运行依赖校验失败'
|
||||
PORT=$(cd "$STAGING" && "$NODE" --input-type=module -e 'import{nextCommand}from"./scripts/next-env.mjs";console.log(nextCommand("test","start",[],{root:process.cwd()}).env.PORT)')
|
||||
if [ -f "$CURRENT/config/test.local.env" ] && [ ! -L "$CURRENT/config/test.local.env" ]; then
|
||||
mkdir -p "$STAGING/config"
|
||||
cp "$CURRENT/config/test.local.env" "$STAGING/config/test.local.env"
|
||||
chmod 600 "$STAGING/config/test.local.env"
|
||||
fi
|
||||
if "$PM2" jlist 2>/dev/null | grep -q "\"name\":\"$APP_NAME\""; then WAS_RUNNING=1; fi
|
||||
"$PM2" delete "$APP_NAME" >/dev/null 2>&1 || true
|
||||
if [ -d "$CURRENT" ]; then
|
||||
PREVIOUS="$RELEASES/next.$(date +%Y%m%d%H%M%S)"
|
||||
mv "$CURRENT" "$PREVIOUS"
|
||||
fi
|
||||
mv "$STAGING" "$CURRENT"; STAGING=''; ACTIVATED=1
|
||||
"$PM2" start "$CURRENT/start.mjs" --name "$APP_NAME" --interpreter "$NODE"
|
||||
"$NODE" "$CURRENT/scripts/check-health.mjs" "http://127.0.0.1:$PORT" test
|
||||
"$PM2" save
|
||||
timestamp=$(date +%Y%m%d%H%M%S)
|
||||
mv "$ARCHIVE" "$RELEASES/web_sgdj_h5.$timestamp.tar.gz"
|
||||
echo "发布完成:$APP_NAME;端口:$PORT;上一版本:${PREVIOUS:-无}"
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
# Linux 测试服 tar.gz 更新入口;需预先配置 Supervisor 程序并手动调用。
|
||||
# 流程对齐 jha-1047-91dj-web:校验 SHA-256、解包、npm ci、切换版本、健康检查。
|
||||
set -Eeuo pipefail
|
||||
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
PROGRAM="${DEPLOY_PROGRAM:-sgdj_next_h5}"
|
||||
PACKAGE_NAME="${DEPLOY_PACKAGE_NAME:-web_sgdj_h5.tar.gz}"
|
||||
CHECKSUM_NAME="$PACKAGE_NAME.sha256"
|
||||
[[ "$PROGRAM" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$ ]] || { echo 'DEPLOY_PROGRAM 格式无效' >&2; exit 1; }
|
||||
[[ "$PACKAGE_NAME" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]*\.tar\.gz$ ]] || { echo 'DEPLOY_PACKAGE_NAME 必须是 .tar.gz 文件名' >&2; exit 1; }
|
||||
CURRENT="$DIR/next"
|
||||
LOCK="$DIR/.$PROGRAM.lock"
|
||||
STAGING=''; UPLOAD=''; PREVIOUS=''; FAILED=''
|
||||
MOVED_OLD=0; INSTALLED=0; STOPPED=0; WAS_RUNNING=0
|
||||
fail() { printf '错误:%s\n' "$*" >&2; exit 1; }
|
||||
for tool in node npm sha256sum tar supervisorctl mktemp; 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'
|
||||
mkdir "$LOCK" 2>/dev/null || fail "已有更新进程或残留锁:$LOCK"
|
||||
finish() {
|
||||
local result=$?
|
||||
trap - EXIT INT TERM
|
||||
if [ "$result" != 0 ] && { [ "$STOPPED" = 1 ] || [ "$INSTALLED" = 1 ] || [ "$MOVED_OLD" = 1 ]; }; then
|
||||
printf '更新失败,恢复上一版本。\n' >&2
|
||||
supervisorctl stop "$PROGRAM" >/dev/null 2>&1 || true
|
||||
if [ "$INSTALLED" = 1 ]; then
|
||||
FAILED="$DIR/next.failed.$(date +%Y%m%d%H%M%S).$$"
|
||||
mv "$CURRENT" "$FAILED" || { echo "无法保存失败版本:$CURRENT" >&2; result=1; }
|
||||
fi
|
||||
if [ "$MOVED_OLD" = 1 ]; then
|
||||
mv "$PREVIOUS" "$CURRENT" || { echo "需要手动恢复:$PREVIOUS → $CURRENT" >&2; result=1; }
|
||||
fi
|
||||
if [ "$WAS_RUNNING" = 1 ]; then supervisorctl start "$PROGRAM" || result=1; fi
|
||||
fi
|
||||
if [ -n "$STAGING" ] && [ -d "$STAGING" ]; then rm -rf "$STAGING"; fi
|
||||
if [ -n "$UPLOAD" ] && [ -d "$UPLOAD" ]; then rm -rf "$UPLOAD"; fi
|
||||
rmdir "$LOCK" 2>/dev/null || true
|
||||
exit "$result"
|
||||
}
|
||||
trap finish EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
[ ! -L "$CURRENT" ] && { [ ! -e "$CURRENT" ] || [ -d "$CURRENT" ]; } || fail 'next 必须是普通目录'
|
||||
STATUS=$(supervisorctl status "$PROGRAM" 2>/dev/null || true)
|
||||
[[ "$STATUS" == "$PROGRAM "* ]] && [[ "$STATUS" != *'ERROR'* ]] || fail "请先配置 Supervisor 程序 $PROGRAM,command 指向 node $CURRENT/start.mjs"
|
||||
if [[ "$STATUS" == *'RUNNING'* ]]; then WAS_RUNNING=1; fi
|
||||
[ -s "$DIR/$PACKAGE_NAME" ] && [ -s "$DIR/$CHECKSUM_NAME" ] || fail "缺少 $PACKAGE_NAME 或 SHA-256"
|
||||
UPLOAD=$(mktemp -d "$DIR/.sgdj-upload.XXXXXX")
|
||||
cp "$DIR/$PACKAGE_NAME" "$DIR/$CHECKSUM_NAME" "$UPLOAD/"
|
||||
(cd "$UPLOAD" && sha256sum -c "$CHECKSUM_NAME") || fail 'SHA-256 校验失败'
|
||||
LC_ALL=C tar -tzf "$UPLOAD/$PACKAGE_NAME" > "$UPLOAD/members" || fail 'tar.gz 压缩包损坏'
|
||||
while IFS= read -r entry; do
|
||||
entry="${entry#./}"
|
||||
case "$entry" in
|
||||
/*|../*|*/../*|*/..|node_modules|node_modules/*|*/node_modules/*|*\\*) fail "非法 tar.gz 路径:$entry" ;;
|
||||
esac
|
||||
done < "$UPLOAD/members"
|
||||
LC_ALL=C tar -tvzf "$UPLOAD/$PACKAGE_NAME" > "$UPLOAD/types" || fail '无法检查 tar.gz 文件类型'
|
||||
while IFS= read -r entry; do
|
||||
case "$entry" in
|
||||
-?*|d?*) ;;
|
||||
*) fail '测试包只允许普通文件和目录,不允许链接或特殊文件' ;;
|
||||
esac
|
||||
done < "$UPLOAD/types"
|
||||
STAGING=$(mktemp -d "$DIR/.sgdj-release.XXXXXX")
|
||||
tar -xzf "$UPLOAD/$PACKAGE_NAME" -C "$STAGING" --no-same-owner --no-same-permissions
|
||||
[ -z "$(find "$STAGING" -type l -print -quit)" ] || fail '测试包不允许符号链接'
|
||||
for file in start.mjs server.js .next-test/BUILD_ID config/test.env package.json package-lock.json scripts/next-env.mjs scripts/check-health.mjs; do
|
||||
[ -s "$STAGING/$file" ] || fail "包内缺少 $file"
|
||||
done
|
||||
[ -d "$STAGING/.next-test/static" ] && [ -d "$STAGING/public" ] || fail '包内缺少静态资源'
|
||||
(cd "$STAGING" && npm ci --omit=dev --no-audit --no-fund)
|
||||
if [ -f "$CURRENT/config/test.local.env" ] && [ ! -L "$CURRENT/config/test.local.env" ]; then
|
||||
cp "$CURRENT/config/test.local.env" "$STAGING/config/test.local.env"
|
||||
chmod 600 "$STAGING/config/test.local.env"
|
||||
fi
|
||||
if [ -d "$CURRENT/.next-test/cache/seo" ] && [ ! -L "$CURRENT/.next-test/cache/seo" ]; then
|
||||
mkdir -p "$STAGING/.next-test/cache"
|
||||
cp -R "$CURRENT/.next-test/cache/seo" "$STAGING/.next-test/cache/seo"
|
||||
fi
|
||||
APP_PORT=$(cd "$STAGING" && node --input-type=module -e 'import{nextCommand}from"./scripts/next-env.mjs";console.log(nextCommand("test","start",[],{root:process.cwd()}).env.PORT)')
|
||||
if [ "$WAS_RUNNING" = 1 ]; then supervisorctl stop "$PROGRAM"; STOPPED=1; fi
|
||||
if [ -d "$CURRENT" ]; then
|
||||
PREVIOUS="$DIR/next.prev.$(date +%Y%m%d%H%M%S).$$"
|
||||
mv "$CURRENT" "$PREVIOUS"; MOVED_OLD=1
|
||||
fi
|
||||
mv "$STAGING" "$CURRENT"; STAGING=''; INSTALLED=1
|
||||
supervisorctl start "$PROGRAM"
|
||||
node "$CURRENT/scripts/check-health.mjs" "http://127.0.0.1:$APP_PORT" test
|
||||
supervisorctl status "$PROGRAM"
|
||||
printf '测试服更新完成:%s;上一版本:%s\n' "$CURRENT" "${PREVIOUS:-无}"
|
||||
Reference in New Issue
Block a user