fix(install): prevent infinite su recursion when running as root

If install_hermes_memory_tencentdb.sh is invoked directly by the root
user (e.g. fresh server with root SSH login), the prior logic:

  USERNAME=$(whoami)             # → "root"
  if [ "$(id -u)" -eq 0 ]; then
      su - $USERNAME -c "bash $TEMP_SCRIPT"   # → su - root → root → loops
  fi

would spin forever: ``su - root`` enters a fresh root shell that re-runs
the script, which sees EUID=0 again and ``su -``-s itself once more.
Symptom (per issue #20):

  [memory-tencentdb] Running as root, switching to root for installation...
  [memory-tencentdb] Running as root, switching to root for installation...
  ...   (only Ctrl+C stops it)

Fix:

  1. ``USERNAME`` resolution adds two precedence steps before
     ``$(whoami)`` so admins running ``sudo bash install.sh`` end up
     installing for the original user instead of for root:
       a. ``INSTALL_AS_USER`` env override (explicit admin choice)
       b. ``SUDO_USER`` (sudo's own record of the calling user)
       c. ``whoami`` (final fallback)

  2. The ``id -u == 0`` branch now skips the ``su -`` step when the
     target user is also root — that's the recursion-trigger case. The
     script proceeds inline as root for the rest of the install.

  3. A new ``elif`` arm logs a clear ``"Running as root; target user is
     also root — installing in place."`` so the operator sees what's
     happening.

Verified by dry-run simulation of 5 scenarios:

  | Case                                    | Branch        | USERNAME |
  | --------------------------------------- | ------------- | -------- |
  | root SSH direct (#20 reproducer)        | INLINE        | root     |
  | non-root user direct                    | NORMAL        | <user>   |
  | sudo bash install.sh from non-root user | SU            | <user>   |
  | root + INSTALL_AS_USER=bar              | SU            | bar      |
  | root + INSTALL_AS_USER=root             | INLINE        | root     |

Closes #20.

Signed-off-by: 李冠辰 <liguanchen@xiaomi.com>
This commit is contained in:
李冠辰
2026-05-19 07:30:35 +08:00
parent c13d020047
commit e2223bd532
+22 -5
View File
@@ -33,8 +33,17 @@
set -e set -e
# 动态获取当前执行用户及 HOME 目录 # 动态获取目标安装用户及 HOME 目录
USERNAME=$(whoami) # 优先级:
# 1. 显式 ``INSTALL_AS_USER`` 环境变量(管理员脚本场景:root 跑安装但
# 想为另一个用户配置)
# 2. ``SUDO_USER``(被 ``sudo`` 调用时,切回原用户而不是 root)
# 3. ``whoami`` —— 当前 EUID 对应的用户
#
# 注意:当 root 直接 ssh 登录跑(非 sudo)时,前两个都不会被设置,
# ``whoami`` 返回 ``root``。下面的 ``id -u`` == 0 分支会识别这种"目标
# 就是 root"的情况、跳过 ``su - root`` 递归。
USERNAME="${INSTALL_AS_USER:-${SUDO_USER:-$(whoami)}}"
USER_HOME=$(eval echo ~$USERNAME) USER_HOME=$(eval echo ~$USERNAME)
# npm 包名 # npm 包名
@@ -62,10 +71,14 @@ LEGACY_INSTALL_DIR="$USER_HOME/tdai-memory-openclaw-plugin"
LEGACY_DATA_DIR="$USER_HOME/memory-tdai" LEGACY_DATA_DIR="$USER_HOME/memory-tdai"
# ==================== root → 自动切换到目标用户 ==================== # ==================== root → 自动切换到目标用户 ====================
# 与 install_hermes_ubuntu.sh 保持一致:如果以 root 执行,自动 su 切到 # 与 install_hermes_ubuntu.sh 保持一致:如果以 root 执行且目标用户不是
# 目标用户运行实际安装逻辑。也可以直接以目标用户身份执行,跳过此段 # root,自动 su 切到目标用户运行实际安装逻辑。
#
# 如果当前是 root 且目标用户也是 root``USERNAME=root``,例如直接 ssh
# 登录 root 跑安装),跳过 ``su - root`` —— 否则会无限递归(``su - root``
# 进入的仍是 root,又走到这个分支,再次 su,永远停不下来)。见 issue #20。
if [ "$(id -u)" -eq 0 ]; then if [ "$(id -u)" -eq 0 ] && [ "$USERNAME" != "root" ]; then
echo "[memory-tencentdb] Running as root, switching to $USERNAME for installation..." echo "[memory-tencentdb] Running as root, switching to $USERNAME for installation..."
# 验证前置条件 # 验证前置条件
@@ -88,6 +101,10 @@ if [ "$(id -u)" -eq 0 ]; then
rm -f "$TEMP_SCRIPT" rm -f "$TEMP_SCRIPT"
echo "[memory-tencentdb] Installation completed successfully" echo "[memory-tencentdb] Installation completed successfully"
exit 0 exit 0
elif [ "$(id -u)" -eq 0 ]; then
# 当前是 root 且目标用户也是 root:直接以 root 跑后续安装逻辑,
# 不再走 ``su -`` 切换(避免 #20 的递归)。
echo "[memory-tencentdb] Running as root; target user is also root — installing in place."
fi fi
# ==================== 用户阶段(核心安装逻辑) ==================== # ==================== 用户阶段(核心安装逻辑) ====================