Files
devstar_plugin/src/remote-container.ts

237 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as vscode from 'vscode';
import * as rd from 'readline'
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;
constructor(user: User) {
this.user = user
}
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
this.openRemoteFolder(host, port, username, path);
}
})
}
// connect with key
async firstConnect(hostname: string, username: string, port: number, context: vscode.ExtensionContext): Promise<string> {
return new Promise(async (resolve) => {
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 {
// 无论local/remote均需检查公私钥是否存在如果不存在需要创建因为有可能在remote上创建了但是local未创建
if (!this.user.existUserPrivateKey() || !this.user.existUserPublicKey()) {
await this.user.createUserSSHKey()
// 上传公钥
const devstarAPIHandler = new DevstarAPIHandler()
const uploadResult = await devstarAPIHandler.uploadUserPublicKey(this.user)
if (uploadResult !== "ok") {
throw new Error('Upload public key failed.')
}
}
} catch (error) {
console.error("Failed to first connect container: ", error)
}
if (vscode.env.remoteName) {
// 远程环境
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
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!'));
}
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") });
// 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!'));
}
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();
}
}
});
});
}
async storeHostInfo(hostname: string, port: number, username: string): Promise<void> {
const sshConfigPath = path.join(os.homedir(), '.ssh', 'config');
// check if the host and related info exist in local ssh config file before saving
var canAppendSSHConfig = true
if (fs.existsSync(sshConfigPath)) {
var reader = rd.createInterface(fs.createReadStream(sshConfigPath))
for await (const line of reader) {
// host format: hostname-port
if (line.includes(`Host ${hostname}-${port}`)) {
// the container ssh info exists
canAppendSSHConfig = false
break;
}
}
}
if (canAppendSSHConfig) {
// save the host to the local ssh config file
const host = `${hostname}-${port}` // host: hostname-port
const privateKeyPath = this.user.getUserPrivateKeyPath();
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');
}
}
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`);
}
}
}
export async function openProjectWithoutLogging(host: string, port: number, username: string, path: string): Promise<void> {
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);
}