Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
22 changes: 22 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,28 @@
"type": "boolean",
"default": false
},
"coder.disableAutostart": {
"markdownDescription": "Disable starting the workspace automatically when connecting via SSH.",
"type": "string",
"enum": [
"auto",
"always",
"never"
],
"markdownEnumDescriptions": [
"Disables autostart on macOS only (recommended to avoid sleep/wake issues)",
"Disables autostart on all platforms",
"Keeps autostart enabled on all platforms"
],
"default": "auto"
},
"coder.sshFlags": {
"markdownDescription": "Additional flags to pass to the `coder ssh` command when establishing SSH connections. Enter each flag as a separate array item; values are passed verbatim and in order. See the [CLI ssh reference](https://coder.com/docs/reference/cli/ssh) for available flags.\n\nNote that the following flags are ignored because they are managed internally by the extension: `--network-info-dir`, `--log-dir`, `--ssh-host-prefix`. To manage the `--disable-autostart` flag, use the `coder.disableAutostart` setting.",
"type": "array",
"items": {
"type": "string"
}
},
"coder.globalFlags": {
"markdownDescription": "Global flags to pass to every Coder CLI invocation. Enter each flag as a separate array item; values are passed verbatim and in order. Do **not** include the `coder` command itself. See the [CLI reference](https://coder.com/docs/reference/cli) for available global flags.\n\nNote that for `--header-command`, precedence is: `#coder.headerCommand#` setting, then `CODER_HEADER_COMMAND` environment variable, then the value specified here. The `--global-config` flag is explicitly ignored.",
"type": "array",
Expand Down
2 changes: 1 addition & 1 deletion src/api/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
import { spawn } from "node:child_process";
import * as vscode from "vscode";

import { getGlobalFlags } from "../cliConfig";
import { type FeatureSet } from "../featureSet";
import { getGlobalFlags } from "../globalFlags";
import { escapeCommandArg } from "../util";
import { type UnidirectionalStream } from "../websocket/eventStreamConnection";

Expand Down
47 changes: 47 additions & 0 deletions src/cliConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { type WorkspaceConfiguration } from "vscode";

import { getHeaderArgs } from "./headers";
import { escapeCommandArg } from "./util";

/**
* Returns global configuration flags for Coder CLI commands.
* Always includes the `--global-config` argument with the specified config directory.
*/
export function getGlobalFlags(
configs: WorkspaceConfiguration,
configDir: string,
): string[] {
// Last takes precedence/overrides previous ones
return [
...(configs.get<string[]>("coder.globalFlags") || []),
"--global-config",
escapeCommandArg(configDir),
...getHeaderArgs(configs),
];
}

type DisableAutostartSetting = "auto" | "always" | "never";

/**
* Determines whether autostart should be disabled based on the setting and platform.
* - "always": disable on all platforms
* - "never": never disable
* - "auto": disable only on macOS (due to sleep/wake issues)
*/
export function shouldDisableAutostart(
configs: WorkspaceConfiguration,
platform: NodeJS.Platform,
): boolean {
const setting = configs.get<DisableAutostartSetting>(
"coder.disableAutostart",
"auto",
);
return setting === "always" || (setting === "auto" && platform === "darwin");
}

/**
* Returns SSH flags for the `coder ssh` command from user configuration.
*/
export function getSshFlags(configs: WorkspaceConfiguration): string[] {
return configs.get<string[]>("coder.sshFlags") || [];
}
2 changes: 1 addition & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import * as vscode from "vscode";
import { createWorkspaceIdentifier, extractAgents } from "./api/api-helper";
import { CoderApi } from "./api/coderApi";
import { needToken } from "./api/utils";
import { getGlobalFlags } from "./cliConfig";
import { type CliManager } from "./core/cliManager";
import { type ServiceContainer } from "./core/container";
import { type ContextManager } from "./core/contextManager";
import { type MementoManager } from "./core/mementoManager";
import { type PathResolver } from "./core/pathResolver";
import { type SecretsManager } from "./core/secretsManager";
import { CertificateError } from "./error";
import { getGlobalFlags } from "./globalFlags";
import { type Logger } from "./logging/logger";
import { maybeAskAgent, maybeAskUrl } from "./promptUtils";
import { escapeCommandArg, toRemoteAuthority, toSafeHost } from "./util";
Expand Down
20 changes: 0 additions & 20 deletions src/globalFlags.ts

This file was deleted.

108 changes: 85 additions & 23 deletions src/remote/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ import {
import { extractAgents } from "../api/api-helper";
import { CoderApi } from "../api/coderApi";
import { needToken } from "../api/utils";
import {
getGlobalFlags,
getSshFlags,
shouldDisableAutostart,
} from "../cliConfig";
import { type Commands } from "../commands";
import { type CliManager } from "../core/cliManager";
import * as cliUtils from "../core/cliUtils";
import { type ServiceContainer } from "../core/container";
import { type ContextManager } from "../core/contextManager";
import { type PathResolver } from "../core/pathResolver";
import { featureSetForVersion, type FeatureSet } from "../featureSet";
import { getGlobalFlags } from "../globalFlags";
import { Inbox } from "../inbox";
import { type Logger } from "../logging/logger";
import {
Expand Down Expand Up @@ -571,16 +575,85 @@ export class Remote {
}

/**
* Formats the --log-dir argument for the ProxyCommand after making sure it
* Builds the ProxyCommand for SSH connections to Coder workspaces.
* Uses `coder ssh` for modern deployments with wildcard support,
* or falls back to `coder vscodessh` for older deployments.
*/
private async buildProxyCommand(
binaryPath: string,
label: string,
hostPrefix: string,
logDir: string,
useWildcardSSH: boolean,
): Promise<string> {
const vscodeConfig = vscode.workspace.getConfiguration();

const escapedBinaryPath = escapeCommandArg(binaryPath);
const globalConfig = getGlobalFlags(
vscodeConfig,
this.pathResolver.getGlobalConfigDir(label),
);
const logArgs = await this.getLogArgs(logDir);

if (useWildcardSSH) {
// User SSH flags are included first; internally-managed flags
// are appended last so they take precedence.
const userSSHFlags = getSshFlags(vscodeConfig);
const disableAutostart = shouldDisableAutostart(
vscodeConfig,
process.platform,
)
? ["--disable-autostart"]
: [];
const internalFlags = [
"--stdio",
"--usage-app=vscode",
"--network-info-dir",
escapeCommandArg(this.pathResolver.getNetworkInfoPath()),
...logArgs,
...disableAutostart,
"--ssh-host-prefix",
hostPrefix,
"%h",
];

const allFlags = [...userSSHFlags, ...internalFlags];
return `${escapedBinaryPath} ${globalConfig.join(" ")} ssh ${allFlags.join(" ")}`;
} else {
const networkInfoDir = escapeCommandArg(
this.pathResolver.getNetworkInfoPath(),
);
const sessionTokenFile = escapeCommandArg(
this.pathResolver.getSessionTokenPath(label),
);
const urlFile = escapeCommandArg(this.pathResolver.getUrlPath(label));

const sshFlags = [
"--network-info-dir",
networkInfoDir,
...logArgs,
"--session-token-file",
sessionTokenFile,
"--url-file",
urlFile,
"%h",
];

return `${escapedBinaryPath} ${globalConfig.join(" ")} vscodessh ${sshFlags.join(" ")}`;
}
}

/**
* Returns the --log-dir argument for the ProxyCommand after making sure it
* has been created.
*/
private async formatLogArg(logDir: string): Promise<string> {
private async getLogArgs(logDir: string): Promise<string[]> {
if (!logDir) {
return "";
return [];
}
await fs.mkdir(logDir, { recursive: true });
this.logger.info("SSH proxy diagnostics are being written to", logDir);
return ` --log-dir ${escapeCommandArg(logDir)} -v`;
return ["--log-dir", escapeCommandArg(logDir), "-v"];
}

// updateSSHConfig updates the SSH configuration with a wildcard that handles
Expand Down Expand Up @@ -666,15 +739,13 @@ export class Remote {
? `${AuthorityPrefix}.${label}--`
: `${AuthorityPrefix}--`;

const globalConfigs = this.globalConfigs(label);

const proxyCommand = featureSet.wildcardSSH
? `${escapeCommandArg(binaryPath)}${globalConfigs} ssh --stdio --usage-app=vscode --disable-autostart --network-info-dir ${escapeCommandArg(this.pathResolver.getNetworkInfoPath())}${await this.formatLogArg(logDir)} --ssh-host-prefix ${hostPrefix} %h`
: `${escapeCommandArg(binaryPath)}${globalConfigs} vscodessh --network-info-dir ${escapeCommandArg(
this.pathResolver.getNetworkInfoPath(),
)}${await this.formatLogArg(logDir)} --session-token-file ${escapeCommandArg(this.pathResolver.getSessionTokenPath(label))} --url-file ${escapeCommandArg(
this.pathResolver.getUrlPath(label),
)} %h`;
const proxyCommand = await this.buildProxyCommand(
binaryPath,
label,
hostPrefix,
logDir,
featureSet.wildcardSSH,
);

const sshValues: SSHValues = {
Host: hostPrefix + `*`,
Expand Down Expand Up @@ -727,15 +798,6 @@ export class Remote {
return sshConfig.getRaw();
}

private globalConfigs(label: string): string {
const vscodeConfig = vscode.workspace.getConfiguration();
const args = getGlobalFlags(
vscodeConfig,
this.pathResolver.getGlobalConfigDir(label),
);
return ` ${args.join(" ")}`;
}

private watchLogDirSetting(
currentLogDir: string,
featureSet: FeatureSet,
Expand Down
Loading