加固部署隔离与备份来源契约(07-26-internal-ops-recovery-baseline)

这个提交包含在:
2026-07-27 06:13:02 +08:00
父节点 130518f471
当前提交 23342035ee
共修改 16 个文件,包含 304 行新增和 80 行删除
+56 -2
查看文件
@@ -4,10 +4,21 @@ const path = require('node:path');
const rootDir = path.resolve(__dirname, '..', '..');
const configPath = path.join(rootDir, 'server/src/config.js');
const envExamplePath = path.join(rootDir, 'deploy/trialrun/.env.example');
const composePath = path.join(rootDir, 'deploy/trialrun/docker-compose.yml');
const envExamplePath = path.resolve(
process.env.ENV_CONTRACT_ENV_EXAMPLE_PATH || path.join(rootDir, 'deploy/trialrun/.env.example'),
);
const composePath = path.resolve(
process.env.ENV_CONTRACT_COMPOSE_PATH || path.join(rootDir, 'deploy/trialrun/docker-compose.yml'),
);
const runbookPath = path.join(rootDir, 'deploy/trialrun/runbook.md');
const checklistPath = path.join(rootDir, 'deploy/trialrun/checklist.md');
const deployScriptPath = path.resolve(
process.env.ENV_CONTRACT_DEPLOY_SCRIPT_PATH || path.join(rootDir, 'deploy/trialrun/deploy.sh'),
);
const timerScriptPath = path.resolve(
process.env.ENV_CONTRACT_TIMER_SCRIPT_PATH
|| path.join(rootDir, 'deploy/trialrun/install-warch3-ops-timer.sh'),
);
const nginxPaths = [
path.join(rootDir, 'deploy/trialrun/nginx-hyb-oa.conf'),
path.join(rootDir, 'deploy/trialrun/nginx.conf.example'),
@@ -130,6 +141,8 @@ const envExampleContent = readFile(envExamplePath);
const composeContent = readFile(composePath);
const runbookContent = readFile(runbookPath);
const checklistContent = readFile(checklistPath);
const deployScriptContent = readFile(deployScriptPath);
const timerScriptContent = readFile(timerScriptPath);
const nginxContents = nginxPaths.map((filePath) => ({
filePath,
content: readFile(filePath),
@@ -150,6 +163,15 @@ for (const key of deploymentInjectedEnvKeys) {
}
}
for (const key of ['APP_HOST_PORT', 'POSTGRES_DATA_DIR', 'STORAGE_HOST_DIR']) {
if (!envExampleKeys.has(key)) {
errors.push(`.env.example 缺少 Compose 宿主契约键 ${key}`);
}
}
if (envExampleKeys.has('COMPOSE_PROJECT_NAME')) {
errors.push('.env.example 不应持久化 COMPOSE_PROJECT_NAME,调用方必须通过 -p 显式选择项目');
}
for (const serviceName of requiredServices) {
const block = extractServiceBlock(composeContent, serviceName);
if (!block) {
@@ -189,6 +211,38 @@ if (!serverBlock || !/^\s{6}-\s*["']127\.0\.0\.1:\$\{APP_HOST_PORT:-3000\}:3000[
errors.push('服务 server 必须通过 APP_HOST_PORT 将容器 3000 端口只绑定到宿主回环地址');
}
const dbBlock = extractServiceBlock(composeContent, 'db');
if (!dbBlock) {
errors.push('docker-compose.yml 缺少服务 db');
} else {
if (/^\s{4}ports:\s*$/m.test(dbBlock)) {
errors.push('服务 db 不得向宿主发布端口');
}
if (!/\$\{POSTGRES_DATA_DIR:-\.\/data\/postgres\}:\/var\/lib\/postgresql\/data/.test(dbBlock)) {
errors.push('服务 db 必须通过 POSTGRES_DATA_DIR 参数化宿主数据目录');
}
}
if (/^\s+container_name:\s*/m.test(composeContent)) {
errors.push('docker-compose.yml 不得固定 container_name,容器身份必须由 Compose project 隔离');
}
for (const serviceName of ['server', 'worker', 'init']) {
const block = extractServiceBlock(composeContent, serviceName);
if (block && !/\$\{STORAGE_HOST_DIR:-\.\/data\/storage\}:\$\{STORAGE_DIR\}/.test(block)) {
errors.push(`服务 ${serviceName} 必须通过 STORAGE_HOST_DIR 参数化宿主附件目录`);
}
}
for (const [name, content] of [
['deploy.sh', deployScriptContent],
['install-warch3-ops-timer.sh', timerScriptContent],
]) {
if (!content.includes('-p "$COMPOSE_PROJECT_NAME"')) {
errors.push(`${name} 必须向 Compose 显式传入独立 project name`);
}
}
for (const { filePath, content } of nginxContents) {
const relativePath = path.relative(rootDir, filePath);
if (content.includes('$proxy_add_x_forwarded_for')) {