package ws import ( "encoding/json" "fmt" "log" "os" "os/exec" "sync" "syscall" "unsafe" "github.com/creack/pty" ) type PTYSession struct { ID string ptmx *os.File cmd *exec.Cmd done chan struct{} } type PTYManager struct { sessions map[string]*PTYSession mu sync.Mutex client *Client } func NewPTYManager(c *Client) *PTYManager { return &PTYManager{ sessions: make(map[string]*PTYSession), client: c, } } // StartPTY startet eine neue PTY-Session mit /bin/sh func (pm *PTYManager) StartPTY(sessionID string, cols, rows uint16) error { pm.mu.Lock() defer pm.mu.Unlock() if _, exists := pm.sessions[sessionID]; exists { return fmt.Errorf("PTY-Session %s existiert bereits", sessionID) } cmd := exec.Command("/bin/sh", "-l") cmd.Env = append(os.Environ(), "TERM=xterm-256color", fmt.Sprintf("COLUMNS=%d", cols), fmt.Sprintf("LINES=%d", rows), ) ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: rows, Cols: cols}) if err != nil { return fmt.Errorf("PTY starten fehlgeschlagen: %w", err) } session := &PTYSession{ ID: sessionID, ptmx: ptmx, cmd: cmd, done: make(chan struct{}), } pm.sessions[sessionID] = session // PTY-Ausgabe lesen und an Backend senden go pm.readPTYOutput(session) log.Printf("PTY-Session gestartet: %s (%dx%d)", sessionID, cols, rows) return nil } // StopPTY beendet eine PTY-Session func (pm *PTYManager) StopPTY(sessionID string) { pm.mu.Lock() session, exists := pm.sessions[sessionID] if exists { delete(pm.sessions, sessionID) } pm.mu.Unlock() if !exists { return } close(session.done) session.cmd.Process.Kill() session.ptmx.Close() log.Printf("PTY-Session beendet: %s", sessionID) } // WriteToPTY schreibt Daten in die PTY-Stdin func (pm *PTYManager) WriteToPTY(sessionID string, data []byte) error { pm.mu.Lock() session, exists := pm.sessions[sessionID] pm.mu.Unlock() if !exists { return fmt.Errorf("PTY-Session %s nicht gefunden", sessionID) } _, err := session.ptmx.Write(data) return err } // ResizePTY aendert die PTY-Groesse func (pm *PTYManager) ResizePTY(sessionID string, cols, rows uint16) error { pm.mu.Lock() session, exists := pm.sessions[sessionID] pm.mu.Unlock() if !exists { return fmt.Errorf("PTY-Session %s nicht gefunden", sessionID) } ws := &struct { Rows, Cols, X, Y uint16 }{Rows: rows, Cols: cols} _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, session.ptmx.Fd(), syscall.TIOCSWINSZ, uintptr(unsafe.Pointer(ws)), ) if errno != 0 { return errno } return nil } func (pm *PTYManager) readPTYOutput(session *PTYSession) { buf := make([]byte, 4096) for { select { case <-session.done: return default: } n, err := session.ptmx.Read(buf) if err != nil { // PTY geschlossen — Session beenden und Backend informieren pm.StopPTY(session.ID) // Exit-Nachricht senden msg := map[string]interface{}{ "type": "pty_exit", "session_id": session.ID, } if data, err := json.Marshal(msg); err == nil { pm.client.SendMessage(Message{ Type: "event", Event: "pty_exit", Params: map[string]interface{}{ "session_id": session.ID, }, }) _ = data } return } if n > 0 { // Daten als Binary-Message senden (session_id Prefix) if err := pm.client.messageHandler.SendSessionData(session.ID, buf[:n]); err != nil { log.Printf("PTY-Daten senden fehlgeschlagen: %v", err) return } } } }