feat: Go SSH interactive shell default, --native flag for system SSH
This commit is contained in:
@@ -4,8 +4,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/term"
|
||||
|
||||
"git.tukangketik.id/swanadiva/hostkeeper/internal/errors"
|
||||
"git.tukangketik.id/swanadiva/hostkeeper/internal/models"
|
||||
cryptossh "golang.org/x/crypto/ssh"
|
||||
@@ -101,6 +106,83 @@ func (c *Client) Execute(_ context.Context, cmd string) (string, error) {
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// Shell opens an interactive shell session
|
||||
func (c *Client) Shell() error {
|
||||
if c.client == nil {
|
||||
return fmt.Errorf("not connected to server")
|
||||
}
|
||||
|
||||
session, err := c.client.NewSession()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create session: %w", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// Get current terminal state
|
||||
fd := int(os.Stdin.Fd())
|
||||
oldState, err := term.MakeRaw(fd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set raw terminal: %w", err)
|
||||
}
|
||||
defer term.Restore(fd, oldState)
|
||||
|
||||
// Set up terminal modes
|
||||
modes := cryptossh.TerminalModes{
|
||||
cryptossh.ECHO: 1,
|
||||
cryptossh.TTY_OP_ISPEED: 14400,
|
||||
cryptossh.TTY_OP_OSPEED: 14400,
|
||||
}
|
||||
|
||||
// Get terminal size
|
||||
width, height, err := term.GetSize(fd)
|
||||
if err != nil {
|
||||
width = 80
|
||||
height = 24
|
||||
}
|
||||
|
||||
// Request PTY
|
||||
if err := session.RequestPty("xterm-256color", height, width, modes); err != nil {
|
||||
return fmt.Errorf("failed to request PTY: %w", err)
|
||||
}
|
||||
|
||||
// Handle window changes
|
||||
sigwinch := make(chan os.Signal, 1)
|
||||
signal.Notify(sigwinch, os.Signal(syscall.SIGWINCH))
|
||||
go func() {
|
||||
for range sigwinch {
|
||||
w, h, err := term.GetSize(fd)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
session.WindowChange(h, w)
|
||||
}
|
||||
}()
|
||||
defer signal.Stop(sigwinch)
|
||||
|
||||
// Link I/O
|
||||
session.Stdin = os.Stdin
|
||||
session.Stdout = os.Stdout
|
||||
session.Stderr = os.Stderr
|
||||
|
||||
// Start shell
|
||||
if err := session.Shell(); err != nil {
|
||||
return fmt.Errorf("failed to start shell: %w", err)
|
||||
}
|
||||
|
||||
// Wait for shell to exit
|
||||
if err := session.Wait(); err != nil {
|
||||
if exitErr, ok := err.(*cryptossh.ExitError); ok {
|
||||
if exitErr.ExitStatus() != 0 {
|
||||
return fmt.Errorf("shell exited with status %d", exitErr.ExitStatus())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("shell session error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the SSH connection
|
||||
func (c *Client) Close() error {
|
||||
if c.client != nil {
|
||||
|
||||
Reference in New Issue
Block a user