diff --git a/extensions/open-remote-ssh/package.json b/extensions/open-remote-ssh/package.json index 04bd4ad8..48276a7a 100644 --- a/extensions/open-remote-ssh/package.json +++ b/extensions/open-remote-ssh/package.json @@ -71,7 +71,7 @@ "type": "string", "description": "The URL from where the vscode server will be downloaded. You can use the following variables and they will be replaced dynamically:\n- ${quality}: vscode server quality, e.g. stable or insiders\n- ${version}: vscode server version, e.g. 1.69.0\n- ${commit}: vscode server release commit\n- ${arch}: vscode server arch, e.g. x64, armhf, arm64\n- ${release}: release number", "scope": "application", - "default": "https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz" + "default": "https://github.com/voideditor/binaries/releases/download/${version}/void-reh-${os}-${arch}-${version}.tar.gz" }, "remote.SSH.remotePlatform": { "type": "object", diff --git a/extensions/open-remote-ssh/src/serverSetup.ts b/extensions/open-remote-ssh/src/serverSetup.ts index 2bd57794..673a7957 100644 --- a/extensions/open-remote-ssh/src/serverSetup.ts +++ b/extensions/open-remote-ssh/src/serverSetup.ts @@ -8,203 +8,203 @@ import { getVSCodeServerConfig } from './serverConfig'; import SSHConnection from './ssh/sshConnection'; export interface ServerInstallOptions { - id: string; - quality: string; - commit: string; - version: string; - release?: string; // void specific - extensionIds: string[]; - envVariables: string[]; - useSocketPath: boolean; - serverApplicationName: string; - serverDataFolderName: string; - serverDownloadUrlTemplate: string; + id: string; + quality: string; + commit: string; + version: string; + release?: string; // void specific + extensionIds: string[]; + envVariables: string[]; + useSocketPath: boolean; + serverApplicationName: string; + serverDataFolderName: string; + serverDownloadUrlTemplate: string; } export interface ServerInstallResult { - exitCode: number; - listeningOn: number | string; - connectionToken: string; - logFile: string; - osReleaseId: string; - arch: string; - platform: string; - tmpDir: string; - [key: string]: any; + exitCode: number; + listeningOn: number | string; + connectionToken: string; + logFile: string; + osReleaseId: string; + arch: string; + platform: string; + tmpDir: string; + [key: string]: any; } export class ServerInstallError extends Error { - constructor(message: string) { - super(message); - } + constructor(message: string) { + super(message); + } } -const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz'; +const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/binaries/releases/download/${version}/void-reh-${os}-${arch}-${version}.tar.gz'; export async function installCodeServer(conn: SSHConnection, serverDownloadUrlTemplate: string | undefined, extensionIds: string[], envVariables: string[], platform: string | undefined, useSocketPath: boolean, logger: Log): Promise { - let shell = 'powershell'; + let shell = 'powershell'; - // detect platform and shell for windows - if (!platform || platform === 'windows') { - const result = await conn.exec('uname -s'); + // detect platform and shell for windows + if (!platform || platform === 'windows') { + const result = await conn.exec('uname -s'); - if (result.stdout) { - if (result.stdout.includes('windows32')) { - platform = 'windows'; - } else if (result.stdout.includes('MINGW64')) { - platform = 'windows'; - shell = 'bash'; - } - } else if (result.stderr) { - if (result.stderr.includes('FullyQualifiedErrorId : CommandNotFoundException')) { - platform = 'windows'; - } + if (result.stdout) { + if (result.stdout.includes('windows32')) { + platform = 'windows'; + } else if (result.stdout.includes('MINGW64')) { + platform = 'windows'; + shell = 'bash'; + } + } else if (result.stderr) { + if (result.stderr.includes('FullyQualifiedErrorId : CommandNotFoundException')) { + platform = 'windows'; + } - if (result.stderr.includes('is not recognized as an internal or external command')) { - platform = 'windows'; - shell = 'cmd'; - } - } + if (result.stderr.includes('is not recognized as an internal or external command')) { + platform = 'windows'; + shell = 'cmd'; + } + } - if (platform) { - logger.trace(`Detected platform: ${platform}, ${shell}`); - } - } + if (platform) { + logger.trace(`Detected platform: ${platform}, ${shell}`); + } + } - const scriptId = crypto.randomBytes(12).toString('hex'); + const scriptId = crypto.randomBytes(12).toString('hex'); - const vscodeServerConfig = await getVSCodeServerConfig(); - const installOptions: ServerInstallOptions = { - id: scriptId, - version: vscodeServerConfig.version, - commit: vscodeServerConfig.commit, - quality: vscodeServerConfig.quality, - release: vscodeServerConfig.release, - extensionIds, - envVariables, - useSocketPath, - serverApplicationName: vscodeServerConfig.serverApplicationName, - serverDataFolderName: vscodeServerConfig.serverDataFolderName, - serverDownloadUrlTemplate: serverDownloadUrlTemplate ?? vscodeServerConfig.serverDownloadUrlTemplate ?? DEFAULT_DOWNLOAD_URL_TEMPLATE, - }; + const vscodeServerConfig = await getVSCodeServerConfig(); + const installOptions: ServerInstallOptions = { + id: scriptId, + version: vscodeServerConfig.version, + commit: vscodeServerConfig.commit, + quality: vscodeServerConfig.quality, + release: vscodeServerConfig.release, + extensionIds, + envVariables, + useSocketPath, + serverApplicationName: vscodeServerConfig.serverApplicationName, + serverDataFolderName: vscodeServerConfig.serverDataFolderName, + serverDownloadUrlTemplate: serverDownloadUrlTemplate ?? vscodeServerConfig.serverDownloadUrlTemplate ?? DEFAULT_DOWNLOAD_URL_TEMPLATE, + }; - let commandOutput: { stdout: string; stderr: string }; - if (platform === 'windows') { - const installServerScript = generatePowerShellInstallScript(installOptions); + let commandOutput: { stdout: string; stderr: string }; + if (platform === 'windows') { + const installServerScript = generatePowerShellInstallScript(installOptions); - logger.trace('Server install command:', installServerScript); + logger.trace('Server install command:', installServerScript); - const installDir = `$HOME\\${vscodeServerConfig.serverDataFolderName}\\install`; - const installScript = `${installDir}\\${vscodeServerConfig.commit}.ps1`; - const endRegex = new RegExp(`${scriptId}: end`); - // investigate if it's possible to use `-EncodedCommand` flag - // https://devblogs.microsoft.com/powershell/invoking-powershell-with-complex-expressions-using-scriptblocks/ - let command = ''; - if (shell === 'powershell') { - command = `md -Force ${installDir}; echo @'\n${installServerScript}\n'@ | Set-Content ${installScript}; powershell -ExecutionPolicy ByPass -File "${installScript}"`; - } else if (shell === 'bash') { - command = `mkdir -p ${installDir.replace(/\\/g, '/')} && echo '\n${installServerScript.replace(/'/g, '\'"\'"\'')}\n' > ${installScript.replace(/\\/g, '/')} && powershell -ExecutionPolicy ByPass -File "${installScript}"`; - } else if (shell === 'cmd') { - const script = installServerScript.trim() - // remove comments - .replace(/^#.*$/gm, '') - // remove empty lines - .replace(/\n{2,}/gm, '\n') - // remove leading spaces - .replace(/^\s*/gm, '') - // escape double quotes (from powershell/cmd) - .replace(/"/g, '"""') - // escape single quotes (from cmd) - .replace(/'/g, `''`) - // escape redirect (from cmd) - .replace(/>/g, `^>`) - // escape new lines (from powershell/cmd) - .replace(/\n/g, '\'`n\''); + const installDir = `$HOME\\${vscodeServerConfig.serverDataFolderName}\\install`; + const installScript = `${installDir}\\${vscodeServerConfig.commit}.ps1`; + const endRegex = new RegExp(`${scriptId}: end`); + // investigate if it's possible to use `-EncodedCommand` flag + // https://devblogs.microsoft.com/powershell/invoking-powershell-with-complex-expressions-using-scriptblocks/ + let command = ''; + if (shell === 'powershell') { + command = `md -Force ${installDir}; echo @'\n${installServerScript}\n'@ | Set-Content ${installScript}; powershell -ExecutionPolicy ByPass -File "${installScript}"`; + } else if (shell === 'bash') { + command = `mkdir -p ${installDir.replace(/\\/g, '/')} && echo '\n${installServerScript.replace(/'/g, '\'"\'"\'')}\n' > ${installScript.replace(/\\/g, '/')} && powershell -ExecutionPolicy ByPass -File "${installScript}"`; + } else if (shell === 'cmd') { + const script = installServerScript.trim() + // remove comments + .replace(/^#.*$/gm, '') + // remove empty lines + .replace(/\n{2,}/gm, '\n') + // remove leading spaces + .replace(/^\s*/gm, '') + // escape double quotes (from powershell/cmd) + .replace(/"/g, '"""') + // escape single quotes (from cmd) + .replace(/'/g, `''`) + // escape redirect (from cmd) + .replace(/>/g, `^>`) + // escape new lines (from powershell/cmd) + .replace(/\n/g, '\'`n\''); - command = `powershell "md -Force ${installDir}" && powershell "echo '${script}'" > ${installScript.replace('$HOME', '%USERPROFILE%')} && powershell -ExecutionPolicy ByPass -File "${installScript.replace('$HOME', '%USERPROFILE%')}"`; + command = `powershell "md -Force ${installDir}" && powershell "echo '${script}'" > ${installScript.replace('$HOME', '%USERPROFILE%')} && powershell -ExecutionPolicy ByPass -File "${installScript.replace('$HOME', '%USERPROFILE%')}"`; - logger.trace('Command length (8191 max):', command.length); + logger.trace('Command length (8191 max):', command.length); - if (command.length > 8191) { - throw new ServerInstallError(`Command line too long`); - } - } else { - throw new ServerInstallError(`Not supported shell: ${shell}`); - } + if (command.length > 8191) { + throw new ServerInstallError(`Command line too long`); + } + } else { + throw new ServerInstallError(`Not supported shell: ${shell}`); + } - commandOutput = await conn.execPartial(command, (stdout: string) => endRegex.test(stdout)); - } else { - const installServerScript = generateBashInstallScript(installOptions); + commandOutput = await conn.execPartial(command, (stdout: string) => endRegex.test(stdout)); + } else { + const installServerScript = generateBashInstallScript(installOptions); - logger.trace('Server install command:', installServerScript); - // Fish shell does not support heredoc so let's workaround it using -c option, - // also replace single quotes (') within the script with ('\'') as there's no quoting within single quotes, see https://unix.stackexchange.com/a/24676 - commandOutput = await conn.exec(`bash -c '${installServerScript.replace(/'/g, `'\\''`)}'`); - } + logger.trace('Server install command:', installServerScript); + // Fish shell does not support heredoc so let's workaround it using -c option, + // also replace single quotes (') within the script with ('\'') as there's no quoting within single quotes, see https://unix.stackexchange.com/a/24676 + commandOutput = await conn.exec(`bash -c '${installServerScript.replace(/'/g, `'\\''`)}'`); + } - if (commandOutput.stderr) { - logger.trace('Server install command stderr:', commandOutput.stderr); - } - logger.trace('Server install command stdout:', commandOutput.stdout); + if (commandOutput.stderr) { + logger.trace('Server install command stderr:', commandOutput.stderr); + } + logger.trace('Server install command stdout:', commandOutput.stdout); - const resultMap = parseServerInstallOutput(commandOutput.stdout, scriptId); - if (!resultMap) { - throw new ServerInstallError(`Failed parsing install script output`); - } + const resultMap = parseServerInstallOutput(commandOutput.stdout, scriptId); + if (!resultMap) { + throw new ServerInstallError(`Failed parsing install script output`); + } - const exitCode = parseInt(resultMap.exitCode, 10); - if (exitCode !== 0) { - throw new ServerInstallError(`Couldn't install vscode server on remote server, install script returned non-zero exit status`); - } + const exitCode = parseInt(resultMap.exitCode, 10); + if (exitCode !== 0) { + throw new ServerInstallError(`Couldn't install vscode server on remote server, install script returned non-zero exit status`); + } - const listeningOn = resultMap.listeningOn.match(/^\d+$/) - ? parseInt(resultMap.listeningOn, 10) - : resultMap.listeningOn; + const listeningOn = resultMap.listeningOn.match(/^\d+$/) + ? parseInt(resultMap.listeningOn, 10) + : resultMap.listeningOn; - const remoteEnvVars = Object.fromEntries(Object.entries(resultMap).filter(([key,]) => envVariables.includes(key))); + const remoteEnvVars = Object.fromEntries(Object.entries(resultMap).filter(([key,]) => envVariables.includes(key))); - return { - exitCode, - listeningOn, - connectionToken: resultMap.connectionToken, - logFile: resultMap.logFile, - osReleaseId: resultMap.osReleaseId, - arch: resultMap.arch, - platform: resultMap.platform, - tmpDir: resultMap.tmpDir, - ...remoteEnvVars - }; + return { + exitCode, + listeningOn, + connectionToken: resultMap.connectionToken, + logFile: resultMap.logFile, + osReleaseId: resultMap.osReleaseId, + arch: resultMap.arch, + platform: resultMap.platform, + tmpDir: resultMap.tmpDir, + ...remoteEnvVars + }; } function parseServerInstallOutput(str: string, scriptId: string): { [k: string]: string } | undefined { - const startResultStr = `${scriptId}: start`; - const endResultStr = `${scriptId}: end`; + const startResultStr = `${scriptId}: start`; + const endResultStr = `${scriptId}: end`; - const startResultIdx = str.indexOf(startResultStr); - if (startResultIdx < 0) { - return undefined; - } + const startResultIdx = str.indexOf(startResultStr); + if (startResultIdx < 0) { + return undefined; + } - const endResultIdx = str.indexOf(endResultStr, startResultIdx + startResultStr.length); - if (endResultIdx < 0) { - return undefined; - } + const endResultIdx = str.indexOf(endResultStr, startResultIdx + startResultStr.length); + if (endResultIdx < 0) { + return undefined; + } - const installResult = str.substring(startResultIdx + startResultStr.length, endResultIdx); + const installResult = str.substring(startResultIdx + startResultStr.length, endResultIdx); - const resultMap: { [k: string]: string } = {}; - const resultArr = installResult.split(/\r?\n/); - for (const line of resultArr) { - const [key, value] = line.split('=='); - resultMap[key] = value; - } + const resultMap: { [k: string]: string } = {}; + const resultArr = installResult.split(/\r?\n/); + for (const line of resultArr) { + const [key, value] = line.split('=='); + resultMap[key] = value; + } - return resultMap; + return resultMap; } function generateBashInstallScript({ id, quality, version, commit, release, extensionIds, envVariables, useSocketPath, serverApplicationName, serverDataFolderName, serverDownloadUrlTemplate }: ServerInstallOptions) { - const extensions = extensionIds.map(id => '--install-extension ' + id).join(' '); - return ` + const extensions = extensionIds.map(id => '--install-extension ' + id).join(' '); + return ` # Server installation script TMP_DIR="\${XDG_RUNTIME_DIR:-"/tmp"}" @@ -427,16 +427,16 @@ print_install_results_and_exit 0 } function generatePowerShellInstallScript({ id, quality, version, commit, release, extensionIds, envVariables, useSocketPath, serverApplicationName, serverDataFolderName, serverDownloadUrlTemplate }: ServerInstallOptions) { - const extensions = extensionIds.map(id => '--install-extension ' + id).join(' '); - const downloadUrl = serverDownloadUrlTemplate - .replace(/\$\{quality\}/g, quality) - .replace(/\$\{version\}/g, version) - .replace(/\$\{commit\}/g, commit) - .replace(/\$\{os\}/g, 'win32') - .replace(/\$\{arch\}/g, 'x64') - .replace(/\$\{release\}/g, release ?? ''); + const extensions = extensionIds.map(id => '--install-extension ' + id).join(' '); + const downloadUrl = serverDownloadUrlTemplate + .replace(/\$\{quality\}/g, quality) + .replace(/\$\{version\}/g, version) + .replace(/\$\{commit\}/g, commit) + .replace(/\$\{os\}/g, 'win32') + .replace(/\$\{arch\}/g, 'x64') + .replace(/\$\{release\}/g, release ?? ''); - return ` + return ` # Server installation script $TMP_DIR="$env:TEMP\\$([System.IO.Path]::GetRandomFileName())" diff --git a/extensions/open-remote-wsl/package.json b/extensions/open-remote-wsl/package.json index e913bdd9..bba06d81 100644 --- a/extensions/open-remote-wsl/package.json +++ b/extensions/open-remote-wsl/package.json @@ -38,7 +38,7 @@ "type": "string", "description": "The URL from where the vscode server will be downloaded. You can use the following variables and they will be replaced dynamically:\n- ${quality}: vscode server quality, e.g. stable or insiders\n- ${version}: vscode server version, e.g. 1.69.0\n- ${commit}: vscode server release commit\n- ${arch}: vscode server arch, e.g. x64, armhf, arm64\n- ${release}: release number", "scope": "application", - "default": "https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz" + "default": "https://github.com/voideditor/binaries/releases/download/${version}/void-reh-${os}-${arch}-${version}.tar.gz" } } }, diff --git a/extensions/open-remote-wsl/src/serverSetup.ts b/extensions/open-remote-wsl/src/serverSetup.ts index 3d254313..2f56862c 100644 --- a/extensions/open-remote-wsl/src/serverSetup.ts +++ b/extensions/open-remote-wsl/src/serverSetup.ts @@ -39,7 +39,7 @@ export class ServerInstallError extends Error { } } -const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/binaries/releases/download/${version}.${release}/void-reh-${os}-${arch}-${version}.${release}.tar.gz'; +const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://github.com/voideditor/binaries/releases/download/${version}/void-reh-${os}-${arch}-${version}.tar.gz'; export async function installCodeServer(wslManager: WSLManager, distroName: string, serverDownloadUrlTemplate: string | undefined, extensionIds: string[], envVariables: string[], logger: Log): Promise { const scriptId = crypto.randomBytes(12).toString('hex');