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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
###
|
|
3
|
+
# Description 配置加密维护(lib)—— 三模式 mode install 共用(被 source,零动作)
|
|
4
|
+
###
|
|
5
|
+
# 由 systemd/pm2/docker 的 install.sh source(调用方已 source 各自 config.sh,
|
|
6
|
+
# MODE/APP_ROOT 已就位)。加密模型见 docs/config-encryption.md:
|
|
7
|
+
# 仓库与传输层明文维护(开发零密钥零加密操作),服务器侧落盘为密文 ——
|
|
8
|
+
# · $APP_ROOT/.prod.secrets 存在 → relock:校验既有密文能解 + 就地加密新增明文
|
|
9
|
+
# (改部署位 config.yaml 明文 → 重跑 ./install.sh 即完成轮换,服务器上不需要 config-crypt)
|
|
10
|
+
# · 首部署选了加密(CONFIG_ENCRYPT_INIT=1,顶层 install.sh bootstrap 交互置位)且无密钥文件
|
|
11
|
+
# → init:生成本机根密钥写 .prod.secrets(600,现场打印一次供抄录进密码管理器),随后同 relock
|
|
12
|
+
# · 都不满足(明文模式)→ 零动作,应用以 security.encrypt=false 接受明文
|
|
13
|
+
# 加密本体在 ops/ensure-encrypt.cjs(零依赖 node 脚本,行级改写保留注释)。
|
|
14
|
+
# node 执行器解析:systemd = config.sh 的 NODE_BIN > PATH;pm2 = PATH;
|
|
15
|
+
# docker = 一次性容器(调用时镜像已构建;--user 以运行者 uid 跑,写回文件的宿主属主不变)。
|
|
16
|
+
# 参数:ensure_config_encryption [<docker镜像引用>](仅 docker 模式需要,= 本次构建的镜像)
|
|
17
|
+
|
|
18
|
+
ensure_config_encryption() {
|
|
19
|
+
local secrets="$APP_ROOT/.prod.secrets" cfg_dir="$APP_ROOT/config" image="${1:-}" key=""
|
|
20
|
+
[[ -f "$cfg_dir/config.yaml" ]] || { echo "⚠ 缺 $cfg_dir/config.yaml,跳过配置加密" >&2; return 0; }
|
|
21
|
+
|
|
22
|
+
if [[ -f "$secrets" ]]; then
|
|
23
|
+
key="$(sed -nE 's/^CONFIG_SECRET_KEY=//p' "$secrets" | head -1 | sed -E "s/^['\"]?//; s/['\"]?$//")"
|
|
24
|
+
[[ -n "$key" ]] || { echo "✗ $secrets 无 CONFIG_SECRET_KEY(补齐后重跑 ./install.sh)" >&2; return 1; }
|
|
25
|
+
elif [[ "${CONFIG_ENCRYPT_INIT:-0}" = "1" ]]; then
|
|
26
|
+
# 生成本机根密钥:openssl(宿主恒有优先)> ensure-encrypt.cjs genkey(宿主无 openssl 时,
|
|
27
|
+
# systemd/pm2 用 NODE_BIN、docker 用一次性容器跑)
|
|
28
|
+
if command -v openssl >/dev/null 2>&1; then
|
|
29
|
+
key="$(openssl rand -base64 32 | tr -d '\n')"
|
|
30
|
+
else
|
|
31
|
+
key="$(encrypt_genkey "$image")" || return 1
|
|
32
|
+
fi
|
|
33
|
+
[[ -n "$key" ]] || { echo "✗ 根密钥生成失败(openssl 与 node 均不可用)" >&2; return 1; }
|
|
34
|
+
{
|
|
35
|
+
echo '# 配置密文根密钥 —— 首部署时由 install.sh 在本机生成,勿外传;务必备份进密码管理器。'
|
|
36
|
+
echo '# 轮换:改部署位 config/config.yaml 明文 → 重跑 ./install.sh 自动重新加密(手册 docs/config-encryption.md)。'
|
|
37
|
+
echo "CONFIG_SECRET_KEY=$key"
|
|
38
|
+
} > "$secrets"
|
|
39
|
+
chmod 600 "$secrets"
|
|
40
|
+
echo ""
|
|
41
|
+
echo "✓ 配置加密根密钥已生成 → $secrets (600;仅此一次打印,存进密码管理器):"
|
|
42
|
+
echo " $key"
|
|
43
|
+
else
|
|
44
|
+
return 0 # 明文模式:无密钥文件即不加密,应用按 security.encrypt=false 接受明文
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
echo "▶ 配置加密:校验既有密文 + 就地加密部署位 config.yaml 的非空明文敏感值 ..."
|
|
48
|
+
encrypt_relock "$image" "$key" "$cfg_dir"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# ensure-encrypt.cjs 定位(共享 ops,lay_out_ops 已就位)
|
|
52
|
+
encrypt_helper() {
|
|
53
|
+
local h="$APP_ROOT/ops/ensure-encrypt.cjs"
|
|
54
|
+
[[ -f "$h" ]] || { echo "✗ 缺 $h(共享 ops 未就位;重跑 ./install.sh 完成部署)" >&2; return 1; }
|
|
55
|
+
printf '%s' "$h"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# genkey(宿主无 openssl 时的兜底):systemd/pm2 走 NODE_BIN/PATH,docker 走一次性容器
|
|
59
|
+
encrypt_genkey() {
|
|
60
|
+
local image="${1:-}" h node_bin out
|
|
61
|
+
h="$(encrypt_helper)" || return 1
|
|
62
|
+
if [[ "$MODE" == "docker" ]]; then
|
|
63
|
+
[[ -n "$image" ]] || { echo "✗ docker 模式配置加密缺镜像引用" >&2; return 1; }
|
|
64
|
+
out="$(docker run --rm -v "$h":/app/ensure-encrypt.cjs:ro "$image" node /app/ensure-encrypt.cjs genkey 2>/dev/null)" \
|
|
65
|
+
|| { echo "✗ 一次性容器 genkey 失败" >&2; return 1; }
|
|
66
|
+
else
|
|
67
|
+
encrypt_resolve_node || return 1
|
|
68
|
+
out="$("$ENCRYPT_NODE_BIN" "$h" genkey)" || { echo "✗ genkey 失败" >&2; return 1; }
|
|
69
|
+
fi
|
|
70
|
+
printf '%s' "$out"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# node 执行器解析(systemd/pm2 宿主直跑;结果写 ENCRYPT_NODE_BIN)
|
|
74
|
+
encrypt_resolve_node() {
|
|
75
|
+
if [[ "$MODE" == "systemd" && -x "${NODE_BIN:-}" ]]; then
|
|
76
|
+
ENCRYPT_NODE_BIN="$NODE_BIN"
|
|
77
|
+
else
|
|
78
|
+
ENCRYPT_NODE_BIN="$(command -v node || true)"
|
|
79
|
+
fi
|
|
80
|
+
[[ -n "$ENCRYPT_NODE_BIN" ]] || { echo "✗ 找不到 node($MODE 模式配置加密需要;明文模式可忽略,修好 NODE_BIN/PATH 后重跑)" >&2; return 1; }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# relock:加密维护本体(systemd/pm2 宿主直跑;docker 一次性容器,--user 保宿主文件属主)
|
|
84
|
+
encrypt_relock() {
|
|
85
|
+
local image="$1" key="$2" cfg_dir="$3" h
|
|
86
|
+
h="$(encrypt_helper)" || return 1
|
|
87
|
+
if [[ "$MODE" == "docker" ]]; then
|
|
88
|
+
[[ -n "$image" ]] || { echo "✗ docker 模式配置加密缺镜像引用(应传本次构建的镜像)" >&2; return 1; }
|
|
89
|
+
docker run --rm --user "$(id -u):$(id -g)" \
|
|
90
|
+
-e CONFIG_SECRET_KEY="$key" \
|
|
91
|
+
-v "$h":/app/ensure-encrypt.cjs:ro \
|
|
92
|
+
-v "$cfg_dir":/app/config \
|
|
93
|
+
"$image" node /app/ensure-encrypt.cjs relock /app/config
|
|
94
|
+
else
|
|
95
|
+
encrypt_resolve_node || return 1
|
|
96
|
+
CONFIG_SECRET_KEY="$key" "$ENCRYPT_NODE_BIN" "$h" relock "$cfg_dir"
|
|
97
|
+
fi
|
|
98
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
###ops 共享函数库(source 时零动作)—— 跨 mode 公共能力,随包全量分发。###
|
|
3
|
+
# 身份纪律:ops 不烙项目内容,服务名/容器名/镜像名一律运行时从 current/package.json
|
|
4
|
+
# 的 name 派生(pkg_name)—— 与镜像 tag/version(同文件)同源自洽,改名只改 package.json。
|
|
5
|
+
# 覆盖口子在各 mode config.sh 的 ${VAR:-$(pkg_name)}:身份需与包名分叉的项目
|
|
6
|
+
# (如 scoped 包名剥 @scope 后不合意)export SERVICE/CONTAINER_NAME/IMAGE/APP_NAME 即可。
|
|
7
|
+
#
|
|
8
|
+
# current/package.json 服务器恒在:docker = current 真目录;systemd/pm2 = current 软链,
|
|
9
|
+
# 且顶层 install.sh 先 go_live/落 release 后才派发 mode install;缺文件 = 部署完整性问题。
|
|
10
|
+
|
|
11
|
+
# 读 APP_ROOT/current/package.json 的 name(与各 config.sh 读 version 同款解析),
|
|
12
|
+
# 剥 @scope/ 取 basename,校验可用作 systemd unit / docker 容器 / pm2 app 名。
|
|
13
|
+
pkg_name() {
|
|
14
|
+
local f n
|
|
15
|
+
[[ -n "${APP_ROOT:-}" ]] || { echo "✗ pkg_name: APP_ROOT 未导出(应由顶层脚本或 config.sh 解析)" >&2; return 1; }
|
|
16
|
+
f="$APP_ROOT/current/package.json"
|
|
17
|
+
[[ -f "$f" ]] || { echo "✗ pkg_name: 缺 $f(current 未落位或部署不完整)" >&2; return 1; }
|
|
18
|
+
n="$(grep -m1 '"name"' "$f" | sed -E 's/.*"name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/' || true)"
|
|
19
|
+
n="${n##*/}" # 剥 @scope/(如 @iscp/backend → backend)
|
|
20
|
+
[[ "$n" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || { echo "✗ pkg_name: 包名 $n 不可用作服务名(仅允许 [A-Za-z0-9._-])" >&2; return 1; }
|
|
21
|
+
printf '%s' "$n"
|
|
22
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# pm2 部署共用配置 —— 运维按环境改这一处即可。
|
|
3
|
-
# 配置项(
|
|
4
|
-
#
|
|
3
|
+
# 配置项(端口/密钥/阈值等)在部署位 APP_ROOT/config/config.yaml 里(ecosystem 注入
|
|
4
|
+
# CONFIG_DIR,跨版本共享),改 yaml + pm2 restart 即生效,不重新打包服务。
|
|
5
5
|
|
|
6
|
-
# pm2
|
|
7
|
-
|
|
6
|
+
# pm2 应用名:不再此处定值 —— 运行时由 lib.sh::pkg_name 从 current/package.json 的 name
|
|
7
|
+
# 派生(见文件末 resolve_app_root 之后的赋值)。
|
|
8
8
|
# 环境(决定读 config.<ENV>.yaml)
|
|
9
9
|
ENV="prod"
|
|
10
10
|
# 后端根目录(current / releases / data / logs 所在)
|
|
11
|
-
# -
|
|
11
|
+
# - 正常由顶层脚本(start/stop/install/versionswitch,运行时自定位)导出 APP_ROOT 环境变量,此处直读、锁定不询问。
|
|
12
12
|
# - 直接跑 mode 脚本(未经顶层封装)时,按脚本位置推导为 ops/<mode> 的上两级(即 APP_ROOT)。
|
|
13
13
|
# - 也可 export APP_ROOT=/xxx 显式指定(锁定、不询问)。
|
|
14
14
|
APP_ROOT="${APP_ROOT:-}"
|
|
@@ -27,7 +27,7 @@ derive_paths() {
|
|
|
27
27
|
LOG_DIR="$APP_ROOT/logs"
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
# ── 解析 APP_ROOT ──
|
|
30
|
+
# ── 解析 APP_ROOT ── 优先级:顶层脚本导出的环境变量 > 脚本位置推导。
|
|
31
31
|
# 正常由顶层 start/stop/install/versionswitch 导出 APP_ROOT,此处直读;直接跑 mode 脚本时按位置推导。
|
|
32
32
|
resolve_app_root() {
|
|
33
33
|
if [[ -z "${APP_ROOT:-}" ]]; then
|
|
@@ -47,3 +47,8 @@ resolve_app_root() {
|
|
|
47
47
|
|
|
48
48
|
# 各脚本 source 时自动解析一次(此后 CURRENT_DIR/... 等即就绪)
|
|
49
49
|
resolve_app_root
|
|
50
|
+
|
|
51
|
+
# 共享函数库 + pm2 应用名派生(须在 resolve_app_root 之后:pkg_name 读 current/package.json,
|
|
52
|
+
# APP_ROOT/current 就绪才能取)。export APP_NAME 可覆盖派生值(身份与包名分叉的项目用)。
|
|
53
|
+
source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/lib.sh"
|
|
54
|
+
APP_NAME="${APP_NAME:-$(pkg_name)}"
|
|
@@ -15,12 +15,36 @@ command -v "$PM2_BIN" >/dev/null 2>&1 || { echo "✗ pm2 不存在: $PM2_BIN (
|
|
|
15
15
|
|
|
16
16
|
mkdir -p "$LOG_DIR"
|
|
17
17
|
|
|
18
|
+
# 配置加密维护(每次部署):首部署选「开启加密」时生成本机根密钥;有根密钥则把部署位
|
|
19
|
+
# config.yaml 新明文就地加密(改明文 + 重跑 ./install.sh = 轮换闭环,见 docs/config-encryption.md)。
|
|
20
|
+
source "$DIR/../lib-encrypt.sh"
|
|
21
|
+
ensure_config_encryption
|
|
22
|
+
|
|
18
23
|
# JS 安全双引号转义(任意值合法):先转 \ 再转 "。替代 bash4.4+ 的 ${var@Q},
|
|
19
24
|
# 兼容服务器 bash 4.2(CentOS/RHEL 7)。生成的字符串直接进 ecosystem.config.cjs。
|
|
20
25
|
q() { local v="$1"; v="${v//\\/\\\\}"; v="${v//\"/\\\"}"; printf '"%s"' "$v"; }
|
|
21
26
|
|
|
22
|
-
#
|
|
23
|
-
#
|
|
27
|
+
# 配置密文根密钥:从 $APP_ROOT/.prod.secrets(600)读 CONFIG_SECRET_KEY 注入 ecosystem env。
|
|
28
|
+
# 缺文件 = 明文模式(不注,应用按 security.encrypt=false 接受明文);但部署位 config.yaml
|
|
29
|
+
# 带 ENC(...) 而无密钥 = 部署不完整,fail-fast(应用启动同样解不开,这里拦在部署期)。
|
|
30
|
+
SECRETS_FILE="$APP_ROOT/.prod.secrets"
|
|
31
|
+
SECRET_ENV_KV=""
|
|
32
|
+
if [[ -f "$SECRETS_FILE" ]]; then
|
|
33
|
+
chmod 600 "$SECRETS_FILE" 2>/dev/null || true
|
|
34
|
+
secret_key="$(sed -nE 's/^CONFIG_SECRET_KEY=//p' "$SECRETS_FILE" | head -1 | sed -E "s/^['\"]?//; s/['\"]?$//")"
|
|
35
|
+
[[ -n "$secret_key" ]] || { echo "✗ $SECRETS_FILE 无 CONFIG_SECRET_KEY(补齐后重跑)" >&2; exit 1; }
|
|
36
|
+
SECRET_ENV_KV="CONFIG_SECRET_KEY: $(q "$secret_key"), "
|
|
37
|
+
elif grep -q 'ENC(' "$APP_ROOT/config/config.yaml" 2>/dev/null; then
|
|
38
|
+
echo "✗ 缺 $SECRETS_FILE 但部署位 config.yaml 含 ENC(...) 密文(重跑 ./install.sh <release 包> 重新部署)" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
else
|
|
41
|
+
echo "▶ 明文模式:无 $SECRETS_FILE,ecosystem 不注 CONFIG_SECRET_KEY"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# 生成 ecosystem(pm2 读 cwd=current;配置经 CONFIG_DIR=APP_ROOT/config 指向部署位
|
|
45
|
+
# (跨版本共享,不随 release 换目录),configuration.ts 据此加载 base + 环境 overlay
|
|
46
|
+
# (config.yaml),改它 + pm2 restart 即生效;env 注入 DB_DIR 指向跨版本共享 data 目录 =
|
|
47
|
+
# APP_ROOT/data;SLOW_SQL_DIR/API_LOG_DIR 同口径指向 APP_ROOT/logs(切版本不散落)
|
|
24
48
|
ECO_PATH="$APP_ROOT/ecosystem.config.cjs"
|
|
25
49
|
echo " 写入 ecosystem → $ECO_PATH"
|
|
26
50
|
cat > "$ECO_PATH" <<EOF
|
|
@@ -34,7 +58,7 @@ module.exports = {
|
|
|
34
58
|
out_file: $(q "$LOG_DIR/$APP_NAME.log"),
|
|
35
59
|
error_file: $(q "$LOG_DIR/$APP_NAME.log"),
|
|
36
60
|
merge_logs: true,
|
|
37
|
-
env: { NODE_ENV: $(q "$ENV"), DB_DIR: $(q "$DATA_DIR") }
|
|
61
|
+
env: { NODE_ENV: $(q "$ENV"), CONFIG_DIR: $(q "$APP_ROOT/config"), DB_DIR: $(q "$DATA_DIR"), ${SECRET_ENV_KV}SLOW_SQL_DIR: $(q "$LOG_DIR/slowsql"), API_LOG_DIR: $(q "$LOG_DIR/apilog") }
|
|
38
62
|
}]
|
|
39
63
|
};
|
|
40
64
|
EOF
|
|
@@ -7,7 +7,7 @@ source "$DIR/config.sh"
|
|
|
7
7
|
echo "▶ 启动 $APP_NAME..."
|
|
8
8
|
if $PM2_BIN jlist 2>/dev/null | grep -q "\"name\":\"$APP_NAME\""; then
|
|
9
9
|
# restart(非 reload):本应用 exec_mode=fork,pm2 reload 仅供 cluster 模式、fork 下不可靠;
|
|
10
|
-
# restart 对 running/stopped 均幂等,且重启进程 →
|
|
10
|
+
# restart 对 running/stopped 均幂等,且重启进程 → 重读部署位 APP_ROOT/config/config.yaml(跨版本共享;改配置+./start.sh 轮换)。
|
|
11
11
|
$PM2_BIN restart "$APP_NAME" --update-env
|
|
12
12
|
echo "✓ restarted."
|
|
13
13
|
else
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
###
|
|
3
|
-
# Description backend 启动薄封装 —— 按
|
|
3
|
+
# Description backend 启动薄封装 —— 按 MODE 派发到 ops/<mode>/start.sh
|
|
4
4
|
###
|
|
5
|
-
#
|
|
5
|
+
# install.sh 首次部署拷到 APP_ROOT 根。零配置(不 baked):
|
|
6
|
+
# APP_ROOT = 脚本所在目录(整个 APP_ROOT 搬迁后开箱即用)
|
|
7
|
+
# MODE = $APP_ROOT/.mode(首次部署时由 install.sh 写入)
|
|
6
8
|
# 直接在 APP_ROOT 下跑: ./start.sh
|
|
7
9
|
set -euo pipefail
|
|
8
|
-
APP_ROOT="${
|
|
9
|
-
MODE="$
|
|
10
|
-
[[ -n "$
|
|
10
|
+
APP_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
MODE="$(cat "$APP_ROOT/.mode" 2>/dev/null || true)"
|
|
12
|
+
[[ -n "$MODE" ]] || { echo "✗ 缺 $APP_ROOT/.mode(先跑 ./install.sh 完成首次部署)" >&2; exit 1; }
|
|
11
13
|
export APP_ROOT MODE
|
|
12
14
|
exec bash "$APP_ROOT/ops/$MODE/start.sh"
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
###
|
|
3
|
-
# Description backend 状态薄封装 —— 按
|
|
3
|
+
# Description backend 状态薄封装 —— 按 MODE 派发到 ops/<mode>/status.sh
|
|
4
4
|
###
|
|
5
|
-
#
|
|
5
|
+
# install.sh 首次部署拷到 APP_ROOT 根。零配置(不 baked):
|
|
6
|
+
# APP_ROOT = 脚本所在目录(整个 APP_ROOT 搬迁后开箱即用)
|
|
7
|
+
# MODE = $APP_ROOT/.mode(首次部署时由 install.sh 写入)
|
|
6
8
|
# 直接在 APP_ROOT 下跑: ./status.sh
|
|
7
9
|
set -euo pipefail
|
|
8
|
-
APP_ROOT="${
|
|
9
|
-
MODE="$
|
|
10
|
-
[[ -n "$
|
|
10
|
+
APP_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
MODE="$(cat "$APP_ROOT/.mode" 2>/dev/null || true)"
|
|
12
|
+
[[ -n "$MODE" ]] || { echo "✗ 缺 $APP_ROOT/.mode(先跑 ./install.sh 完成首次部署)" >&2; exit 1; }
|
|
11
13
|
export APP_ROOT MODE
|
|
12
14
|
exec bash "$APP_ROOT/ops/$MODE/status.sh"
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
###
|
|
3
|
-
# Description backend 停止薄封装 —— 按
|
|
3
|
+
# Description backend 停止薄封装 —— 按 MODE 派发到 ops/<mode>/stop.sh
|
|
4
4
|
###
|
|
5
|
-
#
|
|
5
|
+
# install.sh 首次部署拷到 APP_ROOT 根。零配置(不 baked):
|
|
6
|
+
# APP_ROOT = 脚本所在目录(整个 APP_ROOT 搬迁后开箱即用)
|
|
7
|
+
# MODE = $APP_ROOT/.mode(首次部署时由 install.sh 写入)
|
|
6
8
|
# 直接在 APP_ROOT 下跑: ./stop.sh
|
|
7
9
|
set -euo pipefail
|
|
8
|
-
APP_ROOT="${
|
|
9
|
-
MODE="$
|
|
10
|
-
[[ -n "$
|
|
10
|
+
APP_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
MODE="$(cat "$APP_ROOT/.mode" 2>/dev/null || true)"
|
|
12
|
+
[[ -n "$MODE" ]] || { echo "✗ 缺 $APP_ROOT/.mode(先跑 ./install.sh 完成首次部署)" >&2; exit 1; }
|
|
11
13
|
export APP_ROOT MODE
|
|
12
14
|
exec bash "$APP_ROOT/ops/$MODE/stop.sh"
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# systemd 部署共用配置 —— 运维按环境改这一处即可,各脚本都 source 它。
|
|
3
3
|
# 改完不用重新打包服务:systemd unit 读的是这里的值(install 时生成),配置项
|
|
4
|
-
# (
|
|
4
|
+
# (端口/密钥/阈值等)在部署位 APP_ROOT/config/config.yaml 里(unit 注入 CONFIG_DIR,
|
|
5
|
+
# 跨版本共享),改 yaml + restart 即生效。
|
|
5
6
|
|
|
6
|
-
#
|
|
7
|
-
SERVICE
|
|
8
|
-
# 环境(
|
|
7
|
+
# 服务名:不再此处定值 —— 运行时由 lib.sh::pkg_name 从 current/package.json 的 name 派生
|
|
8
|
+
# (见文件末 resolve_app_root 之后的赋值;unit 文件名 = $SERVICE.service)。
|
|
9
|
+
# 环境(NODE_ENV;prod 不开 synchronize、启动自动跑迁移。yaml 文件选择见 configuration.ts:
|
|
10
|
+
# CONFIG_DIR 里 config.yaml(部署位固定名)优先)
|
|
9
11
|
ENV="prod"
|
|
10
12
|
# 后端根目录(current / releases / data / logs 所在)
|
|
11
|
-
# -
|
|
13
|
+
# - 正常由顶层脚本(start/stop/install/versionswitch,运行时自定位)导出 APP_ROOT 环境变量,此处直读、锁定不询问。
|
|
12
14
|
# - 直接跑 mode 脚本(未经顶层封装)时,按脚本位置推导为 ops/<mode> 的上两级(即 APP_ROOT)。
|
|
13
15
|
# - 也可 export APP_ROOT=/xxx 显式指定(锁定、不询问)。
|
|
14
16
|
APP_ROOT="${APP_ROOT:-}"
|
|
@@ -27,7 +29,7 @@ derive_paths() {
|
|
|
27
29
|
LOG_DIR="$APP_ROOT/logs"
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
# ── 解析 APP_ROOT ──
|
|
32
|
+
# ── 解析 APP_ROOT ── 优先级:顶层脚本导出的环境变量 > 脚本位置推导。
|
|
31
33
|
# 正常由顶层 start/stop/install/versionswitch 导出 APP_ROOT,此处直读;直接跑 mode 脚本时按位置推导。
|
|
32
34
|
resolve_app_root() {
|
|
33
35
|
if [[ -z "${APP_ROOT:-}" ]]; then
|
|
@@ -48,5 +50,10 @@ resolve_app_root() {
|
|
|
48
50
|
# 各脚本 source 时自动解析一次(此后 CURRENT_DIR/... 等即就绪)
|
|
49
51
|
resolve_app_root
|
|
50
52
|
|
|
53
|
+
# 共享函数库 + 服务名派生(须在 resolve_app_root 之后:pkg_name 读 current/package.json,
|
|
54
|
+
# APP_ROOT/current 就绪才能取)。export SERVICE 可覆盖派生值(身份与包名分叉的项目用)。
|
|
55
|
+
source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/lib.sh"
|
|
56
|
+
SERVICE="${SERVICE:-$(pkg_name)}"
|
|
57
|
+
|
|
51
58
|
# 非 root 自动 sudo(systemctl 写 /etc/systemd 需 root)
|
|
52
59
|
if [[ $EUID -ne 0 ]]; then SUDO="sudo"; else SUDO=""; fi
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# 安装 systemd 服务:生成 unit → daemon-reload → enable → restart(新代码生效)。
|
|
3
|
-
# 由顶层 install.sh/versionswitch.sh 派发(APP_ROOT
|
|
3
|
+
# 由顶层 install.sh/versionswitch.sh 派发(APP_ROOT 已导出,config.sh 锁定不弹菜单)。
|
|
4
4
|
# 前提:current 已就绪 —— current 是软链 → releases/<ver>-<ts>(install.sh IS_SYMLINK=1 + go_live ln -sfn),
|
|
5
5
|
# unit 的 WorkingDirectory=current 跟着软链走;每次部署重生成 unit + restart,新 release 生效。
|
|
6
6
|
set -euo pipefail
|
|
@@ -30,7 +30,30 @@ echo " 工作目录: $CURRENT_DIR"
|
|
|
30
30
|
|
|
31
31
|
$SUDO mkdir -p "$LOG_DIR"
|
|
32
32
|
|
|
33
|
+
# 配置加密维护(每次部署):首部署选「开启加密」时生成本机根密钥;有根密钥则把部署位
|
|
34
|
+
# config.yaml 新明文就地加密(改明文 + 重跑 ./install.sh = 轮换闭环,见 docs/config-encryption.md)。
|
|
35
|
+
source "$DIR/../lib-encrypt.sh"
|
|
36
|
+
ensure_config_encryption
|
|
37
|
+
|
|
38
|
+
# 配置密文根密钥:unit 以 EnvironmentFile 读 $APP_ROOT/.prod.secrets(600,仅属主可读),
|
|
39
|
+
# 不直接写进 unit(unit 在 /etc/systemd 下可被多读;secrets 文件独立收紧权限、轮换不动 unit)。
|
|
40
|
+
# 缺文件 = 明文模式(unit 不注 EnvironmentFile);但部署位 config.yaml 带 ENC(...) 而无密钥
|
|
41
|
+
# = 部署不完整,fail-fast(应用启动也会因解不开密文失败,这里拦在部署期更好排查)。
|
|
42
|
+
SECRETS_FILE="$APP_ROOT/.prod.secrets"
|
|
43
|
+
ENVFILE_LINE=""
|
|
44
|
+
if [[ -f "$SECRETS_FILE" ]]; then
|
|
45
|
+
$SUDO chmod 600 "$SECRETS_FILE" 2>/dev/null || true
|
|
46
|
+
ENVFILE_LINE="EnvironmentFile=$SECRETS_FILE"
|
|
47
|
+
elif grep -q 'ENC(' "$APP_ROOT/config/config.yaml" 2>/dev/null; then
|
|
48
|
+
err "缺 $SECRETS_FILE 但部署位 config.yaml 含 ENC(...) 密文(重跑 ./install.sh <release 包> 重新部署)"
|
|
49
|
+
exit 1
|
|
50
|
+
else
|
|
51
|
+
say "明文模式:无 $SECRETS_FILE,unit 不注 EnvironmentFile(应用按 security.encrypt=false 接受明文)"
|
|
52
|
+
fi
|
|
53
|
+
|
|
33
54
|
# unit 字段全走 current 软链 / APP_ROOT / 持久化 config.sh,跨版本逐字节不变。
|
|
55
|
+
# CONFIG_DIR=APP_ROOT/config:部署位配置(跨版本共享,不随 release 换目录),configuration.ts
|
|
56
|
+
# 据此加载 base + 环境 overlay(config.yaml);改它 + systemctl restart 即生效。
|
|
34
57
|
UNIT_PATH="/etc/systemd/system/$SERVICE.service"
|
|
35
58
|
NEW_UNIT="$(cat <<EOF
|
|
36
59
|
[Unit]
|
|
@@ -42,7 +65,11 @@ Type=simple
|
|
|
42
65
|
User=$RUN_USER
|
|
43
66
|
WorkingDirectory=$CURRENT_DIR
|
|
44
67
|
Environment=NODE_ENV=$ENV
|
|
68
|
+
Environment=CONFIG_DIR=$APP_ROOT/config
|
|
45
69
|
Environment=DB_DIR=$APP_ROOT/data
|
|
70
|
+
Environment=SLOW_SQL_DIR=$LOG_DIR/slowsql
|
|
71
|
+
Environment=API_LOG_DIR=$LOG_DIR/apilog
|
|
72
|
+
$ENVFILE_LINE
|
|
46
73
|
ExecStart=$NODE_BIN $CURRENT_DIR/dist/main.js
|
|
47
74
|
Restart=on-failure
|
|
48
75
|
RestartSec=5
|
|
@@ -6,7 +6,7 @@ source "$DIR/config.sh"
|
|
|
6
6
|
|
|
7
7
|
echo "▶ 启动 $SERVICE..."
|
|
8
8
|
# restart(非 start):服务在跑时 systemctl start 是 no-op、不重载配置;restart 始终重启
|
|
9
|
-
# 进程 →
|
|
9
|
+
# 进程 → 重读部署位 APP_ROOT/config/config.yaml(跨版本共享),适用于「服务器改配置后直接 ./start.sh 轮换」。
|
|
10
10
|
$SUDO systemctl restart "$SERVICE"
|
|
11
11
|
$SUDO systemctl --no-pager --full status "$SERVICE" | head -n 12 || true
|
|
12
12
|
echo "✓ restarted. 状态: ./status.sh | 日志: ./logs.sh"
|
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
###
|
|
3
3
|
# Description backend 版本切换薄封装 —— 按模式切换激活版本(可升可降,不限于回滚)
|
|
4
4
|
###
|
|
5
|
-
#
|
|
5
|
+
# install.sh 首次部署拷到 APP_ROOT 根。零配置(不 baked):
|
|
6
|
+
# APP_ROOT = 脚本所在目录(整个 APP_ROOT 搬迁后开箱即用)
|
|
7
|
+
# MODE = $APP_ROOT/.mode(首次部署时由 install.sh 写入)
|
|
6
8
|
# 直接在 APP_ROOT 下跑: ./versionswitch.sh [序号或版本片段]
|
|
7
9
|
#
|
|
8
|
-
# systemd/pm2(current=软链):列 releases 备份,选一个 → 原子改链 current →
|
|
10
|
+
# systemd/pm2(current=软链):列 releases 备份,选一个 → 原子改链 current →
|
|
9
11
|
# 派发 mode install 重启。旧 release 保留在 releases(可再切换)。data/logs 不动。
|
|
10
|
-
# docker(current=真实目录):列镜像 tag,选一个 → 停删容器 → 用目标 tag 重 run(env 同 .env)。
|
|
12
|
+
# docker(current=真实目录):列镜像 tag,选一个 → 停删容器 → 用目标 tag 重 run(env 同 .prod.docker.env)。
|
|
13
|
+
# 配置/端口不随版本走:部署位 APP_ROOT/config/config.yaml(跨版本共享,systemd/pm2 经
|
|
14
|
+
# CONFIG_DIR、docker 经挂载注入)与 .prod.docker.env 的 PORT(docker 权威)恒定,
|
|
15
|
+
# 切版本 = 只换代码,配置/端口原样。
|
|
11
16
|
set -euo pipefail
|
|
12
|
-
APP_ROOT="${
|
|
13
|
-
MODE="$
|
|
14
|
-
[[ -n "$
|
|
15
|
-
[[ -n "$MODE" ]] || MODE="$(cat "$APP_ROOT/.mode" 2>/dev/null || true)"
|
|
16
|
-
[[ -n "$MODE" ]] || { echo "✗ 缺 MODE" >&2; exit 1; }
|
|
17
|
+
APP_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
MODE="$(cat "$APP_ROOT/.mode" 2>/dev/null || true)"
|
|
19
|
+
[[ -n "$MODE" ]] || { echo "✗ 缺 $APP_ROOT/.mode(先跑 ./install.sh 完成首次部署)" >&2; exit 1; }
|
|
17
20
|
|
|
18
21
|
say() { printf '\033[36m▶\033[0m %s\n' "$*"; }
|
|
19
22
|
ok() { printf '\033[32m✓\033[0m %s\n' "$*"; }
|
|
@@ -35,14 +38,13 @@ sort_releases() {
|
|
|
35
38
|
done < <(ls -1d "$rel"/* 2>/dev/null || true) | sort -r -k1,1 | cut -f2-
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
#
|
|
39
|
-
# systemd/pm2:
|
|
40
|
-
# docker
|
|
41
|
+
# 后台服务访问地址:端口 = 部署位权威。
|
|
42
|
+
# systemd/pm2:APP_ROOT/config/config.yaml 的 app.port(CONFIG_DIR 注入,跨版本恒定);
|
|
43
|
+
# docker:运行容器注入的 PORT env → .prod.docker.env PORT → 镜像内 yaml 兜底。
|
|
41
44
|
# 前缀固定 /api。外网访问用服务器 IP 替换 localhost。
|
|
42
45
|
service_addr() {
|
|
43
46
|
local port=""
|
|
44
47
|
if [[ "$MODE" == "docker" ]]; then
|
|
45
|
-
# 优先取运行容器注入的 PORT env(选项 2/3 覆盖时 app 实际监听此端口);否则取镜像内 yaml
|
|
46
48
|
port="$(docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$CONTAINER_NAME" 2>/dev/null \
|
|
47
49
|
| sed -nE 's/^PORT=//p' | head -1 || true)"
|
|
48
50
|
if [[ -z "$port" ]]; then
|
|
@@ -50,7 +52,7 @@ service_addr() {
|
|
|
50
52
|
[[ -n "$img" ]] && port="$(image_yaml_port "$img")"
|
|
51
53
|
fi
|
|
52
54
|
else
|
|
53
|
-
local cfg="$APP_ROOT/
|
|
55
|
+
local cfg="$APP_ROOT/config/config.yaml"
|
|
54
56
|
[[ -f "$cfg" ]] && port="$(yaml_port "$cfg" || true)"
|
|
55
57
|
fi
|
|
56
58
|
printf 'http://localhost:%s/api' "${port:-?}"
|
|
@@ -70,8 +72,9 @@ yaml_port() {
|
|
|
70
72
|
printf '%s' "$p" | sed -E "s/^['\"]?//; s/['\"]?$//; s/^[[:space:]]+//; s/[[:space:]]+$//"
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
# 读 docker 镜像内 /app/config/config.prod.yaml 的 app.port(
|
|
74
|
-
#
|
|
75
|
+
# 读 docker 镜像内 /app/config/config.prod.yaml 的 app.port(镜像内兜底配置;部署位挂载
|
|
76
|
+
# 时的运行端口以 PORT env 为准,此函数仅在容器无 PORT env 的异常形态下兜底)。
|
|
77
|
+
# 镜像缺 config/ 或 docker 异常 → 空串(不阻断切换)。
|
|
75
78
|
image_yaml_port() {
|
|
76
79
|
local img="$1" text
|
|
77
80
|
command -v docker >/dev/null 2>&1 || { printf ''; return 1; }
|
|
@@ -83,65 +86,6 @@ image_yaml_port() {
|
|
|
83
86
|
' | sed -E "s/^['\"]?//; s/['\"]?$//; s/^[[:space:]]+//; s/[[:space:]]+$//"
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
# 规范化绝对路径(软链穿透;cd 失败返回空)。用于判断 current 是否指向同一 release。
|
|
87
|
-
real_dir() { (cd "$1" 2>/dev/null && pwd -P) || true; }
|
|
88
|
-
|
|
89
|
-
# 切换端口选择(仅当目标 != 当前运行版本时调用):端口以 config.prod.yaml 为权威(yaml 即
|
|
90
|
-
# 运行端口),切换到不同端口版本 = 运行端口将真实变更,nginx 若指向旧端口会不通。三选一:
|
|
91
|
-
# 1) 用目标端口(不改动,nginx 需指向目标端口)
|
|
92
|
-
# 2) 用当前运行端口(推荐,nginx 无需改动 —— 把目标 config app.port 改成当前端口)
|
|
93
|
-
# 3) 输入新端口(把目标 config app.port 改成新端口,nginx 需指向新端口)
|
|
94
|
-
# 结果写全局:
|
|
95
|
-
# CHOSEN_PORT = 最终运行端口(供后续 -p/healthcheck/service_addr 用)
|
|
96
|
-
# CHOSEN_PORT_EDIT=1 表示需要把目标 config.prod.yaml 的 app.port 改成 CHOSEN_PORT(选项 2/3)
|
|
97
|
-
# 任一端口读不到 → 跳过(不阻断)。取消 → die(未切链/未重建容器)。
|
|
98
|
-
choose_switch_port() {
|
|
99
|
-
local cur_port="$1" tgt_port="$2" ans np
|
|
100
|
-
CHOSEN_PORT=""; CHOSEN_PORT_EDIT=0
|
|
101
|
-
if [[ -z "$cur_port" || -z "$tgt_port" ]]; then
|
|
102
|
-
say "端口校验跳过(无法读取某方 yaml app.port,不阻断)"
|
|
103
|
-
return 0
|
|
104
|
-
fi
|
|
105
|
-
[[ "$cur_port" != "$tgt_port" ]] || return 0
|
|
106
|
-
echo
|
|
107
|
-
say "⚠ 端口不一致:当前运行端口 app.port=$cur_port,切换目标 app.port=$tgt_port"
|
|
108
|
-
echo " (端口以 config.prod.yaml 为权威;nginx 反代 localhost:<app.port>)"
|
|
109
|
-
echo " 1) 使用切换版本的端口 $tgt_port(切换后 nginx 需指向 $tgt_port)"
|
|
110
|
-
echo " 2) 使用运行版本的端口 $cur_port(推荐,nginx 无需改动)"
|
|
111
|
-
echo " 3) 重新输入新的端口"
|
|
112
|
-
read -rp " 选择 [2]: " ans || ans=""
|
|
113
|
-
ans="${ans:-2}"
|
|
114
|
-
case "$ans" in
|
|
115
|
-
1) CHOSEN_PORT="$tgt_port"; CHOSEN_PORT_EDIT=0 ;;
|
|
116
|
-
2) CHOSEN_PORT="$cur_port"; CHOSEN_PORT_EDIT=1 ;;
|
|
117
|
-
3) read -rp " 输入新端口: " np || np=""
|
|
118
|
-
[[ "$np" =~ ^[0-9]{2,5}$ ]] || die "端口非法: ${np:-空}(未切链/未重建容器)"
|
|
119
|
-
CHOSEN_PORT="$np"; CHOSEN_PORT_EDIT=1 ;;
|
|
120
|
-
*) die "已取消切换(未切链/未重建容器)" ;;
|
|
121
|
-
esac
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
# 把 release 自带 config.prod.yaml 的 app.port 改为指定值(yaml 为权威;systemd/pm2 切换后
|
|
125
|
-
# current→该 release,app 直读此 yaml 监听 CHOSEN_PORT,nginx 指向 CHOSEN_PORT 即通)。
|
|
126
|
-
# 只改顶层 app: 段内的 port: 行 —— 其它段(database/sso 等)自带的 port: 不动
|
|
127
|
-
# (与 install.sh 的 set_app_port 同口径;app 段无 port 行则报错不落盘)。
|
|
128
|
-
set_yaml_port() {
|
|
129
|
-
local cfg="$1" port="$2"
|
|
130
|
-
[[ -f "$cfg" ]] || { err "缺 $cfg,无法改端口"; return 1; }
|
|
131
|
-
if ! awk -v p="$port" '
|
|
132
|
-
/^[^[:space:]#]/ { inapp = ($0 ~ /^app:[[:space:]]*(#.*)?$/) }
|
|
133
|
-
inapp && !done && /^[[:space:]]*port:/ { sub(/:[[:space:]]*.*/, ": " p); done=1 }
|
|
134
|
-
{ print }
|
|
135
|
-
END { exit done ? 0 : 1 }
|
|
136
|
-
' "$cfg" > "$cfg.__port"; then
|
|
137
|
-
rm -f "$cfg.__port"
|
|
138
|
-
err "$cfg 的 app: 段内无 port: 行,无法改端口"
|
|
139
|
-
return 1
|
|
140
|
-
fi
|
|
141
|
-
mv "$cfg.__port" "$cfg"
|
|
142
|
-
ok "已更新 $cfg 的 app.port=$port"
|
|
143
|
-
}
|
|
144
|
-
|
|
145
89
|
# 内联选号:从数组 items + 用户输入(序号或片段)算 idx(不用 nameref,兼容旧 bash)
|
|
146
90
|
# 用法: pick_idx <数组名> <用户输入>;结果写全局 PICK_IDX
|
|
147
91
|
pick_idx() {
|
|
@@ -199,25 +143,8 @@ switch_symlink() {
|
|
|
199
143
|
echo
|
|
200
144
|
say "切换到: ${items[$PICK_IDX]}"
|
|
201
145
|
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
local cur_dir; cur_dir="$(real_dir "$APP_ROOT/current")"
|
|
205
|
-
local tgt_dir; tgt_dir="$(real_dir "$target")"
|
|
206
|
-
if [[ -n "$cur_dir" && -n "$tgt_dir" && "$cur_dir" != "$tgt_dir" ]]; then
|
|
207
|
-
choose_switch_port \
|
|
208
|
-
"$(yaml_port "$cur_dir/config/config.prod.yaml")" \
|
|
209
|
-
"$(yaml_port "$tgt_dir/config/config.prod.yaml")"
|
|
210
|
-
# 选项 2/3:把目标 release 自带 config.prod.yaml 的端口改为运行/新端口(yaml 为权威;
|
|
211
|
-
# 切换后 current→tgt_dir,app 直读此 yaml 监听 CHOSEN_PORT,nginx 指向 CHOSEN_PORT 不变即通)。
|
|
212
|
-
if [[ "${CHOSEN_PORT_EDIT:-0}" == "1" ]]; then
|
|
213
|
-
set_yaml_port "$tgt_dir/config/config.prod.yaml" "$CHOSEN_PORT"
|
|
214
|
-
fi
|
|
215
|
-
else
|
|
216
|
-
say "目标即当前版本,端口校验跳过"
|
|
217
|
-
fi
|
|
218
|
-
|
|
219
|
-
# config 随 release:切换即用目标版本自带的 config.yaml(运维在 current/config.yaml
|
|
220
|
-
# 上做的改动属于当时那个版本,不跨版本迁移)。data/ 跨版本共享,不动。
|
|
146
|
+
# 配置/端口不随版本走:部署位 APP_ROOT/config/config.yaml(跨版本共享,CONFIG_DIR 注入)
|
|
147
|
+
# 恒定,切换 = 只换代码,配置原样 —— 老模型「比对本/目标 release yaml 端口」已删除。
|
|
221
148
|
|
|
222
149
|
# 切链前确认目标完整(防竞态:列表后目录被删)
|
|
223
150
|
[[ -f "$target/dist/main.js" ]] \
|
|
@@ -254,7 +181,7 @@ switch_symlink() {
|
|
|
254
181
|
switch_docker() {
|
|
255
182
|
local dcfg="$APP_ROOT/ops/docker/config.sh"
|
|
256
183
|
[[ -f "$dcfg" ]] || die "缺 $dcfg"
|
|
257
|
-
# source docker config(APP_ROOT
|
|
184
|
+
# source docker config(APP_ROOT 已由顶层自定位)
|
|
258
185
|
export APP_ROOT
|
|
259
186
|
source "$dcfg"
|
|
260
187
|
|
|
@@ -275,52 +202,37 @@ switch_docker() {
|
|
|
275
202
|
echo
|
|
276
203
|
say "切换到镜像: $IMAGE:$tag"
|
|
277
204
|
|
|
278
|
-
# docker 端口权威 = .env 的 PORT(跨 tag 恒定,env 注入;切镜像不改端口)。
|
|
205
|
+
# docker 端口权威 = .prod.docker.env 的 PORT(跨 tag 恒定,env 注入;切镜像不改端口)。
|
|
279
206
|
# 当前运行版本 = 运行中容器所用镜像(docker inspect 取 ref);切换不改 current/,故不读 current/config。
|
|
280
207
|
local cur_img=""
|
|
281
208
|
cur_img="$(docker inspect --format '{{.Config.Image}}' "$CONTAINER_NAME" 2>/dev/null || true)"
|
|
282
209
|
|
|
283
|
-
# env:对齐本次 tag(PORT 在 .env 由运维权威,不覆盖;只对齐 TAG/DATA_DIR/CONTAINER_NAME)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
_sed_i "$APP_ROOT/.env" \
|
|
210
|
+
# env:对齐本次 tag(PORT 在 .prod.docker.env 由运维权威,不覆盖;只对齐 TAG/DATA_DIR/CONTAINER_NAME)
|
|
211
|
+
if [[ -f "$ENV_FILE" ]]; then
|
|
212
|
+
_sed_i "$ENV_FILE" \
|
|
287
213
|
-e "s|^TAG=.*|TAG=$tag|" \
|
|
288
214
|
-e "s|^DATA_DIR=.*|DATA_DIR=$APP_ROOT/data|" \
|
|
289
215
|
-e "s|^CONTAINER_NAME=.*|CONTAINER_NAME=$CONTAINER_NAME|"
|
|
290
|
-
env_flag="--env-file $APP_ROOT/.env"
|
|
291
216
|
else
|
|
292
|
-
err "缺 $
|
|
217
|
+
err "缺 $ENV_FILE,容器 env 无从注入"; exit 1
|
|
293
218
|
fi
|
|
294
219
|
|
|
295
|
-
# 仅当 .env PORT 与当前运行容器的 PORT 不同时告警(
|
|
296
|
-
# 端口权威 = .env PORT(
|
|
220
|
+
# 仅当 .prod.docker.env PORT 与当前运行容器的 PORT 不同时告警(运维改过 env,切换后端口将变,nginx 需指向新端口)。
|
|
221
|
+
# 端口权威 = .prod.docker.env PORT(config.sh source 时经 read_env_port 已赋 PORT/HOST_PORT/CONTAINER_PORT,
|
|
222
|
+
# run-container.sh 的 -p/-e PORT 均用此值)。
|
|
297
223
|
local env_port cur_port
|
|
298
|
-
env_port="$(grep -E '^PORT=' "$
|
|
224
|
+
env_port="$(grep -E '^PORT=' "$ENV_FILE" 2>/dev/null | cut -d= -f2- | tr -d '[:space:]')"
|
|
299
225
|
[[ -z "$env_port" ]] && env_port="3001"
|
|
300
226
|
cur_port="$(docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$CONTAINER_NAME" 2>/dev/null \
|
|
301
227
|
| sed -nE 's/^PORT=//p' | head -1 || true)"
|
|
302
228
|
[[ -z "$cur_port" ]] && cur_port="$(image_yaml_port "$cur_img" 2>/dev/null || true)"
|
|
303
229
|
if [[ -n "$cur_port" && "$cur_port" != "$env_port" ]]; then
|
|
304
|
-
say "⚠
|
|
230
|
+
say "⚠ $ENV_FILE 的 PORT=$env_port 与当前运行端口 $cur_port 不同(切换后端口将变为 $env_port,nginx 需指向 $env_port)"
|
|
305
231
|
fi
|
|
306
|
-
HOST_PORT="$env_port"
|
|
307
|
-
CONTAINER_PORT="$env_port"
|
|
308
|
-
local port_env=( -e PORT="$env_port" )
|
|
309
232
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
docker run -d \
|
|
314
|
-
--name "$CONTAINER_NAME" \
|
|
315
|
-
--restart "$RESTART_POLICY" \
|
|
316
|
-
-p "$HOST_PORT:$CONTAINER_PORT" \
|
|
317
|
-
$env_flag \
|
|
318
|
-
"${port_env[@]}" \
|
|
319
|
-
-e NODE_ENV=prod \
|
|
320
|
-
-e DB_DIR="$CONTAINER_DATA_DIR" \
|
|
321
|
-
-e UPLOAD_STORAGE_PATH="$CONTAINER_DATA_DIR/uploads" \
|
|
322
|
-
-v "$APP_ROOT/data:$CONTAINER_DATA_DIR" \
|
|
323
|
-
"$IMAGE:$tag"
|
|
233
|
+
# 容器创建命令收敛于 run-container.sh(与 start.sh/install.sh 共用,参数勿在调用方手拷)
|
|
234
|
+
source "$APP_ROOT/ops/docker/run-container.sh"
|
|
235
|
+
run_container "$IMAGE:$tag"
|
|
324
236
|
ok "版本切换完成 ($CONTAINER_NAME, tag $tag)"
|
|
325
237
|
echo " 后台服务: $(service_addr) (外网用服务器 IP 替换 localhost)"
|
|
326
238
|
echo " 状态: cd $APP_ROOT && ./ops/docker/status.sh"
|