161 lines
6.6 KiB
Bash
Executable File
161 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 在 mini-desktop 的全新目录准备一个 commit-pinned staging RC。
|
||
# 本脚本只克隆、构建和留证,不切换 current、不停止或启动任何服务。
|
||
set -euo pipefail
|
||
|
||
RC_COMMIT="${RC_COMMIT:-}"
|
||
RC_HOST="${RC_HOST:-root@100.64.0.7}"
|
||
RC_ROOT="${RC_ROOT:-/root/game-staging/releases}"
|
||
RC_REPO_URL="${RC_REPO_URL:-$(git remote get-url origin 2>/dev/null || true)}"
|
||
|
||
if [[ ! "$RC_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
|
||
printf 'RC_COMMIT 必须是完整 40 位小写提交哈希。\n' >&2
|
||
exit 2
|
||
fi
|
||
if [ -z "$RC_REPO_URL" ]; then
|
||
printf '无法解析仓库地址,请显式设置 RC_REPO_URL。\n' >&2
|
||
exit 2
|
||
fi
|
||
|
||
printf '准备 staging RC:host=%s commit=%s\n' "$RC_HOST" "$RC_COMMIT"
|
||
printf '注意:只构建候选,不切流、不起服。\n'
|
||
|
||
# 参数经 bash 位置参数传入;远端脚本使用单引号 heredoc,避免本机提前展开。
|
||
ssh -o ProxyCommand=none "$RC_HOST" bash -s -- "$RC_COMMIT" "$RC_ROOT" "$RC_REPO_URL" <<'REMOTE_SCRIPT'
|
||
set -euo pipefail
|
||
|
||
commit="$1"
|
||
root="$2"
|
||
repo_url="$3"
|
||
release="$root/$commit"
|
||
evidence="$release/evidence"
|
||
status_file="$release/RC_STATUS"
|
||
|
||
if [ -e "$release" ]; then
|
||
printf '拒绝复用既有 RC 目录:%s\n' "$release" >&2
|
||
exit 3
|
||
fi
|
||
|
||
mkdir -p "$root"
|
||
umask 077
|
||
git clone --no-checkout "$repo_url" "$release"
|
||
printf 'preparing\n' >"$status_file"
|
||
|
||
on_error() {
|
||
code=$?
|
||
printf 'failed exit=%s at=%s\n' "$code" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >"$status_file"
|
||
printf 'RC 准备失败,现场保留用于排障:%s\n' "$release" >&2
|
||
exit "$code"
|
||
}
|
||
trap on_error ERR
|
||
|
||
cd "$release"
|
||
git fetch --no-tags origin "$commit"
|
||
git checkout --detach "$commit"
|
||
actual="$(git rev-parse HEAD)"
|
||
[ "$actual" = "$commit" ] || { printf '提交不一致:expected=%s actual=%s\n' "$commit" "$actual" >&2; exit 4; }
|
||
[ -z "$(git status --porcelain --untracked-files=no)" ] || { printf '新检出存在 tracked 脏改动,拒绝构建。\n' >&2; exit 4; }
|
||
|
||
mkdir -p "$evidence"
|
||
previous="none"
|
||
if [ -L "$root/active" ]; then
|
||
previous="$(readlink -f "$root/active" || true)"
|
||
[ -n "$previous" ] || previous="broken-active-symlink"
|
||
elif [ -L "$root/current" ]; then
|
||
# 兼容 active 指针启用前的旧部署;新激活脚本只维护 canonical active。
|
||
previous="$(readlink -f "$root/current" || true)"
|
||
[ -n "$previous" ] || previous="broken-current-symlink"
|
||
fi
|
||
printf '%s\n' "$previous" >"$evidence/rollback-target.txt"
|
||
|
||
# 配置只记录来源、文件哈希和键名,绝不复制或输出密钥值。
|
||
{
|
||
printf 'captured_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||
printf 'rc_commit=%s\n' "$commit"
|
||
printf 'previous_release=%s\n' "$previous"
|
||
if [ -f /root/huijing-dev.env ]; then
|
||
printf 'huijing_dev_env_sha256=%s\n' "$(sha256sum /root/huijing-dev.env | awk '{print $1}')"
|
||
printf 'huijing_dev_env_keys='
|
||
awk -F= '/^[A-Za-z_][A-Za-z0-9_]*=/{print $1}' /root/huijing-dev.env | sort | paste -sd, -
|
||
else
|
||
printf 'huijing_dev_env=missing\n'
|
||
fi
|
||
printf 'backend_staging_yaml_sha256=%s\n' "$(sha256sum game-cloud/huijing-server/src/main/resources/application-staging.yaml | awk '{print $1}')"
|
||
printf 'studio_staging_env_sha256=%s\n' "$(sha256sum game-studio/.env.staging | awk '{print $1}')"
|
||
printf 'admin_stage_env_sha256=%s\n' "$(sha256sum game-admin/.env.stage | awk '{print $1}')"
|
||
} >"$evidence/config-snapshot.txt"
|
||
|
||
printf '[1/6] 构建 Java 后端(跳过全量后端测试)\n'
|
||
(cd game-cloud && mvn clean install -pl huijing-server -am -DskipTests)
|
||
|
||
printf '[2/6] 构建 game-studio staging\n'
|
||
(cd game-studio && npm ci && npm run build -- --mode staging)
|
||
|
||
printf '[3/6] 构建 game-admin stage\n'
|
||
corepack prepare pnpm@9.15.4 --activate
|
||
(cd game-admin && pnpm install --frozen-lockfile && pnpm build:stage)
|
||
|
||
printf '[4/6] 准备 commit 内独立 Python 虚拟环境\n'
|
||
UV_BIN="${UV_BIN:-/root/.local/bin/uv}"
|
||
[ -x "$UV_BIN" ] || { printf '缺少 uv:%s\n' "$UV_BIN" >&2; exit 5; }
|
||
(cd cheap-worker && "$UV_BIN" venv .venv && "$UV_BIN" pip install --python .venv/bin/python -r requirements.txt)
|
||
(cd tier2/gen-worker && "$UV_BIN" venv .venv && "$UV_BIN" pip install --python .venv/bin/python -r requirements.txt)
|
||
|
||
printf '[5/6] 运行发布工具回归测试\n'
|
||
release_tool_tests=(
|
||
deploy/tests/test-staging-rc.sh
|
||
deploy/tests/test-provision-dogfood-users.sh
|
||
deploy/tests/test-provision-dogfood-nacos-transaction.sh
|
||
deploy/tests/test-import-newapi-pool.sh
|
||
)
|
||
(
|
||
export PATH="$release/cheap-worker/.venv/bin:$PATH"
|
||
for test_script in "${release_tool_tests[@]}"; do
|
||
printf 'RUN %s\n' "$test_script"
|
||
bash "$test_script"
|
||
done
|
||
printf 'RUN game-runtime/tools/test_newapi_pool_provision.py\n'
|
||
python3 -m unittest game-runtime/tools/test_newapi_pool_provision.py
|
||
printf 'RELEASE_TOOL_TESTS_PASS shell=%s python=1\n' "${#release_tool_tests[@]}"
|
||
) >"$evidence/release-tool-tests.log" 2>&1
|
||
|
||
printf '[6/6] 记录制品与启动坐标\n'
|
||
{
|
||
printf 'backend_jar=%s/game-cloud/huijing-server/target/huijing-server.jar\n' "$release"
|
||
printf 'studio_dist=%s/game-studio/dist\n' "$release"
|
||
printf 'admin_dist=%s/game-admin/dist-stage\n' "$release"
|
||
printf 'cheap_agent_python=%s/cheap-worker/.venv/bin/python\n' "$release"
|
||
printf 'cheap_agent_entry=%s/cheap-worker/cheap_service_app.py\n' "$release"
|
||
printf 'cheap_worker_entry=%s/cheap-worker/worker_service.py\n' "$release"
|
||
printf 'tier2_python=%s/tier2/gen-worker/.venv/bin/python\n' "$release"
|
||
printf 'tier2_entry=%s/tier2/gen-worker/service/app.py\n' "$release"
|
||
} >"$evidence/artifact-paths.env"
|
||
|
||
{
|
||
sha256sum game-cloud/huijing-server/target/huijing-server.jar
|
||
find game-studio/dist game-admin/dist-stage -type f -print0 | sort -z | xargs -0 sha256sum
|
||
} >"$evidence/artifacts.sha256"
|
||
|
||
{
|
||
printf 'schema=staging-rc-prepare-verification/1\n'
|
||
printf 'rc_commit=%s\n' "$commit"
|
||
printf 'release_tool_shell_tests=PASS\n'
|
||
printf 'release_tool_shell_test_count=%s\n' "${#release_tool_tests[@]}"
|
||
printf 'newapi_pool_python_tests=PASS\n'
|
||
printf 'backend_build=PASS\n'
|
||
printf 'backend_tests=NOT_RUN\n'
|
||
printf 'backend_tests_reason=maven_build_used_DskipTests\n'
|
||
printf 'studio_build=PASS\n'
|
||
printf 'admin_build=PASS\n'
|
||
} >"$evidence/verification-summary.txt"
|
||
|
||
[ -z "$(git status --porcelain --untracked-files=no)" ] || { printf '构建修改了 tracked 文件,拒绝将其标为 ready。\n' >&2; exit 6; }
|
||
printf 'ready commit=%s built_at=%s\n' "$commit" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >"$status_file"
|
||
chmod -R go-rwx "$evidence" "$status_file"
|
||
trap - ERR
|
||
|
||
printf 'RC_PREPARE_PASS release=%s\n' "$release"
|
||
printf 'rollback_target=%s\n' "$previous"
|
||
printf '下一步由统一部署窗口读取 evidence/artifact-paths.env,探活通过后再原子切换 current。\n'
|
||
REMOTE_SCRIPT
|