Add VS Code Remote SSH integration
This commit is contained in:
@@ -56,6 +56,8 @@ dosh status HOST # show remote tmux sessions and Dosh service status
|
||||
dosh restart HOST # restart dosh-server and show service status
|
||||
dosh doctor HOST # check config and connectivity
|
||||
dosh recover HOST # clear cached attach state and re-check
|
||||
dosh proxy-stdio HOST 127.0.0.1 22
|
||||
dosh vscode HOST [PATH] # configure/open VS Code Remote-SSH through Dosh
|
||||
```
|
||||
|
||||
Examples:
|
||||
@@ -72,11 +74,34 @@ dosh cat server:tmp/file.txt
|
||||
dosh forward server -L 8080:127.0.0.1:80
|
||||
dosh forward server -D 1080
|
||||
dosh forward server -R 2222:127.0.0.1:22
|
||||
dosh proxy-stdio server 127.0.0.1 22
|
||||
dosh vscode server /home/me/project
|
||||
```
|
||||
|
||||
Agent forwarding is opt-in with `-A` and must be enabled on the server.
|
||||
File copy is enabled by `allow_file_transfer = true` on the server.
|
||||
|
||||
## VS Code
|
||||
|
||||
Dosh can carry VS Code Remote-SSH without replacing VS Code's SSH workflow.
|
||||
|
||||
```sh
|
||||
dosh vscode setup server
|
||||
dosh vscode server /home/me/project
|
||||
```
|
||||
|
||||
This writes a managed SSH config entry using:
|
||||
|
||||
```sshconfig
|
||||
ProxyCommand dosh proxy-stdio server %h %p
|
||||
```
|
||||
|
||||
VS Code still uses Remote-SSH and the normal remote VS Code Server. Dosh carries
|
||||
the SSH byte stream over its encrypted reconnecting transport.
|
||||
|
||||
The optional extension in `vscode-extension/` provides the same setup from the
|
||||
Command Palette.
|
||||
|
||||
## Library
|
||||
|
||||
Dosh can also be used as a Rust transport for application protocols that need
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Dosh Remote
|
||||
|
||||
Open VS Code Remote-SSH windows through Dosh.
|
||||
|
||||
The extension writes a managed SSH config entry like:
|
||||
|
||||
```sshconfig
|
||||
Host dosh-palav
|
||||
HostName 127.0.0.1
|
||||
HostKeyAlias palav
|
||||
ProxyCommand dosh proxy-stdio palav %h %p
|
||||
```
|
||||
|
||||
VS Code still uses Remote-SSH, so server install, extensions, terminals, and
|
||||
normal SSH behavior stay intact. Dosh carries the SSH TCP byte stream.
|
||||
@@ -0,0 +1,170 @@
|
||||
const vscode = require('vscode');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
function activate(context) {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('dosh.openRemote', openRemote),
|
||||
vscode.commands.registerCommand('dosh.configureHost', configureHostCommand),
|
||||
vscode.commands.registerCommand('dosh.showSshConfig', showSshConfig)
|
||||
);
|
||||
}
|
||||
|
||||
async function openRemote() {
|
||||
const configured = await configureHost();
|
||||
if (!configured) {
|
||||
return;
|
||||
}
|
||||
const remotePath = await vscode.window.showInputBox({
|
||||
title: 'Remote path',
|
||||
prompt: 'Path to open on the remote host',
|
||||
value: '~'
|
||||
});
|
||||
if (remotePath === undefined) {
|
||||
return;
|
||||
}
|
||||
const suffix = remotePath ? `/${remotePath.replace(/^\/+/, '')}` : '';
|
||||
await vscode.commands.executeCommand(
|
||||
'vscode.openFolder',
|
||||
vscode.Uri.parse(`vscode-remote://ssh-remote+${configured.alias}${suffix}`),
|
||||
{ forceNewWindow: true }
|
||||
);
|
||||
}
|
||||
|
||||
async function configureHostCommand() {
|
||||
const configured = await configureHost();
|
||||
if (configured) {
|
||||
vscode.window.showInformationMessage(`Dosh Remote-SSH host ready: ${configured.alias}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function configureHost() {
|
||||
const host = await vscode.window.showInputBox({
|
||||
title: 'Dosh host',
|
||||
prompt: 'Dosh host alias, e.g. palav',
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (!host) {
|
||||
return undefined;
|
||||
}
|
||||
const user = await vscode.window.showInputBox({
|
||||
title: 'Remote SSH user',
|
||||
prompt: 'Optional. Leave blank to let SSH config decide.',
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
const config = vscode.workspace.getConfiguration('dosh');
|
||||
const alias = `dosh-${safeAlias(host)}`;
|
||||
const block = sshBlock({
|
||||
alias,
|
||||
host,
|
||||
user: user || undefined,
|
||||
executable: config.get('executable') || 'dosh',
|
||||
targetHost: config.get('targetHost') || '127.0.0.1',
|
||||
targetPort: Number(config.get('targetPort') || 22),
|
||||
doshPort: Number(config.get('doshPort') || 0)
|
||||
});
|
||||
const sshDir = path.join(os.homedir(), '.ssh');
|
||||
fs.mkdirSync(sshDir, { recursive: true });
|
||||
const generatedPath = config.get('generatedSshConfig') || path.join(sshDir, 'config.dosh');
|
||||
const mainConfig = path.join(sshDir, 'config');
|
||||
ensureInclude(mainConfig, path.basename(generatedPath));
|
||||
upsertBlock(generatedPath, alias, block);
|
||||
return { alias, generatedPath };
|
||||
}
|
||||
|
||||
function sshBlock(options) {
|
||||
let proxy = shellQuote(options.executable);
|
||||
if (options.doshPort > 0) {
|
||||
proxy += ` --dosh-port ${options.doshPort}`;
|
||||
}
|
||||
proxy += ` proxy-stdio ${shellQuote(options.host)} %h %p`;
|
||||
const lines = [
|
||||
`# BEGIN DOSH ${options.alias}`,
|
||||
`Host ${options.alias}`,
|
||||
` HostName ${options.targetHost}`,
|
||||
` Port ${options.targetPort}`,
|
||||
` HostKeyAlias ${options.host}`,
|
||||
options.user ? ` User ${options.user}` : undefined,
|
||||
' ClearAllForwardings yes',
|
||||
' ServerAliveInterval 15',
|
||||
' ServerAliveCountMax 3',
|
||||
` ProxyCommand ${proxy}`,
|
||||
`# END DOSH ${options.alias}`,
|
||||
''
|
||||
].filter(Boolean);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function ensureInclude(configPath, includeFile) {
|
||||
const raw = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
|
||||
if (raw.split(/\r?\n/).some((line) => line.trim().toLowerCase() === `include ${includeFile}`.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
fs.writeFileSync(configPath, `Include ${includeFile}\n${raw ? `\n${raw}` : ''}`);
|
||||
}
|
||||
|
||||
function upsertBlock(configPath, alias, block) {
|
||||
const raw = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
|
||||
const begin = `# BEGIN DOSH ${alias}`;
|
||||
const end = `# END DOSH ${alias}`;
|
||||
const lines = raw.split(/\r?\n/);
|
||||
const out = [];
|
||||
let skipping = false;
|
||||
let replaced = false;
|
||||
for (const line of lines) {
|
||||
if (line.trim() === begin) {
|
||||
out.push(block.trimEnd());
|
||||
skipping = true;
|
||||
replaced = true;
|
||||
continue;
|
||||
}
|
||||
if (skipping) {
|
||||
if (line.trim() === end) {
|
||||
skipping = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (line.length > 0 || out.length > 0) {
|
||||
out.push(line);
|
||||
}
|
||||
}
|
||||
if (!replaced) {
|
||||
if (out.length > 0 && out[out.length - 1] !== '') {
|
||||
out.push('');
|
||||
}
|
||||
out.push(block.trimEnd());
|
||||
}
|
||||
fs.writeFileSync(configPath, `${out.join('\n').trimEnd()}\n`);
|
||||
}
|
||||
|
||||
async function showSshConfig() {
|
||||
const config = vscode.workspace.getConfiguration('dosh');
|
||||
const sshDir = path.join(os.homedir(), '.ssh');
|
||||
const generatedPath = config.get('generatedSshConfig') || path.join(sshDir, 'config.dosh');
|
||||
if (!fs.existsSync(generatedPath)) {
|
||||
vscode.window.showWarningMessage('No generated Dosh SSH config yet.');
|
||||
return;
|
||||
}
|
||||
const doc = await vscode.workspace.openTextDocument(generatedPath);
|
||||
await vscode.window.showTextDocument(doc);
|
||||
}
|
||||
|
||||
function safeAlias(value) {
|
||||
const alias = value.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '');
|
||||
return alias || 'host';
|
||||
}
|
||||
|
||||
function shellQuote(value) {
|
||||
if (/^[a-zA-Z0-9_./:-]+$/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
return `'${value.replace(/'/g, `'\\''`)}'`;
|
||||
}
|
||||
|
||||
function deactivate() {}
|
||||
|
||||
module.exports = {
|
||||
activate,
|
||||
deactivate
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "dosh-vscode",
|
||||
"displayName": "Dosh Remote",
|
||||
"description": "Open VS Code Remote-SSH windows through Dosh.",
|
||||
"version": "0.1.0",
|
||||
"publisher": "palav",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.90.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"extensionDependencies": [
|
||||
"ms-vscode-remote.remote-ssh"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onCommand:dosh.openRemote",
|
||||
"onCommand:dosh.configureHost",
|
||||
"onCommand:dosh.showSshConfig"
|
||||
],
|
||||
"main": "./extension.js",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "dosh.openRemote",
|
||||
"title": "Dosh: Open Remote Folder"
|
||||
},
|
||||
{
|
||||
"command": "dosh.configureHost",
|
||||
"title": "Dosh: Configure Remote-SSH Host"
|
||||
},
|
||||
{
|
||||
"command": "dosh.showSshConfig",
|
||||
"title": "Dosh: Show Generated SSH Config"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"title": "Dosh Remote",
|
||||
"properties": {
|
||||
"dosh.executable": {
|
||||
"type": "string",
|
||||
"default": "dosh",
|
||||
"description": "Path to the local dosh executable."
|
||||
},
|
||||
"dosh.targetHost": {
|
||||
"type": "string",
|
||||
"default": "127.0.0.1",
|
||||
"description": "Host opened from the Dosh server side for VS Code's SSH connection."
|
||||
},
|
||||
"dosh.targetPort": {
|
||||
"type": "number",
|
||||
"default": 22,
|
||||
"description": "SSH port opened from the Dosh server side."
|
||||
},
|
||||
"dosh.doshPort": {
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"description": "Optional Dosh UDP port override. 0 uses Dosh config."
|
||||
},
|
||||
"dosh.generatedSshConfig": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Path for generated SSH config. Empty means ~/.ssh/config.dosh."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user