fix(release): support scoped backend preflight

This commit is contained in:
key
2026-07-29 03:05:16 +08:00
parent ab7d18406f
commit 566f222e82
6 changed files with 181 additions and 103 deletions
+137 -98
View File
@@ -43,34 +43,50 @@ normalized_jar_content_sha256() {
rm -f "$manifest_path"
}
verify_remote_match="${RELEASE_VERIFY_REMOTE_MATCH:-false}"
verify_remote_backend="${RELEASE_VERIFY_REMOTE_BACKEND:-false}"
verify_remote_schema="${RELEASE_VERIFY_REMOTE_SCHEMA:-false}"
remote_verification_requested="false"
if [[ "$verify_remote_match" == "true" || "$verify_remote_backend" == "true" || "$verify_remote_schema" == "true" ]]; then
remote_verification_requested="true"
fi
if [[ "$remote_verification_requested" == "true" && -z "${RELEASE_REMOTE_URL:-}" ]]; then
fail "remote verification flags require RELEASE_REMOTE_URL"
fi
frontend_index="frontend/dist/index.html"
mobile_index="mobile-uni/dist/build/h5/index.html"
backend_jar="backend/ruoyi-admin/target/ruoyi-admin.jar"
require_file "$frontend_index"
require_file "$mobile_index"
require_file "$backend_jar"
backend_module_jar_name="$(unzip -Z1 "$backend_jar" | sed -nE 's#BOOT-INF/lib/(ruoyi-aihr-[^/]+\.jar)#\1#p' | head -1)"
[[ -n "$backend_module_jar_name" ]] || fail "backend jar does not contain the ruoyi-aihr module"
frontend_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$frontend_index" | head -1)"
mobile_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$mobile_index" | head -1)"
[[ -n "$frontend_asset" ]] || fail "frontend index does not reference a JavaScript entry"
[[ -n "$mobile_asset" ]] || fail "mobile H5 index does not reference a JavaScript entry"
frontend_asset_path="frontend/dist/${frontend_asset#/}"
mobile_asset_rel="${mobile_asset#/}"
mobile_asset_rel="${mobile_asset_rel#h5/}"
mobile_asset_path="mobile-uni/dist/build/h5/$mobile_asset_rel"
require_file "$frontend_asset_path"
require_file "$mobile_asset_path"
grep -q '/h5/' "$mobile_index" || fail "mobile H5 index does not contain the /h5/ base path"
if [[ "${RELEASE_VERIFY_REMOTE_BACKEND:-false}" == "true" && "${RELEASE_VERIFY_REMOTE_MATCH:-false}" != "true" ]]; then
fail "RELEASE_VERIFY_REMOTE_BACKEND=true requires RELEASE_VERIFY_REMOTE_MATCH=true"
require_static_artifacts="true"
require_backend_artifact="true"
if [[ "$remote_verification_requested" == "true" ]]; then
require_static_artifacts="$verify_remote_match"
require_backend_artifact="$verify_remote_backend"
fi
if [[ "${RELEASE_VERIFY_REMOTE_SCHEMA:-false}" == "true" && "${RELEASE_VERIFY_REMOTE_MATCH:-false}" != "true" ]]; then
fail "RELEASE_VERIFY_REMOTE_SCHEMA=true requires RELEASE_VERIFY_REMOTE_MATCH=true"
if [[ "$require_static_artifacts" == "true" ]]; then
require_file "$frontend_index"
require_file "$mobile_index"
frontend_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$frontend_index" | head -1)"
mobile_asset="$(sed -nE 's/.*src="([^"]+\.js)".*/\1/p' "$mobile_index" | head -1)"
[[ -n "$frontend_asset" ]] || fail "frontend index does not reference a JavaScript entry"
[[ -n "$mobile_asset" ]] || fail "mobile H5 index does not reference a JavaScript entry"
frontend_asset_path="frontend/dist/${frontend_asset#/}"
mobile_asset_rel="${mobile_asset#/}"
mobile_asset_rel="${mobile_asset_rel#h5/}"
mobile_asset_path="mobile-uni/dist/build/h5/$mobile_asset_rel"
require_file "$frontend_asset_path"
require_file "$mobile_asset_path"
grep -q '/h5/' "$mobile_index" || fail "mobile H5 index does not contain the /h5/ base path"
fi
if [[ "$require_backend_artifact" == "true" ]]; then
require_file "$backend_jar"
backend_module_jar_name="$(unzip -Z1 "$backend_jar" | sed -nE 's#BOOT-INF/lib/(ruoyi-aihr-[^/]+\.jar)#\1#p' | head -1)"
[[ -n "$backend_module_jar_name" ]] || fail "backend jar does not contain the ruoyi-aihr module"
fi
if [[ "${AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP:-false}" == "true" ]]; then
fail "AIHR_PRACTICE_RUNTIME_SCHEMA_BOOTSTRAP must stay false for a release; apply the formal SQL migration instead"
fi
@@ -92,11 +108,15 @@ require_fresh_artifact() {
[[ "$artifact_epoch" -ge "$head_epoch" ]] || fail "artifact is older than HEAD; rebuild before release: $artifact"
}
require_fresh_artifact "$frontend_index"
require_fresh_artifact "$frontend_asset_path"
require_fresh_artifact "$mobile_index"
require_fresh_artifact "$mobile_asset_path"
require_fresh_artifact "$backend_jar"
if [[ "$require_static_artifacts" == "true" ]]; then
require_fresh_artifact "$frontend_index"
require_fresh_artifact "$frontend_asset_path"
require_fresh_artifact "$mobile_index"
require_fresh_artifact "$mobile_asset_path"
fi
if [[ "$require_backend_artifact" == "true" ]]; then
require_fresh_artifact "$backend_jar"
fi
require_remote_business_success() {
local label="$1"
@@ -759,67 +779,30 @@ REMOTE
echo "remote_personal_oss_configuration=true $remote_ssh:$remote_db"
}
changed_files="$(git status --porcelain)"
[[ -z "$changed_files" ]] || fail "worktree has uncommitted changes; commit the release batch before publishing"
require_remote_backend_match() {
command -v ssh >/dev/null 2>&1 || fail "ssh is required for remote backend verification"
local remote_ssh="${RELEASE_REMOTE_SSH:-YCWY}"
local remote_backend_path="${RELEASE_REMOTE_BACKEND_PATH:-/opt/wygj/app/ruoyi-admin.jar}"
[[ "$remote_backend_path" =~ ^/[A-Za-z0-9._/-]+$ ]] || fail "remote backend path must be an absolute safe path: $remote_backend_path"
echo "commit=$(git rev-parse HEAD)"
echo "head_epoch=$head_epoch"
echo "worktree=clean"
echo "frontend_index_sha256=$(sha256 "$frontend_index")"
echo "frontend_asset=$frontend_asset"
echo "frontend_asset_sha256=$(sha256 "$frontend_asset_path")"
echo "mobile_index_sha256=$(sha256 "$mobile_index")"
echo "mobile_asset=$mobile_asset"
echo "mobile_asset_sha256=$(sha256 "$mobile_asset_path")"
echo "backend_jar_sha256=$(sha256 "$backend_jar")"
local remote_backend_jar_sha256
local local_backend_jar_sha256
local local_backend_module_jar
local local_backend_module_sha256
local remote_backend_module_sha_file
local remote_backend_module_sha256
if [[ -n "${RELEASE_REMOTE_URL:-}" ]]; then
remote="${RELEASE_REMOTE_URL%/}"
curl -fsS --max-time 15 "$remote/" >/dev/null || fail "remote root check failed: $remote/"
echo "remote_root=200 $remote/"
require_remote_business_success "remote_tenant_list" "$remote/prod-api/auth/tenant/list"
require_remote_business_success "remote_mobile_home" "$remote/prod-api/api/aihr/mobile/home/user"
remote_backend_jar_sha256="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" "sha256sum '$remote_backend_path'" | awk '{print $1}')" \
|| fail "remote backend hash check failed: $remote_ssh:$remote_backend_path"
[[ "$remote_backend_jar_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend hash is invalid: $remote_ssh:$remote_backend_path"
local_backend_jar_sha256="$(sha256 "$backend_jar")"
local_backend_module_jar="$(mktemp)"
unzip -p "$backend_jar" "BOOT-INF/lib/$backend_module_jar_name" > "$local_backend_module_jar"
local_backend_module_sha256="$(normalized_jar_content_sha256 "$local_backend_module_jar")"
rm -f "$local_backend_module_jar"
if [[ "${RELEASE_VERIFY_REMOTE_SCHEMA:-false}" == "true" ]]; then
require_remote_schema
fi
if [[ "${RELEASE_VERIFY_REMOTE_SCHEMA:-false}" == "true" || "${RELEASE_VERIFY_REMOTE_BACKEND:-false}" == "true" ]]; then
require_remote_practice_schema_guard
fi
if [[ "${RELEASE_VERIFY_REMOTE_MATCH:-false}" == "true" ]]; then
remote_frontend_asset="$(curl -fsS --max-time 15 "$remote/" | sed -nE 's/.*src="(\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
remote_mobile_asset="$(curl -fsS --max-time 15 "$remote/h5/" | sed -nE 's/.*src="(\/h5\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
[[ "$remote_frontend_asset" == /assets/*.js ]] || fail "remote frontend entry asset not found"
[[ "$remote_mobile_asset" == /h5/assets/*.js ]] || fail "remote mobile H5 entry asset not found"
remote_frontend_sha256="$(curl -fsS --max-time 15 "$remote$remote_frontend_asset" | sha256_stream)"
remote_mobile_sha256="$(curl -fsS --max-time 15 "$remote$remote_mobile_asset" | sha256_stream)"
local_frontend_sha256="$(sha256 "$frontend_asset_path")"
local_mobile_sha256="$(sha256 "$mobile_asset_path")"
echo "remote_frontend_asset=$remote_frontend_asset"
echo "remote_frontend_asset_sha256=$remote_frontend_sha256"
echo "remote_mobile_asset=$remote_mobile_asset"
echo "remote_mobile_asset_sha256=$remote_mobile_sha256"
[[ "$remote_frontend_sha256" == "$local_frontend_sha256" ]] || fail "remote frontend asset does not match local build"
[[ "$remote_mobile_sha256" == "$local_mobile_sha256" ]] || fail "remote mobile H5 asset does not match local build"
echo "remote_asset_match=true"
if [[ "${RELEASE_VERIFY_REMOTE_BACKEND:-false}" == "true" ]]; then
command -v ssh >/dev/null 2>&1 || fail "ssh is required for remote backend verification"
remote_ssh="${RELEASE_REMOTE_SSH:-YCWY}"
remote_backend_path="${RELEASE_REMOTE_BACKEND_PATH:-/opt/wygj/app/ruoyi-admin.jar}"
[[ "$remote_backend_path" =~ ^/[A-Za-z0-9._/-]+$ ]] || fail "remote backend path must be an absolute safe path: $remote_backend_path"
remote_backend_jar_sha256="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" "sha256sum '$remote_backend_path'" | awk '{print $1}')" || fail "remote backend hash check failed: $remote_ssh:$remote_backend_path"
[[ "$remote_backend_jar_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend hash is invalid: $remote_ssh:$remote_backend_path"
local_backend_jar_sha256="$(sha256 "$backend_jar")"
local_backend_module_jar="$(mktemp)"
unzip -p "$backend_jar" "BOOT-INF/lib/$backend_module_jar_name" > "$local_backend_module_jar"
local_backend_module_sha256="$(normalized_jar_content_sha256 "$local_backend_module_jar")"
rm -f "$local_backend_module_jar"
remote_backend_module_sha_file="$(mktemp)"
ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_backend_path" "$backend_module_jar_name" > "$remote_backend_module_sha_file" <<'REMOTE'
remote_backend_module_sha_file="$(mktemp)"
ssh -o BatchMode=yes -o ConnectTimeout=10 "$remote_ssh" bash -s -- "$remote_backend_path" "$backend_module_jar_name" > "$remote_backend_module_sha_file" <<'REMOTE'
set -euo pipefail
outer="$1"
module="$2"
@@ -838,18 +821,74 @@ while IFS= read -r entry; do
done < <(unzip -Z1 "$nested" | LC_ALL=C sort)
sha256sum "$manifest" | awk '{print $1}'
REMOTE
remote_backend_module_sha256="$(sed -n '1p' "$remote_backend_module_sha_file")"
rm -f "$remote_backend_module_sha_file"
[[ -n "$remote_backend_module_sha256" ]] || fail "remote backend module hash check returned no value: $remote_ssh:$remote_backend_path"
[[ "$remote_backend_module_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend module hash is invalid: $remote_ssh:$remote_backend_path"
echo "remote_backend_jar_sha256=$remote_backend_jar_sha256"
echo "local_backend_jar_sha256=$local_backend_jar_sha256"
echo "backend_module=$backend_module_jar_name"
echo "remote_backend_module_sha256=$remote_backend_module_sha256"
echo "local_backend_module_sha256=$local_backend_module_sha256"
[[ "$remote_backend_module_sha256" == "$local_backend_module_sha256" ]] || fail "remote AIHR module does not match local build"
echo "remote_backend_module_match=true"
fi
remote_backend_module_sha256="$(sed -n '1p' "$remote_backend_module_sha_file")"
rm -f "$remote_backend_module_sha_file"
[[ -n "$remote_backend_module_sha256" ]] || fail "remote backend module hash check returned no value: $remote_ssh:$remote_backend_path"
[[ "$remote_backend_module_sha256" =~ ^[0-9a-f]{64}$ ]] || fail "remote backend module hash is invalid: $remote_ssh:$remote_backend_path"
echo "remote_backend_jar_sha256=$remote_backend_jar_sha256"
echo "local_backend_jar_sha256=$local_backend_jar_sha256"
echo "backend_module=$backend_module_jar_name"
echo "remote_backend_module_sha256=$remote_backend_module_sha256"
echo "local_backend_module_sha256=$local_backend_module_sha256"
[[ "$remote_backend_module_sha256" == "$local_backend_module_sha256" ]] || fail "remote AIHR module does not match local build"
echo "remote_backend_module_match=true"
}
changed_files="$(git status --porcelain)"
[[ -z "$changed_files" ]] || fail "worktree has uncommitted changes; commit the release batch before publishing"
echo "commit=$(git rev-parse HEAD)"
echo "head_epoch=$head_epoch"
echo "worktree=clean"
if [[ "$require_static_artifacts" == "true" ]]; then
echo "frontend_index_sha256=$(sha256 "$frontend_index")"
echo "frontend_asset=$frontend_asset"
echo "frontend_asset_sha256=$(sha256 "$frontend_asset_path")"
echo "mobile_index_sha256=$(sha256 "$mobile_index")"
echo "mobile_asset=$mobile_asset"
echo "mobile_asset_sha256=$(sha256 "$mobile_asset_path")"
fi
if [[ "$require_backend_artifact" == "true" ]]; then
echo "backend_jar_sha256=$(sha256 "$backend_jar")"
fi
if [[ -n "${RELEASE_REMOTE_URL:-}" ]]; then
remote="${RELEASE_REMOTE_URL%/}"
curl -fsS --max-time 15 "$remote/" >/dev/null || fail "remote root check failed: $remote/"
echo "remote_root=200 $remote/"
require_remote_business_success "remote_tenant_list" "$remote/prod-api/auth/tenant/list"
require_remote_business_success "remote_mobile_home" "$remote/prod-api/api/aihr/mobile/home/user"
if [[ "$verify_remote_schema" == "true" ]]; then
require_remote_schema
fi
if [[ "$verify_remote_schema" == "true" || "$verify_remote_backend" == "true" ]]; then
require_remote_practice_schema_guard
fi
if [[ "$verify_remote_backend" == "true" ]]; then
require_remote_backend_match
fi
if [[ "$verify_remote_match" == "true" ]]; then
remote_frontend_asset="$(curl -fsS --max-time 15 "$remote/" | sed -nE 's/.*src="(\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
remote_mobile_asset="$(curl -fsS --max-time 15 "$remote/h5/" | sed -nE 's/.*src="(\/h5\/assets\/[^" ]+\.js)".*/\1/p' | head -n 1)"
[[ "$remote_frontend_asset" == /assets/*.js ]] || fail "remote frontend entry asset not found"
[[ "$remote_mobile_asset" == /h5/assets/*.js ]] || fail "remote mobile H5 entry asset not found"
remote_frontend_sha256="$(curl -fsS --max-time 15 "$remote$remote_frontend_asset" | sha256_stream)"
remote_mobile_sha256="$(curl -fsS --max-time 15 "$remote$remote_mobile_asset" | sha256_stream)"
local_frontend_sha256="$(sha256 "$frontend_asset_path")"
local_mobile_sha256="$(sha256 "$mobile_asset_path")"
echo "remote_frontend_asset=$remote_frontend_asset"
echo "remote_frontend_asset_sha256=$remote_frontend_sha256"
echo "remote_mobile_asset=$remote_mobile_asset"
echo "remote_mobile_asset_sha256=$remote_mobile_sha256"
[[ "$remote_frontend_sha256" == "$local_frontend_sha256" ]] || fail "remote frontend asset does not match local build"
[[ "$remote_mobile_sha256" == "$local_mobile_sha256" ]] || fail "remote mobile H5 asset does not match local build"
echo "remote_asset_match=true"
fi
fi
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="$ROOT_DIR/scripts/release-preflight.sh"
DEV_SETUP="$ROOT_DIR/docs/DEV_SETUP.md"
bash -n "$SCRIPT"
backend_without_url="$(
RELEASE_VERIFY_REMOTE_BACKEND=true bash "$SCRIPT" 2>&1 || true
)"
grep -Fq 'remote verification flags require RELEASE_REMOTE_URL' <<<"$backend_without_url"
schema_without_url="$(
RELEASE_VERIFY_REMOTE_SCHEMA=true bash "$SCRIPT" 2>&1 || true
)"
grep -Fq 'remote verification flags require RELEASE_REMOTE_URL' <<<"$schema_without_url"
if grep -Fq 'RELEASE_VERIFY_REMOTE_BACKEND=true requires RELEASE_VERIFY_REMOTE_MATCH=true' "$SCRIPT"; then
echo 'FAIL: backend-only verification is still coupled to static resource matching' >&2
exit 1
fi
if grep -Fq 'RELEASE_VERIFY_REMOTE_SCHEMA=true requires RELEASE_VERIFY_REMOTE_MATCH=true' "$SCRIPT"; then
echo 'FAIL: schema-only verification is still coupled to static resource matching' >&2
exit 1
fi
backend_call_line="$(grep -n '^[[:space:]]*require_remote_backend_match$' "$SCRIPT" | cut -d: -f1)"
static_scope_line="$(grep -n '^[[:space:]]*if \[\[ "\$verify_remote_match" == "true" \]\]; then$' "$SCRIPT" | cut -d: -f1)"
[[ -n "$backend_call_line" && -n "$static_scope_line" && "$backend_call_line" -lt "$static_scope_line" ]]
grep -Fq 'require_static_artifacts="$verify_remote_match"' "$SCRIPT"
grep -Fq 'require_backend_artifact="$verify_remote_backend"' "$SCRIPT"
grep -Fq 'RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh' "$DEV_SETUP"
grep -Fq 'RELEASE_VERIFY_REMOTE_MATCH=true RELEASE_VERIFY_REMOTE_BACKEND=true RELEASE_VERIFY_REMOTE_SCHEMA=true ./scripts/release-preflight.sh' "$DEV_SETUP"
echo 'PASS: release preflight supports scoped backend/schema verification and preserves the full-package mode'