@earendil-works/pi-env 1.0.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ssh.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../src/ssh.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, mkdtemp, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Connection, type ConnectionOptions } from \"./connection.ts\";\n\n/** A remote system the package ships a daemon for. */\nexport type RemotePlatform = {\n\tplatform: \"linux\" | \"android\" | \"darwin\" | \"windows\";\n\tarch: \"x64\" | \"arm64\";\n\t/** The remote home directory, in the remote system's own spelling. */\n\thome: string;\n\t/** Windows: the shell `sshd` runs commands with (`DefaultShell`). */\n\tshell?: \"cmd\" | \"powershell\";\n\t/** Problems worth telling the owner about, such as Termux without `termux-exec`. */\n\twarnings: string[];\n};\n\n/** How to reach a machine with the system `ssh`. */\nexport interface SshTarget {\n\t/** Host name or `~/.ssh/config` alias. */\n\thost: string;\n\tuser?: string;\n\tport?: number;\n\tidentityFile?: string;\n\t/** Host keys this application trusts; `acceptHostKey` adds to it. */\n\tknownHostsFile: string;\n\t/** The name keys are stored under, independent of aliases, ports and jump hosts, e.g. `pi-env-<env name>`. */\n\thostKeyAlias: string;\n\t/** The `ssh` program; default `ssh`. */\n\tssh?: string;\n\t/** An `ssh` configuration file instead of `~/.ssh/config` (`-F`). */\n\tconfigFile?: string;\n}\n\n/** The remote host's key is not in `knownHostsFile`; `scanHostKey` shows it so the owner can accept it. */\nexport class HostKeyUnknownError extends Error {}\n\n/** The remote host's key differs from the one in `knownHostsFile`; it is never accepted automatically. */\nexport class HostKeyChangedError extends Error {}\n\n/** An `ssh` invocation that failed for another reason, with its exit code and diagnostics. */\nexport class SshError extends Error {\n\treadonly exitCode: number | null;\n\treadonly stderr: string;\n\n\tconstructor(message: string, exitCode: number | null, stderr: string) {\n\t\tsuper(message);\n\t\tthis.exitCode = exitCode;\n\t\tthis.stderr = stderr;\n\t}\n}\n\nconst packageRoot = fileURLToPath(new URL(\"..\", import.meta.url));\n\n/** No leading `-` (it would be read as an `ssh` option) and no whitespace or control characters. */\nfunction checkField(name: string, value: string): void {\n\tif (value === \"\" || value.startsWith(\"-\") || /[\\s\\x00-\\x1f\\x7f]/.test(value)) {\n\t\tthrow new Error(`Invalid ${name}: ${JSON.stringify(value)}`);\n\t}\n}\n\n/** A path for an `ssh` option that expands `%` tokens: quoted, with `%` literal. */\nfunction configPath(name: string, path: string): string {\n\tif (path === \"\" || /[\"\\x00-\\x1f\\x7f]/.test(path)) throw new Error(`Invalid ${name}: ${JSON.stringify(path)}`);\n\treturn `\"${path.replaceAll(\"%\", \"%%\")}\"`;\n}\n\n/**\n * Arguments for `ssh` up to the host: no prompts, no forwarding of any kind, no shared connections, no commands from\n * the configuration, no locale forwarding (the remote uses its own), and host keys checked strictly against the\n * application's own file under a fixed alias.\n */\nexport function sshArguments(\n\ttarget: SshTarget,\n\tstrictHostKeys = true,\n\tknownHostsFile = target.knownHostsFile,\n): string[] {\n\tcheckField(\"host\", target.host);\n\tcheckField(\"host key alias\", target.hostKeyAlias);\n\tif (target.user !== undefined) checkField(\"user\", target.user);\n\tif (target.port !== undefined && (!Number.isInteger(target.port) || target.port < 1 || target.port > 65535)) {\n\t\tthrow new Error(`Invalid port: ${target.port}`);\n\t}\n\tconst option = (setting: string) => [\"-o\", setting];\n\treturn [\n\t\t...(target.configFile === undefined ? [] : [\"-F\", target.configFile]),\n\t\t\"-T\",\n\t\t\"-a\",\n\t\t\"-x\",\n\t\t...option(\"BatchMode=yes\"),\n\t\t...option(\"ClearAllForwardings=yes\"),\n\t\t...option(\"ForwardAgent=no\"),\n\t\t...option(\"ForwardX11=no\"),\n\t\t...option(\"ControlMaster=no\"),\n\t\t...option(\"ControlPath=none\"),\n\t\t...option(\"RemoteCommand=none\"),\n\t\t...option(\"PermitLocalCommand=no\"),\n\t\t...option(\"SendEnv=-*\"),\n\t\t...option(\"ServerAliveInterval=15\"),\n\t\t...option(`StrictHostKeyChecking=${strictHostKeys ? \"yes\" : \"accept-new\"}`),\n\t\t...option(`UserKnownHostsFile=${configPath(\"known hosts file\", knownHostsFile)}`),\n\t\t...option(\"GlobalKnownHostsFile=none\"),\n\t\t...option(\"HashKnownHosts=no\"),\n\t\t...option(`HostKeyAlias=${target.hostKeyAlias}`),\n\t\t...(target.user === undefined ? [] : [\"-l\", target.user]),\n\t\t...(target.port === undefined ? [] : [\"-p\", String(target.port)]),\n\t\t...(target.identityFile === undefined\n\t\t\t? []\n\t\t\t: [\n\t\t\t\t\t...option(`IdentityFile=${configPath(\"identity file\", target.identityFile)}`),\n\t\t\t\t\t...option(\"IdentitiesOnly=yes\"),\n\t\t\t\t]),\n\t\t\"--\",\n\t\ttarget.host,\n\t];\n}\n\n/** How long a detection, check or upload over `ssh` may take before it is abandoned. */\nconst SSH_TIMEOUT_MS = 60_000;\nconst UPLOAD_TIMEOUT_MS = 300_000;\n\n/** Run one remote command over `ssh`, optionally feeding stdin; resolves with stdout, rejects on failure. */\nfunction runSsh(\n\ttarget: SshTarget,\n\tcommand: string,\n\toptions: { stdin?: Uint8Array; args?: string[]; timeoutMs?: number } = {},\n): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst args = options.args ?? sshArguments(target);\n\t\tconst child = spawn(target.ssh ?? \"ssh\", [...args, command], { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n\t\tconst timeoutMs = options.timeoutMs ?? SSH_TIMEOUT_MS;\n\t\tconst timer = setTimeout(() => {\n\t\t\tchild.kill();\n\t\t\tconst diagnostics = Buffer.concat(stderr).toString(\"utf8\");\n\t\t\treject(\n\t\t\t\tnew SshError(\n\t\t\t\t\t`ssh ${target.host} did not finish within ${timeoutMs / 1000} s: ${diagnostics.trim()}`,\n\t\t\t\t\tnull,\n\t\t\t\t\tdiagnostics,\n\t\t\t\t),\n\t\t\t);\n\t\t}, timeoutMs);\n\t\tconst stdout: Buffer[] = [];\n\t\tconst stderr: Buffer[] = [];\n\t\tchild.stdout.on(\"data\", (chunk: Buffer) => stdout.push(chunk));\n\t\tchild.stderr.on(\"data\", (chunk: Buffer) => stderr.push(chunk));\n\t\tchild.stdin.on(\"error\", () => {});\n\t\tchild.on(\"error\", (error) => {\n\t\t\tclearTimeout(timer);\n\t\t\treject(new SshError(error.message, null, \"\"));\n\t\t});\n\t\tchild.on(\"close\", (code) => {\n\t\t\tclearTimeout(timer);\n\t\t\tconst output = Buffer.concat(stdout).toString(\"utf8\");\n\t\t\tconst diagnostics = Buffer.concat(stderr).toString(\"utf8\");\n\t\t\tif (code === 0) return resolve(output);\n\t\t\tif (/REMOTE HOST IDENTIFICATION HAS CHANGED/.test(diagnostics)) {\n\t\t\t\treturn reject(\n\t\t\t\t\tnew HostKeyChangedError(`The host key of ${target.host} changed; remove the old key to continue`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (/Host key verification failed|No .* host key is known/.test(diagnostics)) {\n\t\t\t\treturn reject(new HostKeyUnknownError(`The host key of ${target.host} is not trusted yet`));\n\t\t\t}\n\t\t\treject(\n\t\t\t\tnew SshError(`ssh ${target.host} failed with exit code ${code}: ${diagnostics.trim()}`, code, diagnostics),\n\t\t\t);\n\t\t});\n\t\tchild.stdin.end(options.stdin ?? new Uint8Array(0));\n\t});\n}\n\n/**\n * Connect once with `accept-new` against a temporary known-hosts file to capture the key the host presents, through\n * the same route (`~/.ssh/config`, jump hosts) as real connections. Returns its known-hosts lines and fingerprints.\n * Showing a fingerprint is not authentication: compare it with one obtained out of band before accepting it.\n */\nexport async function scanHostKey(target: SshTarget): Promise<{ lines: string[]; fingerprints: string[] }> {\n\tconst directory = await mkdtemp(join(tmpdir(), \"pi-env-hostkey-\"));\n\ttry {\n\t\tconst scanned = join(directory, \"known_hosts\");\n\t\t// Authentication may fail without the key; the key is recorded before authentication.\n\t\tawait runSsh(target, \"exit 0\", { args: sshArguments(target, false, scanned) }).catch((error: unknown) => {\n\t\t\tif (!(error instanceof SshError) && !(error instanceof HostKeyUnknownError)) throw error;\n\t\t});\n\t\tconst lines = existsSync(scanned)\n\t\t\t? (await readFile(scanned, \"utf8\")).split(\"\\n\").filter((line) => line.trim() !== \"\")\n\t\t\t: [];\n\t\tif (lines.length === 0) throw new Error(`No host key received from ${target.host}`);\n\t\tconst fingerprints = await new Promise<string[]>((resolve, reject) => {\n\t\t\tconst child = spawn(\"ssh-keygen\", [\"-lf\", scanned], { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\tlet output = \"\";\n\t\t\tchild.stdout.on(\"data\", (chunk: Buffer) => {\n\t\t\t\toutput += chunk.toString(\"utf8\");\n\t\t\t});\n\t\t\tchild.on(\"error\", reject);\n\t\t\tchild.on(\"close\", () => resolve(output.split(\"\\n\").filter((line) => line.trim() !== \"\")));\n\t\t});\n\t\treturn { lines, fingerprints };\n\t} finally {\n\t\tawait rm(directory, { recursive: true, force: true });\n\t}\n}\n\n/** A known-hosts line for `alias`: `alias type key [comment]`, no markers, patterns or hashed names. */\nfunction parseHostKeyLine(alias: string, line: string): { type: string; key: string } {\n\tconst [host, type, key] = line.trim().split(/\\s+/);\n\tif (\n\t\thost !== alias ||\n\t\ttype === undefined ||\n\t\tkey === undefined ||\n\t\t!/^(ssh-[a-z0-9-]+|ecdsa-sha2-[a-z0-9-]+|sk-[a-z0-9@.-]+)$/.test(type) ||\n\t\t!/^[A-Za-z0-9+/]+={0,2}$/.test(key)\n\t) {\n\t\tthrow new Error(`Not a host key line for ${alias}: ${JSON.stringify(line)}`);\n\t}\n\treturn { type, key };\n}\n\n/** Known-hosts files being changed, so changes to one file happen one at a time. */\nconst knownHostsChanges = new Map<string, Promise<unknown>>();\n\n/** Rewrite the known-hosts file one change at a time, atomically. */\nasync function changeKnownHosts(file: string, change: (lines: string[]) => string[]): Promise<void> {\n\tconst previous = knownHostsChanges.get(file) ?? Promise.resolve();\n\tconst next = previous\n\t\t.catch(() => {})\n\t\t.then(async () => {\n\t\t\tawait mkdir(dirname(file), { recursive: true, mode: 0o700 });\n\t\t\tconst existing = existsSync(file)\n\t\t\t\t? (await readFile(file, \"utf8\")).split(\"\\n\").filter((line) => line !== \"\")\n\t\t\t\t: [];\n\t\t\tconst lines = change(existing);\n\t\t\tconst temporary = `${file}.${randomBytes(6).toString(\"hex\")}.tmp`;\n\t\t\tawait writeFile(temporary, lines.length === 0 ? \"\" : `${lines.join(\"\\n\")}\\n`, { mode: 0o600, flag: \"wx\" });\n\t\t\tawait rename(temporary, file).catch(async (error: unknown) => {\n\t\t\t\tawait rm(temporary, { force: true });\n\t\t\t\tthrow error;\n\t\t\t});\n\t\t});\n\tknownHostsChanges.set(file, next);\n\ttry {\n\t\tawait next;\n\t} finally {\n\t\tif (knownHostsChanges.get(file) === next) knownHostsChanges.delete(file);\n\t}\n}\n\n/**\n * Trust host-key lines from `scanHostKey` by adding them to the target's known-hosts file. Only plain lines for the\n * target's alias are accepted. A key that differs from a trusted key of the same type is refused with\n * `HostKeyChangedError`; `forgetHostKey` must remove the old keys first.\n */\nexport async function acceptHostKey(target: SshTarget, lines: readonly string[]): Promise<void> {\n\tconst alias = target.hostKeyAlias;\n\tconst accepted = lines.map((line) => ({ line: line.trim(), ...parseHostKeyLine(alias, line) }));\n\tawait changeKnownHosts(target.knownHostsFile, (existing) => {\n\t\tconst trusted = new Map<string, string>();\n\t\tfor (const line of existing) {\n\t\t\tconst [host, type, key] = line.trim().split(/\\s+/);\n\t\t\tif (host === alias && type !== undefined && key !== undefined) trusted.set(type, key);\n\t\t}\n\t\tconst added: string[] = [];\n\t\tfor (const { line, type, key } of accepted) {\n\t\t\tconst known = trusted.get(type);\n\t\t\tif (known === key) continue;\n\t\t\tif (known !== undefined) {\n\t\t\t\tthrow new HostKeyChangedError(`The ${type} host key of ${target.host} changed; forget the old key first`);\n\t\t\t}\n\t\t\ttrusted.set(type, key);\n\t\t\tadded.push(line);\n\t\t}\n\t\treturn [...existing, ...added];\n\t});\n}\n\n/** Stop trusting every key stored for the target's alias, e.g. after the owner confirmed a changed host key. */\nexport async function forgetHostKey(target: SshTarget): Promise<void> {\n\tawait changeKnownHosts(target.knownHostsFile, (existing) =>\n\t\texisting.filter((line) => line.trim().split(/\\s+/)[0] !== target.hostKeyAlias),\n\t);\n}\n\n/** POSIX detection, run by the remote login shell; a Windows host answers through PowerShell instead. */\nconst POSIX_PROBE = `sh -c 'echo PI-ENV-PROBE; uname -s; uname -m; uname -o 2>/dev/null || echo -; printf \"%s\\\\n\" \"$HOME\" \"\\${TMPDIR:--}\" \"\\${LD_PRELOAD:--}\"'`;\n\nfunction powershell(script: string): string {\n\treturn `powershell -NoProfile -NonInteractive -EncodedCommand ${Buffer.from(script, \"utf16le\").toString(\"base64\")}`;\n}\n\nconst WINDOWS_PROBE = powershell(\n\t\"'PI-ENV-PROBE'; 'Windows'; [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString(); '-'; $HOME; '-'; '-'\",\n);\n\nfunction normalizeArch(machine: string): RemotePlatform[\"arch\"] {\n\tconst lower = machine.toLowerCase();\n\tif (lower === \"x86_64\" || lower === \"amd64\" || lower === \"x64\") return \"x64\";\n\tif (lower === \"aarch64\" || lower === \"arm64\") return \"arm64\";\n\tthrow new Error(`Unsupported remote architecture: ${machine}`);\n}\n\ntype Probe = { system: string; machine: string; os: string; home: string; tmpdir: string; preload: string };\n\nfunction parseProbe(target: SshTarget, output: string): Probe {\n\t// Login shells may print a banner first.\n\tconst lines = output.split(/\\r?\\n/);\n\tconst start = lines.indexOf(\"PI-ENV-PROBE\");\n\tif (start === -1) throw new Error(`Unexpected answer from ${target.host}: ${output.trim()}`);\n\tconst [system = \"\", machine = \"\", os = \"\", home = \"\", tmpdir = \"-\", preload = \"-\"] = lines.slice(start + 1);\n\treturn { system, machine, os, home, tmpdir, preload };\n}\n\n/** Termux works only with its own `TMPDIR` and with `termux-exec`, which makes `#!/usr/bin/env` shebangs work. */\nfunction termuxWarnings(probe: Probe): string[] {\n\tconst warnings: string[] = [];\n\tif (probe.tmpdir === \"-\") warnings.push(\"TMPDIR is not set; Termux's sshd normally sets it to $PREFIX/tmp.\");\n\tif (!probe.preload.includes(\"termux-exec\")) {\n\t\twarnings.push(\"termux-exec is not loaded (LD_PRELOAD); scripts with #!/usr/bin/env shebangs will fail.\");\n\t}\n\twarnings.push(\"Android may suspend Termux; run termux-wake-lock on the device to keep the connection alive.\");\n\treturn warnings;\n}\n\n/** Which system the target runs: `uname` through the login shell, or PowerShell on Windows. */\nexport async function detectPlatform(target: SshTarget): Promise<RemotePlatform> {\n\tlet probe: Probe;\n\ttry {\n\t\tprobe = parseProbe(target, await runSsh(target, POSIX_PROBE));\n\t} catch (error) {\n\t\t// cmd.exe or PowerShell as the remote shell: no `sh`, or one that gets the probe's quotes wrong.\n\t\tif (error instanceof HostKeyUnknownError || error instanceof HostKeyChangedError) throw error;\n\t\tprobe = parseProbe(target, await runSsh(target, WINDOWS_PROBE));\n\t}\n\t// Git Bash as Windows' default SSH shell: ask PowerShell for Windows' own architecture and home spelling.\n\tif (/^(MINGW|MSYS|CYGWIN)/.test(probe.system)) probe = parseProbe(target, await runSsh(target, WINDOWS_PROBE));\n\tconst arch = normalizeArch(probe.machine);\n\tconst { system, os, home } = probe;\n\tif (system === \"Windows\") {\n\t\t// cmd.exe expands %OS%; PowerShell prints it as is.\n\t\tconst shell = (await runSsh(target, \"echo %OS%\")).includes(\"Windows_NT\") ? \"cmd\" : \"powershell\";\n\t\treturn { platform: \"windows\", arch, home, shell, warnings: [] };\n\t}\n\tif (system === \"Darwin\") return { platform: \"darwin\", arch, home, warnings: [] };\n\tif (system === \"Linux\") {\n\t\tconst android = os === \"Android\";\n\t\treturn { platform: android ? \"android\" : \"linux\", arch, home, warnings: android ? termuxWarnings(probe) : [] };\n\t}\n\tthrow new Error(`Unsupported remote system: ${system}`);\n}\n\n/** The daemon binary this package ships for a remote system. */\nexport function packagedDaemon(remote: Pick<RemotePlatform, \"platform\" | \"arch\">): string {\n\tconst name = `pi-env-${remote.platform}-${remote.arch}`;\n\treturn join(packageRoot, \"bin\", name, remote.platform === \"windows\" ? \"pi-env.exe\" : \"pi-env\");\n}\n\nfunction quotePosix(text: string): string {\n\treturn `'${text.replaceAll(\"'\", \"'\\\\''\")}'`;\n}\n\nfunction quotePowerShell(text: string): string {\n\treturn `'${text.replaceAll(\"'\", \"''\")}'`;\n}\n\n/** Where a daemon with this content lives on the remote machine: named by its SHA-256, so versions never collide. */\nfunction daemonPath(remote: RemotePlatform, sha256: string): string {\n\tconst name = `pi-env-${sha256.slice(0, 32)}`;\n\treturn remote.platform === \"windows\"\n\t\t? `${remote.home}\\\\.pi\\\\mobile\\\\tools\\\\${name}.exe`\n\t\t: `${remote.home}/.pi/mobile/tools/${name}`;\n}\n\nconst POSIX_HASH = `hash() { if command -v sha256sum >/dev/null 2>&1; then sha256sum \"$1\" | cut -d' ' -f1; elif command -v shasum >/dev/null 2>&1; then shasum -a 256 \"$1\" | cut -d' ' -f1; elif command -v openssl >/dev/null 2>&1; then openssl dgst -sha256 \"$1\" | sed 's/.*= //'; else echo none; fi; }`;\n\n/** Whether the remote file has this content: `present`, `missing`, or (POSIX without a hash tool) `nohash`. */\nasync function checkDaemon(target: SshTarget, remote: RemotePlatform, file: string, sha256: string): Promise<string> {\n\tif (remote.platform === \"windows\") {\n\t\tconst check = powershell(\n\t\t\t`$f = ${quotePowerShell(file)}; if ((Test-Path -LiteralPath $f) -and ((Get-FileHash -Algorithm SHA256 -LiteralPath $f).Hash.ToLower() -eq '${sha256}')) { 'present' } else { 'missing' }`,\n\t\t);\n\t\treturn (await runSsh(target, check)).trim().endsWith(\"present\") ? \"present\" : \"missing\";\n\t}\n\tconst check = `${POSIX_HASH}; f=${quotePosix(file)}; if [ -f \"$f\" ] && [ \"$(hash \"$f\")\" = ${sha256} ]; then echo present; elif [ \"$(hash /dev/null)\" = none ]; then echo nohash; else echo missing; fi`;\n\treturn (await runSsh(target, `sh -c ${quotePosix(check)}`)).trim().split(\"\\n\").at(-1) ?? \"missing\";\n}\n\n/**\n * Make sure the daemon is on the remote machine, verified by its SHA-256 before it ever runs, and remove daemons of\n * other contents. The upload goes to a new temporary file and is renamed into place only once its hash matches.\n * Returns the remote path of the binary.\n */\nexport async function deployDaemon(\n\ttarget: SshTarget,\n\tremote: RemotePlatform,\n\tbinary = packagedDaemon(remote),\n): Promise<string> {\n\tif (!existsSync(binary)) throw new Error(`No pi-env daemon for ${remote.platform}-${remote.arch} at ${binary}`);\n\tconst bytes = await readFile(binary);\n\tconst sha256 = createHash(\"sha256\").update(bytes).digest(\"hex\");\n\tconst file = daemonPath(remote, sha256);\n\tconst state = await checkDaemon(target, remote, file, sha256);\n\tif (state === \"present\") return file;\n\tif (state === \"nohash\") throw new Error(`${target.host} has no sha256sum, shasum or openssl to verify pi-env`);\n\tif (remote.platform === \"windows\") {\n\t\tconst upload = powershell(\n\t\t\t[\n\t\t\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\t\t`$f = ${quotePowerShell(file)}`,\n\t\t\t\t\"$d = Split-Path -Parent $f\",\n\t\t\t\t\"New-Item -ItemType Directory -Force -Path $d | Out-Null\",\n\t\t\t\t\"$t = Join-Path $d ('.pi-env-' + [guid]::NewGuid().ToString() + '.tmp')\",\n\t\t\t\t// PowerShell reads redirected stdin itself, as text lines for `$input`, so the binary comes as base64 lines\n\t\t\t\t// up to an end marker: Windows' sshd may never pass on the end of stdin.\n\t\t\t\t\"$text = New-Object System.Text.StringBuilder\",\n\t\t\t\t\"foreach ($line in $input) { if ($line -eq 'PI-ENV-END') { break }; [void]$text.Append($line) }\",\n\t\t\t\t\"$bytes = [Convert]::FromBase64String($text.ToString())\",\n\t\t\t\t\"$out = [IO.File]::Open($t, 'CreateNew', 'Write', 'None'); $out.Write($bytes, 0, $bytes.Length); $out.Close()\",\n\t\t\t\t`if ((Get-FileHash -Algorithm SHA256 -LiteralPath $t).Hash.ToLower() -ne '${sha256}') { Remove-Item -LiteralPath $t; throw 'pi-env upload is corrupt' }`,\n\t\t\t\t// A running daemon or a virus scanner can hold the old file for a moment.\n\t\t\t\t\"for ($i = 0; ; $i++) { try { Move-Item -Force -LiteralPath $t -Destination $f; break } catch { if ($i -ge 20) { throw }; Start-Sleep -Milliseconds 250 } }\",\n\t\t\t\t// Older daemons; one that is running stays until it exits.\n\t\t\t\t\"Get-ChildItem -LiteralPath $d -Filter 'pi-env-*.exe' | Where-Object { $_.FullName -ne $f } | ForEach-Object { Remove-Item -LiteralPath $_.FullName -ErrorAction SilentlyContinue }\",\n\t\t\t\t\"'deployed'\",\n\t\t\t].join(\"; \"),\n\t\t);\n\t\tconst lines = bytes.toString(\"base64\").replace(/.{1,76}/g, \"$&\\n\");\n\t\tawait runSsh(target, upload, { stdin: Buffer.from(`${lines}PI-ENV-END\\n`), timeoutMs: UPLOAD_TIMEOUT_MS });\n\t\treturn file;\n\t}\n\tconst upload = [\n\t\t\"set -e\",\n\t\tPOSIX_HASH,\n\t\t`f=${quotePosix(file)}`,\n\t\t'd=$(dirname \"$f\")',\n\t\t'mkdir -p \"$d\"',\n\t\t'chmod 700 \"$d\"',\n\t\t't=$(mktemp \"$d/.pi-env.XXXXXX\")',\n\t\t'cat > \"$t\"',\n\t\t`if [ \"$(hash \"$t\")\" != ${sha256} ]; then rm -f \"$t\"; echo \"pi-env upload is corrupt\" >&2; exit 1; fi`,\n\t\t'chmod 700 \"$t\"',\n\t\t'mv -f \"$t\" \"$f\"',\n\t\t// Older daemons; running ones keep their file open until they exit.\n\t\t'for old in \"$d\"/pi-env-*; do [ \"$old\" = \"$f\" ] || rm -f \"$old\"; done',\n\t\t\"echo deployed\",\n\t].join(\"\\n\");\n\tawait runSsh(target, `sh -c ${quotePosix(upload)}`, { stdin: bytes, timeoutMs: UPLOAD_TIMEOUT_MS });\n\treturn file;\n}\n\n/** Options for `sshConnection` and `connectSsh`: the target, plus the binary to deploy (default: the one this package ships). */\nexport interface SshConnectOptions extends SshTarget {\n\tbinary?: string;\n\t/**\n\t * POSIX: start the daemon through the user's login shell (`$SHELL -l`), so commands see the environment of\n\t * `~/.profile` and similar files. Off by default: `ssh` runs commands without a login shell.\n\t */\n\tloginShell?: boolean;\n\tonLog?: ConnectionOptions[\"onLog\"];\n}\n\n/** The remote command that starts the daemon at `file`, before the `serve` arguments. */\nfunction launchCommand(remote: RemotePlatform, file: string, loginShell: boolean): string {\n\tif (remote.platform === \"windows\") {\n\t\t// PowerShell runs a quoted path only with the call operator; cmd.exe keeps one pair of quotes around a program.\n\t\tif (remote.shell === \"powershell\") return `& ${quotePowerShell(file)}`;\n\t\treturn file.includes(\" \") ? `\"${file}\"` : file;\n\t}\n\t// `$0` is the daemon, `\"$@\"` its arguments; `$SHELL` is the login shell's own name for itself.\n\tif (loginShell) return `exec \"$SHELL\" -lc 'exec \"$0\" \"$@\"' ${quotePosix(file)}`;\n\treturn quotePosix(file);\n}\n\n/** What an SSH connection learned about its remote system, and the daemon it verified last. */\ntype SshState = { remote?: RemotePlatform; verified?: string };\n\n/** A `Connection` whose every start detects the remote system (once), verifies or deploys the daemon, then starts it. */\nfunction sshConnectionFor(options: SshConnectOptions, state: SshState): Connection {\n\treturn new Connection({\n\t\tcommand: async () => {\n\t\t\tif (state.remote === undefined) {\n\t\t\t\tconst remote = await detectPlatform(options);\n\t\t\t\tfor (const warning of remote.warnings) options.onLog?.(`${warning}\\n`);\n\t\t\t\tstate.remote = remote;\n\t\t\t}\n\t\t\t// A deployment that just verified the binary counts for the first start.\n\t\t\tconst file =\n\t\t\t\tstate.verified ??\n\t\t\t\t(await deployDaemon(options, state.remote, options.binary ?? packagedDaemon(state.remote)));\n\t\t\tstate.verified = undefined;\n\t\t\treturn [\n\t\t\t\toptions.ssh ?? \"ssh\",\n\t\t\t\t...sshArguments(options),\n\t\t\t\tlaunchCommand(state.remote, file, options.loginShell === true),\n\t\t\t];\n\t\t},\n\t\t...(options.onLog === undefined ? {} : { onLog: options.onLog }),\n\t});\n}\n\n/**\n * A `Connection` to the target that does nothing until its first request. Each start detects the remote system (the\n * first time), verifies the daemon and deploys it if it is missing or changed, then starts it over `ssh`. A failure\n * (no network, an untrusted or changed host key) fails the requests waiting for that start with code `spawn_error`\n * and the `ssh` diagnostics as message; the next request tries again. `remote()` is the detected system, once known.\n */\nexport function sshConnection(options: SshConnectOptions): {\n\tconnection: Connection;\n\tremote(): RemotePlatform | undefined;\n} {\n\tconst state: SshState = {};\n\treturn { connection: sshConnectionFor(options, state), remote: () => state.remote };\n}\n\n/**\n * Detect the remote system and deploy the daemon now, then return a `Connection` that starts it over `ssh`, like\n * `sshConnection`. Failures reject here: host keys must already be trusted (`scanHostKey`, `acceptHostKey`), otherwise\n * this rejects with `HostKeyUnknownError`.\n */\nexport async function connectSsh(\n\toptions: SshConnectOptions,\n): Promise<{ connection: Connection; remote: RemotePlatform }> {\n\tconst remote = await detectPlatform(options);\n\tconst verified = await deployDaemon(options, remote, options.binary ?? packagedDaemon(remote));\n\tfor (const warning of remote.warnings) options.onLog?.(`${warning}\\n`);\n\treturn { connection: sshConnectionFor(options, { remote, verified }), remote };\n}\n"]}
1
+ {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../src/ssh.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, mkdtemp, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Connection, type ConnectionOptions } from \"./connection.ts\";\n\n/** A remote system the package ships a daemon for. */\nexport type RemotePlatform = {\n\tplatform: \"linux\" | \"android\" | \"darwin\" | \"windows\";\n\tarch: \"x64\" | \"arm64\";\n\t/** The remote home directory, in the remote system's own spelling. */\n\thome: string;\n\t/** Windows: the shell `sshd` runs commands with (`DefaultShell`). */\n\tshell?: \"cmd\" | \"powershell\";\n\t/** Problems worth telling the owner about, such as Termux without `termux-exec`. */\n\twarnings: string[];\n};\n\n/** How to reach a machine with the system `ssh`. */\nexport interface SshTarget {\n\t/** Host name or `~/.ssh/config` alias. */\n\thost: string;\n\tuser?: string;\n\tport?: number;\n\tidentityFile?: string;\n\t/** Host keys this application trusts; `acceptHostKey` adds to it. */\n\tknownHostsFile: string;\n\t/** The name keys are stored under, independent of aliases, ports and jump hosts, e.g. `pi-env-<env name>`. */\n\thostKeyAlias: string;\n\t/** The `ssh` program; default `ssh`. */\n\tssh?: string;\n\t/** An `ssh` configuration file instead of `~/.ssh/config` (`-F`). */\n\tconfigFile?: string;\n}\n\n/** The remote host's key is not in `knownHostsFile`; `scanHostKey` shows it so the owner can accept it. */\nexport class HostKeyUnknownError extends Error {}\n\n/** The remote host's key differs from the one in `knownHostsFile`; it is never accepted automatically. */\nexport class HostKeyChangedError extends Error {}\n\n/** An `ssh` invocation that failed for another reason, with its exit code and diagnostics. */\nexport class SshError extends Error {\n\treadonly exitCode: number | null;\n\treadonly stderr: string;\n\n\tconstructor(message: string, exitCode: number | null, stderr: string) {\n\t\tsuper(message);\n\t\tthis.exitCode = exitCode;\n\t\tthis.stderr = stderr;\n\t}\n}\n\nconst packageRoot = fileURLToPath(new URL(\"..\", import.meta.url));\n\n/** No leading `-` (it would be read as an `ssh` option) and no whitespace or control characters. */\nfunction checkField(name: string, value: string): void {\n\tif (value === \"\" || value.startsWith(\"-\") || /[\\s\\x00-\\x1f\\x7f]/.test(value)) {\n\t\tthrow new Error(`Invalid ${name}: ${JSON.stringify(value)}`);\n\t}\n}\n\n/** A path for an `ssh` option that expands `%` tokens: quoted, with `%` literal. */\nfunction configPath(name: string, path: string): string {\n\tif (path === \"\" || /[\"\\x00-\\x1f\\x7f]/.test(path)) throw new Error(`Invalid ${name}: ${JSON.stringify(path)}`);\n\treturn `\"${path.replaceAll(\"%\", \"%%\")}\"`;\n}\n\n/**\n * Arguments for `ssh` up to the host: no prompts, no forwarding of any kind, no shared connections, no commands from\n * the configuration, no locale forwarding (the remote uses its own), and host keys checked strictly against the\n * application's own file under a fixed alias.\n */\nexport function sshArguments(\n\ttarget: SshTarget,\n\tstrictHostKeys = true,\n\tknownHostsFile = target.knownHostsFile,\n): string[] {\n\tcheckField(\"host\", target.host);\n\tcheckField(\"host key alias\", target.hostKeyAlias);\n\tif (target.user !== undefined) checkField(\"user\", target.user);\n\tif (target.port !== undefined && (!Number.isInteger(target.port) || target.port < 1 || target.port > 65535)) {\n\t\tthrow new Error(`Invalid port: ${target.port}`);\n\t}\n\tconst option = (setting: string) => [\"-o\", setting];\n\treturn [\n\t\t...(target.configFile === undefined ? [] : [\"-F\", target.configFile]),\n\t\t\"-T\",\n\t\t\"-a\",\n\t\t\"-x\",\n\t\t...option(\"BatchMode=yes\"),\n\t\t...option(\"ClearAllForwardings=yes\"),\n\t\t...option(\"ForwardAgent=no\"),\n\t\t...option(\"ForwardX11=no\"),\n\t\t...option(\"ControlMaster=no\"),\n\t\t...option(\"ControlPath=none\"),\n\t\t...option(\"RemoteCommand=none\"),\n\t\t...option(\"PermitLocalCommand=no\"),\n\t\t...option(\"SendEnv=-*\"),\n\t\t...option(\"ServerAliveInterval=15\"),\n\t\t...option(`StrictHostKeyChecking=${strictHostKeys ? \"yes\" : \"accept-new\"}`),\n\t\t...option(`UserKnownHostsFile=${configPath(\"known hosts file\", knownHostsFile)}`),\n\t\t...option(\"GlobalKnownHostsFile=none\"),\n\t\t...option(\"HashKnownHosts=no\"),\n\t\t...option(`HostKeyAlias=${target.hostKeyAlias}`),\n\t\t...(target.user === undefined ? [] : [\"-l\", target.user]),\n\t\t...(target.port === undefined ? [] : [\"-p\", String(target.port)]),\n\t\t...(target.identityFile === undefined\n\t\t\t? []\n\t\t\t: [\n\t\t\t\t\t...option(`IdentityFile=${configPath(\"identity file\", target.identityFile)}`),\n\t\t\t\t\t...option(\"IdentitiesOnly=yes\"),\n\t\t\t\t]),\n\t\t\"--\",\n\t\ttarget.host,\n\t];\n}\n\n/** How long a detection, check or upload over `ssh` may take before it is abandoned. */\nconst SSH_TIMEOUT_MS = 60_000;\nconst UPLOAD_TIMEOUT_MS = 300_000;\n\n/** Run one remote command over `ssh`, optionally feeding stdin; resolves with stdout, rejects on failure. */\nfunction runSsh(\n\ttarget: SshTarget,\n\tcommand: string,\n\toptions: { stdin?: Uint8Array; args?: string[]; timeoutMs?: number } = {},\n): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst args = options.args ?? sshArguments(target);\n\t\tconst child = spawn(target.ssh ?? \"ssh\", [...args, command], { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n\t\tconst timeoutMs = options.timeoutMs ?? SSH_TIMEOUT_MS;\n\t\tconst timer = setTimeout(() => {\n\t\t\tchild.kill();\n\t\t\tconst diagnostics = Buffer.concat(stderr).toString(\"utf8\");\n\t\t\treject(\n\t\t\t\tnew SshError(\n\t\t\t\t\t`ssh ${target.host} did not finish within ${timeoutMs / 1000} s: ${diagnostics.trim()}`,\n\t\t\t\t\tnull,\n\t\t\t\t\tdiagnostics,\n\t\t\t\t),\n\t\t\t);\n\t\t}, timeoutMs);\n\t\tconst stdout: Buffer[] = [];\n\t\tconst stderr: Buffer[] = [];\n\t\tchild.stdout.on(\"data\", (chunk: Buffer) => stdout.push(chunk));\n\t\tchild.stderr.on(\"data\", (chunk: Buffer) => stderr.push(chunk));\n\t\tchild.stdin.on(\"error\", () => {});\n\t\tchild.on(\"error\", (error) => {\n\t\t\tclearTimeout(timer);\n\t\t\treject(new SshError(error.message, null, \"\"));\n\t\t});\n\t\tchild.on(\"close\", (code) => {\n\t\t\tclearTimeout(timer);\n\t\t\tconst output = Buffer.concat(stdout).toString(\"utf8\");\n\t\t\tconst diagnostics = Buffer.concat(stderr).toString(\"utf8\");\n\t\t\tif (code === 0) return resolve(output);\n\t\t\tif (/REMOTE HOST IDENTIFICATION HAS CHANGED/.test(diagnostics)) {\n\t\t\t\treturn reject(\n\t\t\t\t\tnew HostKeyChangedError(`The host key of ${target.host} changed; remove the old key to continue`),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (/Host key verification failed|No .* host key is known/.test(diagnostics)) {\n\t\t\t\treturn reject(new HostKeyUnknownError(`The host key of ${target.host} is not trusted yet`));\n\t\t\t}\n\t\t\treject(\n\t\t\t\tnew SshError(`ssh ${target.host} failed with exit code ${code}: ${diagnostics.trim()}`, code, diagnostics),\n\t\t\t);\n\t\t});\n\t\tchild.stdin.end(options.stdin ?? new Uint8Array(0));\n\t});\n}\n\n/**\n * Connect once with `accept-new` against a temporary known-hosts file to capture the key the host presents, through\n * the same route (`~/.ssh/config`, jump hosts) as real connections. Returns its known-hosts lines and fingerprints.\n * Showing a fingerprint is not authentication: compare it with one obtained out of band before accepting it.\n */\nexport async function scanHostKey(target: SshTarget): Promise<{ lines: string[]; fingerprints: string[] }> {\n\tconst directory = await mkdtemp(join(tmpdir(), \"pi-env-hostkey-\"));\n\ttry {\n\t\tconst scanned = join(directory, \"known_hosts\");\n\t\t// Authentication may fail without the key; the key is recorded before authentication.\n\t\tawait runSsh(target, \"exit 0\", { args: sshArguments(target, false, scanned) }).catch((error: unknown) => {\n\t\t\tif (!(error instanceof SshError) && !(error instanceof HostKeyUnknownError)) throw error;\n\t\t});\n\t\tconst lines = existsSync(scanned)\n\t\t\t? (await readFile(scanned, \"utf8\")).split(\"\\n\").filter((line) => line.trim() !== \"\")\n\t\t\t: [];\n\t\tif (lines.length === 0) throw new Error(`No host key received from ${target.host}`);\n\t\tconst fingerprints = await new Promise<string[]>((resolve, reject) => {\n\t\t\tconst child = spawn(\"ssh-keygen\", [\"-lf\", scanned], { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\tlet output = \"\";\n\t\t\tchild.stdout.on(\"data\", (chunk: Buffer) => {\n\t\t\t\toutput += chunk.toString(\"utf8\");\n\t\t\t});\n\t\t\tchild.on(\"error\", reject);\n\t\t\tchild.on(\"close\", () => resolve(output.split(\"\\n\").filter((line) => line.trim() !== \"\")));\n\t\t});\n\t\treturn { lines, fingerprints };\n\t} finally {\n\t\tawait rm(directory, { recursive: true, force: true });\n\t}\n}\n\n/** A known-hosts line for `alias`: `alias type key [comment]`, no markers, patterns or hashed names. */\nfunction parseHostKeyLine(alias: string, line: string): { type: string; key: string } {\n\tconst [host, type, key] = line.trim().split(/\\s+/);\n\tif (\n\t\thost !== alias ||\n\t\ttype === undefined ||\n\t\tkey === undefined ||\n\t\t!/^(ssh-[a-z0-9-]+|ecdsa-sha2-[a-z0-9-]+|sk-[a-z0-9@.-]+)$/.test(type) ||\n\t\t!/^[A-Za-z0-9+/]+={0,2}$/.test(key)\n\t) {\n\t\tthrow new Error(`Not a host key line for ${alias}: ${JSON.stringify(line)}`);\n\t}\n\treturn { type, key };\n}\n\n/** Known-hosts files being changed, so changes to one file happen one at a time. */\nconst knownHostsChanges = new Map<string, Promise<unknown>>();\n\n/** Rewrite the known-hosts file one change at a time, atomically. */\nasync function changeKnownHosts(file: string, change: (lines: string[]) => string[]): Promise<void> {\n\tconst previous = knownHostsChanges.get(file) ?? Promise.resolve();\n\tconst next = previous\n\t\t.catch(() => {})\n\t\t.then(async () => {\n\t\t\tawait mkdir(dirname(file), { recursive: true, mode: 0o700 });\n\t\t\tconst existing = existsSync(file)\n\t\t\t\t? (await readFile(file, \"utf8\")).split(\"\\n\").filter((line) => line !== \"\")\n\t\t\t\t: [];\n\t\t\tconst lines = change(existing);\n\t\t\tconst temporary = `${file}.${randomBytes(6).toString(\"hex\")}.tmp`;\n\t\t\tawait writeFile(temporary, lines.length === 0 ? \"\" : `${lines.join(\"\\n\")}\\n`, { mode: 0o600, flag: \"wx\" });\n\t\t\tawait rename(temporary, file).catch(async (error: unknown) => {\n\t\t\t\tawait rm(temporary, { force: true });\n\t\t\t\tthrow error;\n\t\t\t});\n\t\t});\n\tknownHostsChanges.set(file, next);\n\ttry {\n\t\tawait next;\n\t} finally {\n\t\tif (knownHostsChanges.get(file) === next) knownHostsChanges.delete(file);\n\t}\n}\n\n/**\n * Trust host-key lines from `scanHostKey` by adding them to the target's known-hosts file. Only plain lines for the\n * target's alias are accepted. A key that differs from a trusted key of the same type is refused with\n * `HostKeyChangedError`; `forgetHostKey` must remove the old keys first.\n */\nexport async function acceptHostKey(target: SshTarget, lines: readonly string[]): Promise<void> {\n\tconst alias = target.hostKeyAlias;\n\tconst accepted = lines.map((line) => ({ line: line.trim(), ...parseHostKeyLine(alias, line) }));\n\tawait changeKnownHosts(target.knownHostsFile, (existing) => {\n\t\tconst trusted = new Map<string, string>();\n\t\tfor (const line of existing) {\n\t\t\tconst [host, type, key] = line.trim().split(/\\s+/);\n\t\t\tif (host === alias && type !== undefined && key !== undefined) trusted.set(type, key);\n\t\t}\n\t\tconst added: string[] = [];\n\t\tfor (const { line, type, key } of accepted) {\n\t\t\tconst known = trusted.get(type);\n\t\t\tif (known === key) continue;\n\t\t\tif (known !== undefined) {\n\t\t\t\tthrow new HostKeyChangedError(`The ${type} host key of ${target.host} changed; forget the old key first`);\n\t\t\t}\n\t\t\ttrusted.set(type, key);\n\t\t\tadded.push(line);\n\t\t}\n\t\treturn [...existing, ...added];\n\t});\n}\n\n/** Stop trusting every key stored for the target's alias, e.g. after the owner confirmed a changed host key. */\nexport async function forgetHostKey(target: SshTarget): Promise<void> {\n\tawait changeKnownHosts(target.knownHostsFile, (existing) =>\n\t\texisting.filter((line) => line.trim().split(/\\s+/)[0] !== target.hostKeyAlias),\n\t);\n}\n\n/** POSIX detection, run by the remote login shell; a Windows host answers through PowerShell instead. */\nconst POSIX_PROBE = `sh -c 'echo PI-ENV-PROBE; uname -s; uname -m; uname -o 2>/dev/null || echo -; printf \"%s\\\\n\" \"$HOME\" \"\\${TMPDIR:--}\" \"\\${LD_PRELOAD:--}\"'`;\n\nfunction powershell(script: string): string {\n\treturn `powershell -NoProfile -NonInteractive -EncodedCommand ${Buffer.from(script, \"utf16le\").toString(\"base64\")}`;\n}\n\nconst WINDOWS_PROBE = powershell(\n\t\"'PI-ENV-PROBE'; 'Windows'; [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString(); '-'; $HOME; '-'; '-'\",\n);\n\nfunction normalizeArch(machine: string): RemotePlatform[\"arch\"] {\n\tconst lower = machine.toLowerCase();\n\tif (lower === \"x86_64\" || lower === \"amd64\" || lower === \"x64\") return \"x64\";\n\tif (lower === \"aarch64\" || lower === \"arm64\") return \"arm64\";\n\tthrow new Error(`Unsupported remote architecture: ${machine}`);\n}\n\ntype Probe = { system: string; machine: string; os: string; home: string; tmpdir: string; preload: string };\n\nfunction parseProbe(target: SshTarget, output: string): Probe {\n\t// Login shells may print a banner first.\n\tconst lines = output.split(/\\r?\\n/);\n\tconst start = lines.indexOf(\"PI-ENV-PROBE\");\n\tif (start === -1) throw new Error(`Unexpected answer from ${target.host}: ${output.trim()}`);\n\tconst [system = \"\", machine = \"\", os = \"\", home = \"\", tmpdir = \"-\", preload = \"-\"] = lines.slice(start + 1);\n\treturn { system, machine, os, home, tmpdir, preload };\n}\n\n/** Termux works only with its own `TMPDIR` and with `termux-exec`, which makes `#!/usr/bin/env` shebangs work. */\nfunction termuxWarnings(probe: Probe): string[] {\n\tconst warnings: string[] = [];\n\tif (probe.tmpdir === \"-\") warnings.push(\"TMPDIR is not set; Termux's sshd normally sets it to $PREFIX/tmp.\");\n\tif (!probe.preload.includes(\"termux-exec\")) {\n\t\twarnings.push(\"termux-exec is not loaded (LD_PRELOAD); scripts with #!/usr/bin/env shebangs will fail.\");\n\t}\n\twarnings.push(\"Android may suspend Termux; run termux-wake-lock on the device to keep the connection alive.\");\n\treturn warnings;\n}\n\n/** Which system the target runs: `uname` through the login shell, or PowerShell on Windows. */\nexport async function detectPlatform(target: SshTarget): Promise<RemotePlatform> {\n\tlet probe: Probe;\n\ttry {\n\t\tprobe = parseProbe(target, await runSsh(target, POSIX_PROBE));\n\t} catch (error) {\n\t\t// cmd.exe or PowerShell as the remote shell: no `sh`, or one that gets the probe's quotes wrong.\n\t\tif (error instanceof HostKeyUnknownError || error instanceof HostKeyChangedError) throw error;\n\t\tprobe = parseProbe(target, await runSsh(target, WINDOWS_PROBE));\n\t}\n\t// Git Bash as Windows' default SSH shell: ask PowerShell for Windows' own architecture and home spelling.\n\tif (/^(MINGW|MSYS|CYGWIN)/.test(probe.system)) probe = parseProbe(target, await runSsh(target, WINDOWS_PROBE));\n\tconst arch = normalizeArch(probe.machine);\n\tconst { system, os, home } = probe;\n\tif (system === \"Windows\") {\n\t\t// cmd.exe expands %OS%; PowerShell prints it as is.\n\t\tconst shell = (await runSsh(target, \"echo %OS%\")).includes(\"Windows_NT\") ? \"cmd\" : \"powershell\";\n\t\treturn { platform: \"windows\", arch, home, shell, warnings: [] };\n\t}\n\tif (system === \"Darwin\") return { platform: \"darwin\", arch, home, warnings: [] };\n\tif (system === \"Linux\") {\n\t\tconst android = os === \"Android\";\n\t\treturn { platform: android ? \"android\" : \"linux\", arch, home, warnings: android ? termuxWarnings(probe) : [] };\n\t}\n\tthrow new Error(`Unsupported remote system: ${system}`);\n}\n\n/** The daemon binary this package ships for a remote system. */\nexport function packagedDaemon(remote: Pick<RemotePlatform, \"platform\" | \"arch\">): string {\n\tconst name = `pi-env-${remote.platform}-${remote.arch}`;\n\treturn join(packageRoot, \"bin\", name, remote.platform === \"windows\" ? \"pi-env.exe\" : \"pi-env\");\n}\n\n/** The daemon this package ships for the machine it runs on, e.g. for terminals of local sessions. */\nexport function localDaemon(): string {\n\tconst platform = process.platform === \"win32\" ? \"windows\" : process.platform;\n\tif (platform !== \"linux\" && platform !== \"android\" && platform !== \"darwin\" && platform !== \"windows\") {\n\t\tthrow new Error(`No pi-env daemon for ${process.platform}`);\n\t}\n\treturn packagedDaemon({ platform, arch: normalizeArch(process.arch) });\n}\n\nfunction quotePosix(text: string): string {\n\treturn `'${text.replaceAll(\"'\", \"'\\\\''\")}'`;\n}\n\nfunction quotePowerShell(text: string): string {\n\treturn `'${text.replaceAll(\"'\", \"''\")}'`;\n}\n\n/** Where a daemon with this content lives on the remote machine: named by its SHA-256, so versions never collide. */\nfunction daemonPath(remote: RemotePlatform, sha256: string): string {\n\tconst name = `pi-env-${sha256.slice(0, 32)}`;\n\treturn remote.platform === \"windows\"\n\t\t? `${remote.home}\\\\.pi\\\\mobile\\\\tools\\\\${name}.exe`\n\t\t: `${remote.home}/.pi/mobile/tools/${name}`;\n}\n\nconst POSIX_HASH = `hash() { if command -v sha256sum >/dev/null 2>&1; then sha256sum \"$1\" | cut -d' ' -f1; elif command -v shasum >/dev/null 2>&1; then shasum -a 256 \"$1\" | cut -d' ' -f1; elif command -v openssl >/dev/null 2>&1; then openssl dgst -sha256 \"$1\" | sed 's/.*= //'; else echo none; fi; }`;\n\n/** Whether the remote file has this content: `present`, `missing`, or (POSIX without a hash tool) `nohash`. */\nasync function checkDaemon(target: SshTarget, remote: RemotePlatform, file: string, sha256: string): Promise<string> {\n\tif (remote.platform === \"windows\") {\n\t\tconst check = powershell(\n\t\t\t`$f = ${quotePowerShell(file)}; if ((Test-Path -LiteralPath $f) -and ((Get-FileHash -Algorithm SHA256 -LiteralPath $f).Hash.ToLower() -eq '${sha256}')) { 'present' } else { 'missing' }`,\n\t\t);\n\t\treturn (await runSsh(target, check)).trim().endsWith(\"present\") ? \"present\" : \"missing\";\n\t}\n\tconst check = `${POSIX_HASH}; f=${quotePosix(file)}; if [ -f \"$f\" ] && [ \"$(hash \"$f\")\" = ${sha256} ]; then echo present; elif [ \"$(hash /dev/null)\" = none ]; then echo nohash; else echo missing; fi`;\n\treturn (await runSsh(target, `sh -c ${quotePosix(check)}`)).trim().split(\"\\n\").at(-1) ?? \"missing\";\n}\n\n/**\n * Make sure the daemon is on the remote machine, verified by its SHA-256 before it ever runs, and remove daemons of\n * other contents. The upload goes to a new temporary file and is renamed into place only once its hash matches.\n * Returns the remote path of the binary.\n */\nexport async function deployDaemon(\n\ttarget: SshTarget,\n\tremote: RemotePlatform,\n\tbinary = packagedDaemon(remote),\n): Promise<string> {\n\tif (!existsSync(binary)) throw new Error(`No pi-env daemon for ${remote.platform}-${remote.arch} at ${binary}`);\n\tconst bytes = await readFile(binary);\n\tconst sha256 = createHash(\"sha256\").update(bytes).digest(\"hex\");\n\tconst file = daemonPath(remote, sha256);\n\tconst state = await checkDaemon(target, remote, file, sha256);\n\tif (state === \"present\") return file;\n\tif (state === \"nohash\") throw new Error(`${target.host} has no sha256sum, shasum or openssl to verify pi-env`);\n\tif (remote.platform === \"windows\") {\n\t\tconst upload = powershell(\n\t\t\t[\n\t\t\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\t\t`$f = ${quotePowerShell(file)}`,\n\t\t\t\t\"$d = Split-Path -Parent $f\",\n\t\t\t\t\"New-Item -ItemType Directory -Force -Path $d | Out-Null\",\n\t\t\t\t\"$t = Join-Path $d ('.pi-env-' + [guid]::NewGuid().ToString() + '.tmp')\",\n\t\t\t\t// PowerShell reads redirected stdin itself, as text lines for `$input`, so the binary comes as base64 lines\n\t\t\t\t// up to an end marker: Windows' sshd may never pass on the end of stdin.\n\t\t\t\t\"$text = New-Object System.Text.StringBuilder\",\n\t\t\t\t\"foreach ($line in $input) { if ($line -eq 'PI-ENV-END') { break }; [void]$text.Append($line) }\",\n\t\t\t\t\"$bytes = [Convert]::FromBase64String($text.ToString())\",\n\t\t\t\t\"$out = [IO.File]::Open($t, 'CreateNew', 'Write', 'None'); $out.Write($bytes, 0, $bytes.Length); $out.Close()\",\n\t\t\t\t`if ((Get-FileHash -Algorithm SHA256 -LiteralPath $t).Hash.ToLower() -ne '${sha256}') { Remove-Item -LiteralPath $t; throw 'pi-env upload is corrupt' }`,\n\t\t\t\t// A running daemon or a virus scanner can hold the old file for a moment.\n\t\t\t\t\"for ($i = 0; ; $i++) { try { Move-Item -Force -LiteralPath $t -Destination $f; break } catch { if ($i -ge 20) { throw }; Start-Sleep -Milliseconds 250 } }\",\n\t\t\t\t// Older daemons; one that is running stays until it exits.\n\t\t\t\t\"Get-ChildItem -LiteralPath $d -Filter 'pi-env-*.exe' | Where-Object { $_.FullName -ne $f } | ForEach-Object { Remove-Item -LiteralPath $_.FullName -ErrorAction SilentlyContinue }\",\n\t\t\t\t\"'deployed'\",\n\t\t\t].join(\"; \"),\n\t\t);\n\t\tconst lines = bytes.toString(\"base64\").replace(/.{1,76}/g, \"$&\\n\");\n\t\tawait runSsh(target, upload, { stdin: Buffer.from(`${lines}PI-ENV-END\\n`), timeoutMs: UPLOAD_TIMEOUT_MS });\n\t\treturn file;\n\t}\n\tconst upload = [\n\t\t\"set -e\",\n\t\tPOSIX_HASH,\n\t\t`f=${quotePosix(file)}`,\n\t\t'd=$(dirname \"$f\")',\n\t\t'mkdir -p \"$d\"',\n\t\t'chmod 700 \"$d\"',\n\t\t't=$(mktemp \"$d/.pi-env.XXXXXX\")',\n\t\t'cat > \"$t\"',\n\t\t`if [ \"$(hash \"$t\")\" != ${sha256} ]; then rm -f \"$t\"; echo \"pi-env upload is corrupt\" >&2; exit 1; fi`,\n\t\t'chmod 700 \"$t\"',\n\t\t'mv -f \"$t\" \"$f\"',\n\t\t// Older daemons; running ones keep their file open until they exit.\n\t\t'for old in \"$d\"/pi-env-*; do [ \"$old\" = \"$f\" ] || rm -f \"$old\"; done',\n\t\t\"echo deployed\",\n\t].join(\"\\n\");\n\tawait runSsh(target, `sh -c ${quotePosix(upload)}`, { stdin: bytes, timeoutMs: UPLOAD_TIMEOUT_MS });\n\treturn file;\n}\n\n/** Options for `sshConnection` and `connectSsh`: the target, plus the binary to deploy (default: the one this package ships). */\nexport interface SshConnectOptions extends SshTarget {\n\tbinary?: string;\n\t/**\n\t * POSIX: start the daemon through the user's login shell (`$SHELL -l`), so commands see the environment of\n\t * `~/.profile` and similar files. Off by default: `ssh` runs commands without a login shell.\n\t */\n\tloginShell?: boolean;\n\tonLog?: ConnectionOptions[\"onLog\"];\n}\n\n/** The remote command that starts the daemon at `file`, before the `serve` arguments. */\nfunction launchCommand(remote: RemotePlatform, file: string, loginShell: boolean): string {\n\tif (remote.platform === \"windows\") {\n\t\t// PowerShell runs a quoted path only with the call operator; cmd.exe keeps one pair of quotes around a program.\n\t\tif (remote.shell === \"powershell\") return `& ${quotePowerShell(file)}`;\n\t\treturn file.includes(\" \") ? `\"${file}\"` : file;\n\t}\n\t// `$0` is the daemon, `\"$@\"` its arguments; `$SHELL` is the login shell's own name for itself.\n\tif (loginShell) return `exec \"$SHELL\" -lc 'exec \"$0\" \"$@\"' ${quotePosix(file)}`;\n\treturn quotePosix(file);\n}\n\n/** What an SSH connection learned about its remote system, and the daemon it verified last. */\ntype SshState = { remote?: RemotePlatform; verified?: string };\n\n/** A `Connection` whose every start detects the remote system (once), verifies or deploys the daemon, then starts it. */\nfunction sshConnectionFor(options: SshConnectOptions, state: SshState): Connection {\n\treturn new Connection({\n\t\tcommand: async () => {\n\t\t\tif (state.remote === undefined) {\n\t\t\t\tconst remote = await detectPlatform(options);\n\t\t\t\tfor (const warning of remote.warnings) options.onLog?.(`${warning}\\n`);\n\t\t\t\tstate.remote = remote;\n\t\t\t}\n\t\t\t// A deployment that just verified the binary counts for the first start.\n\t\t\tconst file =\n\t\t\t\tstate.verified ??\n\t\t\t\t(await deployDaemon(options, state.remote, options.binary ?? packagedDaemon(state.remote)));\n\t\t\tstate.verified = undefined;\n\t\t\treturn [\n\t\t\t\toptions.ssh ?? \"ssh\",\n\t\t\t\t...sshArguments(options),\n\t\t\t\tlaunchCommand(state.remote, file, options.loginShell === true),\n\t\t\t];\n\t\t},\n\t\t...(options.onLog === undefined ? {} : { onLog: options.onLog }),\n\t});\n}\n\n/**\n * A `Connection` to the target that does nothing until its first request. Each start detects the remote system (the\n * first time), verifies the daemon and deploys it if it is missing or changed, then starts it over `ssh`. A failure\n * (no network, an untrusted or changed host key) fails the requests waiting for that start with code `spawn_error`\n * and the `ssh` diagnostics as message; the next request tries again. `remote()` is the detected system, once known.\n */\nexport function sshConnection(options: SshConnectOptions): {\n\tconnection: Connection;\n\tremote(): RemotePlatform | undefined;\n} {\n\tconst state: SshState = {};\n\treturn { connection: sshConnectionFor(options, state), remote: () => state.remote };\n}\n\n/**\n * Detect the remote system and deploy the daemon now, then return a `Connection` that starts it over `ssh`, like\n * `sshConnection`. Failures reject here: host keys must already be trusted (`scanHostKey`, `acceptHostKey`), otherwise\n * this rejects with `HostKeyUnknownError`.\n */\nexport async function connectSsh(\n\toptions: SshConnectOptions,\n): Promise<{ connection: Connection; remote: RemotePlatform }> {\n\tconst remote = await detectPlatform(options);\n\tconst verified = await deployDaemon(options, remote, options.binary ?? packagedDaemon(remote));\n\tfor (const warning of remote.warnings) options.onLog?.(`${warning}\\n`);\n\treturn { connection: sshConnectionFor(options, { remote, verified }), remote };\n}\n"]}
@@ -0,0 +1,44 @@
1
+ import type { Connection } from "./connection.ts";
2
+ export interface TerminalOptions {
3
+ /** Working directory of the program; it must exist. */
4
+ cwd: string;
5
+ /** Program and arguments. Default: the user's shell (`SHELL` in `env` or the daemon's environment) as a login shell. */
6
+ argv?: readonly string[];
7
+ /** Added to the daemon's environment, or the whole environment with `inheritEnv: false`. Set `TERM` here. */
8
+ env?: Record<string, string>;
9
+ inheritEnv?: boolean;
10
+ cols: number;
11
+ rows: number;
12
+ /** The terminal's raw output, in order. */
13
+ onData: (bytes: Uint8Array) => void;
14
+ }
15
+ /** The foreground job of a terminal: its process group id, and its leader's name if the system tells. */
16
+ export interface TerminalForeground {
17
+ pid: number;
18
+ name?: string;
19
+ }
20
+ /**
21
+ * A program running in a pseudo-terminal of a pi-env daemon. It ends when the program exits, `close()` kills it, or
22
+ * the connection is lost (the daemon then kills it).
23
+ */
24
+ export declare class RemoteTerminal {
25
+ #private;
26
+ readonly pid: number;
27
+ /** The exit code; rejects with the daemon's error, with code `aborted` after `close()`. */
28
+ readonly exited: Promise<number>;
29
+ constructor(connection: Connection, id: number, session: number, pid: number, exited: Promise<number>, abort: AbortController);
30
+ /** Send input; once the terminal has ended, input is dropped. */
31
+ write(data: Uint8Array | string): void;
32
+ /** Change the size; the program receives `SIGWINCH`. */
33
+ resize(cols: number, rows: number): void;
34
+ /** Kill the program and everything in its session. */
35
+ close(): void;
36
+ /**
37
+ * The job in the terminal's foreground when it is not the program itself: what a shell runs, such as an editor.
38
+ * Undefined while the program is in front (a shell at its prompt). Rejects once the terminal has ended.
39
+ */
40
+ foreground(): Promise<TerminalForeground | undefined>;
41
+ }
42
+ /** Start a program in a new pseudo-terminal of the daemon behind `connection`. */
43
+ export declare function openTerminal(connection: Connection, options: TerminalOptions): Promise<RemoteTerminal>;
44
+ //# sourceMappingURL=terminal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAQ,MAAM,iBAAiB,CAAC;AAKxD,MAAM,WAAW,eAAe;IAC/B,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,wHAAwH;IACxH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,6GAA6G;IAC7G,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACpC;AAED,yGAAyG;AACzG,MAAM,WAAW,kBAAkB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,qBAAa,cAAc;;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAMjC,YACC,UAAU,EAAE,UAAU,EACtB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EACvB,KAAK,EAAE,eAAe,EAQtB;IAED,iEAAiE;IACjE,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAKrC;IAED,wDAAwD;IACxD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEvC;IAED,sDAAsD;IACtD,KAAK,IAAI,IAAI,CAEZ;IAED;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAK1D;CAQD;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAkCtG"}
@@ -0,0 +1,87 @@
1
+ /** Input larger than this goes to the daemon in several writes; frames are at most 16 MiB. */
2
+ const WRITE_CHUNK = 1024 * 1024;
3
+ /**
4
+ * A program running in a pseudo-terminal of a pi-env daemon. It ends when the program exits, `close()` kills it, or
5
+ * the connection is lost (the daemon then kills it).
6
+ */
7
+ export class RemoteTerminal {
8
+ pid;
9
+ /** The exit code; rejects with the daemon's error, with code `aborted` after `close()`. */
10
+ exited;
11
+ #connection;
12
+ #id;
13
+ #session;
14
+ #abort;
15
+ constructor(connection, id, session, pid, exited, abort) {
16
+ this.#connection = connection;
17
+ this.#id = id;
18
+ this.#session = session;
19
+ this.pid = pid;
20
+ this.exited = exited;
21
+ this.#abort = abort;
22
+ }
23
+ /** Send input; once the terminal has ended, input is dropped. */
24
+ write(data) {
25
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
26
+ for (let offset = 0; offset < bytes.length; offset += WRITE_CHUNK) {
27
+ this.#request("ptyWrite", {}, bytes.subarray(offset, offset + WRITE_CHUNK));
28
+ }
29
+ }
30
+ /** Change the size; the program receives `SIGWINCH`. */
31
+ resize(cols, rows) {
32
+ this.#request("ptyResize", { cols, rows });
33
+ }
34
+ /** Kill the program and everything in its session. */
35
+ close() {
36
+ this.#abort.abort();
37
+ }
38
+ /**
39
+ * The job in the terminal's foreground when it is not the program itself: what a shell runs, such as an editor.
40
+ * Undefined while the program is in front (a shell at its prompt). Rejects once the terminal has ended.
41
+ */
42
+ async foreground() {
43
+ const reply = await this.#connection.request("ptyForeground", { target: this.#id }, { session: this.#session });
44
+ const foreground = reply.json.foreground;
45
+ if (!foreground)
46
+ return undefined;
47
+ return { pid: foreground.pid, ...(foreground.name ? { name: foreground.name } : {}) };
48
+ }
49
+ #request(op, json, payload) {
50
+ this.#connection
51
+ .request(op, { ...json, target: this.#id }, { session: this.#session, ...(payload ? { payload } : {}) })
52
+ // A terminal that ended or a lost connection: `exited` reports it.
53
+ .catch(() => { });
54
+ }
55
+ }
56
+ /** Start a program in a new pseudo-terminal of the daemon behind `connection`. */
57
+ export function openTerminal(connection, options) {
58
+ const abort = new AbortController();
59
+ const started = Promise.withResolvers();
60
+ let request;
61
+ const exited = connection
62
+ .request("pty", {
63
+ cwd: options.cwd,
64
+ ...(options.argv === undefined ? {} : { argv: options.argv }),
65
+ ...(options.env === undefined ? {} : { env: options.env }),
66
+ ...(options.inheritEnv === undefined ? {} : { inheritEnv: options.inheritEnv }),
67
+ cols: options.cols,
68
+ rows: options.rows,
69
+ }, {
70
+ signal: abort.signal,
71
+ onStart: (id, session) => {
72
+ request = { id, session };
73
+ },
74
+ onEvent: (json, payload) => {
75
+ if (json.kind === "output")
76
+ options.onData(payload);
77
+ else if (json.kind === "started" && request) {
78
+ started.resolve(new RemoteTerminal(connection, request.id, request.session, Number(json.pid), exited, abort));
79
+ }
80
+ },
81
+ })
82
+ .then((reply) => Number(reply.json.exitCode));
83
+ // Failing before it started rejects the open; afterwards, `exited` carries it.
84
+ exited.catch(started.reject);
85
+ return started.promise;
86
+ }
87
+ //# sourceMappingURL=terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Connection, Json } from \"./connection.ts\";\n\n/** Input larger than this goes to the daemon in several writes; frames are at most 16 MiB. */\nconst WRITE_CHUNK = 1024 * 1024;\n\nexport interface TerminalOptions {\n\t/** Working directory of the program; it must exist. */\n\tcwd: string;\n\t/** Program and arguments. Default: the user's shell (`SHELL` in `env` or the daemon's environment) as a login shell. */\n\targv?: readonly string[];\n\t/** Added to the daemon's environment, or the whole environment with `inheritEnv: false`. Set `TERM` here. */\n\tenv?: Record<string, string>;\n\tinheritEnv?: boolean;\n\tcols: number;\n\trows: number;\n\t/** The terminal's raw output, in order. */\n\tonData: (bytes: Uint8Array) => void;\n}\n\n/** The foreground job of a terminal: its process group id, and its leader's name if the system tells. */\nexport interface TerminalForeground {\n\tpid: number;\n\tname?: string;\n}\n\n/**\n * A program running in a pseudo-terminal of a pi-env daemon. It ends when the program exits, `close()` kills it, or\n * the connection is lost (the daemon then kills it).\n */\nexport class RemoteTerminal {\n\treadonly pid: number;\n\t/** The exit code; rejects with the daemon's error, with code `aborted` after `close()`. */\n\treadonly exited: Promise<number>;\n\treadonly #connection: Connection;\n\treadonly #id: number;\n\treadonly #session: number;\n\treadonly #abort: AbortController;\n\n\tconstructor(\n\t\tconnection: Connection,\n\t\tid: number,\n\t\tsession: number,\n\t\tpid: number,\n\t\texited: Promise<number>,\n\t\tabort: AbortController,\n\t) {\n\t\tthis.#connection = connection;\n\t\tthis.#id = id;\n\t\tthis.#session = session;\n\t\tthis.pid = pid;\n\t\tthis.exited = exited;\n\t\tthis.#abort = abort;\n\t}\n\n\t/** Send input; once the terminal has ended, input is dropped. */\n\twrite(data: Uint8Array | string): void {\n\t\tconst bytes = typeof data === \"string\" ? new TextEncoder().encode(data) : data;\n\t\tfor (let offset = 0; offset < bytes.length; offset += WRITE_CHUNK) {\n\t\t\tthis.#request(\"ptyWrite\", {}, bytes.subarray(offset, offset + WRITE_CHUNK));\n\t\t}\n\t}\n\n\t/** Change the size; the program receives `SIGWINCH`. */\n\tresize(cols: number, rows: number): void {\n\t\tthis.#request(\"ptyResize\", { cols, rows });\n\t}\n\n\t/** Kill the program and everything in its session. */\n\tclose(): void {\n\t\tthis.#abort.abort();\n\t}\n\n\t/**\n\t * The job in the terminal's foreground when it is not the program itself: what a shell runs, such as an editor.\n\t * Undefined while the program is in front (a shell at its prompt). Rejects once the terminal has ended.\n\t */\n\tasync foreground(): Promise<TerminalForeground | undefined> {\n\t\tconst reply = await this.#connection.request(\"ptyForeground\", { target: this.#id }, { session: this.#session });\n\t\tconst foreground = reply.json.foreground as { pid: number; name: string | null } | null;\n\t\tif (!foreground) return undefined;\n\t\treturn { pid: foreground.pid, ...(foreground.name ? { name: foreground.name } : {}) };\n\t}\n\n\t#request(op: string, json: Json, payload?: Uint8Array): void {\n\t\tthis.#connection\n\t\t\t.request(op, { ...json, target: this.#id }, { session: this.#session, ...(payload ? { payload } : {}) })\n\t\t\t// A terminal that ended or a lost connection: `exited` reports it.\n\t\t\t.catch(() => {});\n\t}\n}\n\n/** Start a program in a new pseudo-terminal of the daemon behind `connection`. */\nexport function openTerminal(connection: Connection, options: TerminalOptions): Promise<RemoteTerminal> {\n\tconst abort = new AbortController();\n\tconst started = Promise.withResolvers<RemoteTerminal>();\n\tlet request: { id: number; session: number } | undefined;\n\tconst exited = connection\n\t\t.request(\n\t\t\t\"pty\",\n\t\t\t{\n\t\t\t\tcwd: options.cwd,\n\t\t\t\t...(options.argv === undefined ? {} : { argv: options.argv }),\n\t\t\t\t...(options.env === undefined ? {} : { env: options.env }),\n\t\t\t\t...(options.inheritEnv === undefined ? {} : { inheritEnv: options.inheritEnv }),\n\t\t\t\tcols: options.cols,\n\t\t\t\trows: options.rows,\n\t\t\t},\n\t\t\t{\n\t\t\t\tsignal: abort.signal,\n\t\t\t\tonStart: (id, session) => {\n\t\t\t\t\trequest = { id, session };\n\t\t\t\t},\n\t\t\t\tonEvent: (json, payload) => {\n\t\t\t\t\tif (json.kind === \"output\") options.onData(payload);\n\t\t\t\t\telse if (json.kind === \"started\" && request) {\n\t\t\t\t\t\tstarted.resolve(\n\t\t\t\t\t\t\tnew RemoteTerminal(connection, request.id, request.session, Number(json.pid), exited, abort),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\t\t.then((reply) => Number(reply.json.exitCode));\n\t// Failing before it started rejects the open; afterwards, `exited` carries it.\n\texited.catch(started.reject);\n\treturn started.promise;\n}\n"]}
package/docs/protocol.md CHANGED
@@ -30,7 +30,7 @@ does not parse gets an `EINVAL` error; framing errors end the connection.
30
30
  | 1 | request | client → daemon | `json.op` names the operation |
31
31
  | 2 | result | daemon → client | success of request `id`; the request is finished |
32
32
  | 3 | error | daemon → client | failure of request `id`; the request is finished |
33
- | 4 | event | daemon → client | progress of a running request (`exec` output, `watch` changes), not finishing it |
33
+ | 4 | event | daemon → client | progress of a running request (`exec` and `pty` output, `watch` changes), not finishing it |
34
34
  | 5 | cancel | client → daemon | cancel request `id`; see below |
35
35
  | 6 | ping | both | liveness; the receiver ignores it |
36
36
 
@@ -45,17 +45,19 @@ contents and output travel as raw payload bytes.
45
45
  ## Cancellation
46
46
 
47
47
  A cancel names one request. The daemon registers each request before running it, so a cancel sent right after its
48
- request is never lost. `exec` kills the command's process group and finishes with `aborted`; `{ mode: "kill" }` kills
48
+ request is never lost. `exec` and `pty` kill the command's process group and finish with `aborted`; `{ mode: "kill" }` kills
49
49
  without aborting, so the command finishes with the killed process's status, as `cleanup()` does. `scanLines` stops
50
- between chunks with `aborted`. `watch` stops and finishes with `{}`. Other operations are short and finish normally.
50
+ between chunks and `listFiles` between entries, with `aborted`. `watch` stops and finishes with `{}`. Other operations
51
+ are short and finish normally.
51
52
 
52
53
  ## Scheduling
53
54
 
54
- File operations run on a fixed pool of worker threads; `exec` and `watch` run on their own threads. Chunk writes to one
55
- handle run in arrival order. Results, errors, pings and `watch` events are sent before queued command output, and
56
- results with payloads above 64 KiB queue behind output too, so a slow link delays bulk data, not replies. Output of a
57
- command without an output window waits while more than 4 MiB of output is unsent, which slows the command as a full
58
- pipe would.
55
+ File operations run on a fixed pool of worker threads; `exec`, `pty` and `watch` run on their own threads. `ptyWrite`,
56
+ `ptyResize` and `ptyForeground` are answered in arrival order by the thread that reads frames, so keystrokes never
57
+ swap. Chunk writes to one handle run in arrival order. Results, errors, pings and `watch` events are sent before queued
58
+ command output, and results with payloads above 64 KiB queue behind output too, so a slow link delays bulk data, not
59
+ replies. Output of a command without an output window waits while more than 4 MiB of output is unsent, which slows the
60
+ command as a full pipe would.
59
61
 
60
62
  ## Liveness
61
63
 
@@ -66,10 +68,13 @@ handles belong to the daemon that opened them, so requests on them fail instead
66
68
 
67
69
  ## Operations
68
70
 
69
- `hello { protocol }` → `{ protocol, version, os, arch, home, tmpdir, cwd, driveCwds, pid }`. The first request.
70
- `version` is the npm package version the daemon shipped with; `os` and `arch` are Rust's `std::env::consts` values;
71
- `tmpdir` follows the remote Node's `os.tmpdir()` rules; `cwd` and `driveCwds` (Windows' `=C:` variables) are what
72
- Node's `path.resolve` falls back to for drive-relative paths.
71
+ `hello { protocol }` → `{ protocol, version, os, arch, home, tmpdir, cwd, driveCwds, pid, features }`. The first
72
+ request. `version` is the npm package version the daemon shipped with; `os` and `arch` are Rust's `std::env::consts`
73
+ values (`linux`, `android`, `macos`, `windows`; `x86_64`, `aarch64`), which the client's `nodePlatform` maps to Node's
74
+ names; `tmpdir` follows the remote Node's `os.tmpdir()` rules; `cwd` and `driveCwds` (Windows' `=C:` variables) are
75
+ what Node's `path.resolve` falls back to for drive-relative paths. `features` lists operations added to protocol 1
76
+ after its first release (`listFiles`); older daemons omit the field and refuse those operations as unknown, so clients
77
+ check it first.
73
78
 
