import type { DesktopSshEnvironmentBootstrap, DesktopSshEnvironmentTarget,} from "@t3tools/contracts";import * as NetService from "@t3tools/shared/Net";import { extractJsonObject, fromLenientJson } from "@t3tools/shared/schemaJson";import { satisfiesSemverRange } from "@t3tools/shared/semver";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 DEFAULT_HTTP_READY_TIMEOUT = Duration.seconds(30);const DEFAULT_HTTP_READY_INTERVAL = Duration.millis(100);const SSH_READY_TIMEOUT = Duration.seconds(20);const SSH_READY_PROBE_TIMEOUT = Duration.seconds(1);const TUNNEL_SHUTDOWN_TIMEOUT = Duration.seconds(2);const REMOTE_READY_TIMEOUT = Duration.seconds(15);const REMOTE_REUSE_READY_TIMEOUT = Duration.seconds(2); export interface RemoteT3RunnerOptions { readonly packageSpec?: string; readonly nodeScriptPath?: string | null; readonly nodeEngineRange?: 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.NetService | SshPasswordPrompt; type SshEnvironmentEffectError = | SshCommandError | SshInvalidTargetError | SshLaunchError | SshPairingError | SshReadinessError | SshPasswordPromptError | NetService.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)); const decodeRemoteJsonOutput = <A, E>( stdout: string, decode: (input: string) => Effect.Effect<A, E>,): Effect.Effect<A, E> => decode(stdout).pipe( Effect.catch((error) => Effect.gen(function* () { const jsonObject = extractJsonObject(stdout); if (jsonObject === stdout.trim()) { return yield* Effect.fail(error); } const exit = yield* Effect.exit(decode(jsonObject)); if (Exit.isSuccess(exit)) { return exit.value; } return yield* Effect.fail(error); }), ), ); const decodeRemoteLaunchOutput = (stdout: string) => decodeRemoteJsonOutput(stdout, decodeRemoteLaunchResult); const decodeRemotePairingOutput = (stdout: string) => decodeRemoteJsonOutput(stdout, decodeRemotePairingResult); const remoteNodeEngineCheckMain = function remoteNodeEngineCheckMain() { const range = process.argv[2] || ""; const rawVersion = process.versions && process.versions.node ? process.versions.node : process.version; if (!satisfiesSemverRange(rawVersion, range)) { process.stderr.write( "Remote node " + rawVersion + " does not satisfy required range " + range + ".\n", ); process.exit(1); }}; function buildRemoteNodeEngineCheckScript(): string { return `${satisfiesSemverRange.toString()}(${remoteNodeEngineCheckMain.toString()})();`;} 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_NODE_ENV_SCRIPT = `prepend_path_if_dir() { if [ -d "$1" ]; then case ":$PATH:" in *":$1:"*) ;; *) PATH="$1:$PATH" ;; esac fi} remote_node_satisfies_engine() { T3_NODE_ENGINE_RANGE=@@T3_NODE_ENGINE_RANGE@@ if [ -z "$T3_NODE_ENGINE_RANGE" ]; then return 0 fi node - "$T3_NODE_ENGINE_RANGE" <<'NODE'@@T3_NODE_ENGINE_CHECK_SCRIPT@@NODE} ensure_remote_node_path() { if command -v node >/dev/null 2>&1 && remote_node_satisfies_engine >/dev/null 2>&1; then return 0 fi prepend_path_if_dir "$HOME/.local/bin" prepend_path_if_dir "$HOME/bin" prepend_path_if_dir "/opt/homebrew/bin" prepend_path_if_dir "/usr/local/bin" prepend_path_if_dir "/usr/bin" prepend_path_if_dir "/bin" if [ -z "\${VOLTA_HOME:-}" ]; then VOLTA_HOME="$HOME/.volta" fi export VOLTA_HOME prepend_path_if_dir "$VOLTA_HOME/bin" prepend_path_if_dir "$HOME/.asdf/shims" prepend_path_if_dir "$HOME/.asdf/bin" if [ ! -x "$HOME/.asdf/shims/node" ] && [ -s "$HOME/.asdf/asdf.sh" ]; then # shellcheck disable=SC1090 . "$HOME/.asdf/asdf.sh" fi prepend_path_if_dir "$HOME/.local/share/mise/shims" prepend_path_if_dir "$HOME/.mise/shims" if ! command -v node >/dev/null 2>&1 && command -v mise >/dev/null 2>&1; then eval "$(mise activate sh)" >/dev/null 2>&1 || true fi if [ -z "\${FNM_DIR:-}" ]; then FNM_DIR="$HOME/.local/share/fnm" fi export FNM_DIR prepend_path_if_dir "$FNM_DIR" prepend_path_if_dir "$HOME/.fnm" if ! command -v node >/dev/null 2>&1 && command -v fnm >/dev/null 2>&1; then eval "$(fnm env --use-on-cd --shell sh)" >/dev/null 2>&1 || eval "$(fnm env --shell sh)" >/dev/null 2>&1 || true fi prepend_path_if_dir "$HOME/.nodenv/bin" prepend_path_if_dir "$HOME/.nodenv/shims" if ! command -v node >/dev/null 2>&1 && command -v nodenv >/dev/null 2>&1; then eval "$(nodenv init -)" >/dev/null 2>&1 || true fi if [ -z "\${NVM_DIR:-}" ]; then NVM_DIR="$HOME/.nvm" fi export NVM_DIR if [ -s "$NVM_DIR/nvm.sh" ]; then # shellcheck disable=SC1090 . "$NVM_DIR/nvm.sh" if ! command -v node >/dev/null 2>&1 && command -v nvm >/dev/null 2>&1; then nvm use --silent default >/dev/null 2>&1 || nvm use --silent node >/dev/null 2>&1 || nvm use --silent --lts >/dev/null 2>&1 || true fi fi if ! command -v node >/dev/null 2>&1 && [ -d "$NVM_DIR/versions/node" ]; then for T3_NODE_BIN in "$NVM_DIR"/versions/node/*/bin; do if [ -x "$T3_NODE_BIN/node" ]; then PATH="$T3_NODE_BIN:$PATH" export PATH fi done fi command -v node >/dev/null 2>&1 && remote_node_satisfies_engine}`; export const REMOTE_RUNNER_SCRIPT = `#!/bin/shset -eu@@T3_NODE_ENV_SCRIPT@@ensure_remote_node_path || trueT3_NODE_SCRIPT_PATH=@@T3_NODE_SCRIPT_PATH@@if [ -n "$T3_NODE_SCRIPT_PATH" ]; then if ! command -v node >/dev/null 2>&1; then printf 'Remote host is missing node on PATH. Install Node or configure a supported version manager for non-interactive shells.\\n' >&2 exit 1 fi 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 node/npm/npx are unavailable on PATH. Install Node or configure a supported version manager for non-interactive shells.\\n' >&2exit 1`; export const REMOTE_LAUNCH_SCRIPT = `set -eu@@T3_NODE_ENV_SCRIPT@@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 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"if ! ensure_remote_node_path; then printf 'Remote host is missing node on PATH. Install Node or configure a supported version manager for non-interactive shells.\\n' >&2 exit 1fipick_port() { node - "$PORT_FILE" "@@T3_DEFAULT_REMOTE_PORT@@" "@@T3_REMOTE_PORT_SCAN_WINDOW@@" <<'NODE'@@T3_PICK_PORT_SCRIPT@@NODELines 1–500 of 1765. Load more
import type { DesktopSshEnvironmentBootstrap, DesktopSshEnvironmentTarget, } from "@t3tools/contracts"; import * as NetService from "@t3tools/shared/Net"; import { extractJsonObject, fromLenientJson } from "@t3tools/shared/schemaJson"; import { satisfiesSemverRange } from "@t3tools/shared/semver"; 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 DEFAULT_HTTP_READY_TIMEOUT = Duration.seconds(30); const DEFAULT_HTTP_READY_INTERVAL = Duration.millis(100); const SSH_READY_TIMEOUT = Duration.seconds(20); const SSH_READY_PROBE_TIMEOUT = Duration.seconds(1); const TUNNEL_SHUTDOWN_TIMEOUT = Duration.seconds(2); const REMOTE_READY_TIMEOUT = Duration.seconds(15); const REMOTE_REUSE_READY_TIMEOUT = Duration.seconds(2); export interface RemoteT3RunnerOptions { readonly packageSpec?: string; readonly nodeScriptPath?: string | null; readonly nodeEngineRange?: 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.NetService | SshPasswordPrompt; type SshEnvironmentEffectError = | SshCommandError | SshInvalidTargetError | SshLaunchError | SshPairingError | SshReadinessError | SshPasswordPromptError | NetService.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)); const decodeRemoteJsonOutput = <A, E>( stdout: string, decode: (input: string) => Effect.Effect<A, E>, ): Effect.Effect<A, E> => decode(stdout).pipe( Effect.catch((error) => Effect.gen(function* () { const jsonObject = extractJsonObject(stdout); if (jsonObject === stdout.trim()) { return yield* Effect.fail(error); } const exit = yield* Effect.exit(decode(jsonObject)); if (Exit.isSuccess(exit)) { return exit.value; } return yield* Effect.fail(error); }), ), ); const decodeRemoteLaunchOutput = (stdout: string) => decodeRemoteJsonOutput(stdout, decodeRemoteLaunchResult); const decodeRemotePairingOutput = (stdout: string) => decodeRemoteJsonOutput(stdout, decodeRemotePairingResult); const remoteNodeEngineCheckMain = function remoteNodeEngineCheckMain() { const range = process.argv[2] || ""; const rawVersion = process.versions && process.versions.node ? process.versions.node : process.version; if (!satisfiesSemverRange(rawVersion, range)) { process.stderr.write( "Remote node " + rawVersion + " does not satisfy required range " + range + ".\n", ); process.exit(1); } }; function buildRemoteNodeEngineCheckScript(): string { return `${satisfiesSemverRange.toString()} (${remoteNodeEngineCheckMain.toString()})();`; } 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_NODE_ENV_SCRIPT = `prepend_path_if_dir() { if [ -d "$1" ]; then case ":$PATH:" in *":$1:"*) ;; *) PATH="$1:$PATH" ;; esac fi } remote_node_satisfies_engine() { T3_NODE_ENGINE_RANGE=@@T3_NODE_ENGINE_RANGE@@ if [ -z "$T3_NODE_ENGINE_RANGE" ]; then return 0 fi node - "$T3_NODE_ENGINE_RANGE" <<'NODE' @@T3_NODE_ENGINE_CHECK_SCRIPT@@ NODE } ensure_remote_node_path() { if command -v node >/dev/null 2>&1 && remote_node_satisfies_engine >/dev/null 2>&1; then return 0 fi prepend_path_if_dir "$HOME/.local/bin" prepend_path_if_dir "$HOME/bin" prepend_path_if_dir "/opt/homebrew/bin" prepend_path_if_dir "/usr/local/bin" prepend_path_if_dir "/usr/bin" prepend_path_if_dir "/bin" if [ -z "\${VOLTA_HOME:-}" ]; then VOLTA_HOME="$HOME/.volta" fi export VOLTA_HOME prepend_path_if_dir "$VOLTA_HOME/bin" prepend_path_if_dir "$HOME/.asdf/shims" prepend_path_if_dir "$HOME/.asdf/bin" if [ ! -x "$HOME/.asdf/shims/node" ] && [ -s "$HOME/.asdf/asdf.sh" ]; then # shellcheck disable=SC1090 . "$HOME/.asdf/asdf.sh" fi prepend_path_if_dir "$HOME/.local/share/mise/shims" prepend_path_if_dir "$HOME/.mise/shims" if ! command -v node >/dev/null 2>&1 && command -v mise >/dev/null 2>&1; then eval "$(mise activate sh)" >/dev/null 2>&1 || true fi if [ -z "\${FNM_DIR:-}" ]; then FNM_DIR="$HOME/.local/share/fnm" fi export FNM_DIR prepend_path_if_dir "$FNM_DIR" prepend_path_if_dir "$HOME/.fnm" if ! command -v node >/dev/null 2>&1 && command -v fnm >/dev/null 2>&1; then eval "$(fnm env --use-on-cd --shell sh)" >/dev/null 2>&1 || eval "$(fnm env --shell sh)" >/dev/null 2>&1 || true fi prepend_path_if_dir "$HOME/.nodenv/bin" prepend_path_if_dir "$HOME/.nodenv/shims" if ! command -v node >/dev/null 2>&1 && command -v nodenv >/dev/null 2>&1; then eval "$(nodenv init -)" >/dev/null 2>&1 || true fi if [ -z "\${NVM_DIR:-}" ]; then NVM_DIR="$HOME/.nvm" fi export NVM_DIR if [ -s "$NVM_DIR/nvm.sh" ]; then # shellcheck disable=SC1090 . "$NVM_DIR/nvm.sh" if ! command -v node >/dev/null 2>&1 && command -v nvm >/dev/null 2>&1; then nvm use --silent default >/dev/null 2>&1 || nvm use --silent node >/dev/null 2>&1 || nvm use --silent --lts >/dev/null 2>&1 || true fi fi if ! command -v node >/dev/null 2>&1 && [ -d "$NVM_DIR/versions/node" ]; then for T3_NODE_BIN in "$NVM_DIR"/versions/node/*/bin; do if [ -x "$T3_NODE_BIN/node" ]; then PATH="$T3_NODE_BIN:$PATH" export PATH fi done fi command -v node >/dev/null 2>&1 && remote_node_satisfies_engine } `; export const REMOTE_RUNNER_SCRIPT = `#!/bin/sh set -eu @@T3_NODE_ENV_SCRIPT@@ ensure_remote_node_path || true T3_NODE_SCRIPT_PATH=@@T3_NODE_SCRIPT_PATH@@ if [ -n "$T3_NODE_SCRIPT_PATH" ]; then if ! command -v node >/dev/null 2>&1; then printf 'Remote host is missing node on PATH. Install Node or configure a supported version manager for non-interactive shells.\\n' >&2 exit 1 fi 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 node/npm/npx are unavailable on PATH. Install Node or configure a supported version manager for non-interactive shells.\\n' >&2 exit 1 `; export const REMOTE_LAUNCH_SCRIPT = `set -eu @@T3_NODE_ENV_SCRIPT@@ 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" if ! ensure_remote_node_path; then printf 'Remote host is missing node on PATH. Install Node or configure a supported version manager for non-interactive shells.\\n' >&2 exit 1 fi pick_port() { node - "$PORT_FILE" "@@T3_DEFAULT_REMOTE_PORT@@" "@@T3_REMOTE_PORT_SCAN_WINDOW@@" <<'NODE' @@T3_PICK_PORT_SCRIPT@@ NODE