authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-05 09:16:06-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-05 09:16:06-07:00
log3fbc497b764ed904c4e50655da5b7a722d611fe5
treefdadaa639d21d84dcf8cc5d1b8a352e88dcf4c3c
parenta324cabc04019be14e8b66ec983fabf510ae2b2e
signature Signed by PGP key B5690EEEBB952194

refactor(ssh): keep package internals private (#10144)


4 files changed, 37 insertions(+), 39 deletions(-)

packages/ssh/src/auth.ts+26-28
...@@ -17,7 +17,7 @@ export interface SshPasswordRequest {...@@ -17,7 +17,7 @@ export interface SshPasswordRequest {
17 readonly attempt: number;17 readonly attempt: number;
18}18}
1919
20export interface SshAskpassFile {20interface SshAskpassFile {
21 readonly path: string;21 readonly path: string;
22 readonly contents: string;22 readonly contents: string;
23 readonly mode?: number;23 readonly mode?: number;
...@@ -71,7 +71,7 @@ function joinSshAskpassPath(...@@ -71,7 +71,7 @@ function joinSshAskpassPath(
71 return platform === "win32" ? `${trimmed}\\${fileName}` : `${trimmed}/${fileName}`;71 return platform === "win32" ? `${trimmed}\\${fileName}` : `${trimmed}/${fileName}`;
72}72}
7373
74export const ASKPASS_POSIX_SCRIPT = `#!/bin/sh74const ASKPASS_POSIX_SCRIPT = `#!/bin/sh
75# Invoked by ssh via SSH_ASKPASS when T3 Code re-runs ssh with a cached password75# Invoked by ssh via SSH_ASKPASS when T3 Code re-runs ssh with a cached password
76# from the renderer's in-app prompt. We never expose a native dialog here - if76# from the renderer's in-app prompt. We never expose a native dialog here - if
77# T3_SSH_AUTH_SECRET is missing, that's a caller bug and we fail loudly.77# T3_SSH_AUTH_SECRET is missing, that's a caller bug and we fail loudly.
...@@ -83,11 +83,11 @@ printf 'T3 Code ssh-askpass invoked without T3_SSH_AUTH_SECRET.\\n' >&2...@@ -83,11 +83,11 @@ printf 'T3 Code ssh-askpass invoked without T3_SSH_AUTH_SECRET.\\n' >&2
83exit 183exit 1
84`;84`;
8585
86export const ASKPASS_WINDOWS_LAUNCHER_SCRIPT = `@echo off\r86const ASKPASS_WINDOWS_LAUNCHER_SCRIPT = `@echo off\r
87powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0ssh-askpass.ps1" %*\r87powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0ssh-askpass.ps1" %*\r
88`;88`;
8989
90export const ASKPASS_WINDOWS_SCRIPT = `# Invoked by ssh via SSH_ASKPASS (through ssh-askpass.cmd) when T3 Code re-runs\r90const ASKPASS_WINDOWS_SCRIPT = `# Invoked by ssh via SSH_ASKPASS (through ssh-askpass.cmd) when T3 Code re-runs\r
91# ssh with a cached password from the renderer's in-app prompt. We never expose\r91# ssh with a cached password from the renderer's in-app prompt. We never expose\r
92# a native dialog here - if T3_SSH_AUTH_SECRET is missing, that's a caller bug\r92# a native dialog here - if T3_SSH_AUTH_SECRET is missing, that's a caller bug\r
93# and we fail loudly.\r93# and we fail loudly.\r
...@@ -99,7 +99,7 @@ if ($null -ne $env:T3_SSH_AUTH_SECRET) {\r...@@ -99,7 +99,7 @@ if ($null -ne $env:T3_SSH_AUTH_SECRET) {\r
99exit 1\r99exit 1\r
100`;100`;
101101
102export const getDefaultSshAskpassDirectory = Effect.fn("ssh/auth.getDefaultSshAskpassDirectory")(102const getDefaultSshAskpassDirectory = Effect.fn("ssh/auth.getDefaultSshAskpassDirectory")(
103 function* () {103 function* () {
104 const fs = yield* FileSystem.FileSystem;104 const fs = yield* FileSystem.FileSystem;
105 const path = yield* Path.Path;105 const path = yield* Path.Path;
...@@ -146,31 +146,29 @@ export const buildSshAskpassHelperDescriptor = Effect.fn(...@@ -146,31 +146,29 @@ export const buildSshAskpassHelperDescriptor = Effect.fn(
146 };146 };
147});147});
148148
149export const ensureSshAskpassHelpers = Effect.fn("ssh/auth.ensureSshAskpassHelpers")(149const ensureSshAskpassHelpers = Effect.fn("ssh/auth.ensureSshAskpassHelpers")(function* (input: {
150 function* (input: {150 readonly directory: string;
151 readonly directory: string;151}): Effect.fn.Return<string, PlatformError.PlatformError, FileSystem.FileSystem | Path.Path> {
152 }): Effect.fn.Return<string, PlatformError.PlatformError, FileSystem.FileSystem | Path.Path> {152 const fs = yield* FileSystem.FileSystem;
153 const fs = yield* FileSystem.FileSystem;153 const path = yield* Path.Path;
154 const path = yield* Path.Path;154 const descriptor = yield* buildSshAskpassHelperDescriptor(input);
155 const descriptor = yield* buildSshAskpassHelperDescriptor(input);155 const platform = yield* HostProcessPlatform;
156 const platform = yield* HostProcessPlatform;156
157157 yield* fs.makeDirectory(path.dirname(descriptor.launcherPath), { recursive: true });
158 yield* fs.makeDirectory(path.dirname(descriptor.launcherPath), { recursive: true });158
159159 for (const file of descriptor.files) {
160 for (const file of descriptor.files) {160 const existing = yield* fs.exists(file.path);
161 const existing = yield* fs.exists(file.path);161 const current = existing ? yield* fs.readFileString(file.path) : null;
162 const current = existing ? yield* fs.readFileString(file.path) : null;162 if (current !== file.contents) {
163 if (current !== file.contents) {163 yield* fs.writeFileString(file.path, file.contents);
164 yield* fs.writeFileString(file.path, file.contents);164 }
165 }165 if (file.mode !== undefined && platform !== "win32") {
166 if (file.mode !== undefined && platform !== "win32") {166 yield* fs.chmod(file.path, file.mode);
167 yield* fs.chmod(file.path, file.mode);
168 }
169 }167 }
168 }
170169
171 return descriptor.launcherPath;170 return descriptor.launcherPath;
172 },171});
173);
174172
175export const buildSshChildEnvironment = Effect.fn("ssh/auth.buildSshChildEnvironment")(function* (173export const buildSshChildEnvironment = Effect.fn("ssh/auth.buildSshChildEnvironment")(function* (
176 input: SshChildEnvironmentOptions = {},174 input: SshChildEnvironmentOptions = {},
packages/ssh/src/command.ts+1-1
...@@ -80,7 +80,7 @@ export function remoteStateKey(target: DesktopSshEnvironmentTarget): string {...@@ -80,7 +80,7 @@ export function remoteStateKey(target: DesktopSshEnvironmentTarget): string {
80 .slice(0, 16);80 .slice(0, 16);
81}81}
8282
83export function buildSshHostSpec(target: DesktopSshEnvironmentTarget): string {83function buildSshHostSpec(target: DesktopSshEnvironmentTarget): string {
84 const destination = target.alias.trim() || target.hostname.trim();84 const destination = target.alias.trim() || target.hostname.trim();
85 if (destination.length === 0) {85 if (destination.length === 0) {
86 throw new Error("SSH target is missing its alias/hostname.");86 throw new Error("SSH target is missing its alias/hostname.");
packages/ssh/src/config.ts+1-1
...@@ -89,7 +89,7 @@ const expandGlob = Effect.fnUntraced(function* (pattern: string) {...@@ -89,7 +89,7 @@ const expandGlob = Effect.fnUntraced(function* (pattern: string) {
89 return matchedPaths.toSorted((left, right) => left.localeCompare(right));89 return matchedPaths.toSorted((left, right) => left.localeCompare(right));
90});90});
9191
92export const collectSshConfigAliasesFromFile = Effect.fnUntraced(function* (92const collectSshConfigAliasesFromFile = Effect.fnUntraced(function* (
93 filePath: string,93 filePath: string,
94 visited = new Set<string>(),94 visited = new Set<string>(),
95 homeDir: string,95 homeDir: string,
packages/ssh/src/tunnel.ts+9-9
...@@ -49,7 +49,7 @@ import {...@@ -49,7 +49,7 @@ import {
49 SshReadinessError,49 SshReadinessError,
50} from "./errors.ts";50} from "./errors.ts";
5151
52export const DEFAULT_REMOTE_PORT = 3773;52const DEFAULT_REMOTE_PORT = 3773;
53const REMOTE_PORT_SCAN_WINDOW = 200;53const REMOTE_PORT_SCAN_WINDOW = 200;
54const SSH_READY_TIMEOUT_MS = 20_000;54const SSH_READY_TIMEOUT_MS = 20_000;
55const SSH_READY_PROBE_TIMEOUT_MS = 1_000;55const SSH_READY_PROBE_TIMEOUT_MS = 1_000;
...@@ -209,7 +209,7 @@ function buildRemoteNodeEngineCheckScript(): string {...@@ -209,7 +209,7 @@ function buildRemoteNodeEngineCheckScript(): string {
209(${remoteNodeEngineCheckMain.toString()})();`;209(${remoteNodeEngineCheckMain.toString()})();`;
210}210}
211211
212export function normalizeSshErrorMessage(stderr: string, fallbackMessage: string): string {212function normalizeSshErrorMessage(stderr: string, fallbackMessage: string): string {
213 const cleaned = stderr.trim();213 const cleaned = stderr.trim();
214 return cleaned.length > 0 ? cleaned : fallbackMessage;214 return cleaned.length > 0 ? cleaned : fallbackMessage;
215}215}
...@@ -270,7 +270,7 @@ function tryPort(port) {...@@ -270,7 +270,7 @@ function tryPort(port) {
270})().catch(() => process.exit(1));270})().catch(() => process.exit(1));
271`;271`;
272272
273export const REMOTE_WAIT_READY_SCRIPT = `const http = require("node:http");273const REMOTE_WAIT_READY_SCRIPT = `const http = require("node:http");
274const port = Number.parseInt(process.argv[2] ?? "", 10);274const port = Number.parseInt(process.argv[2] ?? "", 10);
275const timeoutMs = Number.parseInt(process.argv[3] ?? "", 10);275const timeoutMs = Number.parseInt(process.argv[3] ?? "", 10);
276const probeTimeoutMs = Number.parseInt(process.argv[4] ?? "", 10);276const probeTimeoutMs = Number.parseInt(process.argv[4] ?? "", 10);
...@@ -318,7 +318,7 @@ function probe() {...@@ -318,7 +318,7 @@ function probe() {
318})().catch(() => process.exit(1));318})().catch(() => process.exit(1));
319`;319`;
320320
321export const REMOTE_NODE_ENV_SCRIPT = `prepend_path_if_dir() {321const REMOTE_NODE_ENV_SCRIPT = `prepend_path_if_dir() {
322 if [ -d "$1" ]; then322 if [ -d "$1" ]; then
323 case ":$PATH:" in323 case ":$PATH:" in
324 *":$1:"*) ;;324 *":$1:"*) ;;
...@@ -411,7 +411,7 @@ ensure_remote_node_path() {...@@ -411,7 +411,7 @@ ensure_remote_node_path() {
411}411}
412`;412`;
413413
414export const REMOTE_RUNNER_SCRIPT = `#!/bin/sh414const REMOTE_RUNNER_SCRIPT = `#!/bin/sh
415set -eu415set -eu
416@@T3_NODE_ENV_SCRIPT@@416@@T3_NODE_ENV_SCRIPT@@
417ensure_remote_node_path || true417ensure_remote_node_path || true
...@@ -456,7 +456,7 @@ printf 'Remote host is missing the t3 CLI and could not install @@T3_PACKAGE_SPE...@@ -456,7 +456,7 @@ printf 'Remote host is missing the t3 CLI and could not install @@T3_PACKAGE_SPE
456exit 1456exit 1
457`;457`;
458458
459export const REMOTE_LAUNCH_SCRIPT = `set -eu459const REMOTE_LAUNCH_SCRIPT = `set -eu
460@@T3_NODE_ENV_SCRIPT@@460@@T3_NODE_ENV_SCRIPT@@
461STATE_KEY="$1"461STATE_KEY="$1"
462STATE_DIR="$HOME/.t3/ssh-launch/$STATE_KEY"462STATE_DIR="$HOME/.t3/ssh-launch/$STATE_KEY"
...@@ -615,7 +615,7 @@ fi...@@ -615,7 +615,7 @@ fi
615printf '{"remotePort":%s,"serverKind":"%s"}\\n' "$REMOTE_PORT" "\${REMOTE_MANAGED:-managed}"615printf '{"remotePort":%s,"serverKind":"%s"}\\n' "$REMOTE_PORT" "\${REMOTE_MANAGED:-managed}"
616`;616`;
617617
618export const REMOTE_PAIRING_SCRIPT = `set -eu618const REMOTE_PAIRING_SCRIPT = `set -eu
619STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@"619STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@"
620DEFAULT_SERVER_HOME="$HOME/.t3"620DEFAULT_SERVER_HOME="$HOME/.t3"
621RUNNER_FILE="$STATE_DIR/run-t3.sh"621RUNNER_FILE="$STATE_DIR/run-t3.sh"
...@@ -628,7 +628,7 @@ PAIRING_BASE_DIR="$DEFAULT_SERVER_HOME"...@@ -628,7 +628,7 @@ PAIRING_BASE_DIR="$DEFAULT_SERVER_HOME"
628"$RUNNER_FILE" auth pairing create --base-dir "$PAIRING_BASE_DIR" --json628"$RUNNER_FILE" auth pairing create --base-dir "$PAIRING_BASE_DIR" --json
629`;629`;
630630
631export const REMOTE_STOP_SCRIPT = `set -eu631const REMOTE_STOP_SCRIPT = `set -eu
632STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@"632STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@"
633PID_FILE="$STATE_DIR/pid"633PID_FILE="$STATE_DIR/pid"
634PORT_FILE="$STATE_DIR/port"634PORT_FILE="$STATE_DIR/port"
...@@ -823,7 +823,7 @@ export const issueRemotePairingToken = Effect.fn("ssh/tunnel.issueRemotePairingT...@@ -823,7 +823,7 @@ export const issueRemotePairingToken = Effect.fn("ssh/tunnel.issueRemotePairingT
823 };823 };
824});824});
825825
826export const stopRemoteServer = Effect.fn("ssh/tunnel.stopRemoteServer")(function* (826const stopRemoteServer = Effect.fn("ssh/tunnel.stopRemoteServer")(function* (
827 target: DesktopSshEnvironmentTarget,827 target: DesktopSshEnvironmentTarget,
828 input?: SshAuthOptions,828 input?: SshAuthOptions,
829): Effect.fn.Return<829): Effect.fn.Return<