From 7367d3cf37c80f8ef67843dabf29bbe6a8931d87 Mon Sep 17 00:00:00 2001 From: yinxue <2643126914@qq.com> Date: Mon, 3 Nov 2025 13:44:18 +0800 Subject: [PATCH] =?UTF-8?q?remote-container.ts=E8=B0=83=E8=AF=95=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E6=9B=B4=E5=8A=A0=E5=81=A5=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/remote-container.ts | 218 ++++++++++++++++++++++++++++++++-------- 1 file changed, 175 insertions(+), 43 deletions(-) diff --git a/src/remote-container.ts b/src/remote-container.ts index 80614be..5119c9f 100644 --- a/src/remote-container.ts +++ b/src/remote-container.ts @@ -1,3 +1,4 @@ +// remote-container.ts import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; @@ -32,10 +33,16 @@ export default class RemoteContainer { * @param context 用于支持远程项目环境 */ async firstOpenProject(host: string, hostname: string, port: number, username: string, path: string, context: vscode.ExtensionContext) { + console.log(`[RemoteContainer] firstOpenProject called with:`, { host, hostname, port, username, path }); + if (vscode.env.remoteName) { // 远程环境 - vscode.commands.executeCommand('workbench.action.terminal.newLocal').then(() => { + console.log(`[RemoteContainer] Running in remote environment: ${vscode.env.remoteName}`); + + try { + await vscode.commands.executeCommand('workbench.action.terminal.newLocal'); const terminal = vscode.window.terminals[vscode.window.terminals.length - 1]; + if (terminal) { let devstarDomain: string | undefined = context.globalState.get("devstarDomain_" + vscode.env.sessionId) if (devstarDomain == undefined || devstarDomain == "") @@ -47,39 +54,65 @@ export default class RemoteContainer { const powershellVersion = context.globalState.get('powershellVersion') const powershell_semver_compatible_version = semver.coerce(powershellVersion) + let command = ''; if (devstarDomain === undefined) { // 不传递devstarDomain - if (powershellVersion === undefined) - terminal.sendText(`code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`) - else if (semver.satisfies(powershell_semver_compatible_version, ">=5.1.26100")) { + if (powershellVersion === undefined) { + command = `code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`; + } else if (semver.satisfies(powershell_semver_compatible_version, ">=5.1.26100")) { // win & powershell >= 5.1.26100.0 - terminal.sendText(`code --new-window ; code --% --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`) + command = `code --new-window ; code --% --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`; } else { // win & powershell < 5.1.26100.0 - terminal.sendText(`code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`) + command = `code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`; } } else { - if (powershellVersion === undefined) - terminal.sendText(`code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}&devstar_domain=${devstarDomain}"`) - else if (semver.satisfies(powershell_semver_compatible_version, ">=5.1.26100")) { + if (powershellVersion === undefined) { + command = `code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}&devstar_domain=${devstarDomain}"`; + } else if (semver.satisfies(powershell_semver_compatible_version, ">=5.1.26100")) { // win & powershell >= 5.1.26100.0 - terminal.sendText(`code --new-window ; code --% --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}&devstar_domain=${devstarDomain}"`) + command = `code --new-window ; code --% --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}&devstar_domain=${devstarDomain}"`; } else { // win & powershell < 5.1.26100.0 - terminal.sendText(`code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}&devstar_domain=${devstarDomain}"`) + command = `code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}&devstar_domain=${devstarDomain}"`; } } - } - }) - } else { - await this.firstConnect(host, hostname, username, port) - .then((res) => { - if (res === 'success') { - // only success then open folder - this.openRemoteFolder(host, port, username, path); - } - }) + console.log(`[RemoteContainer] Sending command to terminal: ${command}`); + terminal.sendText(command); + } else { + console.error(`[RemoteContainer] Failed to create or access terminal`); + vscode.window.showErrorMessage('无法创建终端,请检查终端是否可用。'); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '未知错误'; + console.error(`[RemoteContainer] Error in remote environment:`, error); + vscode.window.showErrorMessage(`远程环境操作失败: ${errorMessage}`); + } + } else { + console.log(`[RemoteContainer] Running in local environment, attempting firstConnect`); + try { + await this.firstConnect(host, hostname, username, port) + .then((res) => { + if (res === 'success') { + console.log(`[RemoteContainer] firstConnect succeeded, opening remote folder`); + // only success then open folder + this.openRemoteFolder(host, port, username, path); + } else { + console.error(`[RemoteContainer] firstConnect returned: ${res}`); + vscode.window.showErrorMessage('首次连接容器失败,请检查网络和容器状态。'); + } + }) + .catch(error => { + const errorMessage = error instanceof Error ? error.message : '未知错误'; + console.error(`[RemoteContainer] firstConnect failed:`, error); + vscode.window.showErrorMessage(`首次连接容器时发生错误: ${errorMessage}`); + }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '未知错误'; + console.error(`[RemoteContainer] Error in local environment firstOpenProject:`, error); + vscode.window.showErrorMessage(`打开项目失败: ${errorMessage}`); + } } } @@ -93,48 +126,82 @@ export default class RemoteContainer { */ // connect with key async firstConnect(host: string, hostname: string, username: string, port: number): Promise { - return new Promise(async (resolve) => { + console.log(`[RemoteContainer] firstConnect called with:`, { host, hostname, username, port }); + + return new Promise(async (resolve, reject) => { const ssh = new NodeSSH(); + vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: vscode.l10n.t("Installing vscode-server and devstar extension in container"), cancellable: false }, async (progress) => { try { + console.log(`[RemoteContainer] Checking SSH keys existence`); + // 检查公私钥是否存在,如果不存在,需要创建 if (!this.user.existUserPrivateKey() || !this.user.existUserPublicKey()) { + console.log(`[RemoteContainer] SSH keys not found, creating new keys`); await this.user.createUserSSHKey() + // 上传公钥 + console.log(`[RemoteContainer] Uploading public key`); const devstarAPIHandler = new DevstarAPIHandler() const uploadResult = await devstarAPIHandler.uploadUserPublicKey(this.user) if (uploadResult !== "ok") { throw new Error('Upload public key failed.') } + console.log(`[RemoteContainer] Public key uploaded successfully`); + } else { + console.log(`[RemoteContainer] SSH keys already exist`); } } catch (error) { - console.error("Failed to first connect container: ", error) + const errorMessage = error instanceof Error ? error.message : '未知错误'; + console.error("[RemoteContainer] Failed to first connect container - SSH key setup: ", error) + reject(error); + return; } // 本地环境 try { + console.log(`[RemoteContainer] Attempting SSH connection to ${hostname}:${port} as ${username}`); + // connect with key await ssh.connect({ host: hostname, username: username, port: port, - privateKeyPath: this.user.getUserPrivateKeyPath() + privateKeyPath: this.user.getUserPrivateKeyPath(), + readyTimeout: 30000, // 增加超时时间到30秒 + onKeyboardInteractive: ( + _name: string, + _instructions: string, + _instructionsLang: string, + _prompts: any[], + finish: (responses: string[]) => void + ) => { + console.log(`[RemoteContainer] Keyboard interactive authentication required`); + finish([]); + } }); + + console.log(`[RemoteContainer] SSH connection established successfully`); progress.report({ message: vscode.l10n.t("Connected! Start installation") }); // install vscode-server and devstar extension + console.log(`[RemoteContainer] Getting VSCode commit ID`); const vscodeCommitId = await utils.getVsCodeCommitId() + if ("" != vscodeCommitId) { + console.log(`[RemoteContainer] VSCode commit ID: ${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 + echo "VSCode server already exists, installing extension only" ~/.vscode-server/bin/${vscodeCommitId}/bin/code-server --install-extension mengning.devstar else + echo "Downloading and installing VSCode server" 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} && \\ @@ -143,20 +210,45 @@ 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!')); + + console.log(`[RemoteContainer] Executing installation script`); + const installResult = await ssh.execCommand(installVscodeServerScript); + + if (installResult.code === 0) { + console.log("[RemoteContainer] VSCode server and extension installed successfully"); + console.log("[RemoteContainer] Installation stdout:", installResult.stdout); + if (installResult.stderr) { + console.warn("[RemoteContainer] Installation stderr:", installResult.stderr); + } + + vscode.window.showInformationMessage(vscode.l10n.t('Installation completed!')); + } else { + console.error("[RemoteContainer] Installation failed with code:", installResult.code); + console.error("[RemoteContainer] Installation stderr:", installResult.stderr); + throw new Error(`Installation failed with exit code ${installResult.code}: ${installResult.stderr}`); + } + } else { + throw new Error('Failed to get VSCode commit ID'); } await ssh.dispose(); + console.log(`[RemoteContainer] SSH connection disposed`); // only connect successfully then save the host info + console.log(`[RemoteContainer] Storing project SSH info`); await this.storeProjectSSHInfo(host, hostname, port, username) resolve('success') } catch (error) { - console.error('Failed to install vscode-server and extension: ', error); - await ssh.dispose(); + const errorMessage = error instanceof Error ? error.message : '未知错误'; + console.error('[RemoteContainer] Failed to install vscode-server and extension: ', error); + try { + await ssh.dispose(); + } catch (disposeError) { + const disposeErrorMessage = disposeError instanceof Error ? disposeError.message : '未知错误'; + console.error('[RemoteContainer] Error disposing SSH connection: ', disposeError); + } + reject(error); } }); }); @@ -170,32 +262,49 @@ export default class RemoteContainer { * @param username */ async storeProjectSSHInfo(host: string, hostname: string, port: number, username: string): Promise { + console.log(`[RemoteContainer] storeProjectSSHInfo called with:`, { host, hostname, port, username }); + const sshConfigPath = path.join(os.homedir(), '.ssh', 'config'); + console.log(`[RemoteContainer] SSH config path: ${sshConfigPath}`); + // check if the host and related info exist in local ssh config file before saving - var canAppendSSHConfig = true + let canAppendSSHConfig = true; if (fs.existsSync(sshConfigPath)) { - var reader = rd.createInterface(fs.createReadStream(sshConfigPath)) + console.log(`[RemoteContainer] SSH config file exists, checking for existing host`); + + const reader = rd.createInterface(fs.createReadStream(sshConfigPath)); for await (const line of reader) { if (line.includes(`Host ${host}`)) { // the container ssh info exists - canAppendSSHConfig = false + console.log(`[RemoteContainer] Host ${host} already exists in SSH config`); + canAppendSSHConfig = false; break; } } + } else { + console.log(`[RemoteContainer] SSH config file does not exist, will create it`); } if (canAppendSSHConfig) { // save the host to the local ssh config file const privateKeyPath = this.user.getUserPrivateKeyPath(); + console.log(`[RemoteContainer] Using private key path: ${privateKeyPath}`); + const newSShConfigContent = `\nHost ${host}\n HostName ${hostname}\n Port ${port}\n User ${username}\n PreferredAuthentications publickey\n IdentityFile ${privateKeyPath}\n `; - fs.writeFileSync(sshConfigPath, newSShConfigContent, { encoding: 'utf8', flag: 'a' }); - console.log('Host registered in local ssh config'); + + try { + fs.writeFileSync(sshConfigPath, newSShConfigContent, { encoding: 'utf8', flag: 'a' }); + console.log('[RemoteContainer] Host registered in local ssh config'); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '未知错误'; + console.error('[RemoteContainer] Failed to write SSH config:', error); + throw error; + } } } - /** * local env * 仅支持已经成功连接,并在ssh config file中存储ssh信息的项目连接。 @@ -203,14 +312,26 @@ export default class RemoteContainer { * @host 表示project name */ openRemoteFolder(host: string, port: number, username: string, path: string): void { - 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`); + console.log(`[RemoteContainer] openRemoteFolder called with:`, { host, port, username, path }); + + try { + let terminal = vscode.window.activeTerminal || vscode.window.createTerminal(`Ext Terminal`); + terminal.show(true); + + const command = `code --remote ssh-remote+${username}@${host}:${port} ${path} --reuse-window`; + console.log(`[RemoteContainer] Sending command to terminal: ${command}`); + + // 在原窗口打开 + terminal.sendText(command); + console.log(`[RemoteContainer] Command sent successfully`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '未知错误'; + console.error(`[RemoteContainer] Error in openRemoteFolder:`, error); + vscode.window.showErrorMessage(`打开远程文件夹失败: ${errorMessage}`); + } } } - /** * 打开项目(无须插件登录) * @param hostname 表示ip @@ -219,8 +340,19 @@ export default class RemoteContainer { * @param path */ export async function openProjectWithoutLogging(hostname: string, port: number, username: string, path: string): Promise { + console.log(`[RemoteContainer] openProjectWithoutLogging called with:`, { hostname, port, username, path }); + const command = `code --remote ssh-remote+${username}@${hostname}:${port} ${path} --reuse-window` - let terminal = vscode.window.activeTerminal || vscode.window.createTerminal(`Ext Terminal`); - terminal.show(true); - terminal.sendText(command); + console.log(`[RemoteContainer] Command: ${command}`); + + try { + let terminal = vscode.window.activeTerminal || vscode.window.createTerminal(`Ext Terminal`); + terminal.show(true); + terminal.sendText(command); + console.log(`[RemoteContainer] openProjectWithoutLogging completed successfully`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '未知错误'; + console.error(`[RemoteContainer] Error in openProjectWithoutLogging:`, error); + vscode.window.showErrorMessage(`无登录打开项目失败: ${errorMessage}`); + } } \ No newline at end of file