create-dwy 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/bin/index.js +1 -1
  2. package/package.json +2 -2
  3. package/src/create.js +61 -37
  4. package/src/sync.js +213 -53
  5. package/templates/claude-global/CLAUDE.md +92 -0
  6. package/templates/claude-global/rules/dwy-android-core.md +610 -0
  7. package/templates/claude-global/rules/dwy-api-design.md +110 -0
  8. package/templates/claude-global/rules/dwy-db-migration.md +46 -0
  9. package/templates/claude-global/rules/dwy-flutter-core.md +564 -0
  10. package/templates/claude-global/rules/dwy-flutter-network.md +222 -0
  11. package/templates/claude-global/rules/dwy-flutter-router.md +97 -0
  12. package/templates/claude-global/rules/dwy-flutter-testing.md +94 -0
  13. package/templates/claude-global/rules/dwy-git-commit-convention.md +82 -0
  14. package/templates/claude-global/rules/dwy-git-security.md +53 -0
  15. package/templates/claude-global/rules/dwy-payload-limits.md +178 -0
  16. package/templates/claude-global/rules/{prefer-open-source.md → dwy-prefer-open-source.md} +4 -0
  17. package/templates/claude-global/rules/dwy-project-structure.md +181 -0
  18. package/templates/claude-global/rules/{python-code-style.md → dwy-python-core.md} +314 -357
  19. package/templates/claude-global/rules/dwy-python-fastapi.md +272 -0
  20. package/templates/claude-global/rules/dwy-python-orm.md +61 -0
  21. package/templates/claude-global/rules/{backend-security.md → dwy-python-security.md} +116 -79
  22. package/templates/claude-global/rules/dwy-python-testing.md +53 -0
  23. package/templates/claude-global/rules/{server-security.md → dwy-server-security.md} +1 -2
  24. package/templates/claude-global/rules/dwy-tdd-development.md +70 -0
  25. package/templates/claude-global/rules/dwy-tech-stack.md +60 -0
  26. package/templates/claude-global/rules/dwy-vue-api.md +146 -0
  27. package/templates/claude-global/rules/{vue-code-style.md → dwy-vue-core.md} +361 -152
  28. package/templates/claude-global/rules/dwy-vue-pinia.md +75 -0
  29. package/templates/claude-global/rules/dwy-vue-testing.md +102 -0
  30. package/templates/claude-global/settings.json +20 -0
  31. package/templates/claude-global/skills/dwy-deploy-audit/SKILL.md +481 -0
  32. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-data.md +117 -0
  33. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +74 -0
  34. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-runtime.md +184 -0
  35. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_base.sh +39 -0
  36. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_capacity.sh +278 -0
  37. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_db.sh +122 -0
  38. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_docker.sh +249 -0
  39. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_env.sh +64 -0
  40. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_https.sh +60 -0
  41. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_logs.sh +209 -0
  42. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +92 -0
  43. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_resilience.sh +157 -0
  44. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_secrets.sh +327 -0
  45. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_services.sh +77 -0
  46. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_ssh.sh +56 -0
  47. package/templates/claude-global/skills/dwy-deploy-audit/scripts/run_all.sh +105 -0
  48. package/templates/claude-global/skills/dwy-docker/SKILL.md +187 -0
  49. package/templates/claude-global/skills/dwy-docker/references/ask-templates.md +142 -0
  50. package/templates/claude-global/skills/dwy-docker/references/templates.md +479 -0
  51. package/templates/claude-global/skills/dwy-docker/references/version-rules.md +76 -0
  52. package/templates/claude-global/skills/dwy-docker/scripts/query_dockerhub.py +204 -0
  53. package/templates/claude-global/skills/dwy-docker/scripts/scan_images.sh +185 -0
  54. package/templates/claude-global/skills/dwy-eapi/SKILL.md +433 -0
  55. package/templates/claude-global/skills/dwy-eapi/references/tasks-integration-guide.md +387 -0
  56. package/templates/claude-global/skills/dwy-ekit/SKILL.md +85 -0
  57. package/templates/claude-global/skills/dwy-ekit/references/data.md +135 -0
  58. package/templates/claude-global/skills/dwy-ekit/references/form.md +68 -0
  59. package/templates/claude-global/skills/dwy-ekit/references/request.md +183 -0
  60. package/templates/claude-global/skills/dwy-ekit/references/ui.md +109 -0
  61. package/templates/claude-global/skills/{dwy-frontend-eui → dwy-eui}/SKILL.md +129 -15
  62. package/templates/claude-global/skills/dwy-eui/references/eui-design-guide.md +257 -0
  63. package/templates/claude-global/skills/dwy-eui/references/eui-integration-guide.md +302 -0
  64. package/templates/claude-global/skills/dwy-eui/references/eui-landing-design-guide.md +437 -0
  65. package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +214 -0
  66. package/templates/claude-global/skills/dwy-mirror-source/SKILL.md +275 -0
  67. package/templates/claude-global/skills/dwy-mirror-source/references/dockerfile-snippets.md +228 -0
  68. package/templates/claude-global/skills/dwy-mirror-source/references/mirror-providers.md +150 -0
  69. package/templates/claude-global/skills/dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc +0 -0
  70. package/templates/claude-global/skills/dwy-mirror-source/scripts/apply_mirrors.py +666 -0
  71. package/templates/claude-global/skills/dwy-mirror-source/scripts/check_mirrors.py +868 -0
  72. package/templates/claude-global/skills/dwy-pentest/SKILL.md +344 -0
  73. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__init__.py +1 -0
  74. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc +0 -0
  75. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc +0 -0
  76. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc +0 -0
  77. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc +0 -0
  78. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc +0 -0
  79. package/templates/claude-global/skills/dwy-pentest/scripts/lib/config.py +109 -0
  80. package/templates/claude-global/skills/dwy-pentest/scripts/lib/executor.py +157 -0
  81. package/templates/claude-global/skills/dwy-pentest/scripts/lib/severity.py +96 -0
  82. package/templates/claude-global/skills/dwy-pentest/scripts/lib/validator.py +113 -0
  83. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_auth.py +289 -0
  84. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_recon.py +217 -0
  85. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_report.py +343 -0
  86. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_scan.py +205 -0
  87. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_ssl.py +208 -0
  88. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_vuln.py +205 -0
  89. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_web.py +330 -0
  90. package/templates/claude-global/skills/dwy-pentest/scripts/pyproject.toml +14 -0
  91. package/templates/claude-global/skills/dwy-pentest/scripts/setup_tools.sh +97 -0
  92. package/templates/claude-global/skills/dwy-pentest/scripts/uv.lock +415 -0
  93. package/templates/claude-global/skills/dwy-publish/SKILL.md +135 -0
  94. package/templates/claude-global/skills/dwy-vue-core/SKILL.md +890 -0
  95. package/templates/project/backend/.dwy.hbs +5 -0
  96. package/templates/project/backend/.python-version +1 -0
  97. package/templates/project/backend/CLAUDE.md.hbs +40 -0
  98. package/templates/project/{web/backend → backend}/alembic/script.py.mako +2 -0
  99. package/templates/project/{web/backend → backend}/alembic.ini.hbs +0 -1
  100. package/templates/project/backend/deploy.sh.hbs +18 -0
  101. package/templates/project/{web → backend}/docker-compose.prod.yml.hbs +1 -1
  102. package/templates/project/{web/backend → backend}/pyproject.toml.hbs +4 -5
  103. package/templates/project/{web/backend → backend}/src/app/config.py.hbs +1 -1
  104. package/templates/project/backend/src/app/database.py +10 -0
  105. package/templates/project/backend/src/app/dependencies.py +5 -0
  106. package/templates/project/{web/backend → backend}/src/app/main.py.hbs +3 -3
  107. package/templates/project/backend/src/app/models/base.py +5 -0
  108. package/templates/project/web/CLAUDE.md.hbs +29 -36
  109. package/templates/project/web/{frontend → apps/web}/index.html.hbs +1 -0
  110. package/templates/project/web/{frontend → apps/web}/package.json.hbs +4 -3
  111. package/templates/project/web/apps/web/src/App.vue +9 -0
  112. package/templates/project/web/apps/web/src/api/client.ts +5 -0
  113. package/templates/project/web/apps/web/src/composables/.gitkeep +0 -0
  114. package/templates/project/web/apps/web/src/layouts/DefaultLayout.vue +8 -0
  115. package/templates/project/web/{frontend → apps/web}/src/router/index.ts +1 -1
  116. package/templates/project/web/{frontend → apps/web}/src/stores/auth.ts +4 -2
  117. package/templates/project/web/apps/web/src/style.css +2 -0
  118. package/templates/project/web/apps/web/src/types/router.d.ts +8 -0
  119. package/templates/project/web/apps/web/src/views/HomeView.vue +8 -0
  120. package/templates/project/web/apps/web/tsconfig.app.json +18 -0
  121. package/templates/project/web/apps/web/tsconfig.json +6 -0
  122. package/templates/project/web/{frontend → apps/web}/vite.config.ts.hbs +7 -1
  123. package/templates/project/web/package.json.hbs +16 -0
  124. package/templates/project/web/packages/.gitkeep +0 -0
  125. package/templates/project/web/pnpm-workspace.yaml +3 -0
  126. package/templates/claude-global/rules/agent-team-execution.md +0 -138
  127. package/templates/claude-global/rules/agent-team-patterns.md +0 -157
  128. package/templates/claude-global/rules/git-security.md +0 -144
  129. package/templates/claude-global/skills/dwy-auto-sync/SKILL.md +0 -52
  130. package/templates/claude-global/skills/dwy-backend-base/SKILL.md +0 -279
  131. package/templates/claude-global/skills/dwy-frontend-core/SKILL.md +0 -181
  132. package/templates/project/web/.claude/commands/deploy.md +0 -4
  133. package/templates/project/web/backend/src/app/models/base.py +0 -5
  134. package/templates/project/web/deploy.sh.hbs +0 -113
  135. package/templates/project/web/frontend/src/App.vue +0 -3
  136. package/templates/project/web/frontend/src/api/client.ts +0 -13
  137. package/templates/project/web/frontend/src/style.css +0 -1
  138. package/templates/project/web/frontend/src/views/HomeView.vue +0 -5
  139. package/templates/project/web/frontend/tsconfig.app.json +0 -14
  140. package/templates/project/web/frontend/tsconfig.json +0 -4
  141. /package/templates/{project/web/backend → claude-global/skills/dwy-pentest/scripts}/.python-version +0 -0
  142. /package/templates/project/{web/backend → backend}/Dockerfile +0 -0
  143. /package/templates/project/{web/backend → backend}/alembic/env.py.hbs +0 -0
  144. /package/templates/{claude-global/skills → project/backend/alembic/versions}/.gitkeep +0 -0
  145. /package/templates/project/{web → backend}/docker-compose.dev.yml.hbs +0 -0
  146. /package/templates/project/{web/backend → backend}/src/app/__init__.py +0 -0
  147. /package/templates/project/{web/backend → backend}/src/app/models/__init__.py +0 -0
  148. /package/templates/project/{web/backend → backend}/src/app/routers/__init__.py +0 -0
  149. /package/templates/project/{web/backend → backend}/src/app/routers/health.py +0 -0
  150. /package/templates/project/{web/backend → backend}/src/app/schemas/__init__.py +0 -0
  151. /package/templates/project/{web/backend → backend}/src/app/services/__init__.py +0 -0
  152. /package/templates/project/{web/backend → backend}/tests/__init__.py +0 -0
  153. /package/templates/project/{web/backend → backend}/tests/conftest.py.hbs +0 -0
  154. /package/templates/project/{web/backend/alembic/versions → mobile}/.gitkeep +0 -0
  155. /package/templates/project/web/{frontend → apps/web}/src/components/.gitkeep +0 -0
  156. /package/templates/project/web/{frontend → apps/web}/src/main.ts +0 -0
