Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions extensions/open-remote-ssh/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@
"description": "**Experimental:** The name of the server binary, use this **only if** you are using a client without a corresponding server release",
"scope": "application",
"default": ""
},
"remoteSSH.serverInstallPath": {
"type": "object",
"markdownDescription": "A map of remote hostname to the remote directory where the Positron server data will be installed.\n\nFor the keys, you may use the `*` character to match 0 or more characters in the hostname or host's alias. The first matching host will be used.\n\nFor the values, you may include tildes or environment variables of the form `$ENV_VAR`, which will be resolved on the remote host upon connecting. Relative paths will be parsed relative to `$HOME`.\n\nFor any host not matched here, the server is installed in `$HOME/.positron-server`.",
"scope": "application",
"additionalProperties": {
"type": "string"
},
"default": {}
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion extensions/open-remote-ssh/src/authResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export class RemoteSSHResolver implements vscode.RemoteAuthorityResolver, vscode
envVariables['SSH_AUTH_SOCK'] = null;
}

const installResult = await installCodeServer(this.sshConnection, serverDownloadUrlTemplate, defaultExtensions, Object.keys(envVariables), remotePlatformMap[sshDest.hostname], remoteServerListenOnSocket, this.logger);
const installResult = await installCodeServer(this.sshConnection, serverDownloadUrlTemplate, defaultExtensions, Object.keys(envVariables), remotePlatformMap[sshDest.hostname], remoteServerListenOnSocket, this.logger, sshHostName, sshDest.hostname);

for (const key of Object.keys(envVariables)) {
if (installResult[key] !== undefined) {
Expand Down
65 changes: 62 additions & 3 deletions extensions/open-remote-ssh/src/serverSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// The code in extensions/open-remote-ssh has been adapted from https://github.com/jeanp413/open-remote-ssh,
// which is licensed under the MIT license.

import * as vscode from 'vscode';
import * as crypto from 'crypto';
import Log from './common/logger';
import { getVSCodeServerConfig } from './serverConfig';
Expand Down Expand Up @@ -45,7 +46,48 @@ export class ServerInstallError extends Error {

const DEFAULT_DOWNLOAD_URL_TEMPLATE = 'https://cdn.posit.co/positron/dailies/reh/${arch-long}/positron-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<ServerInstallResult> {
/**
* Converts a wildcard pattern to a regular expression.
* Supports patterns like:
* - "*" matches everything
* - "posit.*" matches strings starting with "posit."
* - "posit.co" matches exactly "posit.co"
*/
function wildcardToRegex(pattern: string): RegExp {
if (pattern === '*') {
return /.*/;
}

// Escape special regex characters except *
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');

// Replace * with .*
const regexPattern = '^' + escaped.replace(/\*/g, '.*') + '$';

return new RegExp(regexPattern);
}

/**
* Finds the first matching path from the serverInstallPath setting.
* Iterates through the setting keys in order and returns the path for the first pattern
* that matches either the hostname or hostAlias.
*
* @param hostname - The hostname to match
* @param hostAlias - The host alias to match
* @returns The matching path or undefined if no match is found
*/
function findFirstMatchingPath(hostname: string, hostAlias: string): string | undefined {
const settings = vscode.workspace.getConfiguration('remoteSSH').get<{ [key: string]: string }>('serverInstallPath', {});
for (const [pattern, path] of Object.entries(settings)) {
const regex = wildcardToRegex(pattern);
if (regex.test(hostname) || regex.test(hostAlias)) {
return path;
}
}
return undefined;
}

export async function installCodeServer(conn: SSHConnection, serverDownloadUrlTemplate: string | undefined, extensionIds: string[], envVariables: string[], platform: string | undefined, useSocketPath: boolean, logger: Log, hostname: string, hostAlias: string): Promise<ServerInstallResult> {
let shell = 'powershell';

// detect platform and shell for windows
Expand Down Expand Up @@ -78,6 +120,14 @@ export async function installCodeServer(conn: SSHConnection, serverDownloadUrlTe
const scriptId = crypto.randomBytes(12).toString('hex');

const vscodeServerConfig = await getVSCodeServerConfig();

// Check the remoteSSH.serverInstallPath setting
let serverDataFolderName = vscodeServerConfig.serverDataFolderName;
const matchedPath = findFirstMatchingPath(hostname, hostAlias);
if (matchedPath) {
serverDataFolderName = matchedPath;
}

const installOptions: ServerInstallOptions = {
id: scriptId,
version: vscodeServerConfig.version,
Expand All @@ -88,7 +138,7 @@ export async function installCodeServer(conn: SSHConnection, serverDownloadUrlTe
envVariables,
useSocketPath,
serverApplicationName: vscodeServerConfig.serverApplicationName,
serverDataFolderName: vscodeServerConfig.serverDataFolderName,
serverDataFolderName,
serverDownloadUrlTemplate: serverDownloadUrlTemplate || vscodeServerConfig.serverDownloadUrlTemplate || DEFAULT_DOWNLOAD_URL_TEMPLATE,
};

Expand All @@ -98,6 +148,7 @@ export async function installCodeServer(conn: SSHConnection, serverDownloadUrlTe

logger.trace('Server install command:', installServerScript);

// TODO upon supporting windows hosts: respect the remoteSSH.serverInstallPath setting here
const installDir = `$HOME\\${vscodeServerConfig.serverDataFolderName}\\install`;
const installScript = `${installDir}\\${vscodeServerConfig.commit}.ps1`;
const endRegex = new RegExp(`${scriptId}: end`);
Expand Down Expand Up @@ -226,7 +277,14 @@ DISTRO_VSCODIUM_RELEASE="${release ?? ''}"
SERVER_APP_NAME="${serverApplicationName}"
SERVER_INITIAL_EXTENSIONS="${extensions}"
SERVER_LISTEN_FLAG="${useSocketPath ? `--socket-path="$TMP_DIR/vscode-server-sock-${crypto.randomUUID()}"` : '--port=0'}"
SERVER_DATA_DIR="$HOME/${serverDataFolderName}"
SERVER_DATA_DIR="${serverDataFolderName}"

# If SERVER_DATA_DIR is relative, make it relative to $HOME
if [[ "$SERVER_DATA_DIR" != /* ]] && [[ "$SERVER_DATA_DIR" != ~* ]]; then
SERVER_DATA_DIR="$HOME/$SERVER_DATA_DIR"
fi
echo "Using server data dir: $SERVER_DATA_DIR"

SERVER_DIR="$SERVER_DATA_DIR/bin/$DISTRO_COMMIT"
SERVER_SCRIPT="$SERVER_DIR/bin/$SERVER_APP_NAME"
SERVER_LOGFILE="$SERVER_DATA_DIR/.$DISTRO_COMMIT.log"
Expand Down Expand Up @@ -467,6 +525,7 @@ $DISTRO_VSCODIUM_RELEASE="${release ?? ''}"
$SERVER_APP_NAME="${serverApplicationName}"
$SERVER_INITIAL_EXTENSIONS="${extensions}"
$SERVER_LISTEN_FLAG="${useSocketPath ? `--socket-path="$TMP_DIR/vscode-server-sock-${crypto.randomUUID()}"` : '--port=0'}"
# TODO upon supporting windows hosts: respect the remoteSSH.serverInstallPath setting here
$SERVER_DATA_DIR="$(Resolve-Path ~)\\${serverDataFolderName}"
$SERVER_DIR="$SERVER_DATA_DIR\\bin\\$DISTRO_COMMIT"
$SERVER_SCRIPT="$SERVER_DIR\\bin\\$SERVER_APP_NAME.cmd"
Expand Down