#!/bin/bash

# cron 的默认 PATH 只有 /usr/bin:/bin，不含 /usr/local/bin —— 而 npm 全局的 aaden-cli
# 就装在这里。不处理的话下面两条命令会 command not found：auto-upgrade 每天静默跳过
# patch 与 light-update，表现为"CLI 一直在升级、后端却永远不动"（2026-09 全网故障根因）。
#
# 用前置而非覆盖，并保持命令为裸名：历史上 aaden-cli 也出现在过 /usr/bin
# （见 2022 年的 ln -s /usr/bin/aaden-cli /usr/local/bin/aaden-cli），
# 写死 /usr/local/bin/aaden-cli 在那种设备上会失败，而保留 "$PATH" 两者都能命中。
export PATH="/usr/local/bin:$PATH"

# 当前目录切到 /：auto-upgrade 是在包目录（.../node_modules/aaden-cli/AadenDeploy）里起的本脚本，
# 而 npm 重装会把整个包目录换掉——当前目录没了，之后的 npm 一启动就 ENOENT uv_cwd（10-08 的 1541/1333/1171：
# npm=1 npm2=1 "syscall: 'uv_cwd'"）。下面所有步骤都不依赖当前目录。
cd / || true

# 每一步的退出码和耗时写进 $AADEN_UPGRADE_STEPS（auto-upgrade 读出来随「自动执行了更新aaden-cli」事件上报），
# 失败的那步再带上输出末尾。起因：2026-10 约 35 台在 cron 里 auto-upgrade 3 秒跑完、后端从不更新，
# 同样的命令 SSH 进去跑却正常，而 /var/log/aaden-auto-upgrade.log 远程读不到。
# 输出照常打到 stdout（cron 重定向进日志）；单独跑这个脚本、没设变量时写 /dev/null。
STEPS="${AADEN_UPGRADE_STEPS:-/dev/null}"
# 失败时摘要优先取 npm 的 code/syscall/path 行：npm 输出的最后两行永远是「完整日志在 …debug-0.log」，看不出原因。
step() {
  local name=$1 start=$SECONDS out code ex
  shift
  out=$("$@" 2>&1)
  code=$?
  printf '%s\n' "$out"
  if [ "$code" = 0 ]; then
    echo "$name=0/$((SECONDS - start))s" >>"$STEPS"
  else
    ex=$(printf '%s\n' "$out" | grep -E 'npm (ERR!|error) (code|errno|syscall|path|dest) ' | head -n 4)
    [ -n "$ex" ] || ex=$(printf '%s' "$out" | tail -n 2)
    echo "$name=$code/$((SECONDS - start))s($(printf '%s' "$ex" | tr '\n' ' ' | cut -c1-160))" >>"$STEPS"
  fi
  return "$code"
}


# 和 aaden-cli 其他地方一样用 -S 从 stdin 喂密码。aaden 没有免密 sudo 的设备上，裸 sudo 没有终端读不了密码、直接失败
# （10-05 步骤上报：clean=1 npm=1 "a terminal is required to read the password"），npm 永远装不上，CLI 卡在 1.6.101。
# root 跑（cron）时 sudo 不要密码，多喂的一行没人读，无害。
sudoS() { echo "Innerken322." | sudo -S "$@"; }

# 以 aaden 跑（远程 selfUpgrade、云端命令、do-log 补跑）时改不了 root 建的目录权限，也要 sudo
step config sudoS sh -c 'mkdir -p /etc/aaden/config && chmod -R 777 /etc/aaden/config'

# 别的 aaden-cli npm 安装还在跑时，不能动手：
# crontab 里 04:55 和开机 sleep 30 各有一条单独的 `npm install aaden-cli -g`，慢的设备上要跑好几分钟，
# 正好撞上 04:59 / 开机 sleep 60 的这里。两个 npm 同时重装同一个包 —— 我们这个 1 秒内 ENOTEMPTY（退出码 217），
# 那一刻 aaden-cli 已被对方挪走，后面 patch/light-update/appcenter 全部 command not found；
# 运气差的设备两边都失败，aaden-cli 整个没了，do-log 从此停报（2026-10-07：1333、1541）。
# SSH 进去手动跑不会撞上，所以以前一直以为是「cron 环境问题」。
# 认进程看命令行：npm 自己的进程标题不一定带参数，但 cron 起的 sh -c / sudo 外壳一直活到 npm 结束。
# 本脚本的祖先不算 —— 远程 selfUpgrade 的 bash -lc 命令行里就有 "npm i -g aaden-cli"，它在等我们跑完。
otherNpm() {
  local anc=" " p=$$ q
  while [ -n "$p" ] && [ "$p" -gt 1 ] 2>/dev/null; do
    anc="$anc$p "
    p=$(ps -o ppid= -p "$p" 2>/dev/null | tr -d ' ')
  done
  # aaden-cli-install：cron 的重装外壳（Utils/cliInstall.sh）。npm 6 会改掉自己的进程名，只认 npm 那串会漏看
  for q in $(pgrep -f 'npm (i|install) (-g )?aaden-cli|aaden-cli-install' 2>/dev/null); do
    case "$anc" in *" $q "*) ;; *) echo "$q" ;; esac
  done
}
waitOtherNpm() {
  local waited=0
  while [ -n "$(otherNpm)" ]; do
    if [ "$waited" -ge 900 ]; then
      echo "等了 ${waited}s 仍有别的 npm 在装 aaden-cli: $(ps -o pid=,etime=,args= -p "$(otherNpm | tr '\n' ',' | sed 's/,$//')" 2>/dev/null | tr '\n' ';')"
      return 1
    fi
    sleep 5
    waited=$((waited + 5))
  done
  echo "waited=${waited}s"
}

