@@ -115,6 +115,7 @@ fun ensureCLI(
115
115
data class Features (
116
116
val disableAutostart : Boolean = false ,
117
117
val reportWorkspaceUsage : Boolean = false ,
118
+ val wildcardSSH : Boolean = false ,
118
119
)
119
120
120
121
/* *
@@ -285,37 +286,57 @@ class CoderCLIManager(
285
286
} else {
286
287
" "
287
288
}
289
+ val sshOpts = """
290
+ ConnectTimeout 0
291
+ StrictHostKeyChecking no
292
+ UserKnownHostsFile /dev/null
293
+ LogLevel ERROR
294
+ SetEnv CODER_SSH_SESSION_TYPE=JetBrains
295
+ """ .trimIndent()
288
296
val blockContent =
297
+ if (feats.wildcardSSH) {
298
+ startBlock + System .lineSeparator() +
299
+ """
300
+ Host ${getHostPrefix()} --*
301
+ ProxyCommand ${proxyArgs.joinToString(" " )} --ssh-host-prefix ${getHostPrefix()} -- %h
302
+ """ .trimIndent()
303
+ .plus(" \n " + sshOpts.prependIndent(" " ))
304
+ .plus(extraConfig)
305
+ .plus(" \n\n " )
306
+ .plus(
307
+ """
308
+ Host ${getHostPrefix()} -bg--*
309
+ ProxyCommand ${backgroundProxyArgs.joinToString(" " )} --ssh-host-prefix ${getHostPrefix()} -bg-- %h
310
+ """ .trimIndent()
311
+ .plus(" \n " + sshOpts.prependIndent(" " ))
312
+ .plus(extraConfig),
313
+ ).replace(" \n " , System .lineSeparator()) +
314
+ System .lineSeparator() + endBlock
315
+
316
+ } else {
289
317
workspaceNames.joinToString(
290
318
System .lineSeparator(),
291
319
startBlock + System .lineSeparator(),
292
320
System .lineSeparator() + endBlock,
293
321
transform = {
294
322
"""
295
- Host ${getHostName(deploymentURL, it.first, currentUser, it.second)}
323
+ Host ${getHostName(it.first, currentUser, it.second)}
296
324
ProxyCommand ${proxyArgs.joinToString(" " )} ${getWorkspaceParts(it.first, it.second)}
297
- ConnectTimeout 0
298
- StrictHostKeyChecking no
299
- UserKnownHostsFile /dev/null
300
- LogLevel ERROR
301
- SetEnv CODER_SSH_SESSION_TYPE=JetBrains
302
325
""" .trimIndent()
326
+ .plus(" \n " + sshOpts.prependIndent(" " ))
303
327
.plus(extraConfig)
304
328
.plus(" \n " )
305
329
.plus(
306
330
"""
307
- Host ${getBackgroundHostName(deploymentURL, it.first, currentUser, it.second)}
331
+ Host ${getBackgroundHostName(it.first, currentUser, it.second)}
308
332
ProxyCommand ${backgroundProxyArgs.joinToString(" " )} ${getWorkspaceParts(it.first, it.second)}
309
- ConnectTimeout 0
310
- StrictHostKeyChecking no
311
- UserKnownHostsFile /dev/null
312
- LogLevel ERROR
313
- SetEnv CODER_SSH_SESSION_TYPE=JetBrains
314
333
""" .trimIndent()
334
+ .plus(" \n " + sshOpts.prependIndent(" " ))
315
335
.plus(extraConfig),
316
336
).replace(" \n " , System .lineSeparator())
317
337
},
318
338
)
339
+ }
319
340
320
341
if (contents == null ) {
321
342
logger.info(" No existing SSH config to modify" )
@@ -489,40 +510,53 @@ class CoderCLIManager(
489
510
Features (
490
511
disableAutostart = version >= SemVer (2 , 5 , 0 ),
491
512
reportWorkspaceUsage = version >= SemVer (2 , 13 , 0 ),
513
+ wildcardSSH = version >= SemVer (2 , 19 , 0 ),
492
514
)
493
515
}
494
516
}
495
517
518
+ /*
519
+ * This function returns the ssh-host-prefix used for Host entries.
520
+ */
521
+ fun getHostPrefix (): String =
522
+ " coder-jetbrains-${deploymentURL.safeHost()} "
523
+
524
+ /* *
525
+ * This function returns the ssh host name generated for connecting to the workspace.
526
+ */
527
+ fun getHostName (
528
+ workspace : Workspace ,
529
+ currentUser : User ,
530
+ agent : WorkspaceAgent ,
531
+ ): String =
532
+ if (features.wildcardSSH) {
533
+ " ${getHostPrefix()} --${workspace.ownerName} --${workspace.name} .${agent.name} "
534
+ } else {
535
+ // For a user's own workspace, we use the old syntax without a username for backwards compatibility,
536
+ // since the user might have recent connections that still use the old syntax.
537
+ if (currentUser.username == workspace.ownerName) {
538
+ " coder-jetbrains--${workspace.name} .${agent.name} --${deploymentURL.safeHost()} "
539
+ } else {
540
+ " coder-jetbrains--${workspace.ownerName} --${workspace.name} .${agent.name} --${deploymentURL.safeHost()} "
541
+ }
542
+ }
543
+
544
+ fun getBackgroundHostName (
545
+ workspace : Workspace ,
546
+ currentUser : User ,
547
+ agent : WorkspaceAgent ,
548
+ ): String =
549
+ if (features.wildcardSSH) {
550
+ " ${getHostPrefix()} -bg--${workspace.ownerName} --${workspace.name} .${agent.name} "
551
+ } else {
552
+ getHostName(workspace, currentUser, agent) + " --bg"
553
+ }
554
+
496
555
companion object {
497
556
val logger = Logger .getInstance(CoderCLIManager ::class .java.simpleName)
498
557
499
558
private val tokenRegex = " --token [^ ]+" .toRegex()
500
559
501
- /* *
502
- * This function returns the ssh host name generated for connecting to the workspace.
503
- */
504
- @JvmStatic
505
- fun getHostName (
506
- url : URL ,
507
- workspace : Workspace ,
508
- currentUser : User ,
509
- agent : WorkspaceAgent ,
510
- ): String =
511
- // For a user's own workspace, we use the old syntax without a username for backwards compatibility,
512
- // since the user might have recent connections that still use the old syntax.
513
- if (currentUser.username == workspace.ownerName) {
514
- " coder-jetbrains--${workspace.name} .${agent.name} --${url.safeHost()} "
515
- } else {
516
- " coder-jetbrains--${workspace.ownerName} --${workspace.name} .${agent.name} --${url.safeHost()} "
517
- }
518
-
519
- fun getBackgroundHostName (
520
- url : URL ,
521
- workspace : Workspace ,
522
- currentUser : User ,
523
- agent : WorkspaceAgent ,
524
- ): String = getHostName(url, workspace, currentUser, agent) + " --bg"
525
-
526
560
/* *
527
561
* This function returns the identifier for the workspace to pass to the
528
562
* coder ssh proxy command.
@@ -536,6 +570,18 @@ class CoderCLIManager(
536
570
@JvmStatic
537
571
fun getBackgroundHostName (
538
572
hostname : String ,
539
- ): String = hostname + " --bg"
573
+ ): String {
574
+ val parts = hostname.split(" --" ).toMutableList()
575
+ if (parts.size < 2 ) {
576
+ throw SSHConfigFormatException (" Invalid hostname: $hostname " )
577
+ }
578
+ // non-wildcard case
579
+ if (parts[0 ] == " coder-jetbrains" ) {
580
+ return hostname + " --bg"
581
+ }
582
+ // wildcard case
583
+ parts[0 ] + = " -bg"
584
+ return parts.joinToString(" --" )
585
+ }
540
586
}
541
587
}
0 commit comments