fix: expose user tool path in ssh scripts

This commit is contained in:
Codex
2026-05-31 07:46:47 +00:00
parent 0542eb98f7
commit b6d088a3cc
2 changed files with 38 additions and 4 deletions
+20 -1
View File
@@ -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 '<command>'` 仍保留为显式 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 -- '<command>'` 或补强 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 <providerId> 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 -- '<command && command>'`,它会把单个字符串按远端 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 -- '<command && command>'`
它会把单个字符串按远端 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
+18 -3
View File
@@ -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}`;
}