Skip to content
84 changes: 84 additions & 0 deletions packages/ssh/src/tunnel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,90 @@ function commandArgs(command: ChildProcess.Command): ReadonlyArray<string> {
const ARCHIVE = { archiveVersion: "1.2.3-preview.20260911.4" } as const;

describe("ssh tunnel scripts", () => {
it.each([
"unavailable",
"recovering",
"discovered",
"missing-port",
"fresh",
"managed-recovering",
] as const)("handles SSH launch with %s external service state", async (scenario) => {
const managed =
scenario === "managed-recovering" ? NodeChildProcess.spawn("sleep", ["60"]) : undefined;
const exited = managed
? new Promise((resolve) => managed.once("exit", (_code, signal) => resolve(signal)))
: undefined;
const home = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-ssh-owner-"));
try {
const state = NodePath.join(home, ".t3/ssh-launch/test");
const runtime = NodePath.join(home, ".t3/runtime/versions", ARCHIVE.archiveVersion);
NodeFS.mkdirSync(state, { recursive: true });
NodeFS.mkdirSync(runtime, { recursive: true });
if (scenario !== "discovered" && scenario !== "fresh") {
NodeFS.writeFileSync(
NodePath.join(state, "managed"),
scenario === "managed-recovering" ? "managed\n" : "external\n",
);
if (managed) NodeFS.writeFileSync(NodePath.join(state, "pid"), String(managed.pid));
if (scenario !== "missing-port")
NodeFS.writeFileSync(NodePath.join(state, "port"), "3773\n");
}
NodeFS.writeFileSync(NodePath.join(runtime, ".install-complete"), ARCHIVE.archiveVersion);
NodeFS.writeFileSync(
NodePath.join(runtime, "t3"),
`#!/bin/sh
case "$1 $2" in
'--version ') exit 0 ;;
'__ssh-helper runtime-port')
${scenario === "discovered" || scenario === "managed-recovering" ? `printf '${process.pid} 3773'` : "exit 1"} ;;
'__ssh-helper wait-ready')
printf '%s\\n' "$4" >> "$HOME/probes"
${scenario === "fresh" ? "exit 0" : scenario === "recovering" || scenario === "managed-recovering" ? '[ "$4" -gt 2000 ]' : '[ "$3" = 3774 ]'} ;;
'__ssh-helper pick-port') printf 'pick-port\\n' >> "$HOME/spawns"; printf '3774' ;;
'serve --host') printf 'serve\\n' >> "$HOME/spawns" ;;
*) exit 2 ;;
esac
`,
{ mode: 0o700 },
);
const result = NodeChildProcess.spawnSync("sh", ["-s", "--", "test"], {
input: SshTunnel.buildRemoteLaunchScript(ARCHIVE),
env: { ...process.env, HOME: home },
encoding: "utf8",
timeout: 10_000,
});
assert.isUndefined(result.error);
if (scenario === "fresh" || scenario === "unavailable" || scenario === "missing-port") {
assert.equal(result.status, 0, result.stderr);
assert.deepEqual(JSON.parse(result.stdout), { remotePort: 3774, serverKind: "managed" });
return;
}
assert.isFalse(NodeFS.existsSync(NodePath.join(home, "spawns")), result.stderr);
if (scenario === "discovered") {
assert.isFalse(NodeFS.existsSync(NodePath.join(state, "managed")));
} else {
assert.equal(
NodeFS.readFileSync(NodePath.join(state, "managed"), "utf8").trim(),
"external",
);
}
if (scenario === "recovering" || scenario === "managed-recovering") {
assert.equal(result.status, 0, result.stderr);
assert.deepEqual(JSON.parse(result.stdout), { remotePort: 3773, serverKind: "external" });
} else {
assert.equal(result.status, 1);
assert.include(result.stderr, "external T3 server");
}
if (managed) {
assert.isFalse(NodeFS.existsSync(NodePath.join(state, "pid")));
assert.equal(await exited, "SIGTERM");
}
} finally {
managed?.kill();
NodeFS.rmSync(home, { recursive: true, force: true, maxRetries: 5 });
}
});

it("rejects archive versions that are not a single exact version segment", () => {
for (const archiveVersion of [
"../other",
Expand Down
15 changes: 9 additions & 6 deletions packages/ssh/src/tunnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ if [ -n "$DEFAULT_REMOTE_PORT" ] && [ "$REMOTE_MANAGED" = "managed" ] && [ "$DEF
printf '%s\\n' "$REMOTE_PORT" >"$PORT_FILE"
elif [ -n "$DEFAULT_REMOTE_PORT" ]; then
REMOTE_PORT="$DEFAULT_REMOTE_PORT"
if wait_ready "@@T3_REUSE_READY_TIMEOUT_MS@@"; then
if wait_ready "@@T3_REUSE_READY_TIMEOUT_MS@@" || wait_ready "@@T3_READY_TIMEOUT_MS@@"; then
ADOPTED_PID="$DEFAULT_RUNTIME_PID"
if [ "$REMOTE_MANAGED" = "managed" ]; then
if [ -n "$REMOTE_PID" ] && kill -0 "$REMOTE_PID" 2>/dev/null; then
Expand All @@ -660,17 +660,20 @@ elif [ -n "$DEFAULT_REMOTE_PORT" ]; then
REMOTE_MANAGED="external"
fi
else
REMOTE_PID="$(cat "$PID_FILE" 2>/dev/null || true)"
REMOTE_PORT="$(cat "$PORT_FILE" 2>/dev/null || true)"
REMOTE_MANAGED="$(cat "$MANAGED_FILE" 2>/dev/null || true)"
printf 'The external T3 server did not become ready. Retry after its update or restart finishes; no replacement server was started.\\n' >&2
exit 1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Comment thread
maria-rcks marked this conversation as resolved.
Resolved
fi
fi
if [ "$REMOTE_MANAGED" = "external" ]; then
# A server adopted above has just answered. While it is still alive, probing
# it again could catch a momentary stall and launch a second server beside it.
if server_running "$ADOPTED_PID"; then
:
elif [ -z "$REMOTE_PORT" ] || ! wait_ready "@@T3_REUSE_READY_TIMEOUT_MS@@"; then
elif [ -z "$REMOTE_PORT" ] || ! wait_ready "@@T3_READY_TIMEOUT_MS@@"; then
if [ -n "$DEFAULT_RUNTIME_PID" ] && server_running "$DEFAULT_RUNTIME_PID"; then
printf 'The external T3 server did not become ready. Retry after its update or restart finishes; no replacement server was started.\\n' >&2
exit 1
fi
REMOTE_PID=""
REMOTE_PORT=""
REMOTE_MANAGED=""
Expand Down Expand Up @@ -827,7 +830,7 @@ export function buildRemoteNodeEnvScript(input?: RemoteT3RunnerOptions): string
);
}

function buildRemoteLaunchScript(input?: RemoteT3RunnerOptions): string {
export function buildRemoteLaunchScript(input?: RemoteT3RunnerOptions): string {
return applyScriptPlaceholders(REMOTE_LAUNCH_SCRIPT, {
T3_ARCHIVE_MODE: isNodeScriptRunner(input) ? "0" : "1",
T3_NODE_ENV_SCRIPT: buildRemoteNodeEnvScript(input),
Expand Down
Loading