From 70fa291e3779ca5edc8aaea716702fecb9364f97 Mon Sep 17 00:00:00 2001 From: Levi Yan Date: Tue, 6 May 2025 23:05:00 +0800 Subject: [PATCH] feat: support first connect in remote environment --- src/home.ts | 2 +- src/main.ts | 6 +- src/remote-container.ts | 170 ++++++++++++++++++++++++++++++++-------- 3 files changed, 141 insertions(+), 37 deletions(-) diff --git a/src/home.ts b/src/home.ts index 9a8c9a8..a6cc5d6 100644 --- a/src/home.ts +++ b/src/home.ts @@ -69,7 +69,7 @@ export default class DSHome { break; } case 'firstOpenRemoteFolder': - await this.remoteContainer.firstConnect(data.host, data.username, data.port) + await this.remoteContainer.firstConnect(data.host, data.username, data.port, this.context) .then((_res) => { if (_res == 'success') { // only success then open folder diff --git a/src/main.ts b/src/main.ts index 3ce35b4..e8bf6dc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -43,12 +43,12 @@ export class DevStarExtension { // 如果没有用户登录,则直接登录; const res = await this.user.login(access_token, devstar_username) if (res === 'ok') { - await this.remoteContainer.firstOpenProject(container_host, container_port, container_username, project_path) + await this.remoteContainer.firstOpenProject(container_host, container_port, container_username, project_path, this.context) } } else if (devstar_username === this.user.getUsernameFromLocal()) { // 如果同用户已经登录,则忽略; // 直接打开项目 - await this.remoteContainer.firstOpenProject(container_host, container_port, container_username, project_path) + await this.remoteContainer.firstOpenProject(container_host, container_port, container_username, project_path, this.context) } else { // 如果不是同用户,可以选择切换用户,或者不切换登录用户,直接打开容器 const selection = await vscode.window.showWarningMessage(`已登录用户:${this.user.getUsernameFromLocal()},是否切换用户?`, @@ -57,7 +57,7 @@ export class DevStarExtension { // 如果没有用户登录,则直接登录; const res = await this.user.login(access_token, devstar_username) if (res === 'ok') { - await this.remoteContainer.firstOpenProject(container_host, container_port, container_username, project_path) + await this.remoteContainer.firstOpenProject(container_host, container_port, container_username, project_path, this.context) } } else if (selection === 'No') { await openProjectWithoutLogging(container_host, container_port, container_username, project_path); diff --git a/src/remote-container.ts b/src/remote-container.ts index e751ee6..40daea4 100644 --- a/src/remote-container.ts +++ b/src/remote-container.ts @@ -7,6 +7,7 @@ const { NodeSSH } = require('node-ssh') import * as utils from './utils'; import User from './user'; +import DevstarAPIHandler from './devstar-api'; export default class RemoteContainer { private user: User; @@ -15,8 +16,8 @@ export default class RemoteContainer { this.user = user } - async firstOpenProject(host: string, port: number, username: string, path: string) { - await this.firstConnect(host, username, port) + async firstOpenProject(host: string, port: number, username: string, path: string, context: vscode.ExtensionContext) { + await this.firstConnect(host, username, port, context) .then((res) => { if (res === 'success') { // only success then open folder @@ -26,7 +27,7 @@ export default class RemoteContainer { } // connect with key - async firstConnect(host: string, username: string, port: number): Promise { + async firstConnect(hostname: string, username: string, port: number, context: vscode.ExtensionContext): Promise { return new Promise(async (resolve) => { const ssh = new NodeSSH(); vscode.window.withProgress({ @@ -34,21 +35,31 @@ export default class RemoteContainer { title: vscode.l10n.t("Installing vscode-server and devstar extension in container"), cancellable: false }, async (progress) => { - try { - // connect with key - await ssh.connect({ - host: host, - username: username, - port: port, - privateKeyPath: this.user.getUserPrivateKeyPath() - }); - progress.report({ message: vscode.l10n.t("Connected! Start installation") }); + if (vscode.env.remoteName) { + // 远程环境 + // 检查公私钥是否存在,如果不存在,需要创建 + if (!this.user.existUserPrivateKey() || !this.user.existUserPublicKey()) { + this.user.createUserSSHKey() + // 上传公钥 + const devstarAPIHandler = new DevstarAPIHandler() + await devstarAPIHandler.uploadUserPublicKey(this.user) + } - // install vscode-server and devstar extension - const vscodeCommitId = await utils.getVsCodeCommitId() - if ("" != vscodeCommitId) { - const vscodeServerUrl = `https://vscode.download.prss.microsoft.com/dbazure/download/stable/${vscodeCommitId}/vscode-server-linux-x64.tar.gz` - const installVscodeServerScript = ` + try { + // 第一次连接 + await ssh.connect({ + host: hostname, + username: username, + port: port, + privateKeyPath: this.user.getUserPrivateKeyPath() + }); + progress.report({ message: vscode.l10n.t("Connected! Start installation") }); + + // 安装vscode-server and devstar extension + const vscodeCommitId = await utils.getVsCodeCommitId() + if ("" != vscodeCommitId) { + const vscodeServerUrl = `https://vscode.download.prss.microsoft.com/dbazure/download/stable/${vscodeCommitId}/vscode-server-linux-x64.tar.gz` + const installVscodeServerScript = ` mkdir -p ~/.vscode-server/bin/${vscodeCommitId} && \\ if [ "$(ls -A ~/.vscode-server/bin/${vscodeCommitId})" ]; then ~/.vscode-server/bin/${vscodeCommitId}/bin/code-server --install-extension mengning.devstar @@ -61,18 +72,98 @@ export default class RemoteContainer { ~/.vscode-server/bin/${vscodeCommitId}/bin/code-server --install-extension mengning.devstar fi `; - await ssh.execCommand(installVscodeServerScript); - console.log("vscode-server and extension installed"); - vscode.window.showInformationMessage(vscode.l10n.t('Installation completed!')); + await ssh.execCommand(installVscodeServerScript); + console.log("vscode-server and extension installed"); + vscode.window.showInformationMessage(vscode.l10n.t('Installation completed!')); + } + + await ssh.dispose(); + + // ssh信息存储到ssh config file中 + // 远程环境,利用global state中记录的localSystemName来决定执行的命令 + const localSystemName = utils.getLocalSystemName(context) + const localSSHConfigPath = utils.getLocalSSHConfigPath(context); + const privateKeyPath = this.user.getLocalUserPrivateKeyPath(); + const host = `${hostname}-${port}` // host: hostname-port + const newSShConfigContent = + `\nHost ${host}\n HostName ${hostname}\n Port ${port}\n User ${username}\n PreferredAuthentications publickey\n IdentityFile ${privateKeyPath}\n `; + let commandToAppend: string; + const delimiter = `EOF_SSH_CONFIG_${Date.now()}`; // 使用一个唯一的分隔符用于 here-docs (Unix) 或 here-strings (PowerShell) + + if (localSystemName === "win32") { + // --- Windows 平台 --- + // 使用 PowerShell 的 Here-String (@'...'@) 来保证多行文本输入的健壮性 + // @'...'@ 内的内容会按字面解释,减少转义问题 + const escapedContent = newSShConfigContent.replace(/'/g, "''"); // 对内容中的单引号进行转义(替换为两个单引号),以防万一变量值包含单引号 + const psTargetPath = localSSHConfigPath; + // 将内容包裹在 @'\n...\n'@ 中,确保前后有换行 + commandToAppend = `Add-Content -Path '${psTargetPath}' -Value @'\n${escapedContent}\n'@`; + } else if (localSystemName === "darwin" || localSystemName === "linux") { + // --- Linux, macOS, 或其他类 Unix 系统 --- + const unixTargetPath = `"${localSSHConfigPath}"`; + commandToAppend = `cat << '${delimiter}' >> ${unixTargetPath} + ${newSShConfigContent} + ${delimiter} + `; + } + + // 通过local terminal命令行写入 + vscode.commands.executeCommand('workbench.action.terminal.newLocal').then(() => { + const terminal = vscode.window.terminals[vscode.window.terminals.length - 1]; + if (terminal) { + terminal.sendText(commandToAppend, true); + } + }) + + resolve('success') + } catch (error) { + console.error('Failed to install vscode-server and extension: ', error); + await ssh.dispose(); } + } else { + // 本地环境 + try { + // connect with key + await ssh.connect({ + host: hostname, + username: username, + port: port, + privateKeyPath: this.user.getUserPrivateKeyPath() + }); + progress.report({ message: vscode.l10n.t("Connected! Start installation") }); - await ssh.dispose(); - await this.storeHostInfo(host, port, username) + // install vscode-server and devstar extension + const vscodeCommitId = await utils.getVsCodeCommitId() + if ("" != vscodeCommitId) { + const vscodeServerUrl = `https://vscode.download.prss.microsoft.com/dbazure/download/stable/${vscodeCommitId}/vscode-server-linux-x64.tar.gz` + const installVscodeServerScript = ` + mkdir -p ~/.vscode-server/bin/${vscodeCommitId} && \\ + if [ "$(ls -A ~/.vscode-server/bin/${vscodeCommitId})" ]; then + ~/.vscode-server/bin/${vscodeCommitId}/bin/code-server --install-extension mengning.devstar + else + wget ${vscodeServerUrl} -O vscode-server-linux-x64.tar.gz && \\ + mv vscode-server-linux-x64.tar.gz ~/.vscode-server/bin/${vscodeCommitId} && \\ + cd ~/.vscode-server/bin/${vscodeCommitId} && \\ + tar -xvzf vscode-server-linux-x64.tar.gz --strip-components 1 && \\ + rm vscode-server-linux-x64.tar.gz && \\ + ~/.vscode-server/bin/${vscodeCommitId}/bin/code-server --install-extension mengning.devstar + fi + `; + await ssh.execCommand(installVscodeServerScript); + console.log("vscode-server and extension installed"); + vscode.window.showInformationMessage(vscode.l10n.t('Installation completed!')); + } - resolve('success') - } catch (error) { - console.error('Failed to install vscode-server and extension: ', error); - await ssh.dispose(); + await ssh.dispose(); + + // only connect successfully then save the host info + await this.storeHostInfo(hostname, port, username) + + resolve('success') + } catch (error) { + console.error('Failed to install vscode-server and extension: ', error); + await ssh.dispose(); + } } }); }); @@ -107,14 +198,27 @@ export default class RemoteContainer { } - openRemoteFolder(host: string, port: number, username: string, path: string): void { - var host = `${host}-${port}` - const command = `code --remote ssh-remote+${username}@${host}:${port} ${path} --reuse-window` - let terminal = vscode.window.activeTerminal || vscode.window.createTerminal(`Ext Terminal`); - terminal.show(true); - terminal.sendText(command); + openRemoteFolder(hostname: string, port: number, username: string, path: string): void { + if (vscode.env.remoteName) { + // 远程环境,打开local terminal + vscode.commands.executeCommand('workbench.action.terminal.newLocal').then(() => { + // 获取最后一个打开的终端实例 + const terminal = vscode.window.terminals[vscode.window.terminals.length - 1]; + if (terminal) { + var host = `${hostname}-${port}` + // 在新窗口打开 + terminal.sendText(`code --remote ssh-remote+${username}@${host}:${port} ${path} --new-window`) + } + }) + } else { + // 本地环境 + var host = `${hostname}-${port}` + let terminal = vscode.window.activeTerminal || vscode.window.createTerminal(`Ext Terminal`); + terminal.show(true); + // 在原窗口打开 + terminal.sendText(`code --remote ssh-remote+${username}@${host}:${port} ${path} --reuse-window`); + } } - }