mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
broader support for SetDoNotAllowColumnSet + tests
This commit is contained in:
parent
eee508c36d
commit
08bdc5ecb4
8 changed files with 82 additions and 25 deletions
|
|
@ -288,6 +288,9 @@ func startServer(cfg *config.Config, manager *session.Manager) error {
|
||||||
return fmt.Errorf("invalid port: %w", err)
|
return fmt.Errorf("invalid port: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the resize flag on the manager
|
||||||
|
manager.SetDoNotAllowColumnSet(doNotAllowColumnSet)
|
||||||
|
|
||||||
// Create and configure server
|
// Create and configure server
|
||||||
server := api.NewServer(manager, staticPath, serverPassword, portInt)
|
server := api.NewServer(manager, staticPath, serverPassword, portInt)
|
||||||
server.SetNoSpawn(noSpawn)
|
server.SetNoSpawn(noSpawn)
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ func TestEscapeParser_Flush(t *testing.T) {
|
||||||
parser := NewEscapeParser()
|
parser := NewEscapeParser()
|
||||||
|
|
||||||
// Process data with incomplete sequence
|
// Process data with incomplete sequence
|
||||||
input := []byte("text\x1b[31") // incomplete CSI sequence
|
input := []byte("text\x1b[31") // incomplete CSI sequence
|
||||||
processed, _ := parser.ProcessData(input)
|
processed, _ := parser.ProcessData(input)
|
||||||
|
|
||||||
if !bytes.Equal(processed, []byte("text")) {
|
if !bytes.Equal(processed, []byte("text")) {
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
controlPath string
|
controlPath string
|
||||||
runningSessions map[string]*Session
|
runningSessions map[string]*Session
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
|
doNotAllowColumnSet bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(controlPath string) *Manager {
|
func NewManager(controlPath string) *Manager {
|
||||||
|
|
@ -26,12 +27,26 @@ func NewManager(controlPath string) *Manager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDoNotAllowColumnSet sets the flag to disable terminal resizing for all sessions
|
||||||
|
func (m *Manager) SetDoNotAllowColumnSet(value bool) {
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
m.doNotAllowColumnSet = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDoNotAllowColumnSet returns the current value of the resize disable flag
|
||||||
|
func (m *Manager) GetDoNotAllowColumnSet() bool {
|
||||||
|
m.mutex.RLock()
|
||||||
|
defer m.mutex.RUnlock()
|
||||||
|
return m.doNotAllowColumnSet
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) CreateSession(config Config) (*Session, error) {
|
func (m *Manager) CreateSession(config Config) (*Session, error) {
|
||||||
if err := os.MkdirAll(m.controlPath, 0755); err != nil {
|
if err := os.MkdirAll(m.controlPath, 0755); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create control directory: %w", err)
|
return nil, fmt.Errorf("failed to create control directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := newSession(m.controlPath, config)
|
session, err := newSession(m.controlPath, config, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -56,7 +71,7 @@ func (m *Manager) CreateSessionWithID(id string, config Config) (*Session, error
|
||||||
return nil, fmt.Errorf("failed to create control directory: %w", err)
|
return nil, fmt.Errorf("failed to create control directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := newSessionWithID(m.controlPath, id, config)
|
session, err := newSessionWithID(m.controlPath, id, config, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +101,7 @@ func (m *Manager) GetSession(id string) (*Session, error) {
|
||||||
m.mutex.RUnlock()
|
m.mutex.RUnlock()
|
||||||
|
|
||||||
// Fall back to loading from disk (for sessions that might have been started before this manager instance)
|
// Fall back to loading from disk (for sessions that might have been started before this manager instance)
|
||||||
return loadSession(m.controlPath, id)
|
return loadSession(m.controlPath, id, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) FindSession(nameOrID string) (*Session, error) {
|
func (m *Manager) FindSession(nameOrID string) (*Session, error) {
|
||||||
|
|
@ -119,7 +134,7 @@ func (m *Manager) ListSessions() ([]*Info, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := loadSession(m.controlPath, entry.Name())
|
session, err := loadSession(m.controlPath, entry.Name(), m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log the error when we can't load a session
|
// Log the error when we can't load a session
|
||||||
if os.Getenv("VIBETUNNEL_DEBUG") != "" {
|
if os.Getenv("VIBETUNNEL_DEBUG") != "" {
|
||||||
|
|
|
||||||
|
|
@ -257,6 +257,12 @@ func (p *PTY) Run() error {
|
||||||
// Handle SIGWINCH in a separate goroutine
|
// Handle SIGWINCH in a separate goroutine
|
||||||
go func() {
|
go func() {
|
||||||
for range winchCh {
|
for range winchCh {
|
||||||
|
// Check if resizing is disabled globally
|
||||||
|
if p.session.manager != nil && p.session.manager.GetDoNotAllowColumnSet() {
|
||||||
|
debugLog("[DEBUG] PTY.Run: Received SIGWINCH but resizing is disabled by server configuration")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Get current terminal size if we're attached to a terminal
|
// Get current terminal size if we're attached to a terminal
|
||||||
if term.IsTerminal(int(os.Stdin.Fd())) {
|
if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||||
width, height, err := term.GetSize(int(os.Stdin.Fd()))
|
width, height, err := term.GetSize(int(os.Stdin.Fd()))
|
||||||
|
|
@ -434,6 +440,11 @@ func (p *PTY) Attach() error {
|
||||||
signal.Notify(ch, syscall.SIGWINCH)
|
signal.Notify(ch, syscall.SIGWINCH)
|
||||||
go func() {
|
go func() {
|
||||||
for range ch {
|
for range ch {
|
||||||
|
// Check if resizing is disabled globally
|
||||||
|
if p.session.manager != nil && p.session.manager.GetDoNotAllowColumnSet() {
|
||||||
|
debugLog("[DEBUG] PTY.Attach: Received SIGWINCH but resizing is disabled by server configuration")
|
||||||
|
continue
|
||||||
|
}
|
||||||
if err := p.updateSize(); err != nil {
|
if err := p.updateSize(); err != nil {
|
||||||
log.Printf("[ERROR] PTY.Attach: Failed to update size: %v", err)
|
log.Printf("[ERROR] PTY.Attach: Failed to update size: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -441,8 +452,13 @@ func (p *PTY) Attach() error {
|
||||||
}()
|
}()
|
||||||
defer signal.Stop(ch)
|
defer signal.Stop(ch)
|
||||||
|
|
||||||
if err := p.updateSize(); err != nil {
|
// Only update size initially if resizing is allowed
|
||||||
log.Printf("[ERROR] PTY.Attach: Failed to update initial size: %v", err)
|
if p.session.manager == nil || !p.session.manager.GetDoNotAllowColumnSet() {
|
||||||
|
if err := p.updateSize(); err != nil {
|
||||||
|
log.Printf("[ERROR] PTY.Attach: Failed to update initial size: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debugLog("[DEBUG] PTY.Attach: Skipping initial resize - resizing is disabled by server configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
errCh := make(chan error, 2)
|
errCh := make(chan error, 2)
|
||||||
|
|
|
||||||
|
|
@ -67,14 +67,15 @@ type Session struct {
|
||||||
stdinPipe *os.File
|
stdinPipe *os.File
|
||||||
stdinMutex sync.Mutex
|
stdinMutex sync.Mutex
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
manager *Manager // Reference to manager for accessing global settings
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSession(controlPath string, config Config) (*Session, error) {
|
func newSession(controlPath string, config Config, manager *Manager) (*Session, error) {
|
||||||
id := uuid.New().String()
|
id := uuid.New().String()
|
||||||
return newSessionWithID(controlPath, id, config)
|
return newSessionWithID(controlPath, id, config, manager)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSessionWithID(controlPath string, id string, config Config) (*Session, error) {
|
func newSessionWithID(controlPath string, id string, config Config, manager *Manager) (*Session, error) {
|
||||||
sessionPath := filepath.Join(controlPath, id)
|
sessionPath := filepath.Join(controlPath, id)
|
||||||
|
|
||||||
// Only log in debug mode
|
// Only log in debug mode
|
||||||
|
|
@ -159,10 +160,11 @@ func newSessionWithID(controlPath string, id string, config Config) (*Session, e
|
||||||
ID: id,
|
ID: id,
|
||||||
controlPath: controlPath,
|
controlPath: controlPath,
|
||||||
info: info,
|
info: info,
|
||||||
|
manager: manager,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadSession(controlPath, id string) (*Session, error) {
|
func loadSession(controlPath, id string, manager *Manager) (*Session, error) {
|
||||||
sessionPath := filepath.Join(controlPath, id)
|
sessionPath := filepath.Join(controlPath, id)
|
||||||
info, err := LoadInfo(sessionPath)
|
info, err := LoadInfo(sessionPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -173,6 +175,7 @@ func loadSession(controlPath, id string) (*Session, error) {
|
||||||
ID: id,
|
ID: id,
|
||||||
controlPath: controlPath,
|
controlPath: controlPath,
|
||||||
info: info,
|
info: info,
|
||||||
|
manager: manager,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that essential session files exist
|
// Validate that essential session files exist
|
||||||
|
|
@ -437,8 +440,9 @@ func (s *Session) cleanup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) Resize(width, height int) error {
|
func (s *Session) Resize(width, height int) error {
|
||||||
if s.pty == nil {
|
// Check if resizing is disabled globally
|
||||||
return NewSessionError("session not started", ErrSessionNotRunning, s.ID)
|
if s.manager != nil && s.manager.GetDoNotAllowColumnSet() {
|
||||||
|
return NewSessionError("terminal resizing is disabled by server configuration", ErrInvalidInput, s.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if session is still alive
|
// Check if session is still alive
|
||||||
|
|
@ -464,7 +468,20 @@ func (s *Session) Resize(width, height int) error {
|
||||||
log.Printf("[ERROR] Failed to save session info after resize: %v", err)
|
log.Printf("[ERROR] Failed to save session info after resize: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize the PTY
|
// If this is a spawned session, send resize command through control FIFO
|
||||||
|
if s.IsSpawned() {
|
||||||
|
cmd := &ControlCommand{
|
||||||
|
Cmd: "resize",
|
||||||
|
Cols: width,
|
||||||
|
Rows: height,
|
||||||
|
}
|
||||||
|
return SendControlCommand(s.Path(), cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-spawned sessions, resize the PTY directly
|
||||||
|
if s.pty == nil {
|
||||||
|
return NewSessionError("session not started", ErrSessionNotRunning, s.ID)
|
||||||
|
}
|
||||||
return s.pty.Resize(width, height)
|
return s.pty.Resize(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ func TestNewSession(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
controlPath := filepath.Join(tmpDir, "control")
|
controlPath := filepath.Join(tmpDir, "control")
|
||||||
|
|
||||||
|
// Create a test manager
|
||||||
|
manager := NewManager(controlPath)
|
||||||
|
|
||||||
config := &Config{
|
config := &Config{
|
||||||
Name: "test-session",
|
Name: "test-session",
|
||||||
Cmdline: []string{"/bin/sh", "-c", "echo test"},
|
Cmdline: []string{"/bin/sh", "-c", "echo test"},
|
||||||
|
|
@ -22,7 +25,7 @@ func TestNewSession(t *testing.T) {
|
||||||
Height: 24,
|
Height: 24,
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := newSession(controlPath, *config)
|
session, err := newSession(controlPath, *config, manager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("newSession() error = %v", err)
|
t.Fatalf("newSession() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -60,10 +63,13 @@ func TestNewSession_Defaults(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
controlPath := filepath.Join(tmpDir, "control")
|
controlPath := filepath.Join(tmpDir, "control")
|
||||||
|
|
||||||
|
// Create a test manager
|
||||||
|
manager := NewManager(controlPath)
|
||||||
|
|
||||||
// Minimal config
|
// Minimal config
|
||||||
config := &Config{}
|
config := &Config{}
|
||||||
|
|
||||||
session, err := newSession(controlPath, *config)
|
session, err := newSession(controlPath, *config, manager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("newSession() error = %v", err)
|
t.Fatalf("newSession() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue