|
36 | 36 | command []string |
37 | 37 | endpoint string |
38 | 38 | platform string // container platform (e.g., linux/amd64, linux/arm64) |
| 39 | + |
| 40 | + // lastPrivateKeyPath stores the private key path from the most recent buildCreateOptions call |
| 41 | + lastPrivateKeyPath string |
39 | 42 | ) |
40 | 43 |
|
41 | 44 | // NewStudioCmd creates the studio command |
@@ -212,7 +215,7 @@ Examples: |
212 | 215 | cmd.Flags().StringVarP(&image, "image", "i", "tensorfusion/studio-torch:latest", "Container image") |
213 | 216 | cmd.Flags().StringVarP(&shareLink, "share-link", "s", "", "Share link or share code to remote vGPU worker (recommended for GPU access)") |
214 | 217 | cmd.Flags().StringVar(&serverURL, "server", api.GetDefaultBaseURL(), "Server URL for resolving share links") |
215 | | - cmd.Flags().StringVar(&sshKey, "ssh-key", "", "SSH public key to authorize (auto-detects from ~/.ssh/ if not provided)") |
| 218 | + cmd.Flags().StringVar(&sshKey, "ssh-key", "", "SSH public key to authorize (auto-generates dedicated key pair if not provided)") |
216 | 219 | cmd.Flags().StringArrayVarP(&ports, "port", "p", nil, "Port mappings (host:container)") |
217 | 220 | cmd.Flags().StringArrayVarP(&volumes, "volume", "v", nil, "Volume mounts (host:container[:ro])") |
218 | 221 | cmd.Flags().StringArrayVarP(&envVars, "env", "e", nil, "Environment variables (KEY=VALUE)") |
@@ -284,13 +287,6 @@ func runCreate(cmd *cobra.Command, args []string) error { |
284 | 287 | return err |
285 | 288 | } |
286 | 289 |
|
287 | | - // Inform user about SSH authentication method |
288 | | - if !out.IsJSON() && opts.SSHPublicKey != "" { |
289 | | - styles := tui.DefaultStyles() |
290 | | - out.Printf("%s Using SSH key authentication (password: ggo-studio as fallback)\n", |
291 | | - styles.Info.Render("ℹ")) |
292 | | - } |
293 | | - |
294 | 290 | if !out.IsJSON() { |
295 | 291 | styles := tui.DefaultStyles() |
296 | 292 | out.Printf("%s Creating studio environment '%s'...\n", |
@@ -345,11 +341,12 @@ Original error: %w`, err) |
345 | 341 | } |
346 | 342 | } |
347 | 343 | return out.Render(&createResult{ |
348 | | - env: env, |
349 | | - mgr: mgr, |
350 | | - noSSH: noSSH, |
351 | | - backendName: backendName, |
352 | | - socketPath: socketPath, |
| 344 | + env: env, |
| 345 | + mgr: mgr, |
| 346 | + noSSH: noSSH, |
| 347 | + backendName: backendName, |
| 348 | + socketPath: socketPath, |
| 349 | + privateKeyPath: lastPrivateKeyPath, |
353 | 350 | }) |
354 | 351 | } |
355 | 352 |
|
@@ -426,15 +423,28 @@ func buildCreateOptions(name string, shareInfo *api.SharePublicInfo) (*studio.Cr |
426 | 423 | return nil, err |
427 | 424 | } |
428 | 425 |
|
429 | | - // Auto-detect SSH public key if not provided via --ssh-key flag |
| 426 | + // Get or create dedicated SSH key pair for TF studio containers |
430 | 427 | effectiveSSHKey := sshKey |
| 428 | + privateKeyPath := "" |
431 | 429 | if effectiveSSHKey == "" { |
432 | | - if autoKey := studio.GetUserSSHPublicKey(); autoKey != "" { |
433 | | - effectiveSSHKey = autoKey |
434 | | - klog.V(2).Info("Using user's SSH public key for authentication") |
| 430 | + pubKey, privPath, err := studio.GetOrCreateStudioSSHKey() |
| 431 | + if err != nil { |
| 432 | + klog.Warningf("Failed to get/create studio SSH key: %v", err) |
| 433 | + } else { |
| 434 | + effectiveSSHKey = pubKey |
| 435 | + privateKeyPath = privPath |
| 436 | + klog.V(2).Infof("Using TF studio SSH key: %s", privPath) |
435 | 437 | } |
436 | 438 | } |
437 | 439 |
|
| 440 | + // Store for use in createResult |
| 441 | + lastPrivateKeyPath = privateKeyPath |
| 442 | + |
| 443 | + // Ensure SSH key is available (required for container access) |
| 444 | + if effectiveSSHKey == "" { |
| 445 | + return nil, fmt.Errorf("SSH public key is required for container access") |
| 446 | + } |
| 447 | + |
438 | 448 | // Set GPU connection info from share link |
439 | 449 | gpuWorkerURL := "" |
440 | 450 | hardwareVendor := "" |
@@ -535,11 +545,12 @@ func parseEnvVars(envVars []string) (map[string]string, error) { |
535 | 545 |
|
536 | 546 | // createResult implements Renderable for create command output |
537 | 547 | type createResult struct { |
538 | | - env *studio.Environment |
539 | | - mgr *studio.Manager |
540 | | - noSSH bool |
541 | | - backendName string |
542 | | - socketPath string |
| 548 | + env *studio.Environment |
| 549 | + mgr *studio.Manager |
| 550 | + noSSH bool |
| 551 | + backendName string |
| 552 | + socketPath string |
| 553 | + privateKeyPath string |
543 | 554 | } |
544 | 555 |
|
545 | 556 | func (r *createResult) RenderJSON() any { |
@@ -583,6 +594,10 @@ func (r *createResult) RenderTUI(out *tui.Output) { |
583 | 594 | Add("Port", fmt.Sprintf("%d", env.SSHPort)). |
584 | 595 | Add("User", env.SSHUser) |
585 | 596 |
|
| 597 | + if r.privateKeyPath != "" { |
| 598 | + sshStatus = sshStatus.Add("Private Key", r.privateKeyPath) |
| 599 | + } |
| 600 | + |
586 | 601 | out.Println(sshStatus.String()) |
587 | 602 |
|
588 | 603 | out.Println() |
|
0 commit comments