修正(08-08-production-deployment-execution): 先验生产公网身份再冻结目录
这个提交包含在:
@@ -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,
|
||||
|
||||
在新工单中引用
屏蔽一个用户