chore(部署): 增加 Docker 部署配置
关键改动: - 新增本地和生产 Docker Compose、Web/Server Dockerfile 与 Nginx 模板 - 增加生产部署校验脚本、环境变量示例和 Prisma 初始迁移 - 更新部署文档、README 和架构/决策/流程/路线图说明 Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
120
scripts/verify-production-deploy.mjs
Normal file
120
scripts/verify-production-deploy.mjs
Normal file
@@ -0,0 +1,120 @@
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = dirname(fileURLToPath(new URL('../package.json', import.meta.url)));
|
||||
|
||||
const checks = [
|
||||
{
|
||||
file: 'Dockerfile.web',
|
||||
snippets: [
|
||||
'FROM node:20-alpine',
|
||||
'pnpm --filter @ftb/shared build',
|
||||
'pnpm --filter web build',
|
||||
'NEXT_PUBLIC_API_URL',
|
||||
'CMD ["pnpm", "--filter", "web", "start"]',
|
||||
],
|
||||
},
|
||||
{
|
||||
file: 'Dockerfile.server',
|
||||
snippets: [
|
||||
'FROM node:20-alpine',
|
||||
'pnpm --filter @ftb/shared build',
|
||||
'pnpm --filter server build',
|
||||
'prisma generate',
|
||||
'CMD ["pnpm", "--filter", "server", "start:prod"]',
|
||||
],
|
||||
},
|
||||
{
|
||||
file: 'docker-compose.prod.yml',
|
||||
snippets: [
|
||||
'web:',
|
||||
'server:',
|
||||
'postgres:',
|
||||
'redis:',
|
||||
'nginx:',
|
||||
'server_data:',
|
||||
'NEXT_PUBLIC_API_URL',
|
||||
'/api/v1/config/ai',
|
||||
],
|
||||
},
|
||||
{
|
||||
file: 'docker-compose.local.yml',
|
||||
snippets: [
|
||||
'web:',
|
||||
'server:',
|
||||
'postgres:',
|
||||
'redis:',
|
||||
'nginx:',
|
||||
'local_server_data:',
|
||||
'NEXT_PUBLIC_API_URL',
|
||||
'${LOCAL_HTTP_PORT:-8080}:80',
|
||||
],
|
||||
},
|
||||
{
|
||||
file: 'deploy/nginx/default.conf.template',
|
||||
snippets: [
|
||||
'proxy_pass http://web:3000',
|
||||
'proxy_pass http://server:3001',
|
||||
'client_max_body_size',
|
||||
'X-Forwarded-Proto',
|
||||
],
|
||||
},
|
||||
{
|
||||
file: '.env.production.example',
|
||||
snippets: [
|
||||
'POSTGRES_PASSWORD=',
|
||||
'DATABASE_URL=postgresql://',
|
||||
'NEXT_PUBLIC_API_URL=',
|
||||
'NEXTAUTH_SECRET=',
|
||||
'ANTHROPIC_API_KEY=',
|
||||
],
|
||||
},
|
||||
{
|
||||
file: '.env.local-server.example',
|
||||
snippets: [
|
||||
'LOCAL_HTTP_PORT=8080',
|
||||
'LOCAL_ACCESS_HOST=localhost',
|
||||
'DATABASE_URL=postgresql://',
|
||||
'NEXT_PUBLIC_API_URL=/api/v1',
|
||||
'NEXTAUTH_URL=http://localhost:8080',
|
||||
],
|
||||
},
|
||||
{
|
||||
file: 'docs/deployment.md',
|
||||
snippets: [
|
||||
'docker-compose.prod.yml',
|
||||
'docker-compose.local.yml',
|
||||
'本地服务器部署',
|
||||
'pnpm deploy:verify',
|
||||
'pnpm db:migrate',
|
||||
'NEXT_PUBLIC_API_URL',
|
||||
'Nginx',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
let failed = false;
|
||||
|
||||
for (const check of checks) {
|
||||
const abs = join(root, check.file);
|
||||
if (!existsSync(abs)) {
|
||||
console.error(`Missing deployment artifact: ${check.file}`);
|
||||
failed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = readFileSync(abs, 'utf8');
|
||||
for (const snippet of check.snippets) {
|
||||
if (!content.includes(snippet)) {
|
||||
console.error(`Missing snippet in ${check.file}: ${snippet}`);
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Production deployment artifacts verified.');
|
||||
Reference in New Issue
Block a user