# npm 全局重装会先把旧目录改名成 .aaden-cli-<随机串>；上次失败留下的这种目录会让之后每次都 ENOTEMPTY 失败，
# CLI 永远停在老版本（10-03 车队里有几台卡在 1.6.88~1.6.98）。全局目录不一定是 /usr/local（node 走 apt 装的是 /usr），
# 两处都清。必须在 waitOtherNpm 之后：正在装的 npm 的 .aaden-cli-* 是它回滚用的，删了它就真回不去了。
cleanResidue() {
  local root
  root=$(sudoS npm root -g 2>/dev/null | tail -n 1)
  # 只认绝对路径的 node_modules，免得把一行 npm 警告当路径拼进 rm -rf
  case "$root" in /*/node_modules) ;; *) root= ;; esac
  sudoS sh -c "rm -rf /usr/local/lib/node_modules/.aaden-cli-* ${root:+$root/.aaden-cli-*}"
}

# 已切到私有分发（POS-46 第 2 期）：CLI 从云端拿签名过的版本，不再 npm 重装。
# 看 /usr/local/bin/aaden-cli 这个链接实际指向哪里，不看标记文件：指向 /opt/aaden-cli 时跑 `npm i -g aaden-cli`，
# npm 会报 EEXIST 失败、还把 /usr/local/lib/node_modules/aaden-cli 整个删掉（node 10.19 + npm 6.13 实测），--back-to-npm 就没得退了。
# 其余设备走下面原来的 npm 步骤，一点没变。
# 只读一层（不加 -f）：/opt 那边链接悬空时 -f 读不出来，会误走 npm
case "$(readlink /usr/local/bin/aaden-cli 2>/dev/null)" in /opt/aaden-cli/*) CLI_PRIVATE=1 ;; *) CLI_PRIVATE= ;; esac
if [ -n "$CLI_PRIVATE" ]; then
  step selfupdate aaden-cli self-update
else
  step wait waitOtherNpm
  step clean cleanResidue
  if ! step npm sudoS npm i aaden-cli -g || ! aaden-cli --version >/dev/null 2>&1; then
    # 失败、或装完 aaden-cli 不见了：等一等、清一遍再装一次。不补救的话后面几步全是 command not found
    step wait2 waitOtherNpm
    step clean2 cleanResidue
    step npm2 sudoS npm i aaden-cli -g
  fi
fi

step patch aaden-cli patch

# 前端目录访问量上报（AppCenter 各应用 + 根目录老前端；只读 Apache 日志，20 小时内报过就空跑）。放在 light-update 前面，后端部署出问题也不影响统计
step usage aaden-cli appcenter-usage

# 后端也走 aaden-cli 通道自愈：light-update 自带 版本门/灰度门/活动单门，
# 已最新则空跑，营业时间被 @reboot 触发也不会打断收银（不 snap refresh 前端、不 apt）。
# 这条让"后端自动更新"重新依赖活着的 npm+S3 管线，而不是失效的 git token。
step lu aaden-cli light-update

# AppCenter 前端（Admin 等）单独同步：light-update 不发 AppCenter。灰度由 S3 manifest 的 rollout 控制，
# 不在灰度内 / 已最新时空跑（一次几 KB 的 manifest 请求）；不碰 PHP 和数据库，营业时间触发也安全。
step sync aaden-cli appcenter-sync

# POS-172：fstab 里 TSE 那一行补 nofail。本机已经有 / 没有 TSE 行 / 云端灰度没开时空跑；只改 fstab，不挂载、不重启，下次开机生效。
# 放在最后，并且真要改时 aaden-cli 在后台（flock）做、这里不等：TSE 卡死时 findmnt 会挂在 D 状态杀不掉，
# 不能让它拖住前面的 usage / light-update / appcenter-sync 和「自动执行了更新aaden-cli」的上报。
# 上一次还卡着时这一步报 fstab=3（锁被占着）。
step fstab aaden-cli fix-tse-fstab
