diff --git a/packages/ssh/src/tunnel.test.ts b/packages/ssh/src/tunnel.test.ts index dc50e01694ec1..6a7b2c01c93a3 100644 --- a/packages/ssh/src/tunnel.test.ts +++ b/packages/ssh/src/tunnel.test.ts @@ -83,16 +83,29 @@ function commandArgs(command: ChildProcess.Command): ReadonlyArray { const ARCHIVE = { archiveVersion: "1.2.3-preview.20260911.4" } as const; const runArchiveRunner = Effect.fn("test.runArchiveRunner")( - function* (installedVersion: string) { + function* ( + installedVersion: string, + installDirectory = ".local/bin", + options?: { readonly cliScript?: string; readonly fifo?: boolean }, + ) { const fs = yield* FileSystem.FileSystem; const root = yield* fs.makeTempDirectoryScoped(); - yield* fs.makeDirectory(`${root}/.local/bin`, { recursive: true }); + yield* fs.makeDirectory(`${root}/${installDirectory}`, { recursive: true }); yield* fs.makeDirectory(`${root}/bin`); - yield* fs.writeFileString( - `${root}/.local/bin/t3`, - `#!/bin/sh\ncase "$1" in\n--version) echo 't3 v${installedVersion}';;\n__ssh-helper) exit 0;;\n*) printf 'installed:%s\\n' "$*";;\nesac\n`, - ); - yield* fs.chmod(`${root}/.local/bin/t3`, 0o700); + if (options?.fifo) { + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; + const mkfifo = yield* spawner.spawn( + ChildProcess.make("mkfifo", ["-m", "700", `${root}/${installDirectory}/t3`]), + ); + yield* mkfifo.exitCode; + } else { + yield* fs.writeFileString( + `${root}/${installDirectory}/t3`, + options?.cliScript ?? + `#!/bin/sh\ncase "$1" in\n--version) echo 't3 v${installedVersion}';;\n__ssh-helper) exit 0;;\n*) printf 'installed:%s\\n' "$*";;\nesac\n`, + ); + yield* fs.chmod(`${root}/${installDirectory}/t3`, 0o700); + } yield* fs.writeFileString(`${root}/bin/curl`, '#!/bin/sh\necho "HTTP 404" >&2\nexit 22\n'); yield* fs.chmod(`${root}/bin/curl`, 0o700); yield* fs.writeFileString(`${root}/runner.sh`, SshTunnel.buildRemoteT3RunnerScript(ARCHIVE)); @@ -161,13 +174,15 @@ describe("ssh tunnel scripts", () => { }), ); - it.live("reuses an installed CLI only when it matches the exact requested release", () => - Effect.gen(function* () { - const result = yield* runArchiveRunner(ARCHIVE.archiveVersion); - assert.equal(result.exitCode, 0, result.stderr); - assert.equal(result.stdout, "installed:pair --json\n"); - assert.isFalse(result.runtimeExists); - }), + it.live.each([".local/bin", ".t3/bin"])( + "reuses an exact-release installed CLI in %s", + (installDirectory) => + Effect.gen(function* () { + const result = yield* runArchiveRunner(ARCHIVE.archiveVersion, installDirectory); + assert.equal(result.exitCode, 0, result.stderr); + assert.equal(result.stdout, "installed:pair --json\n"); + assert.isFalse(result.runtimeExists); + }), ); it.live.each(["1.2.2", "1.2.3", "1.2.3-nightly.20260911.4", "1.2.3-preview.20260911.3"])( @@ -185,6 +200,28 @@ describe("ssh tunnel scripts", () => { }), ); + it.live( + "skips an installed CLI whose probes hang and falls through to the archive download", + () => + Effect.gen(function* () { + const result = yield* runArchiveRunner(ARCHIVE.archiveVersion, ".t3/bin", { + cliScript: `#!/bin/sh\ncase "$1" in\n--version) sleep 300;;\n__ssh-helper) sleep 300;;\n*) printf 'installed:%s\\n' "$*";;\nesac\n`, + }); + assert.notEqual(result.exitCode, 0); + assert.include(result.stderr, `v${ARCHIVE.archiveVersion}/SHA256SUMS`); + assert.isFalse(result.runtimeExists); + }), + ); + + it.live("skips a non-regular file at the desktop CLI path", () => + Effect.gen(function* () { + const result = yield* runArchiveRunner(ARCHIVE.archiveVersion, ".t3/bin", { fifo: true }); + assert.notEqual(result.exitCode, 0); + assert.include(result.stderr, `v${ARCHIVE.archiveVersion}/SHA256SUMS`); + assert.isFalse(result.runtimeExists); + }), + ); + it("rejects archive versions that are not a single exact version segment", () => { for (const archiveVersion of [ "../other", diff --git a/packages/ssh/src/tunnel.ts b/packages/ssh/src/tunnel.ts index efe0fe8cde202..c8c063dc52570 100644 --- a/packages/ssh/src/tunnel.ts +++ b/packages/ssh/src/tunnel.ts @@ -58,6 +58,7 @@ const REMOTE_LAUNCH_TIMEOUT_MS = 90_000; const REMOTE_ARCHIVE_CHECKSUMS_SECONDS = 30; const REMOTE_ARCHIVE_DOWNLOAD_SECONDS = 240; const REMOTE_ARCHIVE_LOCK_WAIT_SECONDS = 360; +const REMOTE_INSTALLED_PROBE_SECONDS = 15; const REMOTE_ARCHIVE_LAUNCH_TIMEOUT_MS = 900_000; const REMOTE_REUSE_READY_TIMEOUT_MS = 2_000; @@ -445,14 +446,36 @@ T3_RUNTIME_DIR="$HOME/.t3/runtime/versions/$T3_ARCHIVE_VERSION" t3_runtime_ready() { [ -x "$T3_RUNTIME_DIR/t3" ] && [ "$(cat "$T3_RUNTIME_DIR/.install-complete" 2>/dev/null)" = "$T3_ARCHIVE_VERSION" ] } -# Without the pinned runtime, reuse only an exact-release CLI from install.sh's PATH symlink. +# Bound each probe so a hung candidate falls through to the archive download +# instead of stalling until the SSH timeout. Output goes through a file and the +# watchdog is detached: orphaned children holding a pipe would block the capture. +t3_probe() { + T3_PROBE_OUT="$(mktemp "\${TMPDIR:-/tmp}/t3-probe.XXXXXX")" + "$@" >"$T3_PROBE_OUT" 2>/dev/null /dev/null + sleep 2 + kill -KILL "$T3_PROBE_PID" 2>/dev/null + ) >/dev/null 2>&1 & + T3_PROBE_DOG=$! + wait "$T3_PROBE_PID" 2>/dev/null + T3_PROBE_RC=$? + kill "$T3_PROBE_DOG" 2>/dev/null + wait "$T3_PROBE_DOG" 2>/dev/null + cat "$T3_PROBE_OUT" + rm -f "$T3_PROBE_OUT" + return "$T3_PROBE_RC" +} +# Reuse only an exact-release CLI from the shell or the standard install locations. if ! t3_runtime_ready; then - for T3_INSTALLED in "$(command -v t3 2>/dev/null || true)" "$HOME/.local/bin/t3"; do - [ -n "$T3_INSTALLED" ] && [ -x "$T3_INSTALLED" ] || continue - T3_INSTALLED_VERSION="$("$T3_INSTALLED" --version 2>/dev/null || true)" + for T3_INSTALLED in "$(command -v t3 2>/dev/null || true)" "$HOME/.local/bin/t3" "$HOME/.t3/bin/t3"; do + [ -n "$T3_INSTALLED" ] && [ -f "$T3_INSTALLED" ] && [ -x "$T3_INSTALLED" ] || continue + T3_INSTALLED_VERSION="$(t3_probe "$T3_INSTALLED" --version 2>/dev/null || true)" case "$T3_INSTALLED_VERSION" in "t3 v$T3_ARCHIVE_VERSION") - if "$T3_INSTALLED" __ssh-helper --help >/dev/null 2>&1; then + if t3_probe "$T3_INSTALLED" __ssh-helper --help >/dev/null 2>&1; then exec "$T3_INSTALLED" "$@" fi ;; @@ -829,6 +852,7 @@ export function buildRemoteT3RunnerScript(input?: RemoteT3RunnerOptions): string T3_ARCHIVE_VERSION: shellSingleQuote(archiveVersion), T3_RELEASE_BASE_URL: shellSingleQuote(releaseBaseUrl), T3_ARCHIVE_LOCK_WAIT_SECONDS: String(REMOTE_ARCHIVE_LOCK_WAIT_SECONDS), + T3_INSTALLED_PROBE_SECONDS: String(REMOTE_INSTALLED_PROBE_SECONDS), T3_ARCHIVE_DOWNLOAD_SECONDS: String(REMOTE_ARCHIVE_DOWNLOAD_SECONDS), T3_ARCHIVE_CHECKSUMS_SECONDS: String(REMOTE_ARCHIVE_CHECKSUMS_SECONDS), T3_NODE_ENV_SCRIPT: buildRemoteNodeEnvScript(input),