| author | |
| committer | |
| log | 3fbc497b764ed904c4e50655da5b7a722d611fe5 |
| tree | fdadaa639d21d84dcf8cc5d1b8a352e88dcf4c3c |
| parent | a324cabc04019be14e8b66ec983fabf510ae2b2e |
| signature | Signed by PGP key B5690EEEBB952194 |
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 | } |
| 19 | 19 | ||
| 20 | export interface SshAskpassFile { | 20 | interface 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 | } |
| 73 | 73 | ||
| 74 | export const ASKPASS_POSIX_SCRIPT = `#!/bin/sh | 74 | const ASKPASS_POSIX_SCRIPT = `#!/bin/sh |
| 75 | # Invoked by ssh via SSH_ASKPASS when T3 Code re-runs ssh with a cached password | 75 | # 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 - if | 76 | # 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 |
| 83 | exit 1 | 83 | exit 1 |
| 84 | `; | 84 | `; |
| 85 | 85 | ||
| 86 | export const ASKPASS_WINDOWS_LAUNCHER_SCRIPT = `@echo off\r | 86 | const ASKPASS_WINDOWS_LAUNCHER_SCRIPT = `@echo off\r |
| 87 | powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0ssh-askpass.ps1" %*\r | 87 | powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0ssh-askpass.ps1" %*\r |
| 88 | `; | 88 | `; |
| 89 | 89 | ||
| 90 | export const ASKPASS_WINDOWS_SCRIPT = `# Invoked by ssh via SSH_ASKPASS (through ssh-askpass.cmd) when T3 Code re-runs\r | 90 | const 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\r | 91 | # 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\r | 92 | # a native dialog here - if T3_SSH_AUTH_SECRET is missing, that's a caller bug\r |
| 93 | # and we fail loudly.\r | 93 | # 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 |
| 99 | exit 1\r | 99 | exit 1\r |
| 100 | `; | 100 | `; |
| 101 | 101 | ||
| 102 | export const getDefaultSshAskpassDirectory = Effect.fn("ssh/auth.getDefaultSshAskpassDirectory")( | 102 | const 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 | }); |
| 148 | 148 | ||
| 149 | export const ensureSshAskpassHelpers = Effect.fn("ssh/auth.ensureSshAskpassHelpers")( | 149 | const 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 | |
| 157 | 157 | yield* fs.makeDirectory(path.dirname(descriptor.launcherPath), { recursive: true }); | |
| 158 | yield* fs.makeDirectory(path.dirname(descriptor.launcherPath), { recursive: true }); | 158 | |
| 159 | 159 | 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 | } | ||
| 170 | 169 | ||
| 171 | return descriptor.launcherPath; | 170 | return descriptor.launcherPath; |
| 172 | }, | 171 | }); |
| 173 | ); | ||
| 174 | 172 | ||
| 175 | export const buildSshChildEnvironment = Effect.fn("ssh/auth.buildSshChildEnvironment")(function* ( | 173 | export 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 | } |
| 82 | 82 | ||
| 83 | export function buildSshHostSpec(target: DesktopSshEnvironmentTarget): string { | 83 | function 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 | }); |
| 91 | 91 | ||
| 92 | export const collectSshConfigAliasesFromFile = Effect.fnUntraced(function* ( | 92 | const 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"; |
| 51 | 51 | ||
| 52 | export const DEFAULT_REMOTE_PORT = 3773; | 52 | const DEFAULT_REMOTE_PORT = 3773; |
| 53 | const REMOTE_PORT_SCAN_WINDOW = 200; | 53 | const REMOTE_PORT_SCAN_WINDOW = 200; |
| 54 | const SSH_READY_TIMEOUT_MS = 20_000; | 54 | const SSH_READY_TIMEOUT_MS = 20_000; |
| 55 | const SSH_READY_PROBE_TIMEOUT_MS = 1_000; | 55 | const 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 | } |
| 211 | 211 | ||
| 212 | export function normalizeSshErrorMessage(stderr: string, fallbackMessage: string): string { | 212 | function 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 | `; |
| 272 | 272 | ||
| 273 | export const REMOTE_WAIT_READY_SCRIPT = `const http = require("node:http"); | 273 | const REMOTE_WAIT_READY_SCRIPT = `const http = require("node:http"); |
| 274 | const port = Number.parseInt(process.argv[2] ?? "", 10); | 274 | const port = Number.parseInt(process.argv[2] ?? "", 10); |
| 275 | const timeoutMs = Number.parseInt(process.argv[3] ?? "", 10); | 275 | const timeoutMs = Number.parseInt(process.argv[3] ?? "", 10); |
| 276 | const probeTimeoutMs = Number.parseInt(process.argv[4] ?? "", 10); | 276 | const 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 | `; |
| 320 | 320 | ||
| 321 | export const REMOTE_NODE_ENV_SCRIPT = `prepend_path_if_dir() { | 321 | const REMOTE_NODE_ENV_SCRIPT = `prepend_path_if_dir() { |
| 322 | if [ -d "$1" ]; then | 322 | if [ -d "$1" ]; then |
| 323 | case ":$PATH:" in | 323 | 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 | `; |
| 413 | 413 | ||
| 414 | export const REMOTE_RUNNER_SCRIPT = `#!/bin/sh | 414 | const REMOTE_RUNNER_SCRIPT = `#!/bin/sh |
| 415 | set -eu | 415 | set -eu |
| 416 | @@T3_NODE_ENV_SCRIPT@@ | 416 | @@T3_NODE_ENV_SCRIPT@@ |
| 417 | ensure_remote_node_path || true | 417 | ensure_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 |
| 456 | exit 1 | 456 | exit 1 |
| 457 | `; | 457 | `; |
| 458 | 458 | ||
| 459 | export const REMOTE_LAUNCH_SCRIPT = `set -eu | 459 | const REMOTE_LAUNCH_SCRIPT = `set -eu |
| 460 | @@T3_NODE_ENV_SCRIPT@@ | 460 | @@T3_NODE_ENV_SCRIPT@@ |
| 461 | STATE_KEY="$1" | 461 | STATE_KEY="$1" |
| 462 | STATE_DIR="$HOME/.t3/ssh-launch/$STATE_KEY" | 462 | STATE_DIR="$HOME/.t3/ssh-launch/$STATE_KEY" |
| ... | @@ -615,7 +615,7 @@ fi | ... | @@ -615,7 +615,7 @@ fi |
| 615 | printf '{"remotePort":%s,"serverKind":"%s"}\\n' "$REMOTE_PORT" "\${REMOTE_MANAGED:-managed}" | 615 | printf '{"remotePort":%s,"serverKind":"%s"}\\n' "$REMOTE_PORT" "\${REMOTE_MANAGED:-managed}" |
| 616 | `; | 616 | `; |
| 617 | 617 | ||
| 618 | export const REMOTE_PAIRING_SCRIPT = `set -eu | 618 | const REMOTE_PAIRING_SCRIPT = `set -eu |
| 619 | STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@" | 619 | STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@" |
| 620 | DEFAULT_SERVER_HOME="$HOME/.t3" | 620 | DEFAULT_SERVER_HOME="$HOME/.t3" |
| 621 | RUNNER_FILE="$STATE_DIR/run-t3.sh" | 621 | RUNNER_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" --json | 628 | "$RUNNER_FILE" auth pairing create --base-dir "$PAIRING_BASE_DIR" --json |
| 629 | `; | 629 | `; |
| 630 | 630 | ||
| 631 | export const REMOTE_STOP_SCRIPT = `set -eu | 631 | const REMOTE_STOP_SCRIPT = `set -eu |
| 632 | STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@" | 632 | STATE_DIR="$HOME/.t3/ssh-launch/@@T3_STATE_KEY@@" |
| 633 | PID_FILE="$STATE_DIR/pid" | 633 | PID_FILE="$STATE_DIR/pid" |
| 634 | PORT_FILE="$STATE_DIR/port" | 634 | PORT_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 | }); |
| 825 | 825 | ||
| 826 | export const stopRemoteServer = Effect.fn("ssh/tunnel.stopRemoteServer")(function* ( | 826 | const 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< |