diff --git a/cmd/ghcs/logs.go b/cmd/ghcs/logs.go index 662e20b79..ec9e63a56 100644 --- a/cmd/ghcs/logs.go +++ b/cmd/ghcs/logs.go @@ -57,7 +57,12 @@ func logs(tail bool, codespaceName string) error { return fmt.Errorf("connecting to liveshare: %v", err) } - tunnelPort, connClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, 0) + remoteSSHServerPort, sshUser, err := codespaces.StartSSHServer(ctx, lsclient, log) + if err != nil { + return fmt.Errorf("error getting ssh server details: %v", err) + } + + tunnelPort, connClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, 0, remoteSSHServerPort) if err != nil { return fmt.Errorf("make ssh tunnel: %v", err) } @@ -67,7 +72,7 @@ func logs(tail bool, codespaceName string) error { cmdType = "tail -f" } - dst := fmt.Sprintf("%s@localhost", getSSHUser(codespace)) + dst := fmt.Sprintf("%s@localhost", sshUser) stdout, err := codespaces.RunCommand( ctx, tunnelPort, dst, fmt.Sprintf("%v /workspaces/.codespaces/.persistedshare/creation.log", cmdType), ) diff --git a/cmd/ghcs/ssh.go b/cmd/ghcs/ssh.go index 23f49c1d1..efdfdcdd4 100644 --- a/cmd/ghcs/ssh.go +++ b/cmd/ghcs/ssh.go @@ -58,6 +58,11 @@ func ssh(sshProfile, codespaceName string, sshServerPort int) error { return fmt.Errorf("error connecting to liveshare: %v", err) } + remoteSSHServerPort, sshUser, err := codespaces.StartSSHServer(ctx, lsclient, log) + if err != nil { + return fmt.Errorf("error getting ssh server details: %v", err) + } + terminal, err := liveshare.NewTerminal(lsclient) if err != nil { return fmt.Errorf("error creating liveshare terminal: %v", err) @@ -70,20 +75,20 @@ func ssh(sshProfile, codespaceName string, sshServerPort int) error { return fmt.Errorf("error getting container id: %v", err) } - if err := setupSSH(ctx, log, terminal, containerID, codespace.RepositoryName); err != nil { + if err := setupEnv(ctx, log, terminal, containerID, codespace.RepositoryName, sshUser); err != nil { return fmt.Errorf("error creating ssh server: %v", err) } } log.Print("\n") - tunnelPort, tunnelClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, sshServerPort) + tunnelPort, tunnelClosed, err := codespaces.MakeSSHTunnel(ctx, lsclient, sshServerPort, remoteSSHServerPort) if err != nil { return fmt.Errorf("make ssh tunnel: %v", err) } connectDestination := sshProfile if connectDestination == "" { - connectDestination = fmt.Sprintf("%s@localhost", getSSHUser(codespace)) + connectDestination = fmt.Sprintf("%s@localhost", sshUser) } usingCustomPort := tunnelPort == sshServerPort @@ -135,8 +140,8 @@ func getContainerID(ctx context.Context, logger *output.Logger, terminal *livesh return containerID, nil } -func setupSSH(ctx context.Context, logger *output.Logger, terminal *liveshare.Terminal, containerID, repositoryName string) error { - setupBashProfileCmd := fmt.Sprintf(`echo "cd /workspaces/%v; export $(cat /workspaces/.codespaces/shared/.env | xargs); exec /bin/zsh;" > /home/codespace/.bash_profile`, repositoryName) +func setupEnv(ctx context.Context, logger *output.Logger, terminal *liveshare.Terminal, containerID, repositoryName, containerUser string) error { + setupBashProfileCmd := fmt.Sprintf(`echo "cd /workspaces/%v; export $(cat /workspaces/.codespaces/shared/.env | xargs); exec /bin/zsh;" > /home/%v/.bash_profile`, repositoryName, containerUser) logger.Print(".") compositeCommand := []string{setupBashProfileCmd} @@ -156,10 +161,3 @@ func setupSSH(ctx context.Context, logger *output.Logger, terminal *liveshare.Te return nil } - -func getSSHUser(codespace *api.Codespace) string { - if codespace.RepositoryNWO == "github/github" { - return "root" - } - return "codespace" -} diff --git a/internal/codespaces/ssh.go b/internal/codespaces/ssh.go index 672ba3b7b..16ffed07b 100644 --- a/internal/codespaces/ssh.go +++ b/internal/codespaces/ssh.go @@ -2,6 +2,7 @@ package codespaces import ( "context" + "errors" "fmt" "io" "math/rand" @@ -14,7 +15,7 @@ import ( "github.com/github/go-liveshare" ) -func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, serverPort int) (int, <-chan error, error) { +func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, localSSHPort int, remoteSSHPort int) (int, <-chan error, error) { tunnelClosed := make(chan error) server, err := liveshare.NewServer(lsclient) @@ -24,12 +25,11 @@ func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, serverPort i rand.Seed(time.Now().Unix()) port := rand.Intn(9999-2000) + 2000 // improve this obviously - if serverPort != 0 { - port = serverPort + if localSSHPort != 0 { + port = localSSHPort } - // TODO(josebalius): This port won't always be 2222 - if err := server.StartSharing(ctx, "sshd", 2222); err != nil { + if err := server.StartSharing(ctx, "sshd", remoteSSHPort); err != nil { return 0, nil, fmt.Errorf("sharing sshd port: %v", err) } @@ -45,6 +45,33 @@ func MakeSSHTunnel(ctx context.Context, lsclient *liveshare.Client, serverPort i return port, tunnelClosed, nil } +// StartSSHServer installs (if necessary) and starts the SSH in the codespace. +// It returns the remote port where it is running, the user to log in with, or an error if something failed. +func StartSSHServer(ctx context.Context, client *liveshare.Client, log logger) (serverPort int, user string, err error) { + log.Println("Fetching SSH details...") + + sshServer, err := liveshare.NewSSHServer(client) + if err != nil { + return 0, "", fmt.Errorf("error creating live share: %v", err) + } + + sshServerStartResult, err := sshServer.StartRemoteServer(ctx) + if err != nil { + return 0, "", fmt.Errorf("error starting live share: %v", err) + } + + if !sshServerStartResult.Result { + return 0, "", errors.New(sshServerStartResult.Message) + } + + portInt, err := strconv.Atoi(sshServerStartResult.ServerPort) + if err != nil { + return 0, "", fmt.Errorf("error parsing port: %v", err) + } + + return portInt, sshServerStartResult.User, nil +} + func makeSSHArgs(port int, dst, cmd string) ([]string, []string) { connArgs := []string{"-p", strconv.Itoa(port), "-o", "NoHostAuthenticationForLocalhost=yes"} cmdArgs := append([]string{dst, "-X", "-Y", "-C"}, connArgs...) // X11, X11Trust, Compression diff --git a/internal/codespaces/states.go b/internal/codespaces/states.go index 5c3dcef45..d09c399e4 100644 --- a/internal/codespaces/states.go +++ b/internal/codespaces/states.go @@ -45,7 +45,12 @@ func PollPostCreateStates(ctx context.Context, log logger, apiClient *api.API, u return fmt.Errorf("connect to liveshare: %v", err) } - tunnelPort, connClosed, err := MakeSSHTunnel(ctx, lsclient, 0) + remoteSSHServerPort, sshUser, err := StartSSHServer(ctx, lsclient, log) + if err != nil { + return fmt.Errorf("error getting ssh server details: %v", err) + } + + tunnelPort, connClosed, err := MakeSSHTunnel(ctx, lsclient, 0, remoteSSHServerPort) if err != nil { return fmt.Errorf("make ssh tunnel: %v", err) } @@ -60,7 +65,7 @@ func PollPostCreateStates(ctx context.Context, log logger, apiClient *api.API, u case err := <-connClosed: return fmt.Errorf("connection closed: %v", err) case <-t.C: - states, err := getPostCreateOutput(ctx, tunnelPort, codespace) + states, err := getPostCreateOutput(ctx, tunnelPort, codespace, sshUser) if err != nil { return fmt.Errorf("get post create output: %v", err) } @@ -70,9 +75,9 @@ func PollPostCreateStates(ctx context.Context, log logger, apiClient *api.API, u } } -func getPostCreateOutput(ctx context.Context, tunnelPort int, codespace *api.Codespace) ([]PostCreateState, error) { +func getPostCreateOutput(ctx context.Context, tunnelPort int, codespace *api.Codespace, user string) ([]PostCreateState, error) { stdout, err := RunCommand( - ctx, tunnelPort, sshDestination(codespace), + ctx, tunnelPort, fmt.Sprintf("%s@localhost", user), "cat /workspaces/.codespaces/shared/postCreateOutput.json", ) if err != nil { @@ -94,12 +99,3 @@ func getPostCreateOutput(ctx context.Context, tunnelPort int, codespace *api.Cod return output.Steps, nil } - -// TODO(josebalius): this won't be needed soon -func sshDestination(codespace *api.Codespace) string { - user := "codespace" - if codespace.RepositoryNWO == "github/github" { - user = "root" - } - return user + "@localhost" -}