@@ -0,0 +1,479 @@
1
+ # Docker 工程结构模板
2
+
3
+ 新建 docker 工程或用户问"compose / Dockerfile / dev.sh 怎么写"时读这份文件。
4
+
5
+ ## docker-compose.dev.yml(开发环境)
6
+
7
+ **核心原则**:快速启动、热更新、方便调试。基础设施容器化,应用代码在宿主机跑(这样 `uvicorn --reload` / `vite dev` 的文件监听才能生效,不用每次改代码都 rebuild 镜像)。
8
+
9
+ ```yaml
10
+ # docker-compose.dev.yml — 只跑基础设施,应用在宿主机跑
11
+ services:
12
+ postgres:
13
+ image: postgres:17.1 # 固定具体版本,按 query_dockerhub.py 推荐
14
+ ports:
15
+ - "${PG_PORT:-15432}:5432" # 映射到宿主机非标准端口,避免与其他项目冲突
16
+ environment:
17
+ POSTGRES_DB: ${DB_NAME:-app}
18
+ POSTGRES_USER: ${DB_USER:-postgres}
19
+ POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
20
+ volumes:
21
+ - pgdata:/var/lib/postgresql/data
22
+
23
+ redis:
24
+ image: redis:7.2.6
25
+ ports:
26
+ - "${REDIS_PORT:-16379}:6379"
27
+
28
+ minio:
29
+ image: minio/minio:RELEASE.2024-11-07T00-52-20Z
30
+ ports:
31
+ - "${MINIO_PORT:-19000}:9000"
32
+ - "${MINIO_CONSOLE_PORT:-19001}:9001"
33
+ environment:
34
+ MINIO_ROOT_USER: ${MINIO_USER:-minioadmin}
35
+ MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD:-minioadmin}
36
+ command: server /data --console-address ":9001"
37
+ volumes:
38
+ - miniodata:/data
39
+
40
+ volumes:
41
+ pgdata:
42
+ miniodata:
43
+ ```
44
+
45
+ **关键要点**:
46
+ - 端口映射到宿主机非标准端口(`15432` / `16379` / `19000`),用环境变量可配,方便多项目并行开发
47
+ - volume 持久化数据,避免 `docker compose down` 丢数据
48
+ - 应用本身在宿主机跑(`uvicorn --reload` / `vite dev`)— 这样改代码立刻生效
49
+
50
+ ---
51
+
52
+ ## docker-compose.prod.yml(生产环境)
53
+
54
+ **核心原则**:安全、稳定、最小权限。
55
+
56
+ 与 dev 关键差异:
57
+ - 基础设施**不暴露端口**(仅通过 docker 网络通信,对外只开 backend / frontend)
58
+ - 所有服务带 `restart: unless-stopped`
59
+ - 数据库 / 缓存有 `healthcheck`,应用 `depends_on` 用 `condition: service_healthy` 等待
60
+ - 后端容器 `read_only: true` + tmpfs,缩小攻击面
61
+
62
+ ```yaml
63
+ services:
64
+ postgres:
65
+ image: postgres:17.1
66
+ # 不暴露端口(仅内网通信)
67
+ environment:
68
+ POSTGRES_DB: ${DB_NAME}
69
+ POSTGRES_USER: ${DB_USER}
70
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
71
+ volumes:
72
+ - pgdata:/var/lib/postgresql/data
73
+ restart: unless-stopped
74
+ healthcheck:
75
+ test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
76
+ interval: 10s
77
+ timeout: 5s
78
+ retries: 5
79
+
80
+ redis:
81
+ image: redis:7.2.6
82
+ restart: unless-stopped
83
+ healthcheck:
84
+ test: ["CMD", "redis-cli", "ping"]
85
+ interval: 10s
86
+
87
+ minio:
88
+ image: minio/minio:RELEASE.2024-11-07T00-52-20Z
89
+ # 不暴露端口
90
+ environment:
91
+ MINIO_ROOT_USER: ${MINIO_USER}
92
+ MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD}
93
+ command: server /data
94
+ volumes:
95
+ - miniodata:/data
96
+ restart: unless-stopped
97
+
98
+ backend:
99
+ build:
100
+ context: ./backend
101
+ dockerfile: Dockerfile.prod
102
+ environment:
103
+ DATABASE_URL: postgresql+asyncpg://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
104
+ REDIS_URL: redis://redis:6379/0
105
+ SECRET_KEY: ${SECRET_KEY}
106
+ ports:
107
+ - "${API_PORT:-8000}:8000"
108
+ depends_on:
109
+ postgres:
110
+ condition: service_healthy
111
+ redis:
112
+ condition: service_healthy
113
+ read_only: true # 只读根文件系统
114
+ tmpfs:
115
+ - /tmp # 临时文件写 tmpfs
116
+ restart: unless-stopped
117
+ healthcheck:
118
+ test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"]
119
+ interval: 30s
120
+
121
+ worker:
122
+ build:
123
+ context: ./backend
124
+ dockerfile: Dockerfile.prod
125
+ command: arq app.worker.WorkerSettings
126
+ environment:
127
+ DATABASE_URL: postgresql+asyncpg://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
128
+ REDIS_URL: redis://redis:6379/0
129
+ SECRET_KEY: ${SECRET_KEY}
130
+ depends_on:
131
+ postgres:
132
+ condition: service_healthy
133
+ redis:
134
+ condition: service_healthy
135
+ restart: unless-stopped
136
+
137
+ frontend:
138
+ build:
139
+ context: ./frontend
140
+ dockerfile: Dockerfile
141
+ ports:
142
+ - "${WEB_PORT:-3000}:80"
143
+ depends_on:
144
+ - backend
145
+ restart: unless-stopped
146
+
147
+ volumes:
148
+ pgdata:
149
+ miniodata:
150
+ ```
151
+
152
+ **关键要点**:
153
+ - 基础设施不暴露端口 → 攻击面缩到只有 `${API_PORT}` 和 `${WEB_PORT}`
154
+ - `restart: unless-stopped` → 单容器崩溃不影响整体,但人工 stop 后不会自启
155
+ - healthcheck → 让 `depends_on: service_healthy` 真正等到数据库可连,而不是容器一启就放行
156
+ - 环境变量从 `.env` 注入,不硬编码 → 凭证不进 git
157
+
158
+ ---
159
+
160
+ ## Dockerfile
161
+
162
+ ### 后端 `Dockerfile.dev`(单阶段,包含 dev 依赖)
163
+
164
+ ```dockerfile
165
+ FROM python:3.11-slim
166
+ WORKDIR /app
167
+ COPY pyproject.toml .
168
+ RUN pip install uv && uv pip install -e ".[dev]" --system
169
+ COPY . .
170
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
171
+ ```
172
+
173
+ ### 后端 `Dockerfile.prod`(多阶段 + non-root)
174
+
175
+ ```dockerfile
176
+ # 多阶段构建
177
+ FROM python:3.11-slim AS builder
178
+ WORKDIR /app
179
+ COPY pyproject.toml .
180
+ RUN pip install uv && uv pip install -e "." --system
181
+
182
+ FROM python:3.11-slim
183
+ RUN useradd -r -s /bin/false appuser
184
+ WORKDIR /app
185
+ COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
186
+ COPY --from=builder /usr/local/bin /usr/local/bin
187
+ COPY src/ ./src/
188
+ COPY alembic/ ./alembic/
189
+ COPY alembic.ini .
190
+ USER appuser
191
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
192
+ ```
193
+
194
+ **为什么这样写**:
195
+ - 多阶段:builder 装包(含编译工具),runtime 只复制 site-packages,镜像缩小一半以上
196
+ - `useradd -r -s /bin/false appuser` + `USER appuser`:容器逃逸时拿不到 root,缩小攻击面
197
+ - 不 COPY 整个项目,只 COPY `src/` / `alembic/` / `alembic.ini`:避免误带 `.git/` / `tests/` / `.env` 进生产镜像
198
+
199
+ ### 前端 `Dockerfile`(多阶段 + nginx 静态托管)
200
+
201
+ ```dockerfile
202
+ FROM node:20.18.0-slim AS builder
203
+ RUN npm i -g pnpm
204
+ WORKDIR /app
205
+ COPY package.json pnpm-lock.yaml .
206
+ RUN pnpm install --frozen-lockfile
207
+ COPY . .
208
+ RUN pnpm build
209
+
210
+ FROM nginx:1.26.2-alpine
211
+ COPY --from=builder /app/dist /usr/share/nginx/html
212
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
213
+ ```
214
+
215
+ ### 配套 `.dockerignore`(必须有)
216
+
217
+ ```
218
+ node_modules/
219
+ .git/
220
+ .env
221
+ .env.*
222
+ __pycache__/
223
+ *.pyc
224
+ dist/
225
+ .pytest_cache/
226
+ .vscode/
227
+ .idea/
228
+ ```
229
+
230
+ 不写这个,构建上下文会把整个 `node_modules/` / `.git/` 上传给 Docker daemon,速度慢且可能泄露 `.env`。
231
+
232
+ ---
233
+
234
+ ## 一键启动脚本(`dev.sh` + `prod.sh`)
235
+
236
+ 每个项目根放两份脚本:`dev.sh` 用于本地开发,`prod.sh` 用于生产环境管理。两个脚本都采用**子命令模式 + 显式传参**设计。
237
+
238
+ **两个脚本的统一约束**:
239
+
240
+ - **必须显式传子命令**(`start` / `stop` / `restart` / ...),不传参数时**只打印帮助文档不执行任何动作**
241
+ - 帮助文档必须列出所有子命令、参数含义、典型示例
242
+ - 子命令未匹配(拼错或不支持)时同样打印帮助并以非零退出码退出
243
+
244
+ **为什么强制传参 + 不传打印 help**:
245
+
246
+ `bash dev.sh` / `bash prod.sh` 不带参数直接拉起服务是**危险的默认行为**。运维人员在生产 SSH 里手抖少敲一个词就可能误启动服务、覆盖容器、拉新镜像。强制传子命令让每一次操作都是**有意识的**,并且帮助文档让新接手项目的人不用看 README 就知道怎么用。
247
+
248
+ **为什么要有这两个脚本**:
249
+
250
+ - `dev.sh` — 新人 clone 项目后一行 `bash dev.sh start` 跑起来全栈,不用查文档;同时支持 `infra` 模式让你在 IDE 里断点调试应用
251
+ - `prod.sh` — 运维人员(或部署 CI)用统一入口管理生产,避免每次手敲长串 `docker compose --env-file ... -f ... ...`,也避免漏 `--env-file` 等参数导致的事故
252
+
253
+ ### `dev.sh`(开发环境管理)
254
+
255
+ ```bash
256
+ #!/bin/bash
257
+ # 开发环境管理脚本
258
+ # 用法: bash dev.sh {start|stop|restart|infra|status|logs} [service]
259
+
260
+ set -e
261
+
262
+ COMPOSE_FILE="docker-compose.dev.yml"
263
+
264
+ # 前置检查
265
+ if [ ! -f "$COMPOSE_FILE" ]; then
266
+ echo "错误:当前目录找不到 $COMPOSE_FILE"
267
+ exit 1
268
+ fi
269
+
270
+ # 统一调用 docker compose 的封装
271
+ dc() {
272
+ docker compose -f "$COMPOSE_FILE" "$@"
273
+ }
274
+
275
+ # Ctrl+C 时清理所有后台进程和容器
276
+ cleanup() {
277
+ echo ""
278
+ echo "→ 停止后台应用进程..."
279
+ jobs -p | xargs -r kill 2>/dev/null || true
280
+ echo "→ 停止基础设施容器..."
281
+ dc down
282
+ echo "✓ 已清理"
283
+ }
284
+
285
+ ACTION="${1:-}"
286
+ SERVICE="${2:-}"
287
+
288
+ case "$ACTION" in
289
+ start)
290
+ echo "→ 启动基础设施..."
291
+ dc up -d
292
+
293
+ echo "→ 等待 PostgreSQL 就绪..."
294
+ until dc exec postgres pg_isready -U postgres > /dev/null 2>&1; do
295
+ sleep 1
296
+ done
297
+
298
+ trap cleanup INT TERM EXIT
299
+
300
+ echo "→ 启动后端 (热更新)..."
301
+ (cd backend && uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload) &
302
+
303
+ echo "→ 启动 Worker..."
304
+ (cd backend && arq app.worker.WorkerSettings) &
305
+
306
+ echo "→ 启动前端 (热更新)..."
307
+ (cd frontend && pnpm dev) &
308
+
309
+ echo ""
310
+ echo "✓ 全栈已启动。Ctrl+C 退出会自动清理所有进程和容器"
311
+ wait
312
+ ;;
313
+
314
+ stop)
315
+ echo "→ 停止基础设施..."
316
+ dc down
317
+ echo "✓ 已停止(volume 数据保留)"
318
+ ;;
319
+
320
+ restart)
321
+ echo "→ 重启基础设施容器..."
322
+ dc restart
323
+ echo "✓ 基础设施已重启。应用层(uvicorn / vite / arq)请重新跑 'bash dev.sh start'"
324
+ ;;
325
+
326
+ infra)
327
+ echo "→ 仅启动基础设施容器(不启动应用)..."
328
+ dc up -d
329
+ echo "✓ 基础设施已启动。应用请你自己在 IDE 里跑(方便断点调试)"
330
+ echo ""
331
+ dc ps
332
+ ;;
333
+
334
+ status)
335
+ dc ps
336
+ ;;
337
+
338
+ logs)
339
+ if [ -n "$SERVICE" ]; then
340
+ dc logs -f --tail=200 "$SERVICE"
341
+ else
342
+ dc logs -f --tail=200
343
+ fi
344
+ ;;
345
+
346
+ *)
347
+ cat <<EOF
348
+ 用法: bash dev.sh {start|stop|restart|infra|status|logs} [service]
349
+
350
+ start 一键拉起全栈:基础设施容器 + 后端 + Worker + 前端
351
+ 前台运行,Ctrl+C 退出会自动清理所有进程和容器
352
+ stop 停止并清理基础设施容器(volume 保留,下次 start 数据还在)
353
+ restart 重启基础设施容器(应用层不动,需自行重新启动)
354
+ infra 只启动基础设施容器,应用让你在 IDE 里跑(推荐:断点调试场景)
355
+ status 查看基础设施容器状态
356
+ logs [service] 实时查看基础设施日志(最近 200 行起),可指定 service
357
+
358
+ 示例:
359
+ bash dev.sh start # 一键全栈,适合纯命令行开发
360
+ bash dev.sh infra # 容器跑数据库,IDE 里跑应用(推荐用于断点调试)
361
+ bash dev.sh logs postgres # 看 postgres 日志
362
+ EOF
363
+ exit 1
364
+ ;;
365
+ esac
366
+ ```
367
+
368
+ ### `prod.sh`(生产环境管理)
369
+
370
+ ```bash
371
+ #!/bin/bash
372
+ # 生产环境部署管理脚本
373
+ # 用法: bash prod.sh {start|stop|restart|status|logs|update} [service]
374
+
375
+ set -e
376
+
377
+ COMPOSE_FILE="docker-compose.prod.yml"
378
+ ENV_FILE=".env.prod"
379
+
380
+ # 前置检查
381
+ if [ ! -f "$COMPOSE_FILE" ]; then
382
+ echo "错误:当前目录找不到 $COMPOSE_FILE"
383
+ exit 1
384
+ fi
385
+
386
+ if [ ! -f "$ENV_FILE" ]; then
387
+ echo "错误:缺少 $ENV_FILE,请先从 .env.prod.example 复制并填写生产环境变量"
388
+ exit 1
389
+ fi
390
+
391
+ # 统一调用 docker compose 的封装(避免重复参数)
392
+ dc() {
393
+ docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" "$@"
394
+ }
395
+
396
+ ACTION="${1:-}"
397
+ SERVICE="${2:-}"
398
+
399
+ case "$ACTION" in
400
+ start)
401
+ echo "→ 启动生产环境(后台运行)..."
402
+ dc up -d
403
+ echo "✓ 已启动。用 'bash prod.sh status' 查看状态"
404
+ ;;
405
+
406
+ stop)
407
+ echo "→ 停止生产环境..."
408
+ dc down
409
+ echo "✓ 已停止(volume 数据保留)"
410
+ ;;
411
+
412
+ restart)
413
+ if [ -n "$SERVICE" ]; then
414
+ echo "→ 重启服务 $SERVICE ..."
415
+ dc restart "$SERVICE"
416
+ else
417
+ echo "→ 重启所有服务..."
418
+ dc restart
419
+ fi
420
+ echo "✓ 已重启"
421
+ ;;
422
+
423
+ status)
424
+ dc ps
425
+ ;;
426
+
427
+ logs)
428
+ if [ -n "$SERVICE" ]; then
429
+ dc logs -f --tail=200 "$SERVICE"
430
+ else
431
+ dc logs -f --tail=200
432
+ fi
433
+ ;;
434
+
435
+ update)
436
+ echo "→ 拉取最新镜像并重建容器..."
437
+ dc pull
438
+ dc up -d --remove-orphans
439
+ echo "✓ 已更新到最新镜像"
440
+ ;;
441
+
442
+ *)
443
+ cat <<EOF
444
+ 用法: bash prod.sh {start|stop|restart|status|logs|update} [service]
445
+
446
+ start 启动所有服务(后台运行)
447
+ stop 停止所有服务并清理容器(volume 保留)
448
+ restart [service] 重启所有服务,或指定单个服务(不重建容器)
449
+ status 查看服务状态
450
+ logs [service] 实时查看日志(最近 200 行起),可指定 service
451
+ update 拉取最新镜像并重建容器(应用 compose 配置变更)
452
+
453
+ 示例:
454
+ bash prod.sh start
455
+ bash prod.sh restart backend
456
+ bash prod.sh logs nginx
457
+ bash prod.sh update
458
+ EOF
459
+ exit 1
460
+ ;;
461
+ esac
462
+ ```
463
+
464
+ **两个脚本共同的设计**:
465
+
466
+ - **子命令模式**:所有操作必须显式传子命令(`start` / `stop` / `restart` / ...),不传或传错都只打印 help,不执行任何动作。生产环境最忌"默认行为"
467
+ - **`dc()` 封装**:所有 docker compose 命令都自动带 `--env-file ... -f ...` 参数,杜绝漏参事故
468
+ - **`restart` 走 `docker compose restart` 而不是 `down + up`**:重启容器但不删除(保留运行时状态),更快也更安全;只有 `update`(prod)/ 重新跑 `start`(dev)才会重建
469
+ - **`stop` 走 `down` 而非 `stop`**:彻底清理网络和容器,下次 `start` 是全新的;volume 默认保留,不丢数据
470
+
471
+ **`dev.sh` 特有设计**:
472
+
473
+ - **`trap cleanup INT TERM EXIT`**:`start` 子命令前台运行(`wait` 阻塞),用户按 Ctrl+C 时会自动 kill 所有后台子进程(uvicorn / arq / vite)并 `docker compose down` 清理容器。避免出现"应用进程已退出但容器还在跑"的孤儿状态
474
+ - **`infra` 子命令**:只启基础设施,应用让用户在 IDE 里跑。这是断点调试场景的最佳工作流,比"全栈启动后再 attach 调试器"轻量得多
475
+
476
+ **`prod.sh` 特有设计**:
477
+
478
+ - **前置检查 `.env.prod`**:生产环境变量缺失时第一时间退出,不会因为读到默认值跑起一个错误配置的服务
479
+ - **`update` 单独命令**:日常运维(重启 / 看日志)不会误触发镜像拉取,只有显式 `update` 才会拉新镜像并重建容器
@@ -0,0 +1,76 @@
1
+ # 镜像版本选择规则(N-1 minor)
2
+
3
+ 选具体版本号时读这份。先用 `scripts/query_dockerhub.py` 查实时数据,再按下面规则推算 N-1。
4
+
5
+ ## 为什么是 N-1 minor 而不是最新
6
+
7
+ 新版镜像刚发布时,回归 bug、性能问题、新依赖不兼容这些坑还没被社区踩过。等下一个 minor 出来,前一个 minor 系列已经经历了几轮 patch 修复,**稳定性 vs 新特性的折中点**就是 N-1。这是行业惯例,不是激进保守。
8
+
9
+ 举例:nginx `1.27.0` 刚发布时有个 HTTP/3 模块的 segfault,3 周后 `1.27.1` 才修复。这期间用 `1.26.x` 系列的人完全没受影响。
10
+
11
+ ## 强制规则
12
+
13
+ 1. **禁止 `latest`** — FROM / image / containers.image / services.image 任何位置都不允许,省略 tag 等同于 `latest`
14
+ 2. **必须固定 tag** — 必须是具体版本号(如 `17.1`、`1.26.2`、`3.20`),禁止浮动 tag
15
+ 3. **优先选 N-1 minor** — 当前最新稳定 minor 的前一个 minor 系列最新 patch
16
+ 4. **生产关键服务用 digest** — 数据库、消息队列、网关在 tag 后再钉 `@sha256:...`,让 image 100% 不可篡改
17
+
18
+ ---
19
+
20
+ ## 选法分类(按版本号格式)
21
+
22
+ ### 1. semver 三段镜像(绝大多数)
23
+
24
+ 格式 `MAJOR.MINOR.PATCH`,例如 `nginx:1.27.3`、`redis:7.4.1`。
25
+
26
+ 步骤:
27
+ 1. DockerHub 找当前最新稳定 minor(**忽略 rc / beta / alpha / preview / nightly**)
28
+ 2. minor 减 1,得到目标 minor 系列
29
+ 3. 取该 minor 系列的最新 patch
30
+
31
+ | 镜像 | 当前最新稳定 | N-1 minor 选择 | 说明 |
32
+ |------|-------------|---------------|------|
33
+ | `nginx` | `1.27.3` | `nginx:1.26.2` | 1.26 系列最新 patch |
34
+ | `redis` | `7.4.1` | `redis:7.2.6` | 7.4 上一个稳定 minor 是 7.2,跳过仅维护版 |
35
+ | `node` | `22.11.0`(LTS)| `node:20.18.0` | LTS 镜像例外,见下文 |
36
+
37
+ ### 2. 两段版本号镜像
38
+
39
+ 格式 `MAJOR.MINOR`,例如 `postgres:17.2`、`mysql:8.4`、`alpine:3.21`。这类镜像把 minor 当 patch 用。
40
+
41
+ 选法:minor 减 1,取该系列最新 tag。
42
+
43
+ | 镜像 | 当前最新稳定 | N-1 minor 选择 |
44
+ |------|-------------|---------------|
45
+ | `postgres` | `17.2` | `postgres:17.1` |
46
+ | `mysql` | `8.4` | `mysql:8.3` |
47
+ | `alpine` | `3.21` | `alpine:3.20` |
48
+
49
+ ### 3. 年份版本号镜像(Ubuntu / Debian)
50
+
51
+ 格式 `YY.MM` 或 codename,例如 `ubuntu:24.04`、`debian:bookworm`。
52
+
53
+ 选法:选**上一个 LTS / stable release**,**不要**用 codename(codename 是浮动 tag,会随 release 漂移),只用版本号。
54
+
55
+ | 镜像 | 当前最新 LTS | N-1 选择 |
56
+ |------|-------------|---------|
57
+ | `ubuntu` | `24.04` | `ubuntu:22.04` |
58
+ | `debian` | `12`(bookworm)| `debian:11`(bullseye)|
59
+
60
+ ### 4. LTS 镜像例外(Node.js / OpenJDK)
61
+
62
+ LTS 镜像(如 Node.js 偶数 major 是 LTS)选**上一个 LTS major** 的最新 patch,**不要**机械 minor - 1。原因是非 LTS 版本生命周期短(6 个月),N-1 落到非 LTS 上反而很快就 EOL。
63
+
64
+ 例:当前 `node:22.11.0`(22 是 LTS),N-1 选 `node:20.18.0`(20 是上一个 LTS)而不是 `node:21.x`。
65
+
66
+ ---
67
+
68
+ ## 例外情况(极少)
69
+
70
+ 以下情况可以**短暂**使用浮动 tag,必须在 commit body 或代码注释中说明原因:
71
+
72
+ 1. **临时调试** — 本地排查时拉一下 `latest` 看新版行为,**禁止**进入 commit
73
+ 2. **官方明确推荐使用 codename** — 极少数镜像(如某些 ML 框架)只发 codename tag
74
+ 3. **基础镜像本身就是浮动的** — 如 `gcr.io/distroless/static:nonroot`(distroless 没有版本号),这类镜像必须钉 digest
75
+
76
+ **禁止以"为了拿安全补丁"为由用 `latest`**。安全补丁应该通过定期升级 N-1 minor 实现,不是放任 image 漂移。