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
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
# Description backend 顶层部署编排器(服务器侧、纯 shell、单一真相源)
|
|
4
4
|
###
|
|
5
5
|
# 开发机只负责 buildops + tar + scp 上传 release 包,然后 ssh 触发本脚本。
|
|
6
|
-
# 真正的部署力学(releases 轮转 / current 装填 /
|
|
6
|
+
# 真正的部署力学(releases 轮转 / current 装填 / 派发进程安装)全在本脚本,
|
|
7
7
|
# 不再由开发机 node-ssh 远程执行 —— 部署模型与运维现场一致,可端到端验证实际问题。
|
|
8
|
+
# 密钥检测不在本脚本职责内:敏感值校验一律由应用启动 fail-fast 兜底(暴露后用
|
|
9
|
+
# ./versionswitch.sh 回滚),ops 只做环境判定 / 依赖安装 / 服务启停。
|
|
8
10
|
#
|
|
9
11
|
# current 形态(按模式):
|
|
10
12
|
# systemd/pm2:current = 软链 → releases/<ver>-<ts>(Capistrano 式;releases 只放业务服务包,
|
|
@@ -13,41 +15,75 @@
|
|
|
13
15
|
# ops/ 共享:ops/ 不进 releases,统一落 APP_ROOT/ops/(与 install.sh 同级、跨版本共享);
|
|
14
16
|
# 各 mode 的 config.sh 持久化(运维改的 NODE_BIN/RUN_USER 等跨版本保留)。
|
|
15
17
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
18
|
+
# 顶层脚本零配置(不 baked):运行时以脚本所在目录为 APP_ROOT、MODE 读 $APP_ROOT/.mode ——
|
|
19
|
+
# APP_ROOT 整体搬迁(换盘/换机)后开箱即用,发布 overlay 自然收敛。
|
|
20
|
+
#
|
|
21
|
+
# 部署位配置(APP_ROOT/config/,跨版本共享 —— 配置不再随 release 走):
|
|
22
|
+
# config.base.yaml 全量默认;每次发布用包内版本覆盖刷新(开发权威:新增键/注释随发布到位)
|
|
23
|
+
# config.yaml 环境 overlay 固定名副本:首部署从包内所选 overlay 播种(config.prod.yaml /
|
|
24
|
+
# config.<env>.yaml → 此名),此后发布永不覆盖 —— 运维/开发在部署位维护本
|
|
25
|
+
# 环境真值(多发布环境 = 各 APP_ROOT 各自的 config.yaml,同一份构建产物通用)。
|
|
26
|
+
# 运行时读取:docker 由 run-container.sh 只读挂载 /app/config;systemd/pm2 由 unit/ecosystem
|
|
27
|
+
# 注入 CONFIG_DIR=$APP_ROOT/config(见 configuration.ts)。改配置 + ./start.sh 即生效,三模式一致。
|
|
28
|
+
#
|
|
29
|
+
# 两种场景(判定:脚本所在目录有无 .mode):
|
|
30
|
+
# 1) 首次部署(bootstrap,无 .mode):交互确认 APP_ROOT 与模式 → 选环境 overlay → 确认端口 →
|
|
31
|
+
# 选是否开启配置加密 → 建 releases/data/logs → 拷顶层脚本到 APP_ROOT → 落 release 到
|
|
32
|
+
# releases/<ver>-<ts>(或 docker current)→ 落 ops 到 APP_ROOT/ops/ → 播种/刷新 APP_ROOT/config
|
|
33
|
+
# → 移 .prod.docker.env(纯部署件)入 APP_ROOT 根(包根 config/ 原件随移删除,不在服务器
|
|
34
|
+
# 留双份)→ 软链 current(systemd/pm2)→ 派发 mode install(mode install 经 ops/lib-encrypt.sh
|
|
35
|
+
# 按加密选择生成/维护根密钥并就地加密部署位 config.yaml)。
|
|
36
|
+
# 2) 二次部署(有 .mode):传新 release(tar/目录)→ 停旧 → 落新 release →
|
|
37
|
+
# overlay ops(保留 config.sh)→ 刷新部署位 base(不动 config.yaml)→ 移 env 文件入根
|
|
38
|
+
# 对齐包内版本(原件删除) → 装依赖 → 改链 go-live → 轮转 → 重派发(mode install 顺带
|
|
39
|
+
# relock:有根密钥则自动加密部署位 config.yaml 新明文)。
|
|
23
40
|
#
|
|
24
41
|
# 用法:
|
|
25
42
|
# 首次: bash install.sh # 在解压后的 release 包根里跑(脚本现位于包根,非 ops/)
|
|
26
|
-
# 二次: bash install.sh <release.tar.gz|dir> # 在 APP_ROOT 下跑(
|
|
43
|
+
# 二次: bash install.sh <release.tar.gz|dir> # 在 APP_ROOT 下跑(.mode 已就位)
|
|
27
44
|
# 切版本: bash install.sh # 在 APP_ROOT 下裸跑(无参):扫 releases/ 找新丢的
|
|
28
45
|
# buildops 产物包目录并部署切 current(等价显式传 <包目录>,部署后清掉暂存包)
|
|
29
46
|
set -euo pipefail
|
|
30
47
|
|
|
31
|
-
# ── baked 字段(首次为空;bootstrap 拷自身到 APP_ROOT 时 sed 改写为字面量) ──
|
|
32
|
-
APP_ROOT="${APP_ROOT:-}"
|
|
33
|
-
MODE="${MODE:-}"
|
|
34
48
|
# 轮转保留数:此处只存显式值(--keep / env),不预设缺省 —— 缺省在轮转前由
|
|
35
49
|
# resolve_keep_versions 从 ops/<mode>/config.sh 取(运维改那处,跨版本保留)。
|
|
36
50
|
KEEP_VERSIONS="${KEEP_VERSIONS:-}"
|
|
37
51
|
|
|
38
52
|
# 脚本所在目录(首次=release 包根,即 install.sh 所在的包根;二次=APP_ROOT)
|
|
39
53
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
54
|
+
|
|
55
|
+
# ── 部署状态自定位(零配置):脚本所在目录有 .mode = 已完成首次部署(即 APP_ROOT);
|
|
56
|
+
# 无 = 还在 release 包根,进 bootstrap。MODE 恒读 .mode,不 bake。 ──
|
|
57
|
+
APP_ROOT=""
|
|
58
|
+
MODE=""
|
|
59
|
+
if [[ -f "$DIR/.mode" ]]; then
|
|
60
|
+
APP_ROOT="$DIR"
|
|
61
|
+
MODE="$(cat "$DIR/.mode" 2>/dev/null || true)"
|
|
62
|
+
fi
|
|
40
63
|
RELEASE_DIR_DEFAULT="$DIR" # 首次=release 包根(install.sh 现位于包根,非 ops/)
|
|
41
64
|
RELEASE_DIR=""
|
|
42
65
|
APP_DIR="" # releases/<version>/(dist/package.json/Dockerfile/config 所在)
|
|
43
66
|
VER=""
|
|
44
67
|
STAGE_CLEANUP=""
|
|
45
68
|
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
|
|
69
|
+
# 本次部署选定的环境 overlay 名(仅 bootstrap 赋值,如 prod / demo;二次部署留空 = 部署位
|
|
70
|
+
# 环境已定,config.yaml 不再动)。ensure_secrets 据此播种 APP_ROOT/config/config.yaml。
|
|
71
|
+
CHOSEN_ENV=""
|
|
72
|
+
|
|
73
|
+
# 本次部署选定端口(部署位权威:systemd/pm2=APP_ROOT/config/config.yaml 的 app.port,
|
|
74
|
+
# docker=.prod.docker.env 的 PORT)。首部署 prompt_port 必赋值,ensure_secrets 据此写入
|
|
75
|
+
# 播种的 config.yaml 与 .prod.docker.env PORT。二次部署恒留空(换端口改部署位 + ./start.sh)。
|
|
49
76
|
DEPLOY_PORT=""
|
|
50
77
|
|
|
78
|
+
# 首部署是否开启配置加密(1/0,prompt_encrypt 置位;dispatch_install 导出给 mode install,
|
|
79
|
+
# 由 ops/lib-encrypt.sh 消费:生成服务器根密钥 + 就地加密部署位 config.yaml)。
|
|
80
|
+
# 二次部署恒保持 0 —— 加密状态由 $APP_ROOT/.prod.secrets 的存在性自表述(存在 = 加密模式)。
|
|
81
|
+
CONFIG_ENCRYPT_INIT=0
|
|
82
|
+
|
|
83
|
+
# 本次部署是否走了「播种 config.yaml」路径(ensure_secrets 置位;deploy 完成输出末尾据此提示
|
|
84
|
+
# 老部署升级的真值迁移 —— 播种的是包内 overlay,不含旧部署在服务器上维护的环境真值)。
|
|
85
|
+
SEEDED_CONFIG=0
|
|
86
|
+
|
|
51
87
|
# 本次部署的工作目录(docker=current;systemd/pm2=releases/<id>,最后软链 current 指向)
|
|
52
88
|
DEPLOY_TARGET=""
|
|
53
89
|
IS_SYMLINK=0
|
|
@@ -96,7 +132,7 @@ resolve_app_dir() {
|
|
|
96
132
|
printf '%s' "$(cd "$vdir" && pwd)"
|
|
97
133
|
}
|
|
98
134
|
|
|
99
|
-
# ── APP_ROOT 裸跑 ./install.sh(
|
|
135
|
+
# ── APP_ROOT 裸跑 ./install.sh(无参、已完成首次部署)时:扫 APP_ROOT/releases/ 找
|
|
100
136
|
# 运维新丢进来的 buildops 产物包并部署,等价于正常二次部署显式传 <包目录>。
|
|
101
137
|
# 产物包判据:目录自身含内层 releases/(正常部署版本目录 <ver>-<ts> 只有 dist/package.json/
|
|
102
138
|
# config;ops 不进 releases、在 APP_ROOT/ops/ 共享,故正常部署目录无内层 releases/)。多个包则交互列选;��署后由 cleanup_staging 清掉暂存包目录。
|
|
@@ -138,7 +174,7 @@ resolve_release() {
|
|
|
138
174
|
local arg="${1:-}"
|
|
139
175
|
if [[ -z "$arg" ]]; then
|
|
140
176
|
if [[ -n "$APP_ROOT" ]]; then
|
|
141
|
-
# 无参 + APP_ROOT
|
|
177
|
+
# 无参 + 已完成首次部署(脚本住 APP_ROOT)= 运维裸跑 ./install.sh 切版本:
|
|
142
178
|
# 扫 APP_ROOT/releases/ 找新丢进来的 buildops 产物包并部署(等价二次部署传 <包目录>)。
|
|
143
179
|
resolve_dropped_package
|
|
144
180
|
return
|
|
@@ -146,7 +182,7 @@ resolve_release() {
|
|
|
146
182
|
RELEASE_DIR="$RELEASE_DIR_DEFAULT"
|
|
147
183
|
[[ -d "$RELEASE_DIR/releases" ]] || die "默认 release 目录 $RELEASE_DIR 无 releases/(应在解压后的 release 包内运行,或显式传包路径)"
|
|
148
184
|
elif [[ -f "$arg" && "$arg" == *.tar.gz ]]; then
|
|
149
|
-
[[ -n "$APP_ROOT" ]] || die "传 tar
|
|
185
|
+
[[ -n "$APP_ROOT" ]] || die "传 tar 包但当前目录未完成首次部署(先在解压目录里裸跑 install.sh 完成引导)"
|
|
150
186
|
local ts; ts="$(date +%Y%m%d-%H%M%S)"
|
|
151
187
|
RELEASE_DIR="$APP_ROOT/.stage-$ts"
|
|
152
188
|
rm -rf "$RELEASE_DIR"; mkdir -p "$RELEASE_DIR"
|
|
@@ -222,36 +258,78 @@ prompt_mode() {
|
|
|
222
258
|
esac
|
|
223
259
|
}
|
|
224
260
|
|
|
261
|
+
# ── 首部署选择环境 overlay ──
|
|
262
|
+
# 包内 config/ 随行 base + 全部环境 overlay(config.<env>.yaml);每个 APP_ROOT = 一个发布环境,
|
|
263
|
+
# 首部署选定一份,ensure_secrets 播种为部署位 APP_ROOT/config/config.yaml(此后发布不覆盖,
|
|
264
|
+
# 本环境真值在部署位维护)。仅一份 overlay(通常 prod)时静默采用;多份交互列选(默认 prod)。
|
|
265
|
+
prompt_env() {
|
|
266
|
+
local dir="$RELEASE_DIR/config" items=() f n pick choice i def
|
|
267
|
+
[[ -d "$dir" ]] || die "缺 $dir(release 包不完整)"
|
|
268
|
+
while IFS= read -r -d '' f; do
|
|
269
|
+
items+=("$(basename "$f" | sed -E 's/^config\.//; s/\.yaml$//')")
|
|
270
|
+
done < <(find "$dir" -maxdepth 1 -name 'config.*.yaml' ! -name 'config.base.yaml' -print0 2>/dev/null | sort -z)
|
|
271
|
+
n=${#items[@]}
|
|
272
|
+
[[ $n -gt 0 ]] || die "$dir 下无环境 overlay(config.<env>.yaml;release 包不完整)"
|
|
273
|
+
if [[ $n -eq 1 ]]; then
|
|
274
|
+
CHOSEN_ENV="${items[0]}"
|
|
275
|
+
ok "部署环境: $CHOSEN_ENV(包内唯一 overlay)"
|
|
276
|
+
return 0
|
|
277
|
+
fi
|
|
278
|
+
echo " 检测到多份环境 overlay(每个 APP_ROOT = 一个发布环境,选定后本机恒用该环境):"
|
|
279
|
+
i=0
|
|
280
|
+
for pick in "${items[@]}"; do i=$((i+1)); echo " $i) $pick"; done
|
|
281
|
+
def=1
|
|
282
|
+
for i in "${!items[@]}"; do [[ "${items[$i]}" == "prod" ]] && def=$((i+1)); done
|
|
283
|
+
read -rp " 选择 [${def}]: " choice || choice=""
|
|
284
|
+
choice="${choice:-$def}"
|
|
285
|
+
if [[ "$choice" =~ ^[0-9]+$ && "$choice" -ge 1 && "$choice" -le $n ]]; then
|
|
286
|
+
CHOSEN_ENV="${items[$((choice-1))]}"
|
|
287
|
+
else
|
|
288
|
+
die "无效选择: $choice"
|
|
289
|
+
fi
|
|
290
|
+
ok "部署环境: $CHOSEN_ENV"
|
|
291
|
+
}
|
|
292
|
+
|
|
225
293
|
# ── 运行端口首次确认 ──
|
|
226
|
-
# 端口 =
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
# 并记入 DEPLOY_PORT:ensure_secrets 据此把 .env 的 PORT 改为同值(docker 权威,部署即生效)。
|
|
230
|
-
# 二次部署端口变更由 deploy() 另行确认。
|
|
294
|
+
# 端口 = 选定 overlay 的 app.port(未覆盖时回落 base);确认值/新值都记入 DEPLOY_PORT,
|
|
295
|
+
# 由 ensure_secrets 写进播种的部署位 config.yaml(systemd/pm2 权威)与 .prod.docker.env
|
|
296
|
+
# 的 PORT(docker 权威)。二次部署不问端口(部署位权威,换端口改部署位 + ./start.sh)。
|
|
231
297
|
prompt_port() {
|
|
232
298
|
local port input
|
|
233
|
-
port="$(yaml_app_port "$
|
|
299
|
+
port="$(yaml_app_port "$RELEASE_DIR/config/config.${CHOSEN_ENV}.yaml")"
|
|
300
|
+
[[ -n "$port" ]] || port="$(yaml_app_port "$RELEASE_DIR/config/config.base.yaml")"
|
|
234
301
|
[[ -n "$port" ]] || port="3001"
|
|
235
|
-
echo " 后端运行端口(来自 config.
|
|
236
|
-
read -rp "
|
|
302
|
+
echo " 后端运行端口(来自 config.${CHOSEN_ENV}.yaml app.port = $port;systemd/pm2=app 直监听,docker=宿主映射)"
|
|
303
|
+
read -rp " 回车确认此端口,或输入新端口(写入部署位 config.yaml): " input || input=""
|
|
237
304
|
input="${input%%$'\n'}"
|
|
238
305
|
if [[ -z "$input" ]]; then
|
|
239
|
-
ok "运行端口: $port
|
|
306
|
+
ok "运行端口: $port"
|
|
240
307
|
DEPLOY_PORT="$port"
|
|
241
308
|
return 0
|
|
242
309
|
fi
|
|
243
|
-
# 校验合法端口(1-65535)后就地改 release 源 yaml;与当前同值则不动
|
|
244
310
|
if [[ ! "$input" =~ ^[0-9]+$ || "$input" -lt 1 || "$input" -gt 65535 ]]; then
|
|
245
|
-
die "无效端口: $input(期望 1-65535
|
|
311
|
+
die "无效端口: $input(期望 1-65535 的数字)"
|
|
246
312
|
fi
|
|
247
|
-
if [[ "$input" == "$port" ]]; then
|
|
248
|
-
ok "运行端口: $port(未变更)"
|
|
249
|
-
DEPLOY_PORT="$port"
|
|
250
|
-
return 0
|
|
251
|
-
fi
|
|
252
|
-
set_app_port "$APP_DIR/config/config.prod.yaml" "$input"
|
|
253
313
|
DEPLOY_PORT="$input"
|
|
254
|
-
ok "
|
|
314
|
+
ok "运行端口: $port → $input(将写入部署位 config.yaml + docker env PORT)"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# ── 首部署:是否开启配置加密 ──
|
|
318
|
+
# 加密模型(见 docs/config-encryption.md):仓库/传输层明文维护(开发机零密钥零加密操作),
|
|
319
|
+
# 服务器侧落盘为密文。选 Y → mode install 经 ops/lib-encrypt.sh 在本机生成根密钥
|
|
320
|
+
# (APP_ROOT/.prod.secrets,600,现场打印一次供抄录)并就地把播种的 config.yaml 敏感值加密;
|
|
321
|
+
# 此后每次部署自动把新明文就地加密(改明文 + 重跑 ./install.sh = 轮换闭环)。
|
|
322
|
+
# 选 n → 明文运行。二次部署不问(加密状态由 .prod.secrets 存在性自表述)。
|
|
323
|
+
prompt_encrypt() {
|
|
324
|
+
local choice
|
|
325
|
+
echo " 配置加密(在本机生成根密钥,把部署位 config.yaml 的敏感值加密为 ENC(...) 密文):"
|
|
326
|
+
echo " Y) 开启(推荐:服务器落盘为密文;此后改明文 + 重跑 ./install.sh 自动重新加密)"
|
|
327
|
+
echo " n) 明文运行(部署位 config.yaml 敏感值保持明文)"
|
|
328
|
+
read -rp " 选择 [Y]: " choice || choice=""
|
|
329
|
+
case "$choice" in
|
|
330
|
+
n|N) CONFIG_ENCRYPT_INIT=0; ok "配置加密:关闭(明文运行)" ;;
|
|
331
|
+
*) CONFIG_ENCRYPT_INIT=1; ok "配置加密:开启(将在部署时生成本机根密钥)" ;;
|
|
332
|
+
esac
|
|
255
333
|
}
|
|
256
334
|
|
|
257
335
|
TOP_SCRIPTS=(install.sh start.sh stop.sh versionswitch.sh status.sh)
|
|
@@ -292,7 +370,7 @@ lay_out_release() {
|
|
|
292
370
|
}
|
|
293
371
|
|
|
294
372
|
# ── lay out ops/ 到 APP_ROOT/ops/(共享、跨版本;releases/ 不放 ops) ──
|
|
295
|
-
# 每次部署 overlay 刷新 mode 脚本 /
|
|
373
|
+
# 每次部署 overlay 刷新 mode 脚本 / 文档,但各 mode 的 config.sh 持久化
|
|
296
374
|
# (运维改的 NODE_BIN/RUN_USER/DATA_DIR 等跨版本不丢;首次用包内 config.sh 作种子)。
|
|
297
375
|
lay_out_ops() {
|
|
298
376
|
local src="$RELEASE_DIR/ops" sub name f fn
|
|
@@ -307,7 +385,7 @@ lay_out_ops() {
|
|
|
307
385
|
[[ -e "$sub" ]] || continue
|
|
308
386
|
name="$(basename "$sub")"
|
|
309
387
|
if [[ -d "$sub" ]]; then
|
|
310
|
-
# mode 目录(docker/pm2/systemd
|
|
388
|
+
# mode 目录(docker/pm2/systemd):刷新其中脚本,保留运维护过的 config.sh
|
|
311
389
|
mkdir -p "$APP_ROOT/ops/$name"
|
|
312
390
|
for f in "$sub"/*; do
|
|
313
391
|
[[ -e "$f" ]] || continue
|
|
@@ -324,32 +402,24 @@ lay_out_ops() {
|
|
|
324
402
|
}
|
|
325
403
|
|
|
326
404
|
# ── 刷新顶层脚本(install/start/stop/versionswitch/status.sh)到 APP_ROOT/ ──
|
|
327
|
-
#
|
|
328
|
-
#
|
|
329
|
-
#
|
|
405
|
+
# 每次部署用新 release 包根的同名脚本 overlay,让顶层脚本的修复能随 publish 下发。
|
|
406
|
+
# 顶层脚本零配置(自定位 + 读 .mode,无运维可改字段),纯覆盖即可 —— 存量 baked
|
|
407
|
+
# 老部署也会被本步骤自然收敛到无 bake 新形态。(与 lay_out_ops 保留 config.sh 不同)
|
|
330
408
|
# 安全:写 temp + mv 替换目录项,bash 持有运行脚本的旧 inode 存活至进程退出
|
|
331
409
|
# (不 cp 直写同 inode —— 会 truncate 正在运行的 install.sh 致其读崩)。
|
|
332
410
|
refresh_top_scripts() {
|
|
333
|
-
[[ -n "$APP_ROOT" && -n "$
|
|
334
|
-
local s src dst
|
|
411
|
+
[[ -n "$APP_ROOT" && -n "$RELEASE_DIR" ]] || return 0
|
|
412
|
+
local s src dst n=0
|
|
335
413
|
for s in "${TOP_SCRIPTS[@]}"; do
|
|
336
414
|
src="$RELEASE_DIR/$s"
|
|
337
415
|
[[ -f "$src" ]] || continue
|
|
338
416
|
dst="$APP_ROOT/$s"
|
|
339
|
-
|
|
340
|
-
sed -e "s|^APP_ROOT=.*|APP_ROOT=\"$APP_ROOT\"|" -e "s|^MODE=.*|MODE=\"$MODE\"|" "$src" > "$dst.__new"
|
|
417
|
+
cp -f "$src" "$dst.__new"
|
|
341
418
|
mv -f "$dst.__new" "$dst"
|
|
342
419
|
chmod +x "$dst"
|
|
343
|
-
|
|
420
|
+
n=$((n+1))
|
|
344
421
|
done
|
|
345
|
-
[[ $
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
# ── 密钥非空检测(只拦空值,不拦占位串) ──
|
|
349
|
-
# 取 yaml 标量值:去 "key:" 前缀、首尾空白与引号;空即未设
|
|
350
|
-
yaml_scalar() {
|
|
351
|
-
sed -nE "s/^[[:space:]]*$2:[[:space:]]*//p" "$1" \
|
|
352
|
-
| sed -E "s/^['\"]?//; s/['\"]?$//; s/^[[:space:]]+//; s/[[:space:]]+$//"
|
|
422
|
+
[[ $n -gt 0 ]] && ok "顶层脚本刷新:$n 个(自定位零配置,随 release 下发)"
|
|
353
423
|
}
|
|
354
424
|
|
|
355
425
|
# 读 yaml 顶层 app: 段内 port: 的值(去引号/首尾空白)。无文件/无 app.port → 空串(不报错)。
|
|
@@ -381,58 +451,103 @@ set_app_port() {
|
|
|
381
451
|
fi
|
|
382
452
|
mv "$f.__port" "$f"
|
|
383
453
|
}
|
|
384
|
-
check_yaml_secrets() {
|
|
385
|
-
local f="$1" bad=0 sk jwt
|
|
386
|
-
sk="$(yaml_scalar "$f" signKey)"
|
|
387
|
-
jwt="$(yaml_scalar "$f" secret)"
|
|
388
|
-
[[ -z "$sk" ]] && { err "appSign.signKey 未设(空)"; bad=1; }
|
|
389
|
-
[[ -z "$jwt" ]] && { err "jwt.secret 未设(空)"; bad=1; }
|
|
390
|
-
[[ $bad -eq 1 ]] && die "请编辑 $f 填入生产真值后重新执行首次部署: bash $DIR/install.sh"
|
|
391
|
-
return 0
|
|
392
|
-
}
|
|
393
|
-
check_env_secrets() {
|
|
394
|
-
local f="$1" bad=0 jwt appsign
|
|
395
|
-
jwt="$(grep -E '^JWT_SECRET=' "$f" | cut -d= -f2- || true)"
|
|
396
|
-
appsign="$(grep -E '^APP_SIGN_KEY=' "$f" | cut -d= -f2- || true)"
|
|
397
|
-
[[ -z "$jwt" ]] && { err "JWT_SECRET 未设(空)"; bad=1; }
|
|
398
|
-
[[ -z "$appsign" ]] && { err "APP_SIGN_KEY 未设(空)"; bad=1; }
|
|
399
|
-
[[ $bad -eq 1 ]] && die "请编辑 $f 填入生产真值后重新执行首次部署: bash $DIR/install.sh"
|
|
400
|
-
return 0
|
|
401
|
-
}
|
|
402
454
|
|
|
403
|
-
# ──
|
|
404
|
-
#
|
|
455
|
+
# ── 配置就位(播种部署位 + 移件入 APP_ROOT 根,零密钥检测)──
|
|
456
|
+
# 包内 config/(transport):config.base.yaml + 全部 overlay + .prod.docker.env(无密钥文件 ——
|
|
457
|
+
# 根密钥改为首部署时在本机生成,见 prompt_encrypt / ops/lib-encrypt.sh;敏感值在仓库与
|
|
458
|
+
# 传输层明文,服务器落盘为密文)。
|
|
459
|
+
# APP_ROOT/config/config.base.yaml 每次发布覆盖刷新(开发权威:新增键/注释随发布到位)
|
|
460
|
+
# APP_ROOT/config/config.yaml 首部署播种一次(选定 overlay → 此名),此后永不覆盖
|
|
461
|
+
# APP_ROOT/.prod.secrets 首部署选「开启加密」时由 mode install 生成(600);
|
|
462
|
+
# 存在即加密模式:每次部署自动把部署位新明文就地加密
|
|
463
|
+
# APP_ROOT/.prod.docker.env 纯部署件(buildops 自动生成),移入覆盖;PORT 沿用现值
|
|
464
|
+
# 拷贝成功即删包内原件(mv 语义)—— 包根 config/ 只作随行通道,不在服务器留双份
|
|
465
|
+
# (尤其场景三:包更名成 APP_ROOT,cleanup_staging 不清,原件会永久残留、易误改错份)。
|
|
466
|
+
# 不做任何密钥非空/格式检测——敏感值校验由应用启动 fail-fast 兜底(见文件头)。
|
|
405
467
|
ensure_secrets() {
|
|
406
|
-
local
|
|
468
|
+
local src_cfg="$RELEASE_DIR/config"
|
|
469
|
+
[[ -d "$src_cfg" ]] || die "缺 $src_cfg(release 包不完整)"
|
|
470
|
+
# 场景三(包更名成 APP_ROOT):transport 目录即部署位本身,只播种、不自我拷贝/删除
|
|
471
|
+
local same_cfg=0
|
|
472
|
+
[[ "$src_cfg" -ef "$APP_ROOT/config" ]] && same_cfg=1
|
|
473
|
+
|
|
474
|
+
# 旧模型迁移提示(只提示,不自动碰密钥文件):老包随行 .prod.secrets 已不再消费,
|
|
475
|
+
# 服务器已有的 $APP_ROOT/.prod.secrets(旧加密部署)继续有效 —— 派发 mode install 时
|
|
476
|
+
# lib-encrypt 检测到它即走 relock,既有 ENC 密文与密钥配对不变。
|
|
477
|
+
if [[ -f "$src_cfg/.prod.secrets" ]]; then
|
|
478
|
+
say "检测到包内随行 .prod.secrets(旧打包模型残留,新版不再消费)—— 服务器侧 $APP_ROOT/.prod.secrets 为权威"
|
|
479
|
+
[[ $same_cfg -eq 1 ]] || rm -f "$src_cfg/.prod.secrets"
|
|
480
|
+
fi
|
|
481
|
+
if [[ -f "$APP_ROOT/.secrets" ]]; then
|
|
482
|
+
say "检测到旧 $APP_ROOT/.secrets —— 已由 .prod.secrets 取代,确认新部署运行正常后可删除"
|
|
483
|
+
fi
|
|
484
|
+
|
|
485
|
+
# 部署位 APP_ROOT/config/:base 刷新 + config.yaml 播种(仅缺时)
|
|
486
|
+
mkdir -p "$APP_ROOT/config"
|
|
487
|
+
if [[ $same_cfg -eq 0 ]]; then
|
|
488
|
+
[[ -f "$src_cfg/config.base.yaml" ]] || die "缺 $src_cfg/config.base.yaml(release 包不完整)"
|
|
489
|
+
cp -f "$src_cfg/config.base.yaml" "$APP_ROOT/config/config.base.yaml"
|
|
490
|
+
fi
|
|
491
|
+
if [[ ! -f "$APP_ROOT/config/config.yaml" ]]; then
|
|
492
|
+
local seed_src seed_name
|
|
493
|
+
if [[ -n "$CHOSEN_ENV" ]]; then
|
|
494
|
+
seed_name="config.${CHOSEN_ENV}.yaml"
|
|
495
|
+
seed_src="$src_cfg/${seed_name}"
|
|
496
|
+
else
|
|
497
|
+
# 二次部署但部署位缺 config.yaml(异常/老部署升级):回落包内 prod overlay
|
|
498
|
+
seed_name="config.prod.yaml"
|
|
499
|
+
seed_src="$src_cfg/${seed_name}"
|
|
500
|
+
[[ -f "$seed_src" ]] || { seed_name="config.base.yaml"; seed_src="$src_cfg/${seed_name}"; }
|
|
501
|
+
say "部署位 config.yaml 缺失,用包内 ${seed_name} 播种(老部署升级:请把原配置的环境真值合并进来)"
|
|
502
|
+
fi
|
|
503
|
+
[[ -f "$seed_src" ]] || die "缺 $seed_src(release 包不完整)"
|
|
504
|
+
cp -f "$seed_src" "$APP_ROOT/config/config.yaml"
|
|
505
|
+
SEEDED_CONFIG=1
|
|
506
|
+
ok "部署位配置播种: $APP_ROOT/config/config.yaml (← ${seed_name},此后发布不覆盖)"
|
|
507
|
+
fi
|
|
508
|
+
# 端口写进播种/现存 config.yaml(仅显式选定;systemd/pm2=yaml 权威;docker 由 env PORT 权威)
|
|
509
|
+
if [[ -n "$DEPLOY_PORT" ]]; then
|
|
510
|
+
set_app_port "$APP_ROOT/config/config.yaml" "$DEPLOY_PORT"
|
|
511
|
+
fi
|
|
512
|
+
|
|
407
513
|
if [[ "$MODE" == "docker" ]]; then
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
514
|
+
# docker env 文件(纯部署件,路径可外配见 ops/docker/config.sh ENV_FILE)。
|
|
515
|
+
# 拷贝前记下 APP_ROOT 现行 PORT:本次未显式选端口则拷贝后回写,不吞运维在发布间隔改的值。
|
|
516
|
+
local src_env="$src_cfg/.prod.docker.env" cur_port=""
|
|
517
|
+
if [[ -f "$APP_ROOT/.prod.docker.env" ]]; then
|
|
518
|
+
cur_port="$(sed -nE 's/^PORT=//p' "$APP_ROOT/.prod.docker.env" | head -1 | sed -E "s/^['\"]?//; s/['\"]?$//")"
|
|
519
|
+
fi
|
|
520
|
+
if [[ -f "$src_env" ]]; then
|
|
521
|
+
cp -f "$src_env" "$APP_ROOT/.prod.docker.env"
|
|
522
|
+
[[ $same_cfg -eq 1 ]] || rm -f "$src_env"
|
|
523
|
+
elif [[ ! -f "$APP_ROOT/.prod.docker.env" ]]; then
|
|
524
|
+
die "缺 $src_env 且无 $APP_ROOT/.prod.docker.env(release 包不完整)"
|
|
525
|
+
fi
|
|
526
|
+
# PORT 同步:显式选定 > 沿用现行 > 包内缺省(prompt_port 首部署必赋值 → 权威生效)
|
|
527
|
+
if [[ -n "$DEPLOY_PORT" ]]; then
|
|
528
|
+
_sed_i "$APP_ROOT/.prod.docker.env" -e "s|^PORT=.*|PORT=$DEPLOY_PORT|"
|
|
529
|
+
elif [[ -n "$cur_port" ]]; then
|
|
530
|
+
_sed_i "$APP_ROOT/.prod.docker.env" -e "s|^PORT=.*|PORT=$cur_port|"
|
|
413
531
|
fi
|
|
414
|
-
#
|
|
415
|
-
#
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
_sed_i "$APP_ROOT/.env" \
|
|
422
|
-
-e "s|^TAG=.*|TAG=$VER|" \
|
|
423
|
-
-e "s|^DATA_DIR=.*|DATA_DIR=$APP_ROOT/data|" \
|
|
424
|
-
-e "s|^CONTAINER_NAME=.*|CONTAINER_NAME=fullstack-template-backend|" \
|
|
425
|
-
$port_e
|
|
426
|
-
check_env_secrets "$APP_ROOT/.env"
|
|
427
|
-
ok "docker env: $APP_ROOT/.env (TAG=$VER)"
|
|
532
|
+
# 旧模型迁移检测:老 env 文件带业务配置条目(JWT_SECRET 等)—— 新版本不读这些 key,
|
|
533
|
+
# 静默丢弃会造成「以为配了实际没生效」,提示迁移到部署位 config.yaml
|
|
534
|
+
if grep -qE '^(JWT_SECRET|ADMIN_PASSWORD|SSO_TICKET_KEY|APP_SIGN_KEY|CORS_ORIGINS|TRUST_PROXY|LOG_LEVEL)=..+' \
|
|
535
|
+
"$APP_ROOT/.prod.docker.env" 2>/dev/null; then
|
|
536
|
+
say "检测到 $APP_ROOT/.prod.docker.env 带业务配置条目(JWT_SECRET 等)—— 新版本只读部署位 config.yaml,请把对应 ENC 密文/值迁入 $APP_ROOT/config/config.yaml"
|
|
537
|
+
fi
|
|
538
|
+
ok "docker env: $APP_ROOT/.prod.docker.env (纯部署件;业务配置在 APP_ROOT/config/config.yaml)"
|
|
428
539
|
else
|
|
429
|
-
# systemd/pm2:
|
|
430
|
-
#
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
540
|
+
# systemd/pm2:docker env 本模式不消费,包内原件一并删除(mv 纪律,防场景三残留)。
|
|
541
|
+
# 运行时配置走 CONFIG_DIR=$APP_ROOT/config(unit/ecosystem 注入,mode install 生成)。
|
|
542
|
+
[[ $same_cfg -eq 1 ]] || rm -f "$src_cfg/.prod.docker.env"
|
|
543
|
+
[[ -f "$APP_ROOT/config/config.yaml" ]] || die "缺 $APP_ROOT/config/config.yaml(部署不完整)"
|
|
544
|
+
ok "配置: $APP_ROOT/config/config.yaml (改后 restart 即生效;跨版本共享)"
|
|
545
|
+
fi
|
|
546
|
+
|
|
547
|
+
# 包内 transport 清空(场景三 src 即部署位则保留;.prod.secrets 一并清理兼容老包)
|
|
548
|
+
if [[ $same_cfg -eq 0 ]]; then
|
|
549
|
+
rm -f "$src_cfg"/config.*.yaml "$src_cfg/.prod.secrets" "$src_cfg/.prod.docker.env"
|
|
550
|
+
rmdir "$src_cfg" 2>/dev/null || true
|
|
436
551
|
fi
|
|
437
552
|
}
|
|
438
553
|
|
|
@@ -449,7 +564,7 @@ dispatch_install() {
|
|
|
449
564
|
local mode_install="$APP_ROOT/ops/$MODE/install.sh" # 共享 ops(releases 不放 ops)
|
|
450
565
|
[[ -f "$mode_install" ]] || die "缺 mode 安装脚本: $mode_install(先跑 lay_out_ops 或检查后台服务目录/ops)"
|
|
451
566
|
say "派发 $MODE 安装: $mode_install"
|
|
452
|
-
export APP_ROOT MODE KEEP_VERSIONS
|
|
567
|
+
export APP_ROOT MODE KEEP_VERSIONS CONFIG_ENCRYPT_INIT
|
|
453
568
|
bash "$mode_install"
|
|
454
569
|
}
|
|
455
570
|
|
|
@@ -498,29 +613,29 @@ cleanup_staging() {
|
|
|
498
613
|
# bootstrap
|
|
499
614
|
# ════════════════════════════════════════════════════════════════
|
|
500
615
|
bootstrap() {
|
|
501
|
-
[[ -z "$APP_ROOT" ]] || die "bootstrap 不应带 baked 后台服务目录"
|
|
502
616
|
prompt_app_root
|
|
503
617
|
[[ -n "$MODE" ]] || prompt_mode
|
|
618
|
+
prompt_env
|
|
504
619
|
prompt_port
|
|
620
|
+
prompt_encrypt
|
|
505
621
|
|
|
506
622
|
say "初始化目录: $APP_ROOT"
|
|
507
623
|
mkdir -p "$APP_ROOT"/{releases,data,logs}
|
|
508
624
|
# current 不预建(systemd/pm2 由 go_live 建软链;docker 由 lay_out_release 建目录)
|
|
509
625
|
|
|
510
|
-
#
|
|
511
|
-
# cp 同文件会 exit1 致死(set -e)
|
|
512
|
-
# bash 持有运行脚本的 open fd
|
|
626
|
+
# 纯拷贝顶层脚本到 APP_ROOT(不 bake:运行时自定位 + 读 .mode,零配置,APP_ROOT 搬迁可用)。
|
|
627
|
+
# 场景三(包更名成 APP_ROOT,DIR==APP_ROOT)时 cp 同文件会 exit1 致死(set -e)→ 跳过拷贝,
|
|
628
|
+
# bash 持有运行脚本的 open fd,目录项替换不影响读取(inode 存活至进程退出)。
|
|
513
629
|
local s src dst same=0
|
|
514
630
|
[[ "$(cd "$DIR" && pwd -P)" = "$(cd "$APP_ROOT" && pwd -P)" ]] && same=1
|
|
515
631
|
for s in "${TOP_SCRIPTS[@]}"; do
|
|
516
632
|
src="$DIR/$s"; dst="$APP_ROOT/$s"
|
|
517
633
|
[[ -f "$src" ]] || die "缺顶层脚本 $src(release 包不完整)"
|
|
518
|
-
[[ $same -eq
|
|
519
|
-
_sed_i "$dst" -e "s|^APP_ROOT=.*|APP_ROOT=\"$APP_ROOT\"|" -e "s|^MODE=.*|MODE=\"$MODE\"|"
|
|
634
|
+
[[ $same -eq 1 ]] || cp -f "$src" "$dst"
|
|
520
635
|
chmod +x "$dst"
|
|
521
636
|
done
|
|
522
637
|
echo "$MODE" > "$APP_ROOT/.mode"
|
|
523
|
-
ok "
|
|
638
|
+
ok "顶层脚本就位:$APP_ROOT(MODE=$MODE,自定位零配置)"
|
|
524
639
|
|
|
525
640
|
resolve_work_dir
|
|
526
641
|
lay_out_release
|
|
@@ -540,8 +655,10 @@ bootstrap() {
|
|
|
540
655
|
rotate_releases
|
|
541
656
|
dispatch_install
|
|
542
657
|
ok "首次部署完成 ✓"
|
|
543
|
-
|
|
544
|
-
|
|
658
|
+
[[ -f "$APP_ROOT/.prod.secrets" ]] \
|
|
659
|
+
&& echo " 配置:已加密(根密钥 $APP_ROOT/.prod.secrets;改部署位 config.yaml 明文后重跑 ./install.sh 自动重新加密)"
|
|
660
|
+
local disp_port; disp_port="$(yaml_app_port "$APP_ROOT/config/config.yaml")"
|
|
661
|
+
echo " 端口: ${disp_port:-3001}(部署位 config.yaml,nginx 反代到 http://localhost:<port>/api)"
|
|
545
662
|
echo " 管理: cd $APP_ROOT && ./start.sh | ./stop.sh | ./status.sh 2>/dev/null || true"
|
|
546
663
|
echo " 版本切换(回滚/升级): cd $APP_ROOT && ./versionswitch.sh"
|
|
547
664
|
cleanup_staging
|
|
@@ -551,43 +668,18 @@ bootstrap() {
|
|
|
551
668
|
# deploy
|
|
552
669
|
# ════════════════════════════════════════════════════════════════
|
|
553
670
|
deploy() {
|
|
554
|
-
[[ -n "$APP_ROOT" ]] || die "
|
|
555
|
-
[[ -n "$MODE" ]] || MODE
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
#
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
# 且 docker 模式记 DEPLOY_PORT → ensure_secrets 写 .env PORT(权威)。同端口静默。
|
|
563
|
-
local new_port cur_port dp_input
|
|
564
|
-
new_port="$(yaml_app_port "$APP_DIR/config/config.prod.yaml")"
|
|
565
|
-
if [[ "$MODE" == "docker" && -f "$APP_ROOT/.env" ]]; then
|
|
566
|
-
# docker 当前运行端口权威 = .env PORT(非 yaml)
|
|
567
|
-
cur_port="$(sed -nE 's/^PORT=//p' "$APP_ROOT/.env" | head -1 | sed -E "s/^['\"]?//; s/['\"]?$//; s/^[[:space:]]+//; s/[[:space:]]+$//")"
|
|
671
|
+
[[ -n "$APP_ROOT" ]] || die "二次部署:当前目录无 .mode(应在 APP_ROOT 下的 install.sh 内运行)"
|
|
672
|
+
[[ -n "$MODE" ]] || die "缺 MODE($APP_ROOT/.mode 不可读)"
|
|
673
|
+
|
|
674
|
+
# 端口 = 部署位权威(release 不再携带端口变更):systemd/pm2 = APP_ROOT/config/config.yaml 的
|
|
675
|
+
# app.port;docker = .prod.docker.env 的 PORT。换端口:改部署位 + ./start.sh(重建容器/重启进程)。
|
|
676
|
+
local cur_port=""
|
|
677
|
+
if [[ "$MODE" == "docker" && -f "$APP_ROOT/.prod.docker.env" ]]; then
|
|
678
|
+
cur_port="$(sed -nE 's/^PORT=//p' "$APP_ROOT/.prod.docker.env" | head -1 | sed -E "s/^['\"]?//; s/['\"]?$//; s/^[[:space:]]+//; s/[[:space:]]+$//")"
|
|
568
679
|
else
|
|
569
|
-
cur_port="$(yaml_app_port "$APP_ROOT/
|
|
570
|
-
fi
|
|
571
|
-
if [[ -n "$new_port" && -n "$cur_port" && "$new_port" != "$cur_port" ]]; then
|
|
572
|
-
echo
|
|
573
|
-
say "⚠ 端口不一致:当前运行端口=$cur_port,新版本 app.port=$new_port"
|
|
574
|
-
echo " ($([[ "$MODE" == "docker" ]] && echo 'docker 权威=.env PORT' || echo 'yaml 为权威');回车=沿用当前端口 $cur_port,或输入新端口)"
|
|
575
|
-
read -rp " 回车沿用当前端口,或输入新端口: " dp_input || dp_input=""
|
|
576
|
-
dp_input="${dp_input%%$'\n'}"
|
|
577
|
-
if [[ -z "$dp_input" ]]; then
|
|
578
|
-
# 沿用当前运行端口:改新 release yaml 对齐(镜像兜底);.env PORT(docker)不动
|
|
579
|
-
set_app_port "$APP_DIR/config/config.prod.yaml" "$cur_port"
|
|
580
|
-
ok "运行端口对齐当前: $cur_port(已改新 release config.prod.yaml)"
|
|
581
|
-
elif [[ ! "$dp_input" =~ ^[0-9]+$ || "$dp_input" -lt 1 || "$dp_input" -gt 65535 ]]; then
|
|
582
|
-
die "无效端口: $dp_input(期望 1-65535 的数字;手动改请编辑 $APP_DIR/config/config.prod.yaml 的 app.port 后重跑)"
|
|
583
|
-
else
|
|
584
|
-
set_app_port "$APP_DIR/config/config.prod.yaml" "$dp_input"
|
|
585
|
-
DEPLOY_PORT="$dp_input" # docker:由 ensure_secrets 写入 .env PORT(权威)
|
|
586
|
-
ok "已改新 release app.port: $new_port → $dp_input($([[ "$MODE" == "docker" ]] && echo 'docker 写 .env PORT' || echo 'yaml 权威'),部署即生效)"
|
|
587
|
-
fi
|
|
588
|
-
elif [[ -n "$new_port" ]]; then
|
|
589
|
-
say "运行端口: $new_port(与当前一致)"
|
|
680
|
+
cur_port="$(yaml_app_port "$APP_ROOT/config/config.yaml")"
|
|
590
681
|
fi
|
|
682
|
+
say "运行端口: ${cur_port:-?}(部署位权威;换端口改 $APP_ROOT/config/config.yaml 的 app.port / .prod.docker.env 的 PORT,再 ./start.sh)"
|
|
591
683
|
|
|
592
684
|
local cur_ver=""
|
|
593
685
|
[[ -L "$APP_ROOT/current" && -f "$APP_ROOT/current/package.json" ]] && cur_ver="$(read_version "$APP_ROOT/current")" || true
|
|
@@ -621,8 +713,17 @@ deploy() {
|
|
|
621
713
|
|
|
622
714
|
cleanup_staging
|
|
623
715
|
ok "部署完成 v$VER ✓"
|
|
624
|
-
local disp_port; disp_port="$(yaml_app_port "$
|
|
625
|
-
echo " 端口: ${disp_port:-3001}(
|
|
716
|
+
local disp_port; disp_port="$(yaml_app_port "$APP_ROOT/config/config.yaml")"
|
|
717
|
+
echo " 端口: ${disp_port:-3001}(部署位 config.yaml) 状态: cd $APP_ROOT && ./ops/$MODE/status.sh"
|
|
718
|
+
if [[ "$SEEDED_CONFIG" = "1" ]]; then
|
|
719
|
+
# 老部署升级:本次 config.yaml 是包内 overlay 播种的默认值,旧部署在服务器上维护的
|
|
720
|
+
# 生产真值不在里面,必须手工合并一次(此后发布永不覆盖,只此一次)。
|
|
721
|
+
err "⚠ 本次播种了 $APP_ROOT/config/config.yaml(包内默认值)—— 老部署升级需手工合并生产真值:"
|
|
722
|
+
echo " 1) 旧配置的改动值:docker 看旧镜像(docker run --rm <旧IMAGE:TAG> cat /app/config/config.prod.yaml;"
|
|
723
|
+
echo " systemd/pm2 看 releases/ 下旧版本目录的 config/;对比合并差异项"
|
|
724
|
+
echo " 2) 旧 .prod.docker.env 的业务条目(JWT_SECRET 等,上方已提示):迁入 config.yaml 对应键"
|
|
725
|
+
echo " 3) 合并后 ./start.sh 生效;若为加密模式(.prod.secrets 存在),重跑 ./install.sh 自动加密新明文"
|
|
726
|
+
fi
|
|
626
727
|
}
|
|
627
728
|
|
|
628
729
|
# ════════════════════════════════════════════════════════════════
|