Skip to content

Commit

Permalink
feat: agent support for windows (#1741)
Browse files Browse the repository at this point in the history
Signed-off-by: ANS-UXI <[email protected]>
Signed-off-by: Rutik7066 <[email protected]>
Co-authored-by: ANS-UXI <[email protected]>
  • Loading branch information
Rutik7066 and unsuman authored Feb 7, 2025
1 parent 5bd48fd commit dfb50e8
Show file tree
Hide file tree
Showing 14 changed files with 288 additions and 106 deletions.
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ DEV_WORKSPACES
./daytona
.DS_Store

tmp
tmp


*.exe

main
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ replace github.com/samber/lo => github.com/samber/lo v1.39.0
require (
code.gitea.io/sdk/gitea v0.17.1
gitee.com/openeuler/go-gitee v0.0.0-20220530104019-3af895bc380c
github.com/UserExistsError/conpty v0.1.4
github.com/antihax/optional v1.0.0
github.com/aws/aws-sdk-go-v2/config v1.27.26
github.com/aws/aws-sdk-go-v2/service/iam v1.34.3
Expand Down Expand Up @@ -332,7 +333,7 @@ require (
github.com/stretchr/objx v0.5.2 // indirect
github.com/xanzy/go-gitlab v0.97.0
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0
golang.org/x/sys v0.29.0
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,8 @@ github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/UserExistsError/conpty v0.1.4 h1:+3FhJhiqhyEJa+K5qaK3/w6w+sN3Nh9O9VbJyBS02to=
github.com/UserExistsError/conpty v0.1.4/go.mod h1:PDglKIkX3O/2xVk0MV9a6bCWxRmPVfxqZoTG/5sSd9I=
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
Expand Down Expand Up @@ -1876,8 +1878,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand Down
32 changes: 26 additions & 6 deletions pkg/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import (
"net/url"
"os"
"os/exec"
"syscall"
"runtime"
"time"

"github.com/daytonaio/daytona/cmd/daytona/config"
"github.com/daytonaio/daytona/internal/util"
apiclient_util "github.com/daytonaio/daytona/internal/util/apiclient"
"github.com/daytonaio/daytona/internal/util/apiclient/conversion"
agent_config "github.com/daytonaio/daytona/pkg/agent/config"
"github.com/daytonaio/daytona/pkg/agent/toolbox/fs"
"github.com/daytonaio/daytona/pkg/apiclient"
"github.com/daytonaio/daytona/pkg/gitprovider"
"github.com/daytonaio/daytona/pkg/models"
Expand Down Expand Up @@ -113,12 +114,31 @@ func (a *Agent) startWorkspaceMode() error {
log.Info("Repository already exists. Skipping clone...")
} else {
if stat, err := os.Stat(a.Config.WorkspaceDir); err == nil {
ownerUid := stat.Sys().(*syscall.Stat_t).Uid
ownerUid, err := fs.GetFileUid(stat)
if err != nil {
log.Error(err)
}
if ownerUid != uint32(os.Getuid()) {
chownCmd := exec.Command("sudo", "chown", "-R", fmt.Sprintf("%s:%s", a.Workspace.User, a.Workspace.User), a.Config.WorkspaceDir)
err = chownCmd.Run()
if err != nil {
log.Error(err)
user := a.Workspace.User
directory := a.Config.WorkspaceDir
if runtime.GOOS == "windows" {
takeownCmd := exec.Command("takeown", "/F", directory, "/R", "/D", "Y")
err := takeownCmd.Run()
if err != nil {
log.Errorf("Failed to take ownership: %v", err)
}

icaclsCmd := exec.Command("icacls", directory, "/grant", fmt.Sprintf("%s:F", user), "/T")
err = icaclsCmd.Run()
if err != nil {
log.Errorf("Failed to grant permissions: %v", err)
}
} else {
chownCmd := exec.Command("sudo", "chown", "-R", fmt.Sprintf("%s:%s", user, user), directory)
err = chownCmd.Run()
if err != nil {
log.Error(err)
}
}
}
}
Expand Down
52 changes: 7 additions & 45 deletions pkg/agent/ssh/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@ import (
"io"
"os"
"os/exec"
"syscall"
"unsafe"

"github.com/creack/pty"
"github.com/daytonaio/daytona/pkg/agent/ssh/config"
"github.com/daytonaio/daytona/pkg/common"
"github.com/gliderlabs/ssh"
"github.com/pkg/sftp"
"golang.org/x/sys/unix"

log "github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -104,16 +100,17 @@ func (s *Server) handlePty(session ssh.Session, ptyReq ssh.Pty, winCh <-chan ssh
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
cmd.Env = append(cmd.Env, os.Environ()...)
cmd.Env = append(cmd.Env, fmt.Sprintf("SHELL=%s", shell))
f, err := pty.Start(cmd)

f, err := Start(cmd)
if err != nil {
log.Errorf("Unable to start command: %v", err)
log.Errorf("Unable to start PTY: %v", err)
return
}
defer f.Close()

go func() {
for win := range winCh {
syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), uintptr(syscall.TIOCSWINSZ),
uintptr(unsafe.Pointer(&struct{ h, w, x, y uint16 }{uint16(win.Height), uint16(win.Width), 0, 0})))
SetPtySize(f, win)
}
}()
go func() {
Expand All @@ -128,7 +125,7 @@ func (s *Server) handleNonPty(session ssh.Session) {
args = append([]string{"-c"}, session.RawCommand())
}

cmd := exec.Command("/bin/sh", args...)
cmd := exec.Command("sh", args...)

cmd.Env = append(cmd.Env, os.Environ()...)

Expand Down Expand Up @@ -177,7 +174,7 @@ func (s *Server) handleNonPty(session ssh.Session) {
}()
go func() {
for sig := range sigs {
signal := s.osSignalFrom(sig)
signal := OsSignalFrom(sig)
err := cmd.Process.Signal(signal)
if err != nil {
log.Warnf("Unable to send signal to process: %v", err)
Expand All @@ -198,41 +195,6 @@ func (s *Server) handleNonPty(session ssh.Session) {
}
}

func (s *Server) osSignalFrom(sig ssh.Signal) os.Signal {
switch sig {
case ssh.SIGABRT:
return unix.SIGABRT
case ssh.SIGALRM:
return unix.SIGALRM
case ssh.SIGFPE:
return unix.SIGFPE
case ssh.SIGHUP:
return unix.SIGHUP
case ssh.SIGILL:
return unix.SIGILL
case ssh.SIGINT:
return unix.SIGINT
case ssh.SIGKILL:
return unix.SIGKILL
case ssh.SIGPIPE:
return unix.SIGPIPE
case ssh.SIGQUIT:
return unix.SIGQUIT
case ssh.SIGSEGV:
return unix.SIGSEGV
case ssh.SIGTERM:
return unix.SIGTERM
case ssh.SIGUSR1:
return unix.SIGUSR1
case ssh.SIGUSR2:
return unix.SIGUSR2

// Unhandled, use sane fallback.
default:
return unix.SIGKILL
}
}

func (s *Server) sftpHandler(session ssh.Session) {
debugStream := io.Discard
serverOptions := []sftp.ServerOption{
Expand Down
75 changes: 75 additions & 0 deletions pkg/agent/ssh/server_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//go:build !windows

// Copyright 2024 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0

package ssh

import (
"fmt"
"os"
"os/exec"
"syscall"
"unsafe"

"golang.org/x/sys/unix"

"github.com/creack/pty"
"github.com/gliderlabs/ssh"
log "github.com/sirupsen/logrus"
)

func Start(cmd interface{}) (*os.File, error) {
if command, ok := cmd.(*exec.Cmd); ok {
f, err := pty.Start(command)
if err != nil {
return nil, fmt.Errorf("Unable to start PTY: %v", err)
}
return f, nil
}
return nil, fmt.Errorf("Unable to start PTY")
}

func SetPtySize(f interface{}, win ssh.Window) {
if file, ok := f.(*os.File); ok {
syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), uintptr(syscall.TIOCSWINSZ),
uintptr(unsafe.Pointer(&struct{ h, w, x, y uint16 }{uint16(win.Height), uint16(win.Width), 0, 0})))
} else {
log.Errorf("Unable to resize PTY")
}
}

func OsSignalFrom(sig ssh.Signal) os.Signal {
switch sig {
case ssh.SIGABRT:
return unix.SIGABRT
case ssh.SIGALRM:
return unix.SIGALRM
case ssh.SIGFPE:
return unix.SIGFPE
case ssh.SIGHUP:
return unix.SIGHUP
case ssh.SIGILL:
return unix.SIGILL
case ssh.SIGINT:
return unix.SIGINT
case ssh.SIGKILL:
return unix.SIGKILL
case ssh.SIGPIPE:
return unix.SIGPIPE
case ssh.SIGQUIT:
return unix.SIGQUIT
case ssh.SIGSEGV:
return unix.SIGSEGV
case ssh.SIGTERM:
return unix.SIGTERM
case ssh.SIGUSR1:
return unix.SIGUSR1
case ssh.SIGUSR2:
return unix.SIGUSR2

// Unhandled, use sane fallback.
default:
return unix.SIGKILL
}
}
54 changes: 54 additions & 0 deletions pkg/agent/ssh/server_win.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//go:build windows

// Copyright 2024 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0

package ssh

import (
"fmt"
"os"
"os/exec"
"syscall"

"github.com/UserExistsError/conpty"
"github.com/gliderlabs/ssh"
log "github.com/sirupsen/logrus"
)

var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
setConsoleWindowInfo = kernel32.NewProc("SetConsoleWindowInfo")
)

func Start(cmd interface{}) (*conpty.ConPty, error) {
if shell, ok := cmd.(*exec.Cmd); ok {
f, err := conpty.Start(`c:\windows\system32\cmd.exe`, conpty.ConPtyEnv(shell.Env), conpty.ConPtyWorkDir(shell.Dir))
if err != nil {
return nil, fmt.Errorf("Unable to start ConPTY: %v", err)
}
return f, nil
}
return nil, fmt.Errorf("Unable to start ConPTY")
}

func SetPtySize(f interface{}, win ssh.Window) {
if cpty, ok := f.(*conpty.ConPty); ok {
cpty.Resize(win.Width, win.Height)
} else {
log.Errorf("Unable to resize ConPTY")
}
}

func OsSignalFrom(sig ssh.Signal) os.Signal {
switch sig {
case ssh.SIGINT:
return os.Interrupt
case ssh.SIGTERM:
return os.Kill
case ssh.SIGKILL:
return os.Kill
default:
return os.Kill
}
}
22 changes: 0 additions & 22 deletions pkg/agent/toolbox/fs/get_file_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ package fs

import (
"errors"
"fmt"
"os"
"strconv"
"syscall"

"github.com/gin-gonic/gin"
)
Expand All @@ -32,22 +29,3 @@ func GetFileInfo(c *gin.Context) {

c.JSON(200, info)
}

func getFileInfo(path string) (FileInfo, error) {
info, err := os.Stat(path)
if err != nil {
return FileInfo{}, err
}

stat := info.Sys().(*syscall.Stat_t)
return FileInfo{
Name: info.Name(),
Size: info.Size(),
Mode: info.Mode().String(),
ModTime: info.ModTime().String(),
IsDir: info.IsDir(),
Owner: strconv.FormatUint(uint64(stat.Uid), 10),
Group: strconv.FormatUint(uint64(stat.Gid), 10),
Permissions: fmt.Sprintf("%04o", info.Mode().Perm()),
}, nil
}
Loading

0 comments on commit dfb50e8

Please sign in to comment.