166 行
7.1 KiB
Bash
可执行文件
166 行
7.1 KiB
Bash
可执行文件
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||
DEPLOY_SERVER="${DEPLOY_SERVER:-}"
|
||
DEPLOY_REFRESH_SERVER="${DEPLOY_REFRESH_SERVER:-}"
|
||
DEPLOY_CONTRACT_UNIT="${DEPLOY_CONTRACT_UNIT:-hyb-oa-automatic-backup-daily.service}"
|
||
PROVENANCE_SCRIPT="deploy/trialrun/check-source-provenance.mjs"
|
||
PRODUCTION_PREFLIGHT_SCRIPT="deploy/trialrun/production-preflight.mjs"
|
||
|
||
# shellcheck source=deployment-contract-lib.sh
|
||
source "$ROOT_DIR/deploy/trialrun/deployment-contract-lib.sh"
|
||
|
||
fail() {
|
||
echo "[deploy-existing][ERROR] $1" >&2
|
||
exit 1
|
||
}
|
||
|
||
deployment_contract_safe_ssh_target "$DEPLOY_SERVER" \
|
||
|| fail "必须显式提供安全的 DEPLOY_SERVER"
|
||
deployment_contract_safe_ssh_target "$DEPLOY_REFRESH_SERVER" \
|
||
|| fail "必须显式提供安全的 DEPLOY_REFRESH_SERVER"
|
||
deployment_contract_safe_service "$DEPLOY_CONTRACT_UNIT" \
|
||
|| fail "DEPLOY_CONTRACT_UNIT 格式无效"
|
||
command -v ssh >/dev/null 2>&1 || fail "缺少 ssh"
|
||
command -v node >/dev/null 2>&1 || fail "缺少 node"
|
||
|
||
if [[ "${DEPLOY_ALLOW_UNPUBLISHED_COMMIT:-0}" == "1" || -n "${DEPLOY_UNPUBLISHED_REASON:-}" ]]; then
|
||
fail "生产部署验证不接受未进入上游的提交;灾备 override 必须走独立获批流程"
|
||
fi
|
||
|
||
case "${PRODUCTION_IDENTITY_ONLY-0}" in
|
||
0) ;;
|
||
*) fail "identity-only 必须使用生产身份引导器,不能进入发布包装器" ;;
|
||
esac
|
||
case "${PRODUCTION_READ_ONLY_PREFLIGHT-0}" in
|
||
0)
|
||
RELEASE_GATE_EXPECTED_COMMIT="$(node "$ROOT_DIR/scripts/qa/release-gate-result.mjs" require "${RELEASE_GATE_RECEIPT_DIR:-}")"
|
||
export RELEASE_GATE_EXPECTED_COMMIT
|
||
;;
|
||
1) ;;
|
||
*) fail "PRODUCTION_READ_ONLY_PREFLIGHT 只能为 0 或 1" ;;
|
||
esac
|
||
|
||
declare -A contract=()
|
||
echo "[deploy-existing] 开始只读聚合体检"
|
||
DEPLOY_SERVER="$DEPLOY_SERVER" \
|
||
DEPLOY_REFRESH_SERVER="$DEPLOY_REFRESH_SERVER" \
|
||
DEPLOY_CONTRACT_UNIT="$DEPLOY_CONTRACT_UNIT" \
|
||
bash "$ROOT_DIR/deploy/trialrun/check-existing-deployment.sh" \
|
||
|| fail "部署前聚合体检未通过"
|
||
|
||
deployment_contract_load "$DEPLOY_SERVER" "$DEPLOY_CONTRACT_UNIT" contract \
|
||
|| fail "无法读取完整既有部署合同"
|
||
|
||
echo "[deploy-existing] 已读取受控 unit 合同;路径与标识不回显"
|
||
|
||
if [[ -z "${EXPECTED_PRODUCTION_ORIGIN:-}" ]]; then
|
||
fail "生产部署必须显式提供 EXPECTED_PRODUCTION_ORIGIN(仅进程环境)"
|
||
fi
|
||
if [[ -z "${EXPECTED_PRODUCTION_DB_NAME_SHA256:-}" ]]; then
|
||
fail "生产部署必须由身份引导器提供 EXPECTED_PRODUCTION_DB_NAME_SHA256"
|
||
fi
|
||
if [[ -z "${EXPECTED_PENDING_MIGRATIONS+x}" ]]; then
|
||
fail "生产部署必须显式提供 EXPECTED_PENDING_MIGRATIONS(可为空)"
|
||
fi
|
||
if [[ -z "${EXPECTED_DEPLOY_SERVER:-}" ]]; then
|
||
fail "生产部署必须由身份引导器提供 EXPECTED_DEPLOY_SERVER"
|
||
fi
|
||
if [[ -z "${EXPECTED_REMOTE_DIR:-}" ]]; then
|
||
fail "生产部署必须由身份引导器提供 EXPECTED_REMOTE_DIR"
|
||
fi
|
||
if [[ -z "${EXPECTED_COMPOSE_PROJECT_NAME:-}" ]]; then
|
||
fail "生产部署必须由身份引导器提供 EXPECTED_COMPOSE_PROJECT_NAME"
|
||
fi
|
||
case "${PRODUCTION_READ_ONLY_PREFLIGHT-0}" in
|
||
0|1) ;;
|
||
*) fail "PRODUCTION_READ_ONLY_PREFLIGHT 只能为 0 或 1" ;;
|
||
esac
|
||
[[ -f "$ROOT_DIR/$PROVENANCE_SCRIPT" ]] || fail "缺少发布来源校验脚本"
|
||
[[ -f "$ROOT_DIR/$PRODUCTION_PREFLIGHT_SCRIPT" ]] || fail "缺少生产迁移 preflight 脚本"
|
||
|
||
echo ">>> 0.1 校验已批准发布来源(生产 preflight 前)"
|
||
provenance_json="$(node "$ROOT_DIR/$PROVENANCE_SCRIPT" 2>/dev/null)" \
|
||
|| fail "发布来源校验未通过"
|
||
source_commit="$(node -e 'const value = JSON.parse(process.argv[1]); process.stdout.write(value.commit || "")' "$provenance_json")" \
|
||
|| fail "发布来源提交不可用"
|
||
[[ "$source_commit" =~ ^[0-9a-f]{40,64}$ ]] || fail "发布来源提交格式无效"
|
||
|
||
echo ">>> 0.2 生产目标与迁移 preflight(只读,先于 deploy.sh)"
|
||
PRODUCTION_PREFLIGHT_REPORT_ROOT="${PRODUCTION_PREFLIGHT_REPORT_ROOT:-artifacts/production-preflight-$(date +%Y%m%d-%H%M%S)}"
|
||
EXPECTED_PRODUCTION_ORIGIN="$EXPECTED_PRODUCTION_ORIGIN" \
|
||
EXPECTED_PRODUCTION_DB_NAME_SHA256="$EXPECTED_PRODUCTION_DB_NAME_SHA256" \
|
||
EXPECTED_PENDING_MIGRATIONS="$EXPECTED_PENDING_MIGRATIONS" \
|
||
EXPECTED_DEPLOY_SERVER="$EXPECTED_DEPLOY_SERVER" \
|
||
EXPECTED_REMOTE_DIR="$EXPECTED_REMOTE_DIR" \
|
||
EXPECTED_COMPOSE_PROJECT_NAME="$EXPECTED_COMPOSE_PROJECT_NAME" \
|
||
DEPLOY_SERVER="$DEPLOY_SERVER" \
|
||
REMOTE_DIR="${contract[REMOTE_DIR]}" \
|
||
REMOTE_ENV_FILE="${contract[REMOTE_ENV_FILE]}" \
|
||
COMPOSE_FILE="${contract[COMPOSE_FILE]}" \
|
||
COMPOSE_PROJECT_NAME="${contract[COMPOSE_PROJECT_NAME]}" \
|
||
POSTGRES_DATA_DIR="${contract[POSTGRES_DATA_DIR]}" \
|
||
STORAGE_HOST_DIR="${contract[STORAGE_HOST_DIR]}" \
|
||
CANDIDATE_COMMIT="$source_commit" \
|
||
PRODUCTION_PREFLIGHT_REPORT_DIR="$PRODUCTION_PREFLIGHT_REPORT_ROOT/pre" \
|
||
node "$ROOT_DIR/$PRODUCTION_PREFLIGHT_SCRIPT" \
|
||
|| fail "生产目标或迁移 preflight 未通过;未进入 deploy.sh"
|
||
|
||
if [[ "${PRODUCTION_READ_ONLY_PREFLIGHT:-0}" == "1" ]]; then
|
||
echo "[deploy-existing][PASS] 只读身份、迁移与回滚窗口门禁通过;未进入 deploy.sh"
|
||
exit 0
|
||
fi
|
||
|
||
node "$ROOT_DIR/scripts/qa/release-gate-result.mjs" require "${RELEASE_GATE_RECEIPT_DIR:-}" "$source_commit" >/dev/null
|
||
|
||
DEPLOY_SERVER="$DEPLOY_SERVER" \
|
||
REMOTE_DIR="${contract[REMOTE_DIR]}" \
|
||
REMOTE_ENV_FILE="${contract[REMOTE_ENV_FILE]}" \
|
||
COMPOSE_FILE="${contract[COMPOSE_FILE]}" \
|
||
COMPOSE_PROJECT_NAME="${contract[COMPOSE_PROJECT_NAME]}" \
|
||
POSTGRES_DATA_DIR="${contract[POSTGRES_DATA_DIR]}" \
|
||
STORAGE_HOST_DIR="${contract[STORAGE_HOST_DIR]}" \
|
||
DEPLOY_DATA_MODE=existing \
|
||
DEPLOY_DEFER_FINAL_SUCCESS=1 \
|
||
DEPLOY_STATUS_OUTPUT_MODE=quiet \
|
||
SYNC_ENV=0 \
|
||
bash "$ROOT_DIR/deploy/trialrun/deploy.sh"
|
||
|
||
echo ">>> 6.1 部署后 migration status 与目标只读复核"
|
||
EXPECTED_PRODUCTION_ORIGIN="$EXPECTED_PRODUCTION_ORIGIN" \
|
||
EXPECTED_PRODUCTION_DB_NAME_SHA256="$EXPECTED_PRODUCTION_DB_NAME_SHA256" \
|
||
EXPECTED_PENDING_MIGRATIONS="" \
|
||
EXPECTED_DEPLOY_SERVER="$EXPECTED_DEPLOY_SERVER" \
|
||
EXPECTED_REMOTE_DIR="$EXPECTED_REMOTE_DIR" \
|
||
EXPECTED_COMPOSE_PROJECT_NAME="$EXPECTED_COMPOSE_PROJECT_NAME" \
|
||
DEPLOY_SERVER="$DEPLOY_SERVER" \
|
||
REMOTE_DIR="${contract[REMOTE_DIR]}" \
|
||
REMOTE_ENV_FILE="${contract[REMOTE_ENV_FILE]}" \
|
||
COMPOSE_FILE="${contract[COMPOSE_FILE]}" \
|
||
COMPOSE_PROJECT_NAME="${contract[COMPOSE_PROJECT_NAME]}" \
|
||
POSTGRES_DATA_DIR="${contract[POSTGRES_DATA_DIR]}" \
|
||
STORAGE_HOST_DIR="${contract[STORAGE_HOST_DIR]}" \
|
||
CANDIDATE_COMMIT="$source_commit" \
|
||
PRODUCTION_PREFLIGHT_REPORT_DIR="$PRODUCTION_PREFLIGHT_REPORT_ROOT/post" \
|
||
node "$ROOT_DIR/$PRODUCTION_PREFLIGHT_SCRIPT" \
|
||
|| fail "部署后迁移 status/目标复核失败;应用已部署但未进入合同 refresh"
|
||
|
||
refresh_output=""
|
||
refresh_status=0
|
||
if refresh_output="$(ssh "$DEPLOY_REFRESH_SERVER" refresh 2>/dev/null)"; then
|
||
if [[ "$refresh_output" != "[deployment-contract-refresh][PASS] refreshed" ]]; then
|
||
refresh_status=127
|
||
fi
|
||
else
|
||
refresh_status=$?
|
||
fi
|
||
|
||
refresh_category="$(deployment_contract_refresh_category "$refresh_status" "$refresh_output")"
|
||
if [[ "$refresh_category" != "success" ]]; then
|
||
echo "[deploy-existing][DEPLOYED_BUT_CONTRACT_REFRESH_FAILED] category=${refresh_category}" >&2
|
||
exit 1
|
||
fi
|
||
|
||
echo "[deploy-existing][PASS] 应用发布与备份合同刷新完整通过"
|