固化部署体检与备份合同刷新(08-02-deployment-preflight-backup-contract-refresh)
这个提交包含在:
@@ -21,6 +21,22 @@ const deployScriptPath = path.resolve(
|
||||
process.env.ENV_CONTRACT_DEPLOY_SCRIPT_PATH || path.join(rootDir, 'deploy/trialrun/deploy.sh'),
|
||||
);
|
||||
const deployExistingScriptPath = path.join(rootDir, 'deploy/trialrun/deploy-existing.sh');
|
||||
const deploymentContractLibraryPath = path.join(
|
||||
rootDir,
|
||||
'deploy/trialrun/deployment-contract-lib.sh',
|
||||
);
|
||||
const deploymentPreflightScriptPath = path.join(
|
||||
rootDir,
|
||||
'deploy/trialrun/check-existing-deployment.sh',
|
||||
);
|
||||
const deploymentRefreshInstallerPath = path.join(
|
||||
rootDir,
|
||||
'deploy/trialrun/install-deployment-contract-refresh.sh',
|
||||
);
|
||||
const deploymentRefreshHelperPath = path.join(
|
||||
rootDir,
|
||||
'scripts/refresh-deployment-contract.sh',
|
||||
);
|
||||
const remotePreflightScriptPath = path.join(rootDir, 'deploy/trialrun/preflight-remote.sh');
|
||||
const dockerfilePath = path.join(rootDir, 'deploy/trialrun/Dockerfile');
|
||||
const timerScriptPath = path.resolve(
|
||||
@@ -251,6 +267,10 @@ const runbookContent = readFile(runbookPath);
|
||||
const checklistContent = readFile(checklistPath);
|
||||
const deployScriptContent = readFile(deployScriptPath);
|
||||
const deployExistingScriptContent = readFile(deployExistingScriptPath);
|
||||
const deploymentContractLibraryContent = readFile(deploymentContractLibraryPath);
|
||||
const deploymentPreflightScriptContent = readFile(deploymentPreflightScriptPath);
|
||||
const deploymentRefreshInstallerContent = readFile(deploymentRefreshInstallerPath);
|
||||
const deploymentRefreshHelperContent = readFile(deploymentRefreshHelperPath);
|
||||
const remotePreflightScriptContent = readFile(remotePreflightScriptPath);
|
||||
const dockerfileContent = readFile(dockerfilePath);
|
||||
const timerScriptContent = readFile(timerScriptPath);
|
||||
@@ -557,7 +577,12 @@ if (deployScriptContent.indexOf('>>> 3. 同步前只读校验远端部署合同'
|
||||
}
|
||||
|
||||
for (const requiredFragment of [
|
||||
'systemctl show',
|
||||
'DEPLOY_REFRESH_SERVER',
|
||||
'check-existing-deployment.sh',
|
||||
'deployment_contract_load',
|
||||
'DEPLOY_DEFER_FINAL_SUCCESS=1',
|
||||
'ssh "$DEPLOY_REFRESH_SERVER" refresh',
|
||||
'DEPLOYED_BUT_CONTRACT_REFRESH_FAILED',
|
||||
'DEPLOY_DATA_MODE=existing',
|
||||
'SYNC_ENV=0',
|
||||
'路径与标识不回显',
|
||||
@@ -567,6 +592,82 @@ for (const requiredFragment of [
|
||||
}
|
||||
}
|
||||
|
||||
for (const requiredFragment of [
|
||||
'systemctl show',
|
||||
'DEPLOY_CONTRACT_KEYS',
|
||||
'REMOTE_DIR',
|
||||
'REMOTE_ENV_FILE',
|
||||
'COMPOSE_FILE',
|
||||
'COMPOSE_PROJECT_NAME',
|
||||
'POSTGRES_DATA_DIR',
|
||||
'STORAGE_HOST_DIR',
|
||||
'deployment_contract_refresh_category',
|
||||
]) {
|
||||
if (!deploymentContractLibraryContent.includes(requiredFragment)) {
|
||||
errors.push(`deployment-contract-lib.sh 缺少共享部署合同: ${requiredFragment}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const requiredFragment of [
|
||||
'set -uo pipefail',
|
||||
'CHECK_NAMES=(',
|
||||
'repository_writable',
|
||||
'env_readable',
|
||||
'runtime_unique',
|
||||
'storage_mount',
|
||||
'pgdata_mount',
|
||||
'backup_build',
|
||||
'root_ssh_identity',
|
||||
'refresh_program',
|
||||
'summary PASS=',
|
||||
]) {
|
||||
if (!deploymentPreflightScriptContent.includes(requiredFragment)) {
|
||||
errors.push(`check-existing-deployment.sh 缺少聚合体检合同: ${requiredFragment}`);
|
||||
}
|
||||
}
|
||||
if (/^set\s+-[A-Za-z]*e[A-Za-z]*(?:\s|$)/m.test(deploymentPreflightScriptContent)) {
|
||||
errors.push('check-existing-deployment.sh 主流程不得启用 set -e');
|
||||
}
|
||||
|
||||
for (const requiredFragment of [
|
||||
'SSH_ORIGINAL_COMMAND',
|
||||
'probe) probe',
|
||||
'refresh) refresh',
|
||||
'20-active-build.conf',
|
||||
'refresh_program_integrity_invalid',
|
||||
'unit_build_not_refreshed',
|
||||
'timer_not_active',
|
||||
'systemctl daemon-reload',
|
||||
]) {
|
||||
if (!deploymentRefreshHelperContent.includes(requiredFragment)) {
|
||||
errors.push(`refresh-deployment-contract.sh 缺少 forced-command 合同: ${requiredFragment}`);
|
||||
}
|
||||
}
|
||||
for (const forbiddenFragment of ['eval ', 'bash -c', 'REMOTE_DIR/']) {
|
||||
if (deploymentRefreshHelperContent.includes(forbiddenFragment)) {
|
||||
errors.push(`refresh-deployment-contract.sh 包含禁止的动态执行片段: ${forbiddenFragment}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const requiredFragment of [
|
||||
'no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-pty,no-user-rc',
|
||||
'dedicated key already has unmanaged or unrestricted root authorization',
|
||||
'install -o root -g root -m 0755',
|
||||
]) {
|
||||
if (!deploymentRefreshInstallerContent.includes(requiredFragment)) {
|
||||
errors.push(`install-deployment-contract-refresh.sh 缺少受限安装合同: ${requiredFragment}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (deployExistingScriptContent.indexOf('check-existing-deployment.sh')
|
||||
> deployExistingScriptContent.indexOf('bash "$ROOT_DIR/deploy/trialrun/deploy.sh"')) {
|
||||
errors.push('deploy-existing.sh 必须在应用部署前执行聚合体检');
|
||||
}
|
||||
if (deployExistingScriptContent.indexOf('ssh "$DEPLOY_REFRESH_SERVER" refresh')
|
||||
< deployExistingScriptContent.indexOf('bash "$ROOT_DIR/deploy/trialrun/deploy.sh"')) {
|
||||
errors.push('deploy-existing.sh 必须在应用部署成功后刷新备份合同');
|
||||
}
|
||||
|
||||
for (const requiredFragment of [
|
||||
'REMOTE_DIR 是现存仓库根目录',
|
||||
'SYNC_ENV=0 要求同步前已有普通远端 .env 文件',
|
||||
|
||||
在新工单中引用
屏蔽一个用户