Skip to content

Commit

Permalink
feat: add support for Browser Terminal
Browse files Browse the repository at this point in the history
Signed-off-by: hunnywar <[email protected]>
  • Loading branch information
hunnywar committed Dec 30, 2024
1 parent 9494180 commit f9ec6d3
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 4 deletions.
1 change: 1 addition & 0 deletions cmd/daytona/config/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func GetIdeList() []Ide {
{"browser", "VS Code - Browser"},
{"cursor", "Cursor"},
{"ssh", "Terminal SSH"},
{"browser-tty", "Browser Terminal"},
{"jupyter", "Jupyter"},
{"fleet", "Fleet"},
{"zed", "Zed"},
Expand Down
2 changes: 1 addition & 1 deletion docs/daytona_code.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ daytona code [WORKSPACE] [PROJECT] [flags]
### Options

```
-i, --ide string Specify the IDE (vscode, browser, cursor, ssh, jupyter, fleet, zed, clion, goland, intellij, phpstorm, pycharm, rider, rubymine, webstorm)
-i, --ide string Specify the IDE (vscode, browser, cursor, ssh, browser-tty, jupyter, fleet, zed, clion, goland, intellij, phpstorm, pycharm, rider, rubymine, webstorm)
-y, --yes Automatically confirm any prompts
```

Expand Down
2 changes: 1 addition & 1 deletion docs/daytona_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ daytona create [REPOSITORY_URL | PROJECT_CONFIG_NAME]... [flags]
--devcontainer-path string Automatically assign the devcontainer builder with the path passed as the flag value
--env stringArray Specify environment variables (e.g. --env 'KEY1=VALUE1' --env 'KEY2=VALUE2' ...')
--git-provider-config string Specify the Git provider configuration ID or alias
-i, --ide string Specify the IDE (vscode, browser, cursor, ssh, jupyter, fleet, zed, clion, goland, intellij, phpstorm, pycharm, rider, rubymine, webstorm)
-i, --ide string Specify the IDE (vscode, browser, cursor, ssh, browser-tty, jupyter, fleet, zed, clion, goland, intellij, phpstorm, pycharm, rider, rubymine, webstorm)
--manual Manually enter the Git repository
--multi-project Workspace with multiple projects/repos
--name string Specify the workspace name
Expand Down
2 changes: 1 addition & 1 deletion hack/docs/daytona_code.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ options:
- name: ide
shorthand: i
usage: |
Specify the IDE (vscode, browser, cursor, ssh, jupyter, fleet, zed, clion, goland, intellij, phpstorm, pycharm, rider, rubymine, webstorm)
Specify the IDE (vscode, browser, cursor, ssh, browser-tty, jupyter, fleet, zed, clion, goland, intellij, phpstorm, pycharm, rider, rubymine, webstorm)
- name: "yes"
shorthand: "y"
default_value: "false"
Expand Down
2 changes: 1 addition & 1 deletion hack/docs/daytona_create.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ options:
- name: ide
shorthand: i
usage: |
Specify the IDE (vscode, browser, cursor, ssh, jupyter, fleet, zed, clion, goland, intellij, phpstorm, pycharm, rider, rubymine, webstorm)
Specify the IDE (vscode, browser, cursor, ssh, browser-tty, jupyter, fleet, zed, clion, goland, intellij, phpstorm, pycharm, rider, rubymine, webstorm)
- name: manual
default_value: "false"
usage: Manually enter the Git repository
Expand Down
39 changes: 39 additions & 0 deletions hack/get-ttyd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash

RELEASE_TAG="1.7.7"
RELEASE_ORG="tsl0922"
TTYD_ROOT="$HOME/ttyd"

# Check if ttyd is already installed
if [ -d "$TTYD_ROOT" ]; then
echo "Terminal Server is already installed. Skipping installation."
exit 0
fi

# Ensure the RELEASE_TAG is set
if [ -z "$RELEASE_TAG" ]; then
echo "The RELEASE_TAG build arg must be set." >&2
exit 1
fi

# Determine system architecture
arch=$(uname -m)
if [ "$arch" = "x86_64" ]; then
arch="x86_64"
elif [ "$arch" = "aarch64" ]; then
arch="aarch64"
elif [ "$arch" = "armv7l" ]; then
arch="armhf"
else
echo "Unsupported architecture: $arch"
exit 1
fi

# Download and set up ttyd
wget https://github.com/$RELEASE_ORG/ttyd/releases/download/$RELEASE_TAG/ttyd.$arch -O $HOME/ttyd-$arch
chmod +x $HOME/ttyd-$arch

# Move ttyd to installation directory
mkdir -p $TTYD_ROOT/bin
mv $HOME/ttyd-$arch $TTYD_ROOT/bin/ttyd

2 changes: 2 additions & 0 deletions pkg/cmd/workspace/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ func openIDE(ideId string, activeProfile config.Profile, workspaceId string, pro
return ide.OpenTerminalSsh(activeProfile, workspaceId, projectName, gpgKey, nil)
case "browser":
return ide.OpenBrowserIDE(activeProfile, workspaceId, projectName, projectProviderMetadata, gpgKey)
case "browser-tty":
return ide.OpenBrowserTerminal(activeProfile, workspaceId, projectName, gpgKey)
case "cursor":
return ide.OpenCursor(activeProfile, workspaceId, projectName, projectProviderMetadata, gpgKey)
case "jupyter":
Expand Down
95 changes: 95 additions & 0 deletions pkg/ide/browser-terminal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2024 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0

package ide

import (
"context"
"fmt"
"io"
"os/exec"
"time"

"github.com/daytonaio/daytona/cmd/daytona/config"
"github.com/daytonaio/daytona/internal/cmd/tailscale"
"github.com/daytonaio/daytona/internal/util"
"github.com/daytonaio/daytona/pkg/ports"
"github.com/daytonaio/daytona/pkg/views"

"github.com/pkg/browser"
log "github.com/sirupsen/logrus"
)

const startCommand = "$TTYD_ROOT/bin/ttyd --port 63777 --writable --cwd"

func OpenBrowserTerminal(activeProfile config.Profile, workspaceId string, projectName string, gpgKey string) error {
// Make sure SSH config exists
err := config.EnsureSshConfigEntryAdded(activeProfile.Id, workspaceId, projectName, gpgKey)
if err != nil {
return err
}

views.RenderInfoMessageBold("Downloading Terminal Server...")
projectHostname := config.GetProjectHostname(activeProfile.Id, workspaceId, projectName)

// Download and start ttyd
installServerCommand := exec.Command("ssh", projectHostname, "curl -fsSL https://download.daytona.io/daytona/get-ttyd.sh | sh")
installServerCommand.Stdout = io.Writer(&util.DebugLogWriter{})
installServerCommand.Stderr = io.Writer(&util.DebugLogWriter{})

err = installServerCommand.Run()
if err != nil {
return err
}

projectDir, err := util.GetProjectDir(activeProfile, workspaceId, projectName, gpgKey)
if err != nil {
return err
}

views.RenderInfoMessageBold("Starting Terminal Server...")

go func() {
startServerCommand := exec.CommandContext(context.Background(), "ssh", projectHostname, fmt.Sprintf("%s %s bash", startCommand, projectDir))
startServerCommand.Stdout = io.Writer(&util.DebugLogWriter{})
startServerCommand.Stderr = io.Writer(&util.DebugLogWriter{})

err = startServerCommand.Run()
if err != nil {
log.Fatal(err)
}
}()

// Forward ttyd (Terminal server) port
browserPort, errChan := tailscale.ForwardPort(workspaceId, projectName, 63777, activeProfile)
if browserPort == nil {
if err := <-errChan; err != nil {
return err
}
}

ideURL := fmt.Sprintf("http://localhost:%d", *browserPort)
// Wait for the port to be ready
for {
if ports.IsPortReady(*browserPort) {
break
}
time.Sleep(500 * time.Millisecond)
}

views.RenderInfoMessageBold(fmt.Sprintf("Forwarded %s Terminal port to %s.\nOpening browser...\n", projectName, ideURL))

err = browser.OpenURL(ideURL)
if err != nil {
log.Error("Error opening URL: " + err.Error())
}

for {
err := <-errChan
if err != nil {
// Log only in debug mode
// Connection errors to the forwarded port should not exit the process
log.Debug(err)
}
}
}

0 comments on commit f9ec6d3

Please sign in to comment.