Starts a remote SSH server to allow the user to connect to the codespace via SSH
(ctx context.Context, options StartSSHServerOptions)
| 219 | |
| 220 | // Starts a remote SSH server to allow the user to connect to the codespace via SSH |
| 221 | func (i *invoker) StartSSHServerWithOptions(ctx context.Context, options StartSSHServerOptions) (int, string, error) { |
| 222 | ctx = i.appendMetadata(ctx) |
| 223 | ctx, cancel := context.WithTimeout(ctx, requestTimeout) |
| 224 | defer cancel() |
| 225 | |
| 226 | userPublicKey := "" |
| 227 | if options.UserPublicKeyFile != "" { |
| 228 | publicKeyBytes, err := os.ReadFile(options.UserPublicKeyFile) |
| 229 | if err != nil { |
| 230 | return 0, "", fmt.Errorf("failed to read public key file: %w", err) |
| 231 | } |
| 232 | |
| 233 | userPublicKey = strings.TrimSpace(string(publicKeyBytes)) |
| 234 | } |
| 235 | |
| 236 | response, err := i.sshClient.StartRemoteServerAsync(ctx, &ssh.StartRemoteServerRequest{UserPublicKey: userPublicKey}) |
| 237 | if err != nil { |
| 238 | return 0, "", fmt.Errorf("failed to invoke SSH RPC: %w", err) |
| 239 | } |
| 240 | |
| 241 | if !response.Result { |
| 242 | return 0, "", fmt.Errorf("failed to start SSH server: %s", response.Message) |
| 243 | } |
| 244 | |
| 245 | port, err := strconv.Atoi(response.ServerPort) |
| 246 | if err != nil { |
| 247 | return 0, "", fmt.Errorf("failed to parse SSH server port: %w", err) |
| 248 | } |
| 249 | |
| 250 | if !isUsernameValid(response.User) { |
| 251 | return 0, "", fmt.Errorf("invalid username: %s", response.User) |
| 252 | } |
| 253 | return port, response.User, nil |
| 254 | } |
| 255 | |
| 256 | func listenTCP() (*net.TCPListener, error) { |
| 257 | // We will end up using this same address to connect, so specify the IP also or the connect will fail |
no test coverage detected