import type { DesktopSshEnvironmentBootstrap, DesktopSshEnvironmentTarget,} from "@t3tools/contracts";import { type NetError, NetService } from "@t3tools/shared/Net";import { fromLenientJson } from "@t3tools/shared/schemaJson";import * as Context from "effect/Context";import * as Deferred from "effect/Deferred";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as Ref from "effect/Ref";import * as Schedule from "effect/Schedule";import * as Schema from "effect/Schema";import * as Scope from "effect/Scope";import * as Stream from "effect/Stream";import { HttpClient, HttpClientRequest } from "effect/unstable/http";import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { buildSshChildEnvironment, type SshAuthOptions, SshPasswordPrompt, isSshAuthFailure,} from "./auth.ts";import { baseSshArgs, buildSshHostSpecEffect, collectProcessOutput, getLastNonEmptyOutputLine, remoteStateKey, resolveSshTarget, runSshCommand, targetConnectionKey,} from "./command.ts";import { SshCommandError, SshHttpBridgeError, SshInvalidTargetError, SshLaunchError, SshPairingError, SshPasswordPromptError, SshReadinessError,} from "./errors.ts"; export const DEFAULT_REMOTE_PORT = 3773;const REMOTE_PORT_SCAN_WINDOW = 200;const SSH_READY_TIMEOUT_MS = 20_000;const SSH_READY_PROBE_TIMEOUT_MS = 1_000;const TUNNEL_SHUTDOWN_TIMEOUT_MS = 2_000;const REMOTE_READY_TIMEOUT_MS = 15_000;const REMOTE_REUSE_READY_TIMEOUT_MS = 2_000; export interface RemoteT3RunnerOptions { readonly packageSpec?: string; readonly nodeScriptPath?: string | null;} export interface SshEnvironmentManagerOptions { readonly resolveCliPackageSpec?: () => string; readonly resolveCliRunner?: Effect.Effect<RemoteT3RunnerOptions>;} interface SshTunnelEntry { readonly key: string; readonly target: DesktopSshEnvironmentTarget; readonly remotePort: number; readonly remoteServerKind: "external" | "managed" | null; readonly localPort: number; readonly httpBaseUrl: string; readonly wsBaseUrl: string; readonly process: ChildProcessSpawner.ChildProcessHandle; readonly scope: Scope.Scope;} type SshEnvironmentEffectContext = | ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | Path.Path | HttpClient.HttpClient | NetService | SshPasswordPrompt; type SshEnvironmentEffectError = | SshCommandError | SshInvalidTargetError | SshLaunchError | SshPairingError | SshReadinessError | SshPasswordPromptError | NetError; function makeSshTunnelCancelledError(target: DesktopSshEnvironmentTarget): SshCommandError { return new SshCommandError({ command: ["ssh"], exitCode: null, stderr: "", message: `SSH environment connection was cancelled for ${target.alias || target.hostname}.`, });} function sshTargetLogFields(target: DesktopSshEnvironmentTarget) { return { alias: target.alias, hostname: target.hostname, username: target.username, port: target.port, };} function sshRunnerLogFields(runner: RemoteT3RunnerOptions | undefined) { if (runner?.nodeScriptPath?.trim()) { return { runner: "node-script", nodeScriptPath: runner.nodeScriptPath.trim() }; } if (runner?.packageSpec?.trim()) { return { runner: "package", packageSpec: runner.packageSpec.trim() }; } return { runner: "default" };} interface SshAuthOperationInput<T> { readonly key: string; readonly target: DesktopSshEnvironmentTarget; readonly operation: ( authOptions: SshAuthOptions, ) => Effect.Effect<T, SshEnvironmentEffectError, SshEnvironmentEffectContext>;} interface SshAuthAttemptInput<T> extends SshAuthOperationInput<T> { readonly promptCount: number; readonly authSecret: string | null;} export interface SshEnvironmentManagerShape { readonly ensureEnvironment: ( target: DesktopSshEnvironmentTarget, options?: { readonly issuePairingToken?: boolean }, ) => Effect.Effect< DesktopSshEnvironmentBootstrap, SshEnvironmentEffectError, SshEnvironmentEffectContext >; readonly disconnectEnvironment: ( target: DesktopSshEnvironmentTarget, ) => Effect.Effect<void, SshEnvironmentEffectError, SshEnvironmentEffectContext>;} const RemoteLaunchResult = Schema.Struct({ remotePort: Schema.Number, serverKind: Schema.optional(Schema.Literals(["external", "managed"])),}); const RemotePairingResult = Schema.Struct({ credential: Schema.String,}); const RemoteHttpError = Schema.Struct({ error: Schema.optional(Schema.String),}); const decodeRemoteLaunchResult = Schema.decodeEffect(fromLenientJson(RemoteLaunchResult));const decodeRemotePairingResult = Schema.decodeEffect(fromLenientJson(RemotePairingResult));const decodeRemoteHttpError = Schema.decodeEffect(Schema.fromJsonString(RemoteHttpError)); export function normalizeSshErrorMessage(stderr: string, fallbackMessage: string): string { const cleaned = stderr.trim(); return cleaned.length > 0 ? cleaned : fallbackMessage;} function stripTrailingNewlines(value: string): string { return value.replace(/\n+$/u, "");} function shellSingleQuote(value: string): string { return `'${value.replaceAll("'", "'\\''")}'`;} function applyScriptPlaceholders( template: string, replacements: Readonly<Record<string, string>>,): string { let result = template; for (const [token, value] of Object.entries(replacements)) { result = result.replaceAll(`@@${token}@@`, value); } return result;} export function describeReadinessCause(cause: unknown): unknown { if (cause instanceof SshReadinessError) { return { _tag: cause._tag, message: cause.message, ...(cause.cause === undefined ? {} : { cause: describeReadinessCause(cause.cause) }), }; } if (cause instanceof Error) { return { name: cause.name, message: cause.message, ...(cause.cause === undefined ? {} : { cause: describeReadinessCause(cause.cause) }), }; } if (typeof cause !== "object" || cause === null) { return cause; } const record = cause as Readonly<Record<string, unknown>>; return { ...(typeof record._tag === "string" ? { _tag: record._tag } : {}), ...(typeof record.message === "string" ? { message: record.message } : {}), ...(record.reason === undefined ? {} : { reason: describeReadinessCause(record.reason) }), ...(record.cause === undefined ? {} : { cause: describeReadinessCause(record.cause) }), };} export const REMOTE_PICK_PORT_SCRIPT = `const fs = require("node:fs");const net = require("node:net");const filePath = process.argv[2] ?? "";const defaultPort = Number.parseInt(process.argv[3] ?? "", 10);const scanWindow = Number.parseInt(process.argv[4] ?? "", 10);const raw = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8").trim() : "";const preferred = Number.parseInt(raw, 10);const start = Number.isInteger(preferred) ? preferred : defaultPort;const end = start + scanWindow; function tryPort(port) { return new Promise((resolve) => { const server = net.createServer(); server.unref(); server.once("error", () => resolve(false)); server.listen(port, "127.0.0.1", () => { server.close((error) => resolve(error ? false : port)); }); });} (async () => { for (let port = start; port < end; port += 1) { const available = await tryPort(port); if (available) { process.stdout.write(String(port)); return; } } process.exit(1);})().catch(() => process.exit(1));`; export const REMOTE_WAIT_READY_SCRIPT = `const http = require("node:http");const port = Number.parseInt(process.argv[2] ?? "", 10);const timeoutMs = Number.parseInt(process.argv[3] ?? "", 10);const probeTimeoutMs = Number.parseInt(process.argv[4] ?? "", 10);if (!Number.isInteger(port) || !Number.isInteger(timeoutMs) || !Number.isInteger(probeTimeoutMs)) { process.exit(1);}const deadline = Date.now() + timeoutMs; function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms));} function probe() { return new Promise((resolve) => { const request = http.get( { hostname: "127.0.0.1", port, path: "/", timeout: probeTimeoutMs, }, (response) => { response.resume(); response.once("end", () => { resolve(response.statusCode >= 200 && response.statusCode < 300); }); }, ); request.once("timeout", () => { request.destroy(); resolve(false); }); request.once("error", () => resolve(false)); });} (async () => { while (Date.now() < deadline) { if (await probe()) { process.exit(0); } await sleep(100); } process.exit(1);})().catch(() => process.exit(1));`; export const REMOTE_RUNNER_SCRIPT = `#!/bin/shset -euT3_NODE_SCRIPT_PATH=@@T3_NODE_SCRIPT_PATH@@if [ -n "$T3_NODE_SCRIPT_PATH" ]; then exec node "$T3_NODE_SCRIPT_PATH" "$@"fiif command -v t3 >/dev/null 2>&1; then exec t3 "$@"fiif command -v npx >/dev/null 2>&1; then exec npx --yes @@T3_PACKAGE_SPEC@@ "$@"fiif command -v npm >/dev/null 2>&1; then exec npm exec --yes @@T3_PACKAGE_SPEC@@ -- "$@"fiprintf 'Remote host is missing the t3 CLI and could not install @@T3_PACKAGE_SPEC@@ because npx and npm are unavailable on PATH.\\n' >&2exit 1`; export const REMOTE_LAUNCH_SCRIPT = `set -euSTATE_KEY="$1"STATE_DIR="$HOME/.t3/ssh-launch/$STATE_KEY"DEFAULT_SERVER_HOME="$HOME/.t3"DEFAULT_RUNTIME_FILE="$DEFAULT_SERVER_HOME/userdata/server-runtime.json"PORT_FILE="$STATE_DIR/port"PID_FILE="$STATE_DIR/pid"MANAGED_FILE="$STATE_DIR/managed"LOG_FILE="$STATE_DIR/server.log"RUNNER_FILE="$STATE_DIR/run-t3.sh"RUNNER_NEXT="$STATE_DIR/run-t3.next.$$"mkdir -p "$STATE_DIR"cleanup_runner_next() { rm -f "$RUNNER_NEXT"}trap cleanup_runner_next EXITcat >"$RUNNER_NEXT" <<'SH'@@T3_RUNNER_SCRIPT@@SHRUNNER_CHANGED=0if [ ! -f "$RUNNER_FILE" ] || ! cmp -s "$RUNNER_NEXT" "$RUNNER_FILE"; then RUNNER_CHANGED=1fimv "$RUNNER_NEXT" "$RUNNER_FILE"chmod 700 "$RUNNER_FILE"pick_port() { node - "$PORT_FILE" "@@T3_DEFAULT_REMOTE_PORT@@" "@@T3_REMOTE_PORT_SCAN_WINDOW@@" <<'NODE'@@T3_PICK_PORT_SCRIPT@@NODE}wait_ready() { node - "$REMOTE_PORT" "$1" "@@T3_READY_PROBE_TIMEOUT_MS@@" <<'NODE'@@T3_WAIT_READY_SCRIPT@@NODE}wait_for_pid_exit() { PID_TO_WAIT="$1" WAIT_COUNT=0 while kill -0 "$PID_TO_WAIT" 2>/dev/null && [ "$WAIT_COUNT" -lt 20 ]; do WAIT_COUNT=$((WAIT_COUNT + 1)) sleep 0.1 done}resolve_default_runtime_port() { node - "$DEFAULT_RUNTIME_FILE" <<'NODE'const fs = require("node:fs");const runtimePath = process.argv[2] ?? "";try { const runtime = JSON.parse(fs.readFileSync(runtimePath, "utf8")); const pid = Number(runtime.pid); const port = Number(runtime.port); if (!Number.isInteger(pid) || !Number.isInteger(port)) { process.exit(1); } const origin = new URL(String(runtime.origin ?? "")); if (origin.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(origin.hostname)) { process.exit(1); } process.kill(pid, 0); process.stdout.write(String(port));} catch { process.exit(1);}NODE}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)"DEFAULT_REMOTE_PORT="$(resolve_default_runtime_port 2>/dev/null || true)"if [ -n "$DEFAULT_REMOTE_PORT" ]; then REMOTE_PORT="$DEFAULT_REMOTE_PORT" if wait_ready "@@T3_REUSE_READY_TIMEOUT_MS@@"; then if [ "$REMOTE_MANAGED" = "managed" ] && [ -n "$REMOTE_PID" ] && kill -0 "$REMOTE_PID" 2>/dev/null; then kill "$REMOTE_PID" 2>/dev/null || true wait_for_pid_exit "$REMOTE_PID" fi printf '%s\\n' "$REMOTE_PORT" >"$PORT_FILE" printf 'external\\n' >"$MANAGED_FILE" REMOTE_PID="" REMOTE_MANAGED="external" 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)" fifiif [ "$REMOTE_MANAGED" = "external" ]; then if [ -z "$REMOTE_PORT" ] || ! wait_ready "@@T3_REUSE_READY_TIMEOUT_MS@@"; then REMOTE_PID="" REMOTE_PORT="" REMOTE_MANAGED="" fielif [ -n "$REMOTE_PID" ] && [ -n "$REMOTE_PORT" ] && kill -0 "$REMOTE_PID" 2>/dev/null; then if [ "$RUNNER_CHANGED" -eq 1 ]; then kill "$REMOTE_PID" 2>/dev/null || true wait_for_pid_exit "$REMOTE_PID" REMOTE_PID="" REMOTE_PORT="" REMOTE_MANAGED="" elif ! wait_ready "@@T3_REUSE_READY_TIMEOUT_MS@@"; then kill "$REMOTE_PID" 2>/dev/null || true wait_for_pid_exit "$REMOTE_PID" REMOTE_PID="" REMOTE_PORT="" REMOTE_MANAGED="" fielse REMOTE_PID="" REMOTE_PORT="" REMOTE_MANAGED=""fiif [ -z "$REMOTE_PID" ] || [ -z "$REMOTE_PORT" ]; then REMOTE_PORT="$(pick_port)" || true if [ -z "$REMOTE_PORT" ]; then printf 'Failed to find an available port on the remote host. Ensure node is available on PATH.\\n' >&2 exit 1 fi nohup env T3CODE_NO_BROWSER=1 "$RUNNER_FILE" serve --host 127.0.0.1 --port "$REMOTE_PORT" --base-dir "$DEFAULT_SERVER_HOME" >>"$LOG_FILE" 2>&1 < /dev/null & REMOTE_PID="$!" printf '%s\\n' "$REMOTE_PID" >"$PID_FILE" printf '%s\\n' "$REMOTE_PORT" >"$PORT_FILE" printf 'managed\\n' >"$MANAGED_FILE" if ! wait_ready "@@T3_READY_TIMEOUT_MS@@"; then printf 'Remote T3 server did not become ready on 127.0.0.1:%s.\\n' "$REMOTE_PORT" >&2 tail -n 80 "$LOG_FILE" >&2 2>/dev/null || true kill "$REMOTE_PID" 2>/dev/null || true wait_for_pid_exit "$REMOTE_PID" rm -f "$PID_FILE" "$PORT_FILE" "$MANAGED_FILE" exit 1 fifiprintf '{"remotePort":%s,"serverKind":"%s"}\\n' "$REMOTE_PORT" "\${REMOTE_MANAGED:-managed}"`; export const REMOTE_PAIRING_SCRIPT = `set -euSTATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@"DEFAULT_SERVER_HOME="$HOME/.t3"RUNNER_FILE="$STATE_DIR/run-t3.sh"mkdir -p "$STATE_DIR"cat >"$RUNNER_FILE" <<'SH'@@T3_RUNNER_SCRIPT@@SHchmod 700 "$RUNNER_FILE"PAIRING_BASE_DIR="$DEFAULT_SERVER_HOME""$RUNNER_FILE" auth pairing create --base-dir "$PAIRING_BASE_DIR" --json`; export const REMOTE_STOP_SCRIPT = `set -euSTATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@"PID_FILE="$STATE_DIR/pid"PORT_FILE="$STATE_DIR/port"MANAGED_FILE="$STATE_DIR/managed"REMOTE_MANAGED="$(cat "$MANAGED_FILE" 2>/dev/null || true)"REMOTE_PID="$(cat "$PID_FILE" 2>/dev/null || true)"if [ "$REMOTE_MANAGED" != "external" ] && [ -n "$REMOTE_PID" ] && kill -0 "$REMOTE_PID" 2>/dev/null; then kill "$REMOTE_PID" 2>/dev/null || true WAIT_COUNT=0 while kill -0 "$REMOTE_PID" 2>/dev/null && [ "$WAIT_COUNT" -lt 20 ]; do WAIT_COUNT=$((WAIT_COUNT + 1)) sleep 0.1 donefirm -f "$PID_FILE" "$PORT_FILE" "$MANAGED_FILE"printf '{"stopped":true}\\n'`; const REMOTE_LOG_TAIL_SCRIPT = `set -euSTATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@"LOG_FILE="$STATE_DIR/server.log"if [ -f "$LOG_FILE" ]; then tail -n 80 "$LOG_FILE" 2>/dev/null || truefi`; export function buildRemoteT3RunnerScript(input?: RemoteT3RunnerOptions): string { const packageSpec = shellSingleQuote(input?.packageSpec?.trim() || "t3@latest"); const nodeScriptPath = input?.nodeScriptPath?.trim() || ""; return stripTrailingNewlines( applyScriptPlaceholders(REMOTE_RUNNER_SCRIPT, { T3_PACKAGE_SPEC: packageSpec,Lines 1–500 of 1586. Load more
import type { DesktopSshEnvironmentBootstrap, DesktopSshEnvironmentTarget, } from "@t3tools/contracts"; import { type NetError, NetService } from "@t3tools/shared/Net"; import { fromLenientJson } from "@t3tools/shared/schemaJson"; import * as Context from "effect/Context"; import * as Deferred from "effect/Deferred"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Ref from "effect/Ref"; import * as Schedule from "effect/Schedule"; import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; import { HttpClient, HttpClientRequest } from "effect/unstable/http"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { buildSshChildEnvironment, type SshAuthOptions, SshPasswordPrompt, isSshAuthFailure, } from "./auth.ts"; import { baseSshArgs, buildSshHostSpecEffect, collectProcessOutput, getLastNonEmptyOutputLine, remoteStateKey, resolveSshTarget, runSshCommand, targetConnectionKey, } from "./command.ts"; import { SshCommandError, SshHttpBridgeError, SshInvalidTargetError, SshLaunchError, SshPairingError, SshPasswordPromptError, SshReadinessError, } from "./errors.ts"; export const DEFAULT_REMOTE_PORT = 3773; const REMOTE_PORT_SCAN_WINDOW = 200; const SSH_READY_TIMEOUT_MS = 20_000; const SSH_READY_PROBE_TIMEOUT_MS = 1_000; const TUNNEL_SHUTDOWN_TIMEOUT_MS = 2_000; const REMOTE_READY_TIMEOUT_MS = 15_000; const REMOTE_REUSE_READY_TIMEOUT_MS = 2_000; export interface RemoteT3RunnerOptions { readonly packageSpec?: string; readonly nodeScriptPath?: string | null; } export interface SshEnvironmentManagerOptions { readonly resolveCliPackageSpec?: () => string; readonly resolveCliRunner?: Effect.Effect<RemoteT3RunnerOptions>; } interface SshTunnelEntry { readonly key: string; readonly target: DesktopSshEnvironmentTarget; readonly remotePort: number; readonly remoteServerKind: "external" | "managed" | null; readonly localPort: number; readonly httpBaseUrl: string; readonly wsBaseUrl: string; readonly process: ChildProcessSpawner.ChildProcessHandle; readonly scope: Scope.Scope; } type SshEnvironmentEffectContext = | ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | Path.Path | HttpClient.HttpClient | NetService | SshPasswordPrompt; type SshEnvironmentEffectError = | SshCommandError | SshInvalidTargetError | SshLaunchError | SshPairingError | SshReadinessError | SshPasswordPromptError | NetError; function makeSshTunnelCancelledError(target: DesktopSshEnvironmentTarget): SshCommandError { return new SshCommandError({ command: ["ssh"], exitCode: null, stderr: "", message: `SSH environment connection was cancelled for ${target.alias || target.hostname}.`, }); } function sshTargetLogFields(target: DesktopSshEnvironmentTarget) { return { alias: target.alias, hostname: target.hostname, username: target.username, port: target.port, }; } function sshRunnerLogFields(runner: RemoteT3RunnerOptions | undefined) { if (runner?.nodeScriptPath?.trim()) { return { runner: "node-script", nodeScriptPath: runner.nodeScriptPath.trim() }; } if (runner?.packageSpec?.trim()) { return { runner: "package", packageSpec: runner.packageSpec.trim() }; } return { runner: "default" }; } interface SshAuthOperationInput<T> { readonly key: string; readonly target: DesktopSshEnvironmentTarget; readonly operation: ( authOptions: SshAuthOptions, ) => Effect.Effect<T, SshEnvironmentEffectError, SshEnvironmentEffectContext>; } interface SshAuthAttemptInput<T> extends SshAuthOperationInput<T> { readonly promptCount: number; readonly authSecret: string | null; } export interface SshEnvironmentManagerShape { readonly ensureEnvironment: ( target: DesktopSshEnvironmentTarget, options?: { readonly issuePairingToken?: boolean }, ) => Effect.Effect< DesktopSshEnvironmentBootstrap, SshEnvironmentEffectError, SshEnvironmentEffectContext >; readonly disconnectEnvironment: ( target: DesktopSshEnvironmentTarget, ) => Effect.Effect<void, SshEnvironmentEffectError, SshEnvironmentEffectContext>; } const RemoteLaunchResult = Schema.Struct({ remotePort: Schema.Number, serverKind: Schema.optional(Schema.Literals(["external", "managed"])), }); const RemotePairingResult = Schema.Struct({ credential: Schema.String, }); const RemoteHttpError = Schema.Struct({ error: Schema.optional(Schema.String), }); const decodeRemoteLaunchResult = Schema.decodeEffect(fromLenientJson(RemoteLaunchResult)); const decodeRemotePairingResult = Schema.decodeEffect(fromLenientJson(RemotePairingResult)); const decodeRemoteHttpError = Schema.decodeEffect(Schema.fromJsonString(RemoteHttpError)); export function normalizeSshErrorMessage(stderr: string, fallbackMessage: string): string { const cleaned = stderr.trim(); return cleaned.length > 0 ? cleaned : fallbackMessage; } function stripTrailingNewlines(value: string): string { return value.replace(/\n+$/u, ""); } function shellSingleQuote(value: string): string { return `'${value.replaceAll("'", "'\\''")}'`; } function applyScriptPlaceholders( template: string, replacements: Readonly<Record<string, string>>, ): string { let result = template; for (const [token, value] of Object.entries(replacements)) { result = result.replaceAll(`@@${token}@@`, value); } return result; } export function describeReadinessCause(cause: unknown): unknown { if (cause instanceof SshReadinessError) { return { _tag: cause._tag, message: cause.message, ...(cause.cause === undefined ? {} : { cause: describeReadinessCause(cause.cause) }), }; } if (cause instanceof Error) { return { name: cause.name, message: cause.message, ...(cause.cause === undefined ? {} : { cause: describeReadinessCause(cause.cause) }), }; } if (typeof cause !== "object" || cause === null) { return cause; } const record = cause as Readonly<Record<string, unknown>>; return { ...(typeof record._tag === "string" ? { _tag: record._tag } : {}), ...(typeof record.message === "string" ? { message: record.message } : {}), ...(record.reason === undefined ? {} : { reason: describeReadinessCause(record.reason) }), ...(record.cause === undefined ? {} : { cause: describeReadinessCause(record.cause) }), }; } export const REMOTE_PICK_PORT_SCRIPT = `const fs = require("node:fs"); const net = require("node:net"); const filePath = process.argv[2] ?? ""; const defaultPort = Number.parseInt(process.argv[3] ?? "", 10); const scanWindow = Number.parseInt(process.argv[4] ?? "", 10); const raw = fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8").trim() : ""; const preferred = Number.parseInt(raw, 10); const start = Number.isInteger(preferred) ? preferred : defaultPort; const end = start + scanWindow; function tryPort(port) { return new Promise((resolve) => { const server = net.createServer(); server.unref(); server.once("error", () => resolve(false)); server.listen(port, "127.0.0.1", () => { server.close((error) => resolve(error ? false : port)); }); }); } (async () => { for (let port = start; port < end; port += 1) { const available = await tryPort(port); if (available) { process.stdout.write(String(port)); return; } } process.exit(1); })().catch(() => process.exit(1)); `; export const REMOTE_WAIT_READY_SCRIPT = `const http = require("node:http"); const port = Number.parseInt(process.argv[2] ?? "", 10); const timeoutMs = Number.parseInt(process.argv[3] ?? "", 10); const probeTimeoutMs = Number.parseInt(process.argv[4] ?? "", 10); if (!Number.isInteger(port) || !Number.isInteger(timeoutMs) || !Number.isInteger(probeTimeoutMs)) { process.exit(1); } const deadline = Date.now() + timeoutMs; function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function probe() { return new Promise((resolve) => { const request = http.get( { hostname: "127.0.0.1", port, path: "/", timeout: probeTimeoutMs, }, (response) => { response.resume(); response.once("end", () => { resolve(response.statusCode >= 200 && response.statusCode < 300); }); }, ); request.once("timeout", () => { request.destroy(); resolve(false); }); request.once("error", () => resolve(false)); }); } (async () => { while (Date.now() < deadline) { if (await probe()) { process.exit(0); } await sleep(100); } process.exit(1); })().catch(() => process.exit(1)); `; export const REMOTE_RUNNER_SCRIPT = `#!/bin/sh set -eu T3_NODE_SCRIPT_PATH=@@T3_NODE_SCRIPT_PATH@@ if [ -n "$T3_NODE_SCRIPT_PATH" ]; then exec node "$T3_NODE_SCRIPT_PATH" "$@" fi if command -v t3 >/dev/null 2>&1; then exec t3 "$@" fi if command -v npx >/dev/null 2>&1; then exec npx --yes @@T3_PACKAGE_SPEC@@ "$@" fi if command -v npm >/dev/null 2>&1; then exec npm exec --yes @@T3_PACKAGE_SPEC@@ -- "$@" fi printf 'Remote host is missing the t3 CLI and could not install @@T3_PACKAGE_SPEC@@ because npx and npm are unavailable on PATH.\\n' >&2 exit 1 `; export const REMOTE_LAUNCH_SCRIPT = `set -eu STATE_KEY="$1" STATE_DIR="$HOME/.t3/ssh-launch/$STATE_KEY" DEFAULT_SERVER_HOME="$HOME/.t3" DEFAULT_RUNTIME_FILE="$DEFAULT_SERVER_HOME/userdata/server-runtime.json" PORT_FILE="$STATE_DIR/port" PID_FILE="$STATE_DIR/pid" MANAGED_FILE="$STATE_DIR/managed" LOG_FILE="$STATE_DIR/server.log" RUNNER_FILE="$STATE_DIR/run-t3.sh" RUNNER_NEXT="$STATE_DIR/run-t3.next.$$" mkdir -p "$STATE_DIR" cleanup_runner_next() { rm -f "$RUNNER_NEXT" } trap cleanup_runner_next EXIT cat >"$RUNNER_NEXT" <<'SH' @@T3_RUNNER_SCRIPT@@ SH RUNNER_CHANGED=0 if [ ! -f "$RUNNER_FILE" ] || ! cmp -s "$RUNNER_NEXT" "$RUNNER_FILE"; then RUNNER_CHANGED=1 fi mv "$RUNNER_NEXT" "$RUNNER_FILE" chmod 700 "$RUNNER_FILE" pick_port() { node - "$PORT_FILE" "@@T3_DEFAULT_REMOTE_PORT@@" "@@T3_REMOTE_PORT_SCAN_WINDOW@@" <<'NODE' @@T3_PICK_PORT_SCRIPT@@ NODE } wait_ready() { node - "$REMOTE_PORT" "$1" "@@T3_READY_PROBE_TIMEOUT_MS@@" <<'NODE' @@T3_WAIT_READY_SCRIPT@@ NODE } wait_for_pid_exit() { PID_TO_WAIT="$1" WAIT_COUNT=0 while kill -0 "$PID_TO_WAIT" 2>/dev/null && [ "$WAIT_COUNT" -lt 20 ]; do WAIT_COUNT=$((WAIT_COUNT + 1)) sleep 0.1 done } resolve_default_runtime_port() { node - "$DEFAULT_RUNTIME_FILE" <<'NODE' const fs = require("node:fs"); const runtimePath = process.argv[2] ?? ""; try { const runtime = JSON.parse(fs.readFileSync(runtimePath, "utf8")); const pid = Number(runtime.pid); const port = Number(runtime.port); if (!Number.isInteger(pid) || !Number.isInteger(port)) { process.exit(1); } const origin = new URL(String(runtime.origin ?? "")); if (origin.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(origin.hostname)) { process.exit(1); } process.kill(pid, 0); process.stdout.write(String(port)); } catch { process.exit(1); } NODE } 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)" DEFAULT_REMOTE_PORT="$(resolve_default_runtime_port 2>/dev/null || true)" if [ -n "$DEFAULT_REMOTE_PORT" ]; then REMOTE_PORT="$DEFAULT_REMOTE_PORT" if wait_ready "@@T3_REUSE_READY_TIMEOUT_MS@@"; then if [ "$REMOTE_MANAGED" = "managed" ] && [ -n "$REMOTE_PID" ] && kill -0 "$REMOTE_PID" 2>/dev/null; then kill "$REMOTE_PID" 2>/dev/null || true wait_for_pid_exit "$REMOTE_PID" fi printf '%s\\n' "$REMOTE_PORT" >"$PORT_FILE" printf 'external\\n' >"$MANAGED_FILE" REMOTE_PID="" REMOTE_MANAGED="external" 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)" fi fi if [ "$REMOTE_MANAGED" = "external" ]; then if [ -z "$REMOTE_PORT" ] || ! wait_ready "@@T3_REUSE_READY_TIMEOUT_MS@@"; then REMOTE_PID="" REMOTE_PORT="" REMOTE_MANAGED="" fi elif [ -n "$REMOTE_PID" ] && [ -n "$REMOTE_PORT" ] && kill -0 "$REMOTE_PID" 2>/dev/null; then if [ "$RUNNER_CHANGED" -eq 1 ]; then kill "$REMOTE_PID" 2>/dev/null || true wait_for_pid_exit "$REMOTE_PID" REMOTE_PID="" REMOTE_PORT="" REMOTE_MANAGED="" elif ! wait_ready "@@T3_REUSE_READY_TIMEOUT_MS@@"; then kill "$REMOTE_PID" 2>/dev/null || true wait_for_pid_exit "$REMOTE_PID" REMOTE_PID="" REMOTE_PORT="" REMOTE_MANAGED="" fi else REMOTE_PID="" REMOTE_PORT="" REMOTE_MANAGED="" fi if [ -z "$REMOTE_PID" ] || [ -z "$REMOTE_PORT" ]; then REMOTE_PORT="$(pick_port)" || true if [ -z "$REMOTE_PORT" ]; then printf 'Failed to find an available port on the remote host. Ensure node is available on PATH.\\n' >&2 exit 1 fi nohup env T3CODE_NO_BROWSER=1 "$RUNNER_FILE" serve --host 127.0.0.1 --port "$REMOTE_PORT" --base-dir "$DEFAULT_SERVER_HOME" >>"$LOG_FILE" 2>&1 < /dev/null & REMOTE_PID="$!" printf '%s\\n' "$REMOTE_PID" >"$PID_FILE" printf '%s\\n' "$REMOTE_PORT" >"$PORT_FILE" printf 'managed\\n' >"$MANAGED_FILE" if ! wait_ready "@@T3_READY_TIMEOUT_MS@@"; then printf 'Remote T3 server did not become ready on 127.0.0.1:%s.\\n' "$REMOTE_PORT" >&2 tail -n 80 "$LOG_FILE" >&2 2>/dev/null || true kill "$REMOTE_PID" 2>/dev/null || true wait_for_pid_exit "$REMOTE_PID" rm -f "$PID_FILE" "$PORT_FILE" "$MANAGED_FILE" exit 1 fi fi printf '{"remotePort":%s,"serverKind":"%s"}\\n' "$REMOTE_PORT" "\${REMOTE_MANAGED:-managed}" `; export const REMOTE_PAIRING_SCRIPT = `set -eu STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@" DEFAULT_SERVER_HOME="$HOME/.t3" RUNNER_FILE="$STATE_DIR/run-t3.sh" mkdir -p "$STATE_DIR" cat >"$RUNNER_FILE" <<'SH' @@T3_RUNNER_SCRIPT@@ SH chmod 700 "$RUNNER_FILE" PAIRING_BASE_DIR="$DEFAULT_SERVER_HOME" "$RUNNER_FILE" auth pairing create --base-dir "$PAIRING_BASE_DIR" --json `; export const REMOTE_STOP_SCRIPT = `set -eu STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@" PID_FILE="$STATE_DIR/pid" PORT_FILE="$STATE_DIR/port" MANAGED_FILE="$STATE_DIR/managed" REMOTE_MANAGED="$(cat "$MANAGED_FILE" 2>/dev/null || true)" REMOTE_PID="$(cat "$PID_FILE" 2>/dev/null || true)" if [ "$REMOTE_MANAGED" != "external" ] && [ -n "$REMOTE_PID" ] && kill -0 "$REMOTE_PID" 2>/dev/null; then kill "$REMOTE_PID" 2>/dev/null || true WAIT_COUNT=0 while kill -0 "$REMOTE_PID" 2>/dev/null && [ "$WAIT_COUNT" -lt 20 ]; do WAIT_COUNT=$((WAIT_COUNT + 1)) sleep 0.1 done fi rm -f "$PID_FILE" "$PORT_FILE" "$MANAGED_FILE" printf '{"stopped":true}\\n' `; const REMOTE_LOG_TAIL_SCRIPT = `set -eu STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@" LOG_FILE="$STATE_DIR/server.log" if [ -f "$LOG_FILE" ]; then tail -n 80 "$LOG_FILE" 2>/dev/null || true fi `; export function buildRemoteT3RunnerScript(input?: RemoteT3RunnerOptions): string { const packageSpec = shellSingleQuote(input?.packageSpec?.trim() || "t3@latest"); const nodeScriptPath = input?.nodeScriptPath?.trim() || ""; return stripTrailingNewlines( applyScriptPlaceholders(REMOTE_RUNNER_SCRIPT, { T3_PACKAGE_SPEC: packageSpec,