74
79
  File operations take `{ path }` (and the listed fields) and return `{}` unless noted:
75
80
 
@@ -93,6 +98,19 @@ File operations take `{ path }` (and the listed fields) and return `{}` unless n
93
98
  | `readdir` | `handle`, `max` | `{ entries, done }` |
94
99
  | `close` | `handle` | |
95
100
 
101
+ `listFiles { path, hidden?, gitignore?, exclude?, maxEntries?, maxBytes?, timeoutMs? }` lists the tree below the
102
+ directory `path` (feature `listFiles`): files, directories and symbolic links, which are not followed; other kinds of
103
+ files are left out, and entries that cannot be read are skipped. The payload holds one record per entry: a kind byte
104
+ (`f` file, `d` directory, `l` symbolic link), the path relative to `path` with `/` separators as UTF-8 (names that are
105
+ not UTF-8 get replacement characters), and a NUL byte. Records come depth first, siblings in byte order of their
106
+ names. The result is `{ count, truncated }`. Entries named `.git` and the names in `exclude` are skipped with
107
+ everything below them. `hidden` (default true) includes names starting with `.` (and Windows' hidden files).
108
+ `gitignore` (default true) skips, inside git work trees, what the `.gitignore` files of `path`, its subdirectories and
109
+ its parents, `.git/info/exclude` and the global excludes file ignore, using the `ignore` crate of `fd` and ripgrep. The
110
+ listing stops with `truncated: true` at the first entry beyond `maxEntries` (default 50 000), beyond `maxBytes` of
111
+ payload (default 4 MiB, at most the frame limit less 64 KiB), or after `timeoutMs` (default 10 000). A missing `path`
112
+ fails with `ENOENT`, a file with `ENOTDIR`.
113
+
96
114
  `info` is `{ name, kind, size, mtimeSec, mtimeNsec, dev, ino }` with `kind` one of `file`, `directory`, `symlink`, `other`.
97
115
  `readdir` entries are `{ name, raw?, info }` or `{ name, raw?, error }` when the entry's `lstat` failed. Names are
98
116
  decoded as UTF-8 with replacement characters, and lstat'ed under that name, as Node does; `raw` holds the original
@@ -112,3 +130,22 @@ are `{ path, recursive, exclude: { hidden, names } }`. Once coverage is establis
112
130
  follows; then `{ kind: "change", paths }` or `{ kind: "change", overflow: true, mode: "polling" }` (native watching
113
131
  failed), or `{ kind: "error", code, message }`, after which nothing follows. The request lasts until cancelled.
114
132
  Failing to establish coverage is an error of the request.
133
+
134
+ `pty { argv?, cwd, env, inheritEnv, cols, rows }` runs a program in a new pseudo-terminal of `cols` × `rows` cells
135
+ (1 to 4096), as the leader of a new session with the terminal as its controlling terminal. Without `argv`, it runs
136
+ `SHELL` (from `env`, else the daemon's environment, else `/bin/sh`) as a login shell (`-` before its name). The
137
+ environment is built as for `exec`; set `TERM` in `env`. Once the program runs, an event `{ kind: "started", pid }`
138
+ comes first; then `{ kind: "output" }` events carry the terminal's raw output bytes as payload (not decoded, unlike
139
+ `exec`). Output waits while more than 4 MiB of output is unsent, like a full pipe. After the program exits, output is
140
+ collected until the terminal closes or 100 ms pass without output. The result is `{ exitCode }`. Not supported on
141
+ Windows yet (`ENOTSUP`).
142
+
143
+ While a `pty` request runs, `ptyWrite { target }` writes its payload to the terminal's input, and
144
+ `ptyResize { target, cols, rows }` changes its size (the program gets `SIGWINCH`); `target` is the id of the `pty`
145
+ request. Both return `{}` at once; input is queued and written in order, waiting while the program does not read it.
146
+ Once the terminal has ended, they fail with `EBADF`.
147
+
148
+ `ptyForeground { target }` reports the terminal's foreground process group when it is not the program's own:
149
+ `{ foreground: { pid, name } }`, where `pid` is the group's id and `name` its leader's process name (`null` if unknown),
150
+ or `{ foreground: null }` while the program itself is in front. For a shell this tells a running job (an editor, a
151
+ build) from the prompt; background jobs are not reported. It fails with `EBADF` once the terminal has ended.
package/docs/semantics.md CHANGED
@@ -34,6 +34,9 @@ env conformance suite and `test/differential.test.ts` check each rule below.
34
34
  - `readBinaryFile`, `readTextFile` and `openTextLineReader` open any file like `open(path, "r")`: `/dev/null` reads
35
35
  empty, a FIFO blocks until it has a writer, and a directory fails when read (a line reader at `readLine`). A regular
36
36
  file is read up to its size at open; other files to their end.
37
+ - `listFiles` has no `NodeExecutionEnv` counterpart: the daemon implements it for remote and local environments
38
+ alike (`LocalExecutionEnv` runs a daemon on the same machine), so both list with the same code. Unlike `fd`,
39
+ symbolic links are not followed; unlike `git ls-files`, a tracked file that matches an ignore rule is left out.
37
40
  - `readBinaryFile` returns a `Buffer`, reader reads a plain `Uint8Array`, as Node does.
38
41
  - Windows: error codes are libuv's translations; `rm` of a symbolic link or junction to a directory removes the link;
39
42
  creating a file where a directory exists fails with `EISDIR`; modification times before 1970 are negative.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-env",
3
- "version": "1.0.4",
3
+ "version": "1.2.0",
4
4
  "description": "Remote execution environments for Pi Durable: a small daemon deployed over SSH and its ExecutionEnv client",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,11 +45,11 @@
45
45
  "node": ">=22.19.0"
46
46
  },
47
47
  "dependencies": {
48
- "@earendil-works/chord": "^1.0.4",
49
- "@earendil-works/pi-durable": "^1.0.4"
48
+ "@earendil-works/chord": "^1.2.0",
49
+ "@earendil-works/pi-durable": "^1.2.0"
50
50
  },
51
51
  "devDependencies": {
52
- "shx": "0.4.0",
52
+ "shx": "0.3.4",
53
53
  "vitest": "4.1.11"
54
54
  }
55
55
  }