create-ssnode-cli 0.2.5 → 0.2.7
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.
- package/README.md +1 -1
- package/index.js +17 -21
- package/package.json +1 -1
- package/secrets.js +32 -34
- package/templates/backend/Dockerfile +18 -8
- package/templates/backend/config/config.base.yaml +130 -0
- package/templates/backend/config/config.develop.yaml +8 -75
- package/templates/backend/config/config.prod.yaml +12 -67
- package/templates/backend/docker-compose.yml +30 -22
- package/templates/backend/ops/docker/config.sh +30 -15
- package/templates/backend/ops/docker/install.sh +25 -34
- package/templates/backend/ops/docker/run-container.sh +100 -0
- package/templates/backend/ops/docker/start.sh +37 -8
- package/templates/backend/ops/docker/stop.sh +1 -1
- package/templates/backend/ops/ensure-encrypt.cjs +217 -0
- package/templates/backend/ops/install.sh +249 -148
- package/templates/backend/ops/lib-encrypt.sh +98 -0
- package/templates/backend/ops/lib.sh +22 -0
- package/templates/backend/ops/pm2/config.sh +11 -6
- package/templates/backend/ops/pm2/install.sh +27 -3
- package/templates/backend/ops/pm2/start.sh +1 -1
- package/templates/backend/ops/start.sh +7 -5
- package/templates/backend/ops/status.sh +7 -5
- package/templates/backend/ops/stop.sh +7 -5
- package/templates/backend/ops/systemd/config.sh +13 -6
- package/templates/backend/ops/systemd/install.sh +28 -1
- package/templates/backend/ops/systemd/start.sh +1 -1
- package/templates/backend/ops/versionswitch.sh +34 -122
- package/templates/backend/package-lock.json +7561 -0
- package/templates/backend/package.json +3 -1
- package/templates/backend/scripts/build.cjs +104 -10
- package/templates/backend/scripts/config-crypt.cjs +108 -0
- package/templates/backend/scripts/lib/ssh.cjs +72 -3
- package/templates/backend/scripts/publish.cjs +17 -9
- package/templates/backend/scripts/pulldocker.cjs +208 -0
- package/templates/backend/scripts/pushdocker.cjs +323 -0
- package/templates/backend/scripts/rollback.cjs +6 -3
- package/templates/backend/scripts/server.config.example.cjs +43 -15
- package/templates/backend/scripts/slowsql-report.mjs +2 -2
- package/templates/backend/src/common/api-log/api-log.service.ts +1 -1
- package/templates/backend/src/common/config-crypto.ts +133 -0
- package/templates/backend/src/config/config.interface.ts +22 -3
- package/templates/backend/src/config/configuration.ts +173 -86
- package/templates/backend/src/database/migrations/0001-init.migration.ts +1 -1
- package/templates/backend/src/database/slow-sql.logger.ts +1 -1
- package/templates/backend/src/database/sqlite.config.ts +2 -2
- package/templates/backend/src/main.ts +3 -3
- package/templates/backend/src/modules/ops/ops-probe.interface.ts +3 -3
- package/templates/backend/src/modules/ops/ops.module.ts +2 -11
- package/templates/backend/src/modules/user/user.service.ts +2 -2
- package/templates/docs/config-encryption.md +164 -0
- package/templates/root/AGENTS.md +5 -4
- package/templates/root/CLAUDE.md +1 -1
- package/templates/root/README.md +25 -15
- package/templates/root/_gitignore +5 -0
- package/templates/root/scripts/publish.cjs +20 -2
- package/templates/root/scripts/rollback.cjs +20 -2
- package/templates/web/config/router.config.ts +8 -0
- package/templates/web/package.json +5 -1
- package/templates/web/scripts/compress-src-images.cjs +332 -0
- package/templates/web/scripts/lib/interactive.cjs +134 -0
- package/templates/web/scripts/publish.cjs +299 -200
- package/templates/web/scripts/rollback.cjs +110 -41
- package/templates/web/scripts/server.config.example.json +17 -0
- package/templates/web/src/layouts/Background.module.less +0 -4
- package/templates/web/src/layouts/Background.tsx +48 -74
- package/templates/web/src/layouts/components/PageTabs/SortableTab.tsx +133 -0
- package/templates/web/src/layouts/components/PageTabs/index.module.less +105 -0
- package/templates/web/src/layouts/components/PageTabs/index.tsx +110 -0
- package/templates/web/src/layouts/components/PageTabs/usePageTabs.ts +224 -0
- package/templates/web/src/pages/Backend/Image/index.tsx +4 -4
- package/templates/web/src/pages/Maintenance/index.module.less +177 -0
- package/templates/web/src/pages/Maintenance/index.tsx +142 -0
- package/templates/web/src/services/api.ts +4 -0
- package/templates/web/src/services/app-request.ts +8 -1
- package/templates/web/src/services/maintenance.ts +67 -0
- package/templates/web/src/services/request.ts +5 -0
- package/transforms.js +21 -28
- package/templates/backend/ops/docker/.env.example +0 -40
- package/templates/backend/ops/sqlite/README.md +0 -57
- package/templates/backend/ops/sqlite/backup.sh +0 -65
- package/templates/backend/ops/sqlite/config.sh +0 -91
- package/templates/backend/ops/sqlite/healthcheck.sh +0 -25
- package/templates/backend/ops/sqlite/restore.sh +0 -105
- package/templates/backend/src/modules/ops/sqlite-backup-list.probe.ts +0 -100
- package/templates/backend/src/modules/ops/sqlite-backup-log.probe.ts +0 -63
- package/templates/backend/src/modules/ops/sqlite-backup.probe.ts +0 -144
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ $ npm create ssnode-cli my-app
|
|
|
85
85
|
|
|
86
86
|
生成过程自动完成,不需要手工改任何模板占位:
|
|
87
87
|
|
|
88
|
-
- **身份替换**:包名(子包派生 `<name>-web`/`<name>-backend`)、后台顶栏标题、Swagger 标题、SQLite 文件名、docker 镜像/容器名、systemd/pm2 服务名、服务器部署根目录、编排脚本 `--filter`
|
|
88
|
+
- **身份替换**:包名(子包派生 `<name>-web`/`<name>-backend`)、后台顶栏标题、Swagger 标题、SQLite 文件名、docker 镜像/容器名、systemd/pm2 服务名、服务器部署根目录、编排脚本 `--filter`(路径形态 `./web`/`./backend`,包名改名免疫)——生成物零模板残留(CI 守卫每次发布前扫描断言)
|
|
89
89
|
- **密钥生成注入**:dev/prod 两套独立密钥(JWT secret、大屏签名 signKey、超管密码、AES cryptKey),前后端成对同值写入并回读校验;超管账号打印在控制台
|
|
90
90
|
- **git init + 首次 commit**、**pnpm install**(交互可选)
|
|
91
91
|
|
package/index.js
CHANGED
|
@@ -272,7 +272,8 @@ async function main() {
|
|
|
272
272
|
pkg.name = name;
|
|
273
273
|
pkg.description = `${displayName}(create-ssnode-cli 生成)`;
|
|
274
274
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
275
|
-
// web/backend 子包名:<name>-web / <name>-backend(
|
|
275
|
+
// web/backend 子包名:<name>-web / <name>-backend(编排 filter 已用 ./web 路径形态,
|
|
276
|
+
// 包名改名免疫;保留派生名是为 yarn1 workspace——它只认包名,不认路径)
|
|
276
277
|
for (const [dir, suffix] of [['web', '-web'], ['backend', '-backend']]) {
|
|
277
278
|
const p2 = join(targetDir, dir, 'package.json');
|
|
278
279
|
const sub = JSON.parse(readFileSync(p2, 'utf-8'));
|
|
@@ -299,22 +300,13 @@ async function main() {
|
|
|
299
300
|
const envPath = stack === 'fullstack' ? join(targetDir, 'web', envFile) : join(targetDir, envFile);
|
|
300
301
|
writeFileSync(envPath, injectWebEnv(readFileSync(envPath, 'utf-8'), mode, secrets[mode].signKey));
|
|
301
302
|
}
|
|
302
|
-
// backend yaml(仅 fullstack)
|
|
303
|
+
// backend yaml(仅 fullstack):凭证明文注入环境 overlay —— dev 开箱即跑,prod 明文维护,
|
|
304
|
+
// 加密交由服务器首部署按需开启(install.sh 询问,见 docs/config-encryption.md)
|
|
303
305
|
if (stack === 'fullstack') {
|
|
304
306
|
for (const [mode, yamlFile] of [['develop', 'config.develop.yaml'], ['prod', 'config.prod.yaml']]) {
|
|
305
307
|
const yamlPath = join(targetDir, 'backend', 'config', yamlFile);
|
|
306
308
|
writeFileSync(yamlPath, injectBackendYaml(readFileSync(yamlPath, 'utf-8'), secrets[mode]));
|
|
307
309
|
}
|
|
308
|
-
// docker .env.example 一并种子化(README 必改清单第 2 条的 docker 面)
|
|
309
|
-
const envExamplePath = join(targetDir, 'backend', 'ops', 'docker', '.env.example');
|
|
310
|
-
let envExample = readFileSync(envExamplePath, 'utf-8');
|
|
311
|
-
envExample = envExample
|
|
312
|
-
.replace('JWT_SECRET=', `JWT_SECRET=${secrets.prod.jwtSecret}`)
|
|
313
|
-
.replace('ADMIN_PASSWORD=', `ADMIN_PASSWORD=${secrets.prod.adminPassword}`)
|
|
314
|
-
.replace('APP_SIGN_KEY=', `APP_SIGN_KEY=${secrets.prod.signKey}`)
|
|
315
|
-
.replace('SSO_TICKET_KEY=', `SSO_TICKET_KEY=${secrets.prod.ssoTicketKey}`)
|
|
316
|
-
.replace('# ⚠️ 模板占位:新项目务必换成自己的强密钥(与 config.prod.yaml / web/.env.production 同款)。', '# 已由脚手架生成(与 config.prod.yaml / web/.env.production 同款)。');
|
|
317
|
-
writeFileSync(envExamplePath, envExample);
|
|
318
310
|
}
|
|
319
311
|
// 回读校验:fullstack 做前后端成对一致(硬约束);web-only 只需确认注入成功
|
|
320
312
|
const files = {};
|
|
@@ -334,14 +326,14 @@ async function main() {
|
|
|
334
326
|
secretsPrintout = secrets;
|
|
335
327
|
s.message('密钥已生成并注入(前后端成对一致)');
|
|
336
328
|
} else {
|
|
337
|
-
// backend-only
|
|
329
|
+
// backend-only:只注环境 overlay yaml(无前端 env);凭证明文,加密由服务器首部署按需开启
|
|
338
330
|
const secrets = generateSecrets();
|
|
339
331
|
for (const [mode, yamlFile] of [['develop', 'config.develop.yaml'], ['prod', 'config.prod.yaml']]) {
|
|
340
332
|
const yamlPath = join(targetDir, 'config', yamlFile);
|
|
341
333
|
writeFileSync(yamlPath, injectBackendYaml(readFileSync(yamlPath, 'utf-8'), secrets[mode]));
|
|
342
334
|
}
|
|
343
335
|
secretsPrintout = secrets;
|
|
344
|
-
s.message('
|
|
336
|
+
s.message('密钥已生成并明文注入 yaml(dev 开箱即跑;prod 加密由服务器首部署按需开启)');
|
|
345
337
|
}
|
|
346
338
|
|
|
347
339
|
// ── ⑦ git init + 首次 commit ──
|
|
@@ -365,19 +357,20 @@ async function main() {
|
|
|
365
357
|
}
|
|
366
358
|
p.log.success(`项目已生成:${targetDir}`);
|
|
367
359
|
|
|
368
|
-
// ── ⑧ 密钥打印(
|
|
360
|
+
// ── ⑧ 密钥打印(明文凭证只进控制台 + 生成项目文件;服务器侧加密按需开启) ──
|
|
369
361
|
if (secretsPrintout) {
|
|
370
362
|
if (stack !== 'web') {
|
|
371
363
|
// 超管账号只对含 backend 的变体有意义(web-only 无后端可 reconcile)
|
|
372
364
|
const cfgPath = (f) => (stack === 'fullstack' ? `backend/config/${f}` : `config/${f}`);
|
|
373
365
|
p.log.info('超管账号(启动时 reconcile 入库,牢记):');
|
|
374
|
-
console.log(` dev : admin / ${secretsPrintout.develop.adminPassword} ←
|
|
375
|
-
console.log(` prod: admin / ${secretsPrintout.prod.adminPassword} ←
|
|
366
|
+
console.log(` dev : admin / ${secretsPrintout.develop.adminPassword} ← 改密:换入 ${cfgPath('config.develop.yaml')} 明文 + 重启`);
|
|
367
|
+
console.log(` prod: admin / ${secretsPrintout.prod.adminPassword} ← 改密:换部署位 config.yaml 明文 + 重跑 ./install.sh(加密模式自动重新加密)`);
|
|
368
|
+
p.log.info('凭证均为明文注入 yaml(dev 开箱即跑,prod 明文维护);服务器首部署时 install.sh 会询问是否开启配置加密(开启后服务器生成根密钥并就地加密,见 docs/config-encryption.md)');
|
|
376
369
|
}
|
|
377
370
|
if (stack === 'fullstack') {
|
|
378
|
-
p.log.info('
|
|
371
|
+
p.log.info('签名密钥已写入 web/.env.* 与 backend yaml 的 signKey(前后端同值)');
|
|
379
372
|
} else if (stack === 'backend') {
|
|
380
|
-
p.log.info('
|
|
373
|
+
p.log.info('签名密钥已写入 config/*.yaml 的 signKey;将来接前端时,把同值填入前端 .env');
|
|
381
374
|
} else {
|
|
382
375
|
p.log.info('签名密钥已写入 .env.development / .env.production;将来接后端时,把同值填入后端 yaml 的 appSign.signKey');
|
|
383
376
|
}
|
|
@@ -406,12 +399,14 @@ function nextSteps(stack, targetDir, installed, pm) {
|
|
|
406
399
|
if (stack === 'fullstack') {
|
|
407
400
|
lines.push(`${pm} ${pm === 'npm' ? 'run ' : ''}dev # web(6177)+ backend(3001)${pm === 'npm' ? '(workspace 顺序执行)' : ''}`);
|
|
408
401
|
lines.push('→ 部署前:配 SSH 凭证 web/scripts/server.config.json + backend/scripts/server.config.cjs(从 example 拷)');
|
|
402
|
+
lines.push('→ 首部署时 install.sh 会询问是否开启配置加密(服务器生成根密钥并就地加密 config.yaml,无需手抄)');
|
|
409
403
|
} else if (stack === 'web') {
|
|
410
404
|
lines.push(`${pm} ${pm === 'npm' ? 'run ' : ''}dev # 端口 6177;checkToken=false 免登录`);
|
|
411
405
|
lines.push('→ 接后端时:public/env.config.js 的 checkToken 改回 true,并填 .env.* 签名密钥');
|
|
412
406
|
} else {
|
|
413
407
|
lines.push(`${pm} ${pm === 'npm' ? 'run ' : ''}dev # 端口 3001;管理走 /api/docs Swagger`);
|
|
414
408
|
lines.push('→ 部署前:配 SSH 凭证 scripts/server.config.cjs(从 example 拷)');
|
|
409
|
+
lines.push('→ 首部署时 install.sh 会询问是否开启配置加密(服务器生成根密钥并就地加密 config.yaml,无需手抄)');
|
|
415
410
|
}
|
|
416
411
|
return lines.join('\n');
|
|
417
412
|
}
|
|
@@ -442,10 +437,11 @@ ${common}
|
|
|
442
437
|
${common}
|
|
443
438
|
## 本变体要点(backend-only)
|
|
444
439
|
|
|
445
|
-
- **管理走 Swagger**:dev 起动后 /docs(Swagger 挂根路径,不带 /api 前缀);超管账号见 config
|
|
440
|
+
- **管理走 Swagger**:dev 起动后 /docs(Swagger 挂根路径,不带 /api 前缀);超管账号见 config.develop.yaml 的 admin(password 已由脚手架生成,启动 reconcile 入库)。
|
|
441
|
+
- **prod 配置加密**:首部署时 install.sh 询问是否开启(服务器生成根密钥并就地加密部署位 config.yaml);日常轮换 = 改明文 + 重跑 ./install.sh,详见 docs/config-encryption.md。
|
|
446
442
|
- **迁移显式注册**:新增迁移必须手动加进 src/database/sqlite.config.ts 的 migrations 数组(既定约定,漏注册 prod 不建表)。
|
|
447
443
|
- **构建** = nest build(tsc)+ terser 混淆;better-sqlite3 v13 需 python3/make/g++ 源码编译。
|
|
448
|
-
- **DB 文件名程序定死**:<name>.prod.sqlite / <name>.dev.sqlite(
|
|
444
|
+
- **DB 文件名程序定死**:<name>.prod.sqlite / <name>.dev.sqlite(已随生成物改名,同步点见 sqlite.config.ts)。
|
|
449
445
|
- \`${cmd('dev')}\` 端口 3001;\`${cmd('build')}\` = nest build(tsc)+ terser 混淆。
|
|
450
446
|
`;
|
|
451
447
|
}
|
package/package.json
CHANGED
package/secrets.js
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* secrets.js — 密钥生成与注入(纯函数 + 注入器)
|
|
3
3
|
*
|
|
4
|
-
* 生成 dev/prod 两套独立密钥,注入前后端共
|
|
4
|
+
* 生成 dev/prod 两套独立密钥,注入前后端共 6 个点:
|
|
5
5
|
* web/.env.development VITE_APP_SIGN_KEY ← dev signKey
|
|
6
6
|
* web/.env.production VITE_APP_SIGN_KEY ← prod signKey
|
|
7
|
-
* backend/config/config.develop.yaml jwt.secret / admin.password / appSign.signKey
|
|
8
|
-
* backend/config/config.prod.yaml
|
|
7
|
+
* backend/config/config.develop.yaml 环境 overlay:jwt.secret / admin.password / appSign.signKey 等
|
|
8
|
+
* backend/config/config.prod.yaml 环境 overlay:同上(密码只入 yaml,不再有 env 通道)
|
|
9
9
|
*
|
|
10
10
|
* 硬约束(README「必改清单」第 2 条):大屏签名密钥前后端必须逐字同值 ——
|
|
11
11
|
* 本模块从同一个生成值写到两端,从根上消灭「改了一端忘另一端」。
|
|
12
12
|
*
|
|
13
|
+
* ⚠️ 凭证形态(2026-10 收口,运维手册 docs/config-encryption.md):
|
|
14
|
+
* 仓库/开发机层一律【明文】注入环境 overlay yaml —— 开发零密钥零加密操作(dev 开箱即跑,
|
|
15
|
+
* prod 明文维护)。ENC(...) 加密只在服务器侧发生:首部署 install.sh 会询问是否开启配置
|
|
16
|
+
* 加密,选开启则服务器生成根密钥(APP_ROOT/.prod.secrets)并把部署位 config.yaml 的
|
|
17
|
+
* 敏感值就地加密,此后每次部署自动加密新增明文(改明文 + 重跑 ./install.sh = 轮换闭环)。
|
|
18
|
+
* signKey 前后端 bundle 均可见,恒明文、前后端同值注入。
|
|
19
|
+
*
|
|
13
20
|
* 密钥形态:
|
|
14
|
-
* signKey
|
|
15
|
-
* jwtSecret
|
|
16
|
-
* password
|
|
17
|
-
* cryptKey
|
|
18
|
-
* ssoTicket
|
|
21
|
+
* signKey 32 字节 hex(64 字符) — HMAC-SHA256(明文,前后端同值)
|
|
22
|
+
* jwtSecret 48 字节 hex(96 字符) — 签发/校验 JWT(明文入 overlay yaml,服务器侧可选加密)
|
|
23
|
+
* password 16 字节 urlSafe — 超管密码(打印给用户;入 overlay yaml,启动 reconcile 到 DB)
|
|
24
|
+
* cryptKey 32 字节 base64 — AES-256-GCM 预留(入 overlay yaml)
|
|
25
|
+
* ssoTicket 32 字节 hex(64 字符) — SSO 票据校验(入 overlay yaml;默认关闭)
|
|
19
26
|
*/
|
|
20
27
|
import { randomBytes } from 'node:crypto';
|
|
21
28
|
|
|
@@ -58,37 +65,28 @@ export function injectWebEnv(content, mode, signKey) {
|
|
|
58
65
|
}
|
|
59
66
|
|
|
60
67
|
/**
|
|
61
|
-
* 注入 backend/config/config.<env>.yaml
|
|
62
|
-
*
|
|
63
|
-
*
|
|
68
|
+
* 注入 backend/config/config.<env>.yaml(环境 overlay,只含差异值 + 凭证锚点;全量默认在
|
|
69
|
+
* config.base.yaml,由 configuration.ts 深合并不需要注入):
|
|
70
|
+
* signKey / jwt.secret / admin.password / appSign.cryptKey / sso.ticketKey 五个字段,全部明文
|
|
71
|
+
* (加密交由服务器首部署按需进行,见文件头)。
|
|
72
|
+
* 锚点 = `<field>: '<任意单引号串>'`(模板统一单引号占位);只替换 yaml 值层,不碰注释。
|
|
73
|
+
* 若锚点缺失则报错,绝不静默跳过。
|
|
64
74
|
*/
|
|
65
75
|
export function injectBackendYaml(content, secrets) {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
];
|
|
73
|
-
// 源仓 develop yaml 的 jwt.secret / admin.password 为 dev 固定值(npm run dev 可直启),
|
|
74
|
-
// 注入时同样替换——生成项目的 develop 也用随机密钥。
|
|
75
|
-
const DEV_DEFAULTS = {
|
|
76
|
-
secret: `secret: dev-only-jwt-secret-not-for-prod`,
|
|
77
|
-
password: `password: admin123`
|
|
76
|
+
const fields = {
|
|
77
|
+
secret: secrets.jwtSecret,
|
|
78
|
+
password: secrets.adminPassword,
|
|
79
|
+
cryptKey: secrets.cryptKey,
|
|
80
|
+
ticketKey: secrets.ssoTicketKey,
|
|
81
|
+
signKey: secrets.signKey,
|
|
78
82
|
};
|
|
79
83
|
let out = content;
|
|
80
|
-
for (const [
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const devAnchor = DEV_DEFAULTS[field];
|
|
85
|
-
const useDev = !out.includes(anchor) && devAnchor && out.includes(devAnchor);
|
|
86
|
-
if (!out.includes(anchor) && !useDev) {
|
|
87
|
-
throw new Error(`yaml 注入失败:未找到锚点「${anchor}」`);
|
|
84
|
+
for (const [field, value] of Object.entries(fields)) {
|
|
85
|
+
const re = new RegExp(`${field}: '[^']*'`);
|
|
86
|
+
if (!re.test(out)) {
|
|
87
|
+
throw new Error(`yaml 注入失败:未找到锚点「${field}: '<值>'」`);
|
|
88
88
|
}
|
|
89
|
-
out =
|
|
90
|
-
? out.replace(devAnchor, `${field}: '${value}'`)
|
|
91
|
-
: out.replace(anchor, `${field}: '${value}'`);
|
|
89
|
+
out = out.replace(re, `${field}: '${value}'`);
|
|
92
90
|
}
|
|
93
91
|
return out;
|
|
94
92
|
}
|
|
@@ -4,18 +4,24 @@
|
|
|
4
4
|
# 每次 version bump 打断,better-sqlite3 编译只在依赖树真变时重跑;稳态 build 仅 COPY dist,秒级。
|
|
5
5
|
# - host 的 current/package.json(真 version)由 config.sh::read_tag 读拼 <version>-<ts> tag,不受镜像内归一影响。
|
|
6
6
|
# - 两阶段同 node:22-slim(debian glibc)→ better-sqlite3 .node ABI 一致,可直接拷。
|
|
7
|
-
# -
|
|
8
|
-
#
|
|
7
|
+
# - 配置注入:业务配置走部署位 APP_ROOT/config(只读挂载 /app/config,含 config.base.yaml +
|
|
8
|
+
# config.yaml 环境 overlay 真值,见 configuration.ts);镜像自带 config/(base+prod)仅作
|
|
9
|
+
# 无挂载时的兜底。env 只剩部署拓扑项(PORT/DB_DIR/UPLOAD_STORAGE_PATH/SLOW_SQL_DIR/API_LOG_DIR)。
|
|
9
10
|
# - data(sqlite+uploads)持久化走 volume 挂载 /app/data,不进镜像。
|
|
10
11
|
# 改代码:重 build(runtime 的 dist 层变,deps 层 cache 命中,秒级);改依赖:deps 层 cache miss,重装+编译。
|
|
11
12
|
#
|
|
12
13
|
# base:docker.1ms.run/library/node:22-slim(Docker Hub 国内直连不稳,1ms 可用),首次 build 联网。
|
|
13
14
|
|
|
15
|
+
# app label:构建期由 --build-arg APP_LABEL 注入(ops/docker/install.sh 传运行时 IMAGE,
|
|
16
|
+
# compose build 读 env IMAGE)—— label 与 IMAGE 恒同源,rotate_images 据此定向 prune
|
|
17
|
+
# 本应用悬空镜像,改名无需手动同步。缺省值仅无注入时兜底(与 docker-compose.yml image 对齐)。
|
|
18
|
+
ARG APP_LABEL=fullstack-template-backend
|
|
19
|
+
|
|
14
20
|
# ── deps 阶段:构建工具 + npm ci + 编译 better-sqlite3 ──
|
|
15
21
|
FROM docker.1ms.run/library/node:22-slim AS deps
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
LABEL app
|
|
22
|
+
# 全局 ARG 须在阶段内重声明才可用;label 烙进每一层中间镜像(build 失败/中断的 <none> 半成品也带标)
|
|
23
|
+
ARG APP_LABEL
|
|
24
|
+
LABEL app=$APP_LABEL
|
|
19
25
|
WORKDIR /app
|
|
20
26
|
# better-sqlite3 v13 无预编译二进制(GitHub release 0 asset、npmmirror 仅镜像源码包),prebuild-install
|
|
21
27
|
# 取不到必回退 node-gyp 源码编译,需 python3/make/g++。node:22-slim 无,装之。
|
|
@@ -32,20 +38,24 @@ RUN npm config set registry https://registry.npmmirror.com \
|
|
|
32
38
|
|
|
33
39
|
# ── runtime 阶段:精简,只带运行所需(无构建工具)──
|
|
34
40
|
FROM docker.1ms.run/library/node:22-slim AS runtime
|
|
35
|
-
|
|
41
|
+
ARG APP_LABEL
|
|
42
|
+
LABEL app=$APP_LABEL
|
|
36
43
|
WORKDIR /app
|
|
37
44
|
# 编译好的 node_modules(含 better-sqlite3 .node;两阶段同 node:22-slim → ABI 一致)
|
|
38
45
|
COPY --from=deps /app/node_modules ./node_modules
|
|
39
46
|
# 代码层:每次改代码这层变(deps 层仍 cache 命中)
|
|
40
47
|
COPY dist ./dist
|
|
41
|
-
#
|
|
48
|
+
# 兜底配置(base + prod overlay;正常部署被部署位挂载 /app/config 覆盖,仅无挂载时生效)
|
|
42
49
|
COPY config ./config
|
|
43
50
|
# 真 package.json(带 version;运行时/镜像内一致;read_tag 读的是 host current/package.json)
|
|
44
51
|
COPY package.json ./package.json
|
|
45
52
|
|
|
46
53
|
ENV NODE_ENV=prod
|
|
54
|
+
# 时区:镜像默认 UTC,不设则日志按天轮转(jsonl-daily-writer)/时间戳比北京时间差 8 小时。
|
|
55
|
+
# 镜像级注入对 ops/docker 与 compose 两条部署路径同时生效;运维临时换区可 run -e TZ=... 覆盖。
|
|
56
|
+
ENV TZ=Asia/Shanghai
|
|
47
57
|
# 运行端口权威 = PORT env(install.sh/compose 从 .env 注入,app 读 PORT 监听;迁移换端口只改 .env)。
|
|
48
|
-
# 镜像内 config/
|
|
58
|
+
# 镜像内 config/(base+prod)的 app.port 仅作未注入 PORT 且无挂载时的兜底默认值。
|
|
49
59
|
# EXPOSE 仅为元数据(默认 3001),实际端口由运行期 PORT env 决定。
|
|
50
60
|
EXPOSE 3001
|
|
51
61
|
CMD ["node", "dist/main.js"]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
2
|
+
# 基础配置(全量默认值 + 注释权威)—— 所有环境共享,单一真相源
|
|
3
|
+
#
|
|
4
|
+
# 配置形态(见 configuration.ts):
|
|
5
|
+
# config.base.yaml ← 本文件:全量键 + 注释(配置说明只写这一份)
|
|
6
|
+
# config.<env>.yaml ← 环境 overlay:只写与 base 不同的键 + 本环境凭证(明文维护;
|
|
7
|
+
# 服务器侧可选加密,见下 security 段);开发 = config.develop.yaml、
|
|
8
|
+
# 生产 = config.prod.yaml,新发布环境 = 复制一份 overlay 改值
|
|
9
|
+
# (如 config.demo.yaml)。
|
|
10
|
+
# 合并语义 = 深合并(overlay 的键覆盖 base 同位键,其余继承 base)。
|
|
11
|
+
#
|
|
12
|
+
# 新增配置:只在 base 加键 + config.interface.ts 加类型 + 消费处读取 ——
|
|
13
|
+
# 无需再动任何 env 模板/部署文件(部署位 config.yaml 只是 overlay 的副本)。
|
|
14
|
+
#
|
|
15
|
+
# 部署模型:buildops 包随行 base + 全部 overlay;首部署 install.sh 选 overlay 播种为
|
|
16
|
+
# APP_ROOT/config/config.yaml(此后发布不覆盖,运维/开发在其上维护该环境真值);
|
|
17
|
+
# 改部署位 config.yaml + 重启 即生效(改密轮换同路径,三模式行为一致)。
|
|
18
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
19
|
+
|
|
20
|
+
# 配置加密总开关(见 docs/config-encryption.md §0):true 时 encryptedFields 清单内的敏感值
|
|
21
|
+
# 非空必须写 ENC(...) 密文(base 与环境 overlay 同强制),启动校验、明文即拒启;
|
|
22
|
+
# false 时全部接受明文(已存在的 ENC(...) 仍照常解密)。
|
|
23
|
+
# 模板恒 false(仓库层明文维护,dev 零密钥);服务器首部署选「开启加密」时由
|
|
24
|
+
# install.sh(ops/ensure-encrypt.cjs)把部署位 config.yaml 的此键置 true 并就地加密敏感值。
|
|
25
|
+
# 受控字段清单为 dot-path:新增敏感字段加进清单即可,无需改源码(dist 混淆产物同理)。
|
|
26
|
+
security:
|
|
27
|
+
encrypt: false
|
|
28
|
+
encryptedFields:
|
|
29
|
+
- jwt.secret
|
|
30
|
+
- admin.password
|
|
31
|
+
- sso.ticketKey
|
|
32
|
+
- appSign.cryptKey
|
|
33
|
+
|
|
34
|
+
app:
|
|
35
|
+
port: 3001
|
|
36
|
+
globalPrefix: api
|
|
37
|
+
corsOrigins:
|
|
38
|
+
- 'http://localhost:6177'
|
|
39
|
+
# 是否信任前置反代(nginx)。默认 false:直连部署下 x-forwarded-for 可伪造;
|
|
40
|
+
# 前置 nginx 时置 true,限流/审计 IP 才会按 XFF 解析
|
|
41
|
+
trustProxy: false
|
|
42
|
+
|
|
43
|
+
database:
|
|
44
|
+
type: better-sqlite3
|
|
45
|
+
# 仅目录:文件名由程序定死为 template.<env>.sqlite(prod=template.prod.sqlite),不外配
|
|
46
|
+
# 部署时由 publish-common 注入 backendDataDir(绝对路径),或 DB_DIR env 覆盖
|
|
47
|
+
dir: data
|
|
48
|
+
synchronize: false
|
|
49
|
+
logging:
|
|
50
|
+
- error
|
|
51
|
+
|
|
52
|
+
jwt:
|
|
53
|
+
# 明文维护(环境 overlay 填真实值);服务器侧可选加密(首部署开启后,部署位 config.yaml
|
|
54
|
+
# 存 ENC(...) 密文,由 APP_ROOT/.prod.secrets 的根密钥在启动时解密;空占位跳过校验)。
|
|
55
|
+
secret: ''
|
|
56
|
+
expiresIn: 8h
|
|
57
|
+
|
|
58
|
+
log:
|
|
59
|
+
level: info
|
|
60
|
+
|
|
61
|
+
# 限流:每分钟 60 次(精确路径匹配,忽略 query)
|
|
62
|
+
# login/ssologin 不进白名单——在 auth.controller 单独紧限流(每 IP 10/min)防爆破;
|
|
63
|
+
# captcha 也不豁免——SVG 生成 CPU 密集,免限流会被打挂(登录页每分钟最多取 60 次足够)
|
|
64
|
+
throttle:
|
|
65
|
+
limit: 60
|
|
66
|
+
ttl: 60
|
|
67
|
+
whitelist:
|
|
68
|
+
- /api/user/islogin
|
|
69
|
+
|
|
70
|
+
# 文件上传:nginx 静态资源目录
|
|
71
|
+
upload:
|
|
72
|
+
urlPrefix: /static/uploads
|
|
73
|
+
# nginx 静态目录路径:dev 相对(随 cwd),prod 由 overlay 覆盖为服务器绝对路径
|
|
74
|
+
storagePath: ./data/uploads
|
|
75
|
+
allowedExt:
|
|
76
|
+
- .jpg
|
|
77
|
+
- .jpeg
|
|
78
|
+
- .png
|
|
79
|
+
- .webp
|
|
80
|
+
- .gif
|
|
81
|
+
- .mp4
|
|
82
|
+
maxSize: 10485760
|
|
83
|
+
|
|
84
|
+
# 慢SQL探针:挂 TypeORM Logger 层,记录落 logs/slowsql/slowsql-YYYY-MM-DD.jsonl
|
|
85
|
+
# (sql 慢查询样本按指纹每日限 5 条/error/lag 事件循环漂移/host 宿主基线/stats 每小时汇总)
|
|
86
|
+
# 分析:node scripts/slowsql-report.mjs
|
|
87
|
+
slowSql:
|
|
88
|
+
thresholdMs: 50 # 慢查询阈值 ms
|
|
89
|
+
lagMs: 200 # 事件循环漂移记录阈值
|
|
90
|
+
hostSampleSec: 60 # 宿主基线落盘周期(内存环形采样固定 10s 不外配)
|
|
91
|
+
retainDays: 14 # 日志保留天数
|
|
92
|
+
maxFileMb: 10 # 单日文件上限 MB(超限滚动瘦身留新去旧,丢条计数随 stats 留痕)
|
|
93
|
+
|
|
94
|
+
# 运维调试日志:只记 ≥400 失败与 >slowMs 慢请求,2xx 成功不记。
|
|
95
|
+
# 失败含 query/body(脱敏截断:password/token/sign 等→***)与失败结果(5xx 带异常 detail);
|
|
96
|
+
# 慢请求只记 method/url/耗时/用户,query/body 同口径脱敏。
|
|
97
|
+
# 落 logs/apilog/apilog-YYYY-MM-DD.jsonl,traceId 经 X-Trace-Id 响应头回传,报 traceId 即可全链路 grep。
|
|
98
|
+
apiLog:
|
|
99
|
+
slowMs: 1000 # 慢请求阈值 ms
|
|
100
|
+
retainDays: 14 # 保留天数
|
|
101
|
+
maxFileMb: 10 # 单日文件上限(超限丢条,每 500 条 warn 留痕)
|
|
102
|
+
|
|
103
|
+
# 超管账号:yaml 为权威,启动时 user.service 把 DB reconcile 到此值
|
|
104
|
+
# (改部署位 config.yaml 的 admin.password + 重启 即完成轮换,如三月一换;加密模式下重跑
|
|
105
|
+
# ./install.sh 自动把新明文加密,服务器上不需要 config-crypt)。
|
|
106
|
+
# 占位值(change-me/空)启动跳过 reconcile(不写弱默认),作防御兜底。
|
|
107
|
+
admin:
|
|
108
|
+
username: admin
|
|
109
|
+
# 环境 overlay 填真实明文(CLI 生成项目时自动随机生成);服务器侧可选加密为 ENC(...)
|
|
110
|
+
password: ''
|
|
111
|
+
|
|
112
|
+
# 单点登录 /api/ssologin(演示接入范式,默认关闭;未开启时接口返回 404)
|
|
113
|
+
# 开启后接入方(门户/统一认证)持 ticketKey 生成跳转 URL:
|
|
114
|
+
# GET /api/ssologin?username=xxx&ts=<unix秒>&sign=HMAC-SHA256('username\nts', ticketKey) hex
|
|
115
|
+
# role 固定 'web'(最小权限),勿给该角色挂敏感路由。
|
|
116
|
+
sso:
|
|
117
|
+
enabled: false
|
|
118
|
+
# 票据密钥为纯后端秘密:接入时环境 overlay 填真实值(服务器侧可选加密为 ENC(...))
|
|
119
|
+
ticketKey: ''
|
|
120
|
+
# 时间戳允许偏差(秒),±5min
|
|
121
|
+
tsWindow: 300
|
|
122
|
+
|
|
123
|
+
# 大屏 API 签名加密(无 token、无过期)
|
|
124
|
+
# signKey 与前端 VITE_APP_SIGN_KEY 须逐字一致(前端 bundle 可见,聚类恒明文,不走 ENC);
|
|
125
|
+
# 轮换改环境 overlay(或部署位 config.yaml)+ 重启。
|
|
126
|
+
# cryptKey 是纯后端秘密:环境 overlay 填真实值(服务器侧可选加密为 ENC(...))。
|
|
127
|
+
appSign:
|
|
128
|
+
signKey: ''
|
|
129
|
+
cryptKey: ''
|
|
130
|
+
tsWindow: 300
|
|
@@ -1,95 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 开发环境 overlay —— 深合并到 config.base.yaml 之上,只写与 base 不同的键。
|
|
2
|
+
# dev 一律明文(security.encrypt=false,无根密钥/无 .develop.secrets):
|
|
3
|
+
# 下方占位值即开箱即跑的 dev 假值,锚点行('<field>: '<任意单引号串>'')留给 CLI 生成项目时
|
|
4
|
+
# 注入真实明文凭证。prod 加密(ENC 密文)只在服务器侧发生,见 docs/config-encryption.md。
|
|
2
5
|
app:
|
|
3
6
|
port: 4001
|
|
4
|
-
globalPrefix: api
|
|
5
|
-
corsOrigins:
|
|
6
|
-
- 'http://localhost:6177'
|
|
7
|
-
# 是否信任前置反代(nginx)。默认 false:直连部署下 x-forwarded-for 可伪造;
|
|
8
|
-
# 前置 nginx 时置 true,限流/审计 IP 才会按 XFF 解析(env: TRUST_PROXY)
|
|
9
|
-
trustProxy: false
|
|
10
7
|
|
|
11
8
|
database:
|
|
12
|
-
type: better-sqlite3
|
|
13
|
-
# 仅目录:文件名由程序定死为 template.<env>.sqlite(dev=template.dev.sqlite),不外配
|
|
14
|
-
dir: data
|
|
15
9
|
synchronize: true
|
|
16
10
|
logging: true
|
|
17
11
|
|
|
18
12
|
jwt:
|
|
19
|
-
# dev
|
|
20
|
-
secret: dev-only-jwt-secret-not-for-prod
|
|
21
|
-
expiresIn: 8h
|
|
13
|
+
# dev 假密钥(dev-only,不用于 prod)
|
|
14
|
+
secret: 'dev-only-jwt-secret-not-for-prod'
|
|
22
15
|
|
|
23
16
|
log:
|
|
24
17
|
level: debug
|
|
25
18
|
|
|
26
|
-
# 限流:每分钟 60 次(精确路径匹配,忽略 query)
|
|
27
|
-
# login/ssologin 不进白名单——在 auth.controller 单独紧限流(每 IP 10/min)防爆破;
|
|
28
|
-
# captcha 也不豁免——SVG 生成 CPU 密集,免限流会被打挂(登录页每分钟最多取 60 次足够)
|
|
29
|
-
throttle:
|
|
30
|
-
limit: 60
|
|
31
|
-
ttl: 60
|
|
32
|
-
whitelist:
|
|
33
|
-
- /api/user/islogin
|
|
34
|
-
|
|
35
|
-
# 文件上传:nginx 静态资源目录
|
|
36
|
-
upload:
|
|
37
|
-
# 可访问 URL 前缀(nginx location 映射)
|
|
38
|
-
urlPrefix: /static/uploads
|
|
39
|
-
# nginx 静态目录绝对路径,后端把文件写到此处
|
|
40
|
-
storagePath: ./data/uploads
|
|
41
|
-
allowedExt:
|
|
42
|
-
- .jpg
|
|
43
|
-
- .jpeg
|
|
44
|
-
- .png
|
|
45
|
-
- .webp
|
|
46
|
-
- .gif
|
|
47
|
-
- .mp4
|
|
48
|
-
maxSize: 10485760 # 10MB
|
|
49
|
-
|
|
50
|
-
# 慢SQL探针:挂 TypeORM Logger 层,记录落 data/slowsql/slowsql-YYYY-MM-DD.jsonl
|
|
51
|
-
# (sql 慢查询样本按指纹每日限 5 条/error/lag 事件循环漂移/host 宿主基线/stats 每小时汇总)
|
|
52
|
-
# 分析:node scripts/slowsql-report.mjs
|
|
53
|
-
slowSql:
|
|
54
|
-
thresholdMs: 50 # 慢查询阈值 ms
|
|
55
|
-
lagMs: 200 # 事件循环漂移记录阈值
|
|
56
|
-
hostSampleSec: 60 # 宿主基线落盘周期(内存环形采样固定 10s 不外配)
|
|
57
|
-
retainDays: 14 # 日志保留天数
|
|
58
|
-
maxFileMb: 10 # 单日文件上限 MB(超限滚动瘦身留新去旧,丢条计数随 stats 留痕)
|
|
59
|
-
|
|
60
|
-
# 运维调试日志:只记 ≥400 失败与 >slowMs 慢请求,2xx 成功不记。
|
|
61
|
-
# 失败含 query/body(脱敏截断:password/token/sign 等→***)与失败结果(5xx 带异常 detail);
|
|
62
|
-
# 慢请求只记 method/url/耗时/用户,query/body 同口径脱敏。
|
|
63
|
-
# 落 data/apilog/apilog-YYYY-MM-DD.jsonl,traceId 经 X-Trace-Id 响应头回传,报 traceId 即可全链路 grep。
|
|
64
|
-
apiLog:
|
|
65
|
-
slowMs: 1000 # 慢请求阈值 ms
|
|
66
|
-
retainDays: 14 # 保留天数
|
|
67
|
-
maxFileMb: 10 # 单日文件上限(超限丢条,每 500 条 warn 留痕)
|
|
68
|
-
|
|
69
|
-
# 超管账号:yaml 为权威,启动时 user.service 把 DB reconcile 到此值
|
|
70
|
-
# (改这里 + 重启 即完成轮换)。dev/prod 真值均入库 yaml 直读;ADMIN_PASSWORD env 为可选覆盖。
|
|
71
19
|
admin:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
password: admin123
|
|
75
|
-
|
|
20
|
+
# dev 口令 admin123(启动 reconcile 入库;CLI 生成项目时随机覆盖)
|
|
21
|
+
password: 'admin123'
|
|
76
22
|
|
|
77
|
-
# 单点登录 /api/ssologin(演示接入范式,默认关闭;未开启时接口返回 404)
|
|
78
|
-
# 开启后接入方(门户/统一认证)持 ticketKey 生成跳转 URL:
|
|
79
|
-
# GET /api/ssologin?username=xxx&ts=<unix秒>&sign=HMAC-SHA256('username\nts', ticketKey) hex
|
|
80
|
-
# role 固定 'web'(最小权限),勿给该角色挂敏感路由。
|
|
81
23
|
sso:
|
|
82
|
-
enabled: false
|
|
83
24
|
ticketKey: ''
|
|
84
|
-
# 时间戳允许偏差(秒),±5min
|
|
85
|
-
tsWindow: 300
|
|
86
25
|
|
|
87
|
-
# 大屏 API 签名加密(无 token、无过期)
|
|
88
|
-
# 签名密钥前端构建期注入 bundle,本期只启用请求侧签名;cryptKey 后期响应/body 加密用
|
|
89
26
|
appSign:
|
|
90
|
-
# HMAC-SHA256 签名密钥,32+ 字符随机串,永不过期。须与 web/.env.development 逐字一致。
|
|
91
27
|
signKey: ''
|
|
92
|
-
# AES-256-GCM 内容加密密钥,base64(32 字节)。本期保留不校验
|
|
93
28
|
cryptKey: ''
|
|
94
|
-
# 时间戳允许偏差(秒),±5min
|
|
95
|
-
tsWindow: 300
|
|
@@ -1,93 +1,38 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 生产环境 overlay —— 深合并到 config.base.yaml 之上,只写与 base 不同的键。
|
|
2
|
+
# 部署模型:本文件随 buildops 包走;首部署 install.sh 把它播种为 APP_ROOT/config/config.yaml
|
|
3
|
+
# (此后发布不覆盖,运维/开发在部署位副本上维护本环境真值;新发布环境 = 复制本文件改值)。
|
|
4
|
+
# 凭证锚点行(空占位 '')留给 CLI 生成项目时注入真实值。
|
|
5
|
+
# 加密模型(见 docs/config-encryption.md):本文件【明文】维护(开发机零密钥零加密操作),
|
|
6
|
+
# 敏感值明文随包传输;首部署 install.sh 会询问是否开启配置加密 —— 选开启则服务器生成
|
|
7
|
+
# 根密钥(.prod.secrets)并把部署位 config.yaml 的非空明文敏感值就地加密为 ENC(...) 密文,
|
|
8
|
+
# 此后每次部署自动加密新增明文(改明文 + 重跑 ./install.sh = 轮换闭环)。
|
|
2
9
|
app:
|
|
3
10
|
port: 3001
|
|
4
|
-
globalPrefix: api
|
|
5
|
-
corsOrigins:
|
|
6
|
-
- 'http://localhost:6177'
|
|
7
|
-
# 是否信任前置反代(nginx)。默认 false:直连部署下 x-forwarded-for 可伪造;
|
|
8
|
-
# 前置 nginx 时置 true,限流/审计 IP 才会按 XFF 解析(env: TRUST_PROXY)
|
|
9
|
-
trustProxy: false
|
|
10
11
|
|
|
11
12
|
database:
|
|
12
|
-
type: better-sqlite3
|
|
13
|
-
# 仅目录:文件名由程序定死为 template.<env>.sqlite(prod=template.prod.sqlite),不外配
|
|
14
|
-
# 部署时由 publish-common 注入 backendDataDir(绝对路径),或 DB_DIR env 覆盖
|
|
15
|
-
dir: data
|
|
16
13
|
synchronize: false
|
|
17
14
|
logging:
|
|
18
15
|
- error
|
|
19
16
|
|
|
20
17
|
jwt:
|
|
18
|
+
# prod 明文维护,服务器侧(可选)加密为 ENC(...)
|
|
21
19
|
secret: ''
|
|
22
|
-
expiresIn: 8h
|
|
23
20
|
|
|
24
21
|
log:
|
|
25
22
|
level: warn
|
|
26
23
|
|
|
27
|
-
# 限流:每分钟 60 次(精确路径匹配,忽略 query)
|
|
28
|
-
# login/ssologin 不进白名单——在 auth.controller 单独紧限流(每 IP 10/min)防爆破;
|
|
29
|
-
# captcha 也不豁免——SVG 生成 CPU 密集,免限流会被打挂(登录页每分钟最多取 60 次足够)
|
|
30
|
-
throttle:
|
|
31
|
-
limit: 60
|
|
32
|
-
ttl: 60
|
|
33
|
-
whitelist:
|
|
34
|
-
- /api/user/islogin
|
|
35
|
-
|
|
36
|
-
# 文件上传:nginx 静态资源目录
|
|
37
24
|
upload:
|
|
38
|
-
|
|
25
|
+
# 服务器 nginx 静态目录绝对路径(docker 部署由 run-container.sh 的
|
|
26
|
+
# UPLOAD_STORAGE_PATH env 覆盖为 /app/data/uploads,此值兜底 systemd/pm2)
|
|
39
27
|
storagePath: /data/uploads
|
|
40
|
-
allowedExt:
|
|
41
|
-
- .jpg
|
|
42
|
-
- .jpeg
|
|
43
|
-
- .png
|
|
44
|
-
- .webp
|
|
45
|
-
- .gif
|
|
46
|
-
- .mp4
|
|
47
|
-
maxSize: 10485760
|
|
48
|
-
|
|
49
|
-
# 慢SQL探针:挂 TypeORM Logger 层,记录落 data/slowsql/slowsql-YYYY-MM-DD.jsonl
|
|
50
|
-
# (sql 慢查询样本按指纹每日限 5 条/error/lag 事件循环漂移/host 宿主基线/stats 每小时汇总)
|
|
51
|
-
# 分析:node scripts/slowsql-report.mjs
|
|
52
|
-
slowSql:
|
|
53
|
-
thresholdMs: 50 # 慢查询阈值 ms
|
|
54
|
-
lagMs: 200 # 事件循环漂移记录阈值
|
|
55
|
-
hostSampleSec: 60 # 宿主基线落盘周期(内存环形采样固定 10s 不外配)
|
|
56
|
-
retainDays: 14 # 日志保留天数
|
|
57
|
-
maxFileMb: 10 # 单日文件上限 MB(超限滚动瘦身留新去旧,丢条计数随 stats 留痕)
|
|
58
|
-
|
|
59
|
-
# 运维调试日志:只记 ≥400 失败与 >slowMs 慢请求,2xx 成功不记。
|
|
60
|
-
# 失败含 query/body(脱敏截断:password/token/sign 等→***)与失败结果(5xx 带异常 detail);
|
|
61
|
-
# 慢请求只记 method/url/耗时/用户,query/body 同口径脱敏。
|
|
62
|
-
# 落 data/apilog/apilog-YYYY-MM-DD.jsonl,traceId 经 X-Trace-Id 响应头回传,报 traceId 即可全链路 grep。
|
|
63
|
-
apiLog:
|
|
64
|
-
slowMs: 1000 # 慢请求阈值 ms
|
|
65
|
-
retainDays: 14 # 保留天数
|
|
66
|
-
maxFileMb: 10 # 单日文件上限(超限丢条,每 500 条 warn 留痕)
|
|
67
28
|
|
|
68
|
-
# 超管账号:yaml 为权威,启动时 user.service 把 DB reconcile 到此值
|
|
69
|
-
# (改 current/config/config.prod.yaml 或 ADMIN_PASSWORD env + 重启 即完成轮换,如三月一换)。
|
|
70
|
-
# prod 真值已入库本 yaml(systemd/pm2 直读);ADMIN_PASSWORD env 为可选覆盖(docker 经 .env、
|
|
71
|
-
# 或免改 yaml 轮换时用)。占位值(change-me/空)启动跳过 reconcile(不写弱默认),作防御兜底。
|
|
72
29
|
admin:
|
|
73
|
-
|
|
74
|
-
# ⚠️ 模板占位:新项目务必改成强密码(启动 reconcile 到 DB)
|
|
30
|
+
# prod 明文维护;改部署位 config.yaml 此值 + 重跑 ./install.sh(加密模式自动重新加密)= 轮换
|
|
75
31
|
password: ''
|
|
76
32
|
|
|
77
|
-
# 单点登录 /api/ssologin(演示接入范式,默认关闭;未开启时接口返回 404)
|
|
78
|
-
# 开启后接入方(门户/统一认证)持 ticketKey 生成跳转 URL:
|
|
79
|
-
# GET /api/ssologin?username=xxx&ts=<unix秒>&sign=HMAC-SHA256('username\nts', ticketKey) hex
|
|
80
|
-
# role 固定 'web'(最小权限),勿给该角色挂敏感路由。
|
|
81
33
|
sso:
|
|
82
|
-
enabled: false
|
|
83
34
|
ticketKey: ''
|
|
84
|
-
# 时间戳允许偏差(秒),±5min
|
|
85
|
-
tsWindow: 300
|
|
86
35
|
|
|
87
|
-
# 大屏 API 签名加密(无 token、无过期)
|
|
88
|
-
# signKey 与前端 VITE_APP_SIGN_KEY 须逐字一致;轮换改本 yaml + 重启或 APP_SIGN_KEY env 覆盖。
|
|
89
|
-
# cryptKey 后期响应/body 加密用(本期未启用,占位不可移除——aes-js 解密侧待二期端到端做)。
|
|
90
36
|
appSign:
|
|
91
37
|
signKey: ''
|
|
92
38
|
cryptKey: ''
|
|
93
|
-
tsWindow: 300
|