aaden-cli 1.6.97 → 1.6.101

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.
@@ -70,7 +70,7 @@ network:
70
70
  gateway4: 192.168.121.1
71
71
  nameservers:
72
72
  addresses:
73
- - 192.168.121.1
73
+ - 8.8.8.8
74
74
  EOF
75
75
 
76
76
  echo "已生成配置文件:$CONFIG_FILE"
@@ -26,10 +26,14 @@ echo "END Aaden Deploy CONFIG"
26
26
  echo "Innerken322." | sudo -S rm -rf /etc/cron.d/aaden-cli-upgrade
27
27
  echo "Innerken322." | sudo -S touch conf
28
28
  echo "Innerken322." | sudo -S chmod 777 conf
29
+ # root 的 cron 默认 PATH 只有 /usr/bin:/bin,不含 /usr/local/bin;auto-upgrade 及其
30
+ # 子脚本里的 aaden-cli 裸命令需要它(crontab 不支持 $PATH 展开,只能写完整值)。
31
+ echo "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" >>conf
29
32
  echo "55 4 * * * sudo npm install aaden-cli -g" >>conf
30
- echo "59 4 * * * /usr/local/bin/aaden-cli auto-upgrade" >>conf
33
+ # auto-upgrade 的输出重定向到日志:cron 默认只在有本地邮件时才留输出,很多设备查不到。
34
+ echo "59 4 * * * /usr/local/bin/aaden-cli auto-upgrade >> /var/log/aaden-auto-upgrade.log 2>&1" >>conf
31
35
  echo "@reboot sleep 30 && sudo npm install aaden-cli -g" >>conf
32
- echo "@reboot sleep 60 &&/usr/local/bin/aaden-cli auto-upgrade" >>conf
36
+ echo "@reboot sleep 60 && /usr/local/bin/aaden-cli auto-upgrade >> /var/log/aaden-auto-upgrade.log 2>&1" >>conf
33
37
  echo "Innerken322." | sudo -S crontab -u root -r
34
38
  echo "Innerken322." | sudo -S crontab -u root conf
35
39
  echo "Innerken322." | sudo -S rm -f conf
File without changes
@@ -2,9 +2,12 @@
2
2
  # 轻量后端部署(light-update 的执行体)。
3
3
  # - composer.lock 门:远端 raw/PHP/composer.lock == 本机? 相同→code.zip(~1MB,保留vendor);不同→latest.zip全量
4
4
  # - **先下载校验,确认源存在后才做任何破坏性操作**(避免"删了迁移又没铺上")
5
+ # - 产物版本门:版本号只认包里的 package.json,不新于本机就拒绝部署(见下)
5
6
  # - 部署前 tar 备份 PHP 代码(排除 vendor);migrate 后健康检查;失败自动回滚
6
7
  # - 绝不 snap refresh 前端、绝不 apt
7
- # 退出码:0=成功(或无需更新),1=失败/已回滚。末行输出 DEPLOY_OK <ver> | ROLLED_BACK | ABORT_*
8
+ # 退出码:0=成功(或无需更新),1=失败/已回滚。末行输出 DEPLOY_OK <ver> | ROLLED_BACK | ABORT_* | SKIP_*
9
+ # 同一行也写进 /var/tmp/aaden-lu-status,供 index.js 读出来发到 cloud deviceLog
10
+ # —— 光靠退出码分不清"TSE 忙跳过"和"产物陈旧",deviceLog 里就只剩一句含糊的失败。
8
11
  set -uo pipefail
9
12
  S3="https://aaden-backend.s3.eu-central-1.amazonaws.com"
10
13
  STAGE=/var/tmp/aaden-lu-stage # 绝对路径:download 与 sudo cp 必须看到同一个目录(~ 在 sudo 下会变 /root)
@@ -12,10 +15,29 @@ BACKUP=/var/lib/aaden-php-backup
12
15
  PW='Innerken322.'
13
16
  sudoS(){ echo "$PW" | sudo -S bash -c "$*"; }
14
17
 
