修正(08-08-production-deployment-execution): 先验生产公网身份再冻结目录
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-08-08 19:36:32 +08:00
父节点 eb76de7c98
当前提交 bf2631c0d4
共修改 8 个文件,包含 151 行新增和 48 行删除
@@ -9,11 +9,16 @@ fail() {
exit 1
}
for key in REMOTE_DIR REMOTE_ENV_FILE COMPOSE_FILE COMPOSE_PROJECT_NAME \
EXPECTED_PRODUCTION_ORIGIN_SHA256; do
for key in IDENTITY_BOOTSTRAP_MODE REMOTE_DIR REMOTE_ENV_FILE COMPOSE_FILE \
COMPOSE_PROJECT_NAME EXPECTED_PRODUCTION_ORIGIN_SHA256; do
[[ -n "${!key:-}" ]] || fail "${key}_MISSING"
done
case "$IDENTITY_BOOTSTRAP_MODE" in
origin|database) ;;
*) fail "IDENTITY_BOOTSTRAP_MODE_INVALID" ;;
esac
[[ "$EXPECTED_PRODUCTION_ORIGIN_SHA256" =~ ^[0-9a-f]{64}$ ]] \
|| fail "PROD_TARGET_ORIGIN_HASH_INVALID"
cd "$REMOTE_DIR"
@@ -62,11 +67,8 @@ reject_non_production_identity() {
}
actual_origin="$(dotenv_value APP_ORIGIN)" || fail "PROD_TARGET_ORIGIN_MISSING"
actual_db_name="$(dotenv_value POSTGRES_DB)" || fail "PROD_TARGET_DB_NAME_MISSING"
[[ "$(hash_text "$actual_origin")" == "$EXPECTED_PRODUCTION_ORIGIN_SHA256" ]] \
|| fail "PROD_TARGET_ORIGIN_MISMATCH"
reject_non_production_identity "$actual_db_name" \
|| fail "PROD_TARGET_DB_NON_PRODUCTION_IDENTITY"
if docker compose version >/dev/null 2>&1; then
compose_cmd=(docker compose -p "$COMPOSE_PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$REMOTE_ENV_FILE")
@@ -90,6 +92,14 @@ runtime_origin="$(
[[ "$(hash_text "$runtime_origin")" == "$EXPECTED_PRODUCTION_ORIGIN_SHA256" ]] \
|| fail "PROD_TARGET_RUNTIME_ORIGIN_MISMATCH"
if [[ "$IDENTITY_BOOTSTRAP_MODE" == "origin" ]]; then
printf '[production-identity-bootstrap][PASS] origin_identity\n'
exit 0
fi
# Only the comparison baseline crosses the SSH channel. The database name
# itself remains remote and cannot be reconstructed from task evidence.
actual_db_name="$(dotenv_value POSTGRES_DB)" || fail "PROD_TARGET_DB_NAME_MISSING"
reject_non_production_identity "$actual_db_name" \
|| fail "PROD_TARGET_DB_NON_PRODUCTION_IDENTITY"
printf '[production-identity-bootstrap][DB_SHA256] %s\n' "$(hash_text "$actual_db_name")"
@@ -60,14 +60,10 @@ function parseHistoricalDefault(source, key) {
export function parseHistoricalDeploymentIdentity(targetSource, projectSource) {
const deployServer = parseHistoricalDefault(targetSource, "DEPLOY_SERVER");
const remoteDir = parseHistoricalDefault(targetSource, "REMOTE_DIR");
const projectRemoteDir = parseHistoricalDefault(projectSource, "REMOTE_DIR");
const composeProjectName = parseHistoricalDefault(projectSource, "COMPOSE_PROJECT_NAME");
if (projectRemoteDir !== remoteDir) fail("HISTORICAL_REMOTE_DIR_DIVERGED");
if (!SAFE_SSH_TARGET_PATTERN.test(deployServer)) fail("HISTORICAL_DEPLOY_SERVER_INVALID");
if (!SAFE_ABSOLUTE_PATH_PATTERN.test(remoteDir)) fail("HISTORICAL_REMOTE_DIR_INVALID");
if (!SAFE_PROJECT_PATTERN.test(composeProjectName)) fail("HISTORICAL_COMPOSE_PROJECT_INVALID");
return { deployServer, remoteDir, composeProjectName };
return { deployServer, composeProjectName };
}
export function parseExpectedProductionOrigin(runbook) {
@@ -145,9 +141,8 @@ export function parseDeploymentContract(raw) {
return contract;
}
export function assertExactHistoricalContract(identity, contract) {
if (contract.REMOTE_DIR !== identity.remoteDir) fail("SYSTEMD_REMOTE_DIR_MISMATCH");
if (contract.REMOTE_ENV_FILE !== `${identity.remoteDir}/deploy/trialrun/.env`) {
export function assertContractForOriginVerification(identity, contract) {
if (contract.REMOTE_ENV_FILE !== `${contract.REMOTE_DIR}/deploy/trialrun/.env`) {
fail("SYSTEMD_REMOTE_ENV_FILE_MISMATCH");
}
if (contract.COMPOSE_FILE !== "deploy/trialrun/docker-compose.yml") {
@@ -166,8 +161,9 @@ function shellQuote(value) {
return `'${String(value).replaceAll("'", "'\\''")}'`;
}
function bootstrapDatabaseIdentityHash(identity, contract, expectedOrigin) {
function runRemoteIdentityBootstrap(mode, identity, contract, expectedOrigin) {
const remoteCommand = [
`IDENTITY_BOOTSTRAP_MODE=${shellQuote(mode)}`,
`REMOTE_DIR=${shellQuote(contract.REMOTE_DIR)}`,
`REMOTE_ENV_FILE=${shellQuote(contract.REMOTE_ENV_FILE)}`,
`COMPOSE_FILE=${shellQuote(contract.COMPOSE_FILE)}`,
@@ -181,7 +177,18 @@ function bootstrapDatabaseIdentityHash(identity, contract, expectedOrigin) {
const code = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.match(/code=([A-Z0-9_]+)/u)?.[1];
fail(code || "REMOTE_IDENTITY_BOOTSTRAP_FAILED");
}
const output = String(result.stdout ?? "").trim();
return String(result.stdout ?? "").trim();
}
function verifyOriginIdentity(identity, contract, expectedOrigin) {
const output = runRemoteIdentityBootstrap("origin", identity, contract, expectedOrigin);
if (output !== "[production-identity-bootstrap][PASS] origin_identity") {
fail("REMOTE_ORIGIN_IDENTITY_PAYLOAD_INVALID");
}
}
function bootstrapDatabaseIdentityHash(identity, contract, expectedOrigin) {
const output = runRemoteIdentityBootstrap("database", identity, contract, expectedOrigin);
const match = output.match(/^\[production-identity-bootstrap\]\[DB_SHA256\] ([0-9a-f]{64})$/u);
if (!match) fail("REMOTE_DB_IDENTITY_PAYLOAD_INVALID");
return match[1];
@@ -189,6 +196,8 @@ function bootstrapDatabaseIdentityHash(identity, contract, expectedOrigin) {
function main() {
try {
const identityOnly = process.env.PRODUCTION_IDENTITY_ONLY ?? "0";
if (!["0", "1"].includes(identityOnly)) fail("PRODUCTION_IDENTITY_ONLY_INVALID");
const historicalTargetScript = requiredCommandOutput(
"git",
["show", `${HISTORICAL_TARGET_COMMIT}:${DEPLOY_SCRIPT_AT_COMMIT}`],
@@ -208,7 +217,14 @@ function main() {
"SYSTEMD_CONTRACT_UNAVAILABLE",
);
const contract = parseDeploymentContract(contractRaw);
assertExactHistoricalContract(identity, contract);
assertContractForOriginVerification(identity, contract);
verifyOriginIdentity(identity, contract, expectedOrigin);
const expectedRemoteDir = contract.REMOTE_DIR;
if (identityOnly === "1") {
console.log("[production-identity-bootstrap][PASS] origin_verified_remote_dir_baseline=systemd_contract");
return;
}
const databaseIdentityHash = bootstrapDatabaseIdentityHash(identity, contract, expectedOrigin);
console.log("[production-identity-bootstrap][PASS] exact_identity_group");
@@ -220,7 +236,7 @@ function main() {
DEPLOY_SERVER: identity.deployServer,
DEPLOY_REFRESH_SERVER: refreshTarget,
EXPECTED_DEPLOY_SERVER: identity.deployServer,
EXPECTED_REMOTE_DIR: identity.remoteDir,
EXPECTED_REMOTE_DIR: expectedRemoteDir,
EXPECTED_COMPOSE_PROJECT_NAME: identity.composeProjectName,
EXPECTED_PRODUCTION_ORIGIN: expectedOrigin,
EXPECTED_PRODUCTION_DB_NAME_SHA256: databaseIdentityHash,
+13 -1
查看文件
@@ -453,6 +453,14 @@ for (const requiredFragment of [
errors.push(`install-warch3-ops-timer.sh 缺少活动部署环境绑定: ${requiredFragment}`);
}
}
if (productionIdentityBootstrapScriptContent.indexOf('if (identityOnly === "1")')
> productionIdentityBootstrapScriptContent.indexOf('const databaseIdentityHash = bootstrapDatabaseIdentityHash')) {
errors.push('production-identity-bootstrap.mjs 的 identity-only 停止点必须位于数据库身份读取前');
}
if (productionIdentityBootstrapScriptContent.indexOf('verifyOriginIdentity(identity, contract, expectedOrigin);')
> productionIdentityBootstrapScriptContent.indexOf('const expectedRemoteDir = contract.REMOTE_DIR')) {
errors.push('production-identity-bootstrap.mjs 必须在 origin 双取样后冻结 contract REMOTE_DIR');
}
for (const requiredFragment of [
'MODE="${1:-install}"',
@@ -634,8 +642,10 @@ for (const requiredFragment of [
'HISTORICAL_TARGET_COMMIT',
'HISTORICAL_PROJECT_COMMIT',
'discoverRefreshTarget',
'assertExactHistoricalContract',
'assertContractForOriginVerification',
'verifyOriginIdentity',
'bootstrapDatabaseIdentityHash',
'PRODUCTION_IDENTITY_ONLY',
'EXPECTED_PRODUCTION_DB_NAME_SHA256',
'DEPLOY_EXISTING_SCRIPT',
]) {
@@ -647,6 +657,8 @@ for (const requiredFragment of [
'EXPECTED_PRODUCTION_ORIGIN_SHA256',
'PROD_TARGET_RUNTIME_ORIGIN_MISMATCH',
'PROD_TARGET_DB_NON_PRODUCTION_IDENTITY',
'origin_identity',
'IDENTITY_BOOTSTRAP_MODE',
'DB_SHA256',
]) {
if (!productionIdentityBootstrapRemoteScriptContent.includes(requiredFragment)) {