import os from "node:os"; import path from "node:path"; export const WINDOWS_TASK_NAME = "PalmTTY Agent"; export const WINDOWS_INSTALLATION_VERSION = 1; export const LINUX_UNIT_NAME = "palmtty.service"; export function defaultConfigPath(platform = process.platform, env = process.env, home = os.homedir()) { const platformPath = platform === "win32" ? path.win32 : path.posix; if (platform === "win32") { return platformPath.join(env.APPDATA ?? home, "PalmTTY", "config.yaml"); } if (platform === "darwin") { return platformPath.join(home, "Library", "Application Support", "PalmTTY", "config.yaml"); } return platformPath.join(env.XDG_CONFIG_HOME ?? platformPath.join(home, ".config"), "palmtty", "config.yaml"); } export function defaultLinuxUnitPath(env = process.env, home = os.homedir()) { return path.join(env.XDG_CONFIG_HOME ?? path.join(home, ".config"), "systemd", "user", LINUX_UNIT_NAME); } export function defaultWindowsPowerShellPath(env = process.env) { const systemRoot = env.SystemRoot ?? env.WINDIR; if (!systemRoot) { throw new Error("Windows SystemRoot/WINDIR is unavailable"); } return path.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"); } export function defaultWindowsAutostartDir(env = process.env, home = os.homedir()) { const localAppData = env.LOCALAPPDATA ?? env.APPDATA ?? home; return path.win32.join(localAppData, "PalmTTY", "autostart"); } export function windowsAutostartPaths(env = process.env, home = os.homedir()) { const directory = defaultWindowsAutostartDir(env, home); return { directory, host: path.win32.join(directory, "palmtty-autostart-host.exe"), installation: path.win32.join(directory, "installation.json"), runtime: path.win32.join(directory, "runtime.json"), lastError: path.win32.join(directory, "last-error.txt") }; } export function parseAutostartArgs(argv) { const args = [...argv]; const command = args.shift(); if (!command || !["install", "status", "restart", "uninstall"].includes(command)) { throw new Error("Usage: pnpm autostart [--config ] [--env-file ]"); } if (command !== "install" && args.length > 0) { throw new Error(`${command} does not accept install options`); } let configPath; let envFile; for (let index = 0; index < args.length; index += 1) { const option = args[index]; if (option !== "--config" && option !== "--env-file") { throw new Error(`Unknown autostart option: ${option}`); } const value = args[index + 1]; if (!value || value.startsWith("--")) { throw new Error(`Missing value for ${option}`); } if (option === "--config") { if (configPath !== undefined) throw new Error("--config may be specified only once"); configPath = path.resolve(value); } else { if (envFile !== undefined) throw new Error("--env-file may be specified only once"); envFile = path.resolve(value); } index += 1; } return { command, configPath, envFile }; } export function quoteWindowsArg(value) { if (value.length === 0) return '""'; if (!/[\s"]/u.test(value)) return value; let result = '"'; let backslashes = 0; for (const character of value) { if (character === "\\") { backslashes += 1; continue; } if (character === '"') { result += "\\".repeat(backslashes * 2 + 1) + '"'; backslashes = 0; continue; } result += "\\".repeat(backslashes) + character; backslashes = 0; } result += "\\".repeat(backslashes * 2) + '"'; return result; } export function escapeXml(value) { return value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function assertSingleLine(value, name) { if (/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/u.test(value)) { throw new Error(`${name} contains an unsupported control character`); } if (/\r|\n/u.test(value)) { throw new Error(`${name} must be a single line`); } } export function buildAgentArguments({ agentPath, configPath, envFile }) { const args = [agentPath, "--config", configPath]; if (envFile) args.push("--env-file", envFile); return args; } export function quotePowerShellLiteral(value) { assertSingleLine(value, "PowerShell argument"); return `'${value.replaceAll("'", "''")}'`; } export function encodePowerShellCommand(command) { return Buffer.from(command, "utf16le").toString("base64"); } export function buildWindowsHostCompilePowerShellCommand({ sourcePath, outputPath }) { assertSingleLine(sourcePath, "sourcePath"); assertSingleLine(outputPath, "outputPath"); return [ "$ErrorActionPreference = 'Stop'", "Add-Type `", ` -Path ${quotePowerShellLiteral(sourcePath)} \``, ` -OutputAssembly ${quotePowerShellLiteral(outputPath)} \``, " -OutputType WindowsApplication `", " -ReferencedAssemblies 'System.dll','System.Core.dll','System.Runtime.Serialization.dll','System.Xml.dll'" ].join("\n"); } export function buildWindowsInstallationManifest({ nodePath, agentPath, repoRoot, configPath, envFile }) { for (const [name, value] of Object.entries({ nodePath, agentPath, repoRoot, configPath })) { assertSingleLine(value, name); } if (envFile) assertSingleLine(envFile, "envFile"); return { version: WINDOWS_INSTALLATION_VERSION, nodePath, agentPath, workingDirectory: repoRoot, configPath, ...(envFile ? { envFile } : {}) }; } export function parseWindowsInstallationManifest(source) { let value; try { value = JSON.parse(source); } catch { throw new Error("Windows autostart installation manifest is invalid JSON"); } if (!value || typeof value !== "object" || value.version !== WINDOWS_INSTALLATION_VERSION) { throw new Error("Windows autostart installation manifest has an unsupported version"); } for (const key of ["nodePath", "agentPath", "workingDirectory", "configPath"]) { if (typeof value[key] !== "string" || value[key].length === 0) { throw new Error(`Windows autostart installation manifest is missing ${key}`); } } if (value.envFile !== undefined && (typeof value.envFile !== "string" || value.envFile.length === 0)) { throw new Error("Windows autostart installation manifest has an invalid envFile"); } return { version: WINDOWS_INSTALLATION_VERSION, nodePath: value.nodePath, agentPath: value.agentPath, workingDirectory: value.workingDirectory, configPath: value.configPath, ...(value.envFile ? { envFile: value.envFile } : {}) }; } export function buildWindowsTaskXml({ userSid, hostPath, installationPath, workingDirectory }) { for (const [name, value] of Object.entries({ userSid, hostPath, installationPath, workingDirectory })) { assertSingleLine(value, name); } const argumentsText = [ "--installation", installationPath ].map(quoteWindowsArg).join(" "); return ` Start PalmTTY Agent for the current user at sign-in. true ${escapeXml(userSid)} ${escapeXml(userSid)} InteractiveToken LeastPrivilege IgnoreNew false false true true false false false true true false false false PT0S 7 PT1M 3 ${escapeXml(hostPath)} ${escapeXml(argumentsText)} ${escapeXml(workingDirectory)} `; } export function buildWindowsTaskStatusPowerShellCommand(taskName = WINDOWS_TASK_NAME) { const taskNameLiteral = quotePowerShellLiteral(taskName); return `[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)\n` + `$task = Get-ScheduledTask -TaskName ${taskNameLiteral} -ErrorAction SilentlyContinue\n` + `if ($null -eq $task) {\n` + ` [PSCustomObject]@{ installed = $false } | ConvertTo-Json -Compress\n` + ` exit 0\n` + `}\n` + `$info = Get-ScheduledTaskInfo -TaskName ${taskNameLiteral}\n` + `$lastRunTime = $null\n` + `if ($info.LastRunTime -ne [datetime]::MinValue) { $lastRunTime = $info.LastRunTime.ToUniversalTime().ToString('o') }\n` + `$nextRunTime = $null\n` + `if ($info.NextRunTime -ne [datetime]::MinValue) { $nextRunTime = $info.NextRunTime.ToUniversalTime().ToString('o') }\n` + `[PSCustomObject]@{\n` + ` installed = $true\n` + ` state = [string]$task.State\n` + ` lastRunTime = $lastRunTime\n` + ` nextRunTime = $nextRunTime\n` + ` lastTaskResult = [int64]$info.LastTaskResult\n` + `} | ConvertTo-Json -Compress`; } export function parseWindowsTaskStatus(output) { let parsed; try { parsed = JSON.parse(output.trim()); } catch { throw new Error("Windows Task Scheduler returned an invalid status payload"); } if (!parsed || typeof parsed !== "object" || typeof parsed.installed !== "boolean") { throw new Error("Windows Task Scheduler returned an invalid status payload"); } if (!parsed.installed) return { installed: false }; if (typeof parsed.state !== "string" || parsed.state.length === 0) { throw new Error("Windows Task Scheduler returned an invalid task state"); } for (const key of ["lastRunTime", "nextRunTime"]) { const value = parsed[key]; if (value !== null && value !== undefined && typeof value !== "string") { throw new Error(`Windows Task Scheduler returned an invalid ${key}`); } } if (typeof parsed.lastTaskResult !== "number" || !Number.isSafeInteger(parsed.lastTaskResult)) { throw new Error("Windows Task Scheduler returned an invalid lastTaskResult"); } return { installed: true, state: parsed.state.toLowerCase(), lastRunTime: parsed.lastRunTime ?? null, nextRunTime: parsed.nextRunTime ?? null, lastTaskResult: parsed.lastTaskResult }; } export function quoteSystemdArg(value) { assertSingleLine(value, "systemd argument"); return `"${value .replaceAll("\\", "\\\\") .replaceAll('"', '\\"') .replaceAll("%", "%%")}"`; } export function buildSystemdUserUnit({ nodePath, agentPath, repoRoot, configPath, envFile }) { const execArgs = [nodePath, ...buildAgentArguments({ agentPath, configPath, envFile })] .map(quoteSystemdArg) .join(" "); return `[Unit] Description=PalmTTY Agent [Service] Type=simple WorkingDirectory=${quoteSystemdArg(repoRoot)} ExecStart=${execArgs} Restart=on-failure RestartSec=3s # Session Workers are deliberately independent from the Agent control plane. KillMode=process [Install] WantedBy=default.target `; }