18
+ STATUS_FILE=/var/tmp/aaden-lu-status
19
+ rm -f "$STATUS_FILE" 2>/dev/null || true
20
+ # 每条退出路径都经 finish:状态既打到 stdout(人看日志),也落盘(index.js 读了发 deviceLog)。
21
+ finish(){ echo "$1"; echo "$1" > "$STATUS_FILE" 2>/dev/null || true; exit "$2"; }
22
+
23
+ # 点分数字版本比较:a > b 返回 0。与 index.js 的 versionGt 同一套语义('1.9.61' > '1.7.908')。
24
+ verGt(){ python3 -c "
25
+ import sys
26
+ pa=[int(x) if x.isdigit() else 0 for x in sys.argv[1].split('.')]
27
+ pb=[int(x) if x.isdigit() else 0 for x in sys.argv[2].split('.')]
28
+ n=max(len(pa),len(pb)); pa+=[0]*(n-len(pa)); pb+=[0]*(n-len(pb))
29
+ sys.exit(0 if pa>pb else 1)" "$1" "$2" 2>/dev/null; }
30
+
31
+ # 读 package.json 的 version;读不到输出空串。
32
+ pkgVer(){ python3 -c "
33
+ import json,sys
34
+ try: print(json.load(open(sys.argv[1]))['version'])
35
+ except Exception: print('')" "$1" 2>/dev/null; }
36
+
15
37
  # 早失败:老 aaden 设备的 sudo 密码可能不是共享密码。先探一次 ——
16
38
  # 否则会一路穿到 ABORT_DOWNLOAD,把"密码不符"误报成"下载问题",排障跑偏。
17
39
  # NOPASSWD 设备此探测同样通过(sudo 不问,管道密码被忽略)。
18
- if ! echo "$PW" | sudo -S true 2>/dev/null; then echo "ABORT_SUDO_AUTH (aaden sudo 密码不符?)"; exit 1; fi
40
+ if ! echo "$PW" | sudo -S true 2>/dev/null; then finish "ABORT_SUDO_AUTH (aaden sudo 密码不符?)" 1; fi
19
41
 
20
42
  # ---- composer.lock 门 ----
21
43
  REMOTE_LOCK=$(curl -s "$S3/raw/PHP/composer.lock" --max-time 30)
@@ -34,14 +56,32 @@ sudoS "rm -rf $STAGE && mkdir -p $STAGE && chown $(id -un):$(id -gn) $STAGE"
34
56
  # code.zip 仅 ~1MB,curl 瞬间完成;latest.zip 全量走 curl 稍慢但那是罕见 fallback。
35
57
  ZIP="$STAGE/$ARTIFACT"
36
58
  curl -fsSL "$S3/$ARTIFACT" -o "$ZIP" --max-time 1200
37
- if [ ! -s "$ZIP" ]; then echo "下载失败或为空: $ARTIFACT"; echo "ABORT_DOWNLOAD"; exit 1; fi
59
+ if [ ! -s "$ZIP" ]; then echo "下载失败或为空: $ARTIFACT"; finish "ABORT_DOWNLOAD ($ARTIFACT)" 1; fi
38
60
  unzip -q -o "$ZIP" -d "$STAGE" && rm -f "$ZIP"
39
61
 
40
62
  # ---- 关键:破坏任何东西之前,先确认源 PHP 代码确实在 ----
41
63
  if [ ! -f "$STAGE/PHP/composer.lock" ] || [ ! -d "$STAGE/PHP" ]; then
42
64
  echo "源包缺 PHP/ 或 composer.lock,中止(未改动任何文件)"
43
- echo "ABORT_BAD_ARTIFACT"; exit 1
65
+ finish "ABORT_BAD_ARTIFACT ($ARTIFACT)" 1
66
+ fi
67
+
68
+ # ---- 产物版本门:版本号只认包里的 package.json ----
69
+ # 早先是部署完单独 curl raw/package.json 当版本号,那是两个独立对象,可以不一致:
70
+ # 2026-09 就出现过 raw 已经 1.9.69、code.zip 还停在 8/19 的 1.9.67(发布工作流挂在
71
+ # GitHub Release 上,而那两次发版只推了 tag 没建 Release)。设备于是铺了旧代码、
72
+ # 盖上新版本号,上报 1.9.69 —— 版本门(本机==远端)从此永远跳过,再也不会自愈。
73
+ # 现在:包里没版本号、或不比本机新,就在动任何文件之前拒绝,并把状态上报出去。
74
+ ART_VER=$(pkgVer "$STAGE/package.json")
75
+ LOCAL_VER=$(pkgVer /var/www/html/package.json)
76
+ if [ -z "$ART_VER" ]; then
77
+ echo "源包里没有 package.json,取不到版本号,中止(未改动任何文件)"
78
+ finish "ABORT_NO_VERSION ($ARTIFACT)" 1
79
+ fi
80
+ if ! verGt "$ART_VER" "${LOCAL_VER:-0.0.0}"; then
81
+ echo "源包版本 $ART_VER 不比本机 ${LOCAL_VER:-未知} 新 —— 产物陈旧或发布流水线没跑,中止(未改动任何文件)"
82
+ finish "ABORT_STALE_ARTIFACT (包 $ART_VER <= 本机 ${LOCAL_VER:-未知})" 1
44
83
  fi
84
+ echo "产物版本门通过:${LOCAL_VER:-未知} -> $ART_VER"
45
85
 
46
86
  # ---- TSE 门:绝不在有进行中的交易/TSE 签名时部署 ----
47
87
  # 结账把 START/FINISH 塞进 TseExecuteQueue,后台 worker 签名后置 finished=1(出错也置 1)。
@@ -62,10 +102,10 @@ fi
62
102
  if [ -z "$DUE" ]; then
63
103
  # 查不到 = 无法确认 TSE 状态 → 对财政数据保守跳过(不部署也不冒险打断签名)。
64
104
  # 非破坏性:此刻还没动任何文件。版本停滞会被 fleet-rollout-status 监控发现。
65
- echo "TSE 队列查询失败,保守跳过本轮(未改动任何文件)"; echo "SKIP_TSE_QUERY"; exit 0
105
+ echo "TSE 队列查询失败,保守跳过本轮(未改动任何文件)"; finish "SKIP_TSE_QUERY" 0
66
106
  fi
67
107
  if [ "$DUE" -gt 0 ]; then
68
- echo "有进行中的交易/TSE 签名 (due=$DUE),跳过本轮,等下个周期"; echo "SKIP_TSE_BUSY"; exit 0
108
+ echo "有进行中的交易/TSE 签名 (due=$DUE),跳过本轮,等下个周期"; finish "SKIP_TSE_BUSY (due=$DUE)" 0
69
109
  fi
70
110
  echo "TSE 队列空闲 (due=0),可安全部署"
71
111
 
@@ -76,7 +116,9 @@ sudoS "rm -rf $BACKUP && mkdir -p $BACKUP && tar czf $BACKUP/php-code.tgz --excl
76
116
  sudoS "rm -rf /var/www/html/PHP/DB/migrations/*"
77
117
  if [ "$ARTIFACT" = "code.zip" ]; then
78
118
  sudoS "cp -rf $STAGE/PHP/. /var/www/html/PHP/"
79
- sudoS "curl -s '$S3/raw/package.json' -o /var/www/html/package.json --max-time 30"
119
+ # 版本号取包里的那份 —— 与刚铺下去的代码同源同批,不可能对不上。
120
+ # (旧版这里是 curl raw/package.json,正是"旧代码贴新版本号"的来源。)
121
+ sudoS "cp -f $STAGE/package.json /var/www/html/package.json"
80
122
  else
81
123
  sudoS "cp -rf $STAGE/. /var/www/html/"
82
124
  fi
@@ -96,7 +138,7 @@ done
96
138
  if [ "$HEALTHY" != "1" ]; then
97
139
  echo "健康检查失败 → 回滚"
98
140
  sudoS "tar xzf $BACKUP/php-code.tgz -C /var/www/html && chown -hR www-data:www-data /var/www/html"
99
- sudoS "rm -rf $STAGE"; echo "ROLLED_BACK"; exit 1
141
+ sudoS "rm -rf $STAGE"; finish "ROLLED_BACK (回到 ${LOCAL_VER:-未知})" 1
100
142
  fi
101
143
  sudoS "rm -rf $STAGE"
102
- echo "DEPLOY_OK $(python3 -c "import json;print(json.load(open('/var/www/html/package.json'))['version'])" 2>/dev/null)"
144
+ finish "DEPLOY_OK $(pkgVer /var/www/html/package.json)" 0
File without changes
File without changes
@@ -1,5 +1,14 @@
1
1
  #!/bin/bash
2
2
 
3
+ # cron 的默认 PATH 只有 /usr/bin:/bin,不含 /usr/local/bin —— 而 npm 全局的 aaden-cli
4
+ # 就装在这里。不处理的话下面两条命令会 command not found:auto-upgrade 每天静默跳过
5
+ # patch 与 light-update,表现为"CLI 一直在升级、后端却永远不动"(2026-09 全网故障根因)。
6
+ #
7
+ # 用前置而非覆盖,并保持命令为裸名:历史上 aaden-cli 也出现在过 /usr/bin
8
+ # (见 2022 年的 ln -s /usr/bin/aaden-cli /usr/local/bin/aaden-cli),
9
+ # 写死 /usr/local/bin/aaden-cli 在那种设备上会失败,而保留 "$PATH" 两者都能命中。
10
+ export PATH="/usr/local/bin:$PATH"
11
+
3
12
  mkdir -p /etc/aaden/config
4
13
  chmod -R 777 /etc/aaden/config
5
14
 
package/dist/index.js CHANGED
@@ -18507,7 +18507,7 @@ module.exports = JSON.parse('{"name":"axios","version":"0.19.2","description":"P
18507
18507
  /***/ ((module) => {
18508
18508
 
18509
18509
  "use strict";
18510
- module.exports = {"i8":"1.6.97"};
18510
+ module.exports = {"i8":"1.6.101"};
18511
18511
 
18512
18512
  /***/ })
18513
18513
 
@@ -18811,12 +18811,27 @@ async function patch() {
18811
18811
  return !readyList.includes(p)
18812
18812
  })
18813
18813
 
18814
- if (runList.length > 0) {
18815
- await runMultiple(runList)
18816
- readyList = readyList.concat(runList)
18814
+ // 逐个执行并检查退出码:失败的补丁绝不写进 patchLog.json,下次才会重试。
18815
+ // 旧实现把整批用 && 串成一条、又不看退出码 —— 任一补丁报错或命令找不到时,
18816
+ // 后面的补丁根本没跑,却全被记为已完成(2026-09 全网 patch 静默失效就是这么被掩盖的)。
18817
+ for (const p of runList) {
18818
+ const {result, code} = await runMultiple([p])
18819
+ if (!result) {
18820
+ // debug 模式只打印不执行,不能因此把补丁标记成已完成。
18821
+ return
18822
+ }
18823
+ if (code !== 0) {
18824
+ console.error(('补丁执行失败,未记录、下次重试: ' + p + ' (exit ' + code + ')').bgRed)
18825
+ try {
18826
+ await logAction('patch 执行失败: ' + p + ' (exit ' + code + ')', code || 1)
18827
+ } catch (e) {
18828
+ console.error('上报 patch 失败异常: ' + (e.message || e))
18829
+ }
18830
+ return
18831
+ }
18832
+ readyList.push(p)
18817
18833
  fs.writeFileSync(patchRecordFilePath, JSON.stringify(readyList))
18818
18834
  }
18819
-
18820
18835
  }
18821
18836
 
18822
18837
  async function runMultiple(files) {
@@ -18825,7 +18840,7 @@ async function runMultiple(files) {
18825
18840
  arr.push('./' + f)
18826
18841
  return arr
18827
18842
  }, [])
18828
- await cli.executeInShell(command, program.debug)
18843
+ return cli.executeInShell(command, program.debug)
18829
18844
  }
18830
18845
 
18831
18846
  async function run(filePath) {
@@ -19079,13 +19094,16 @@ async function update(channel = '') {
19079
19094
  * 与 `update`(走 update.sh)的区别:
19080
19095
  * - **不 snap refresh aaden-vue-app**(不会重启收银前端)
19081
19096
  * - **不 apt install**
19082
- * - 三道闸门保证「营业时间被触发也安全」:
19097
+ * - 四道闸门保证「营业时间被触发也安全」:
19083
19098
  * ① 版本门:S3 版本 == 本机版本 → 跳过(已最新,重启也不白部署)
19084
19099
  * ② 灰度门:只部署到 cloud `deviceLog.maxVersion` 允许的版本(-1=不设限)
19085
19100
  * → 可先给少数设备设 maxVersion 做金丝雀,其余自动跳过
19086
- * ③ TSE 门:有进行中的交易/TSE 签名 → 跳过(在 lightUpdate.sh 里查 TseExecuteQueue,
19101
+ * ③ 产物版本门:包里的 package.json 不比本机新 → 拒绝(在 lightUpdate.sh 里,下载解压之后、
19102
+ * 动任何文件之前)。挡的是"发布流水线没跑、S3 上产物陈旧"这种情况 —— 见下面的说明。
19103
+ * ④ TSE 门:有进行中的交易/TSE 签名 → 跳过(在 lightUpdate.sh 里查 TseExecuteQueue,
19087
19104
  * 紧贴 cp 之前,避免"下载期间开了新交易"的竞态;开桌吃饭不阻塞,只有正在签的交易阻塞)
19088
- * - 结果(部署/跳过/失败)经 logAction 写入 cloud 事件日志,可在 deviceLog/eventList 观测
19105
+ * - 结果(部署/跳过/失败)经 logAction 写入 cloud 事件日志,可在 deviceLog/eventList 观测;
19106
+ * 具体状态由 lightUpdate.sh 写在 /var/tmp/aaden-lu-status,这里读出来一并上报
19089
19107
  */
19090
19108
  async function lightUpdate() {
19091
19109
  try {
@@ -19115,8 +19133,10 @@ async function lightUpdate() {
19115
19133
  // 有进行中的交易/TSE 签名就跳过。放在脚本里而非这里,是为了紧贴破坏性 cp、
19116
19134
  // 消掉"这里查过没单 → 下载几秒 → 期间开了新交易 → cp 打断签名"的竞态窗口。
19117
19135
 
19118
- // lightUpdate.sh 自己完成:TSE 门 + composer.lock 门(选 code.zip 或 latest.zip) + 下载 + 备份
19119
- // + 部署 + migrate + 健康检查/失败回滚。退出码:0=成功,1=已回滚。
19136
+ // lightUpdate.sh 自己完成:composer.lock 门 + 下载 + 产物版本门 + TSE 门 + 备份
19137
+ // + 部署 + migrate + 健康检查/失败回滚。退出码:0=成功/跳过,1=拒绝或已回滚。
19138
+ // 先删掉上轮的状态文件,免得脚本没跑起来时把旧状态当成本轮结果报上去。
19139
+ try { fs.unlinkSync(LU_STATUS_FILE) } catch (e) { /* 本来就没有,正常 */ }
19120
19140
  const command = [
19121
19141
  ...getPrivilegeCMD('AadenDeploy/lightUpdate.sh'),
19122
19142
  'cd AadenDeploy',
@@ -19124,13 +19144,17 @@ async function lightUpdate() {
19124
19144
  ]
19125
19145
  const {code} = await cli.executeInShell(command, program.debug)
19126
19146
 
19147
+ // 退出码只有 0/1,分不清"TSE 忙跳过"和"产物陈旧被拒"。脚本把末行状态写了盘,这里读出来
19148
+ // 一起发 deviceLog —— 否则像这次"发布流水线没跑"这种故障在云端就是一片沉默。
19149
+ const status = readLightUpdateStatus()
19150
+ const suffix = status ? ' [' + status + ']' : ''
19127
19151
  const newVersion = readBackendVersionFresh()
19128
19152
  if (code !== 0) {
19129
- await logAction('light-update 失败/已回滚 (仍 ' + newVersion + ')', code || 1)
19153
+ await logAction('light-update 失败/未部署 (仍 ' + newVersion + ')' + suffix, code || 1)
19130
19154
  } else if (newVersion !== localVersion) {
19131
- await logAction('light-update 完成 ' + localVersion + ' -> ' + newVersion, 0)
19155
+ await logAction('light-update 完成 ' + localVersion + ' -> ' + newVersion + suffix, 0)
19132
19156
  } else {
19133
- await logAction('light-update 无实际变化 (' + newVersion + ')', 0)
19157
+ await logAction('light-update 无实际变化 (' + newVersion + ')' + suffix, 0)
19134
19158
  }
19135
19159
  } catch (e) {
19136
19160
  console.error(e)
@@ -19161,6 +19185,18 @@ async function getDeviceMaxVersion(deviceId) {
19161
19185
  }
19162
19186
  }
19163
19187
 
19188
+ /** lightUpdate.sh 落盘的末行状态(DEPLOY_OK / ROLLED_BACK / ABORT_* / SKIP_*)。 */
19189
+ const LU_STATUS_FILE = '/var/tmp/aaden-lu-status'
19190
+
19191
+ /** 读 lightUpdate.sh 的最终状态;读不到返回空串(脚本没跑到 finish,或权限异常)。 */
19192
+ function readLightUpdateStatus() {
19193
+ try {
19194
+ return fs.readFileSync(LU_STATUS_FILE, 'utf8').trim().split('\n').pop().slice(0, 200)
19195
+ } catch (e) {
19196
+ return ''
19197
+ }
19198
+ }
19199
+
19164
19200
  /** 直接读文件、绕过 require 缓存 —— 部署后读到的必须是新版本号,不能是进程启动时缓存的旧值。 */
19165
19201
  function readBackendVersionFresh() {
19166
19202
  try {
package/index.js CHANGED
@@ -230,12 +230,27 @@ async function patch() {
230
230
  return !readyList.includes(p)
231
231
  })
232
232
 
233
- if (runList.length > 0) {
234
- await runMultiple(runList)
235
- readyList = readyList.concat(runList)
233
+ // 逐个执行并检查退出码:失败的补丁绝不写进 patchLog.json,下次才会重试。
234
+ // 旧实现把整批用 && 串成一条、又不看退出码 —— 任一补丁报错或命令找不到时,
235
+ // 后面的补丁根本没跑,却全被记为已完成(2026-09 全网 patch 静默失效就是这么被掩盖的)。
236
+ for (const p of runList) {
237
+ const {result, code} = await runMultiple([p])
238
+ if (!result) {
239
+ // debug 模式只打印不执行,不能因此把补丁标记成已完成。
240
+ return
241
+ }
242
+ if (code !== 0) {
243
+ console.error(('补丁执行失败,未记录、下次重试: ' + p + ' (exit ' + code + ')').bgRed)
244
+ try {
245
+ await logAction('patch 执行失败: ' + p + ' (exit ' + code + ')', code || 1)
246
+ } catch (e) {
247
+ console.error('上报 patch 失败异常: ' + (e.message || e))
248
+ }
249
+ return
250
+ }
251
+ readyList.push(p)
236
252
  fs.writeFileSync(patchRecordFilePath, JSON.stringify(readyList))
237
253
  }
238
-
239
254
  }
240
255
 
241
256
  async function runMultiple(files) {
@@ -244,7 +259,7 @@ async function runMultiple(files) {
244
259
  arr.push('./' + f)
245
260
  return arr
246
261
  }, [])
247
- await cli.executeInShell(command, program.debug)
262
+ return cli.executeInShell(command, program.debug)
248
263
  }
249
264
 
250
265
  async function run(filePath) {
@@ -498,13 +513,16 @@ async function update(channel = '') {
498
513
  * 与 `update`(走 update.sh)的区别:
499
514
  * - **不 snap refresh aaden-vue-app**(不会重启收银前端)
500
515
  * - **不 apt install**
501
- * - 三道闸门保证「营业时间被触发也安全」:
516
+ * - 四道闸门保证「营业时间被触发也安全」:
502
517
  * ① 版本门:S3 版本 == 本机版本 → 跳过(已最新,重启也不白部署)
503
518
  * ② 灰度门:只部署到 cloud `deviceLog.maxVersion` 允许的版本(-1=不设限)
504
519
  * → 可先给少数设备设 maxVersion 做金丝雀,其余自动跳过
505
- * ③ TSE 门:有进行中的交易/TSE 签名 → 跳过(在 lightUpdate.sh 里查 TseExecuteQueue,
520
+ * ③ 产物版本门:包里的 package.json 不比本机新 → 拒绝(在 lightUpdate.sh 里,下载解压之后、
521
+ * 动任何文件之前)。挡的是"发布流水线没跑、S3 上产物陈旧"这种情况 —— 见下面的说明。
522
+ * ④ TSE 门:有进行中的交易/TSE 签名 → 跳过(在 lightUpdate.sh 里查 TseExecuteQueue,
506
523
  * 紧贴 cp 之前,避免"下载期间开了新交易"的竞态;开桌吃饭不阻塞,只有正在签的交易阻塞)
507
- * - 结果(部署/跳过/失败)经 logAction 写入 cloud 事件日志,可在 deviceLog/eventList 观测
524
+ * - 结果(部署/跳过/失败)经 logAction 写入 cloud 事件日志,可在 deviceLog/eventList 观测;
525
+ * 具体状态由 lightUpdate.sh 写在 /var/tmp/aaden-lu-status,这里读出来一并上报
508
526
  */
509
527
  async function lightUpdate() {
510
528
  try {
@@ -534,8 +552,10 @@ async function lightUpdate() {
534
552
  // 有进行中的交易/TSE 签名就跳过。放在脚本里而非这里,是为了紧贴破坏性 cp、
535
553
  // 消掉"这里查过没单 → 下载几秒 → 期间开了新交易 → cp 打断签名"的竞态窗口。
536
554
 
537
- // lightUpdate.sh 自己完成:TSE 门 + composer.lock 门(选 code.zip 或 latest.zip) + 下载 + 备份
538
- // + 部署 + migrate + 健康检查/失败回滚。退出码:0=成功,1=已回滚。
555
+ // lightUpdate.sh 自己完成:composer.lock 门 + 下载 + 产物版本门 + TSE 门 + 备份
556
+ // + 部署 + migrate + 健康检查/失败回滚。退出码:0=成功/跳过,1=拒绝或已回滚。
557
+ // 先删掉上轮的状态文件,免得脚本没跑起来时把旧状态当成本轮结果报上去。
558
+ try { fs.unlinkSync(LU_STATUS_FILE) } catch (e) { /* 本来就没有,正常 */ }
539
559
  const command = [
540
560
  ...getPrivilegeCMD('AadenDeploy/lightUpdate.sh'),
541
561
  'cd AadenDeploy',
@@ -543,13 +563,17 @@ async function lightUpdate() {
543
563
  ]
544
564
  const {code} = await cli.executeInShell(command, program.debug)
545
565
 
566
+ // 退出码只有 0/1,分不清"TSE 忙跳过"和"产物陈旧被拒"。脚本把末行状态写了盘,这里读出来
567
+ // 一起发 deviceLog —— 否则像这次"发布流水线没跑"这种故障在云端就是一片沉默。
568
+ const status = readLightUpdateStatus()
569
+ const suffix = status ? ' [' + status + ']' : ''
546
570
  const newVersion = readBackendVersionFresh()
547
571
  if (code !== 0) {
548
- await logAction('light-update 失败/已回滚 (仍 ' + newVersion + ')', code || 1)
572
+ await logAction('light-update 失败/未部署 (仍 ' + newVersion + ')' + suffix, code || 1)
549
573
  } else if (newVersion !== localVersion) {
550
- await logAction('light-update 完成 ' + localVersion + ' -> ' + newVersion, 0)
574
+ await logAction('light-update 完成 ' + localVersion + ' -> ' + newVersion + suffix, 0)
551
575
  } else {
552
- await logAction('light-update 无实际变化 (' + newVersion + ')', 0)
576
+ await logAction('light-update 无实际变化 (' + newVersion + ')' + suffix, 0)
553
577
  }
554
578
  } catch (e) {
555
579
  console.error(e)
@@ -580,6 +604,18 @@ async function getDeviceMaxVersion(deviceId) {
580
604
  }
581
605
  }
582
606
 
607
+ /** lightUpdate.sh 落盘的末行状态(DEPLOY_OK / ROLLED_BACK / ABORT_* / SKIP_*)。 */
608
+ const LU_STATUS_FILE = '/var/tmp/aaden-lu-status'
609
+
610
+ /** 读 lightUpdate.sh 的最终状态;读不到返回空串(脚本没跑到 finish,或权限异常)。 */
611
+ function readLightUpdateStatus() {
612
+ try {
613
+ return fs.readFileSync(LU_STATUS_FILE, 'utf8').trim().split('\n').pop().slice(0, 200)
614
+ } catch (e) {
615
+ return ''
616
+ }
617
+ }
618
+
583
619
  /** 直接读文件、绕过 require 缓存 —— 部署后读到的必须是新版本号,不能是进程启动时缓存的旧值。 */
584
620
  function readBackendVersionFresh() {
585
621
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aaden-cli",
3
- "version": "1.6.97",
3
+ "version": "1.6.101",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
File without changes