diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 2c5af5a4..4ba48a6a 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -146,6 +146,15 @@ exec /root/unidesk/scripts/tran "$@" `tran` 同样遵守 route/operation 解析器;route 后面的第一个 token 不是原生 ssh 命令字符串。不要写 `tran G14:/root/hwlab sh -lc '...'`,因为 `sh` 会被解析为 stdin script helper 的别名,`-lc` 会变成不受支持的 script 选项。带变量展开、管道、重定向或多条命令的远端逻辑,默认使用 `tran G14:/root/hwlab script <<'SCRIPT'`;默认 `script` 走目标节点 `/bin/sh`,并继承 provider-gateway/G14 已长期化的 proxy 环境。需要临时单步执行一行远端 shell 逻辑、且不想先创建脚本文件或 heredoc 时,优先使用 `tran G14:/root/hwlab script -- 'sed -n "1,20p" a && sed -n "1,20p" b'`,CLI 会把单个字符串放进目标节点的 `sh -c`,第二个 `sed`、管道和重定向都会留在远端;等价 `shell ''` 仍保留为显式 shell operation。`script` 和 `shell` helper 会在用户 shell 文本前注入一个极小的 POSIX 兼容 `printf` wrapper,使 `printf "--- section ---\n"` 这类高频排障分隔标题在 dash/sh 与 bash 下行为一致;direct argv 形态不注入该 wrapper。`script --` 后跟多个 token 时保持 direct argv,例如 `tran G14:/root/hwlab script -- sed -n '1,20p' AGENTS.md`。只有脚本确实使用 `pipefail`、数组、`[[ ... ]]` 等 bash 专有语义时才加 `--shell bash`,不能把 `--shell bash` 当作 proxy 修复手段。单进程命令才直接写成 argv,例如 `tran G14:/root/hwlab git status --short --branch`。遇到分布式开发摩擦时,优先补强 `tran` 的 route/operation、stdin helper 或目标节点环境,并把稳定解法写回长期参考文档,不要退回多层 shell 字符串拼接。 +非交互 `ssh`/`tran` 不是登录 shell,不能依赖 `.bashrc`、`.profile` 或交互 alias。 +CLI 会在 Host route、workspace route、k3s 控制面脚本和 pod route 的 shell/script helper +前统一注入用户级工具 PATH:`$HOME/.bun/bin`、`$HOME/.local/bin`、`$HOME/bin` +和 `/root/.bun/bin`。因此 G14 这类节点只要已经安装了 `/root/.bun/bin/bun`, +`tran G14:/root/hwlab-v02 script -- 'bun --version'` 应该直接可用, +不需要在任务里硬写绝对路径。direct argv 不经过 shell 初始化;如果某个 direct argv 工具找不到, +优先改用 `script -- ''` 或补强 CLI 的 argv PATH 处理, +不要在业务脚本里长期散落绝对路径 workaround。 + 本地 shell 运算符不是 `tran` 可以拦截的内容。`tran G14:/root/hwlab sed -n '1,20p' AGENTS.md && sed -n '1,20p' docs/reference/g14.md` 会先由 master server 的本地 shell 拆成两个命令,只有第一个 `sed` 进入 G14,第二个 `sed` 会在 master server 当前目录执行。需要把两个命令都放到目标节点时,必须写成 `tran G14:/root/hwlab script -- 'sed -n "1,20p" AGENTS.md && sed -n "1,20p" docs/reference/g14.md'`,或者用 `tran G14:/root/hwlab script <<'SCRIPT'` 把多行脚本送到远端。 `tran` 不做本地 provider/plane 串行锁;本地目录锁不是 G14 原生 k3s/Tekton/GitOps 的业务协调机制,stale lock 会阻塞所有后续短查询。以后不要在 `tran` wrapper 里恢复本地锁。业务并发、发布互斥和 rollout 协调必须交给 k8s/Tekton/Argo/Lease 等原生运行面机制;若 provider session allocator 需要限流,应在服务端实现带 TTL 的队列或 lease,而不是在客户端加目录锁。 @@ -219,7 +228,17 @@ printf 'import sys\nprint(sys.argv)\n' | bun scripts/cli.ts ssh D601 py foo '--b `ssh py` 的附加参数是脚本参数,不是 Python 解释器参数;如需 `-m`、`-X` 或多条 shell 命令,仍使用原始远端命令入口。为了保证 CLI 输出及时可见,helper 固定采用“临时文件 + `python3 -u`”模式;provider 命令模式不分配 TTY,因此脚本内容不应被远端回显。 -如果远端逻辑需要 shell 特性,不要再把整段脚本作为原生 ssh-like 命令字符串传入。正式入口是 `bun scripts/cli.ts ssh D601 script`,脚本正文从 stdin 进入;CLI 会把本地 stdin 直接送到远端 `sh -s --`,`--shell bash` 可切换为 bash,`--` 后的内容会作为脚本参数传入。`script`/`shell` helper 会在用户脚本文本前注入兼容前缀,让 `printf "--- section ---\n"` 这类分隔标题不再因目标 `/bin/sh` 方言失败;已有 `printf '%s\n' value`、`printf -- ...` 和 bash 的 `printf -v` 仍按原语义工作。临时单步执行优先用 quoted heredoc;只有命令很短、明确希望一行内完成时才用 `script -- ''`,它会把单个字符串按远端 shell one-liner 执行且不等待 stdin;复用脚本时才用 `< script.sh` 文件重定向。`script -- <多个 argv>` 仍是 direct argv,不经过远端 shell,适合 `script -- sed -n '1,20p' file`。典型用法: +如果远端逻辑需要 shell 特性,不要再把整段脚本作为原生 ssh-like 命令字符串传入。正式入口是 +`bun scripts/cli.ts ssh D601 script`,脚本正文从 stdin 进入;CLI 会把本地 stdin 直接送到远端 +`sh -s --`,`--shell bash` 可切换为 bash,`--` 后的内容会作为脚本参数传入。 +`script`/`shell` helper 会在用户脚本文本前注入用户级工具 PATH 和兼容前缀, +让 `bun`、`tsx` 等用户级工具在非交互 shell 中可见,也让 `printf "--- section ---\n"` +这类分隔标题不再因目标 `/bin/sh` 方言失败;已有 `printf '%s\n' value`、`printf -- ...` +和 bash 的 `printf -v` 仍按原语义工作。临时单步执行优先用 quoted heredoc; +只有命令很短、明确希望一行内完成时才用 `script -- ''`, +它会把单个字符串按远端 shell one-liner 执行且不等待 stdin; +复用脚本时才用 `< script.sh` 文件重定向。`script -- <多个 argv>` 仍是 direct argv, +不经过远端 shell,适合 `script -- sed -n '1,20p' file`。典型用法: ```bash cat <<'SCRIPT' | bun scripts/cli.ts ssh D601 script --shell bash -- alpha diff --git a/scripts/src/ssh.ts b/scripts/src/ssh.ts index 9e45a4e8..01ecfc9b 100644 --- a/scripts/src/ssh.ts +++ b/scripts/src/ssh.ts @@ -89,6 +89,16 @@ const defaultSshSlowWarningMs = 10_000; const defaultSshRuntimeTimeoutMs = 60_000; const maxSshRuntimeTimeoutMs = 60_000; export const sshShellCompatibilityPrelude = 'printf(){ if [ "${1+x}" = x ] && [ "$1" = "-v" ] && [ -n "${BASH_VERSION:-}" ]; then command printf "$@"; return $?; fi; if [ "${1+x}" = x ] && [ "$1" = "--" ]; then shift; fi; command printf -- "$@"; }'; +export const sshUserToolPathPrelude = [ + 'for unidesk_path_dir in "$HOME/.bun/bin" "$HOME/.local/bin" "$HOME/bin" "/root/.bun/bin"; do', + ' [ -d "$unidesk_path_dir" ] || continue', + ' case ":$PATH:" in', + ' *":$unidesk_path_dir:"*) ;;', + ' *) PATH="$unidesk_path_dir:$PATH" ;;', + " esac", + "done", + "export PATH", +].join("\n"); const k3sResourceKindAliases = new Set(["pod", "po", "pods", "deployment", "deploy", "deployments", "statefulset", "sts", "daemonset", "ds", "job", "jobs"]); const k3sPodRoutePrefixes = ["pod:", "po:", "pods:"]; const legacyK3sOperationRouteSegments = new Set([ @@ -1857,12 +1867,16 @@ function k3sScriptShell(value: string, option: string): string { return value; } +function shellScriptPrelude(): string { + return `${sshUserToolPathPrelude}\n${sshShellCompatibilityPrelude}`; +} + function shellScriptWithCompatibility(command: string): string { - return `${sshShellCompatibilityPrelude}\n${command}`; + return `${shellScriptPrelude()}\n${command}`; } function shellScriptStdinPrefix(): string { - return `${sshShellCompatibilityPrelude}\n`; + return `${shellScriptPrelude()}\n`; } function buildShellCommand(args: string[]): ParsedSshArgs { @@ -1963,7 +1977,8 @@ function remoteToolBootstrapCommand(helpers: readonly SshHelperName[] = []): str export function wrapSshRemoteCommand(command: string | null, helpers: readonly SshHelperName[] = []): string { const bootstrap = remoteToolBootstrapCommand(helpers); - const prefix = bootstrap.length > 0 ? `${bootstrap}; ` : ""; + const prelude = [sshUserToolPathPrelude, bootstrap].filter((part) => part.length > 0).join("; "); + const prefix = prelude.length > 0 ? `${prelude}; ` : ""; if (command === null) return `${prefix}exec "\${SHELL:-/bin/bash}" -l`; return `${prefix}stty -echo 2>/dev/null || true; ${command}`; }