CI / verify (push) Canceled after 0s
- 内置 SSH 终端(/terminal):复用已保存的 SSH 密码/私钥与独立 known_hosts 校验 - 终端二次认证、会话审计(/terminal/audits)、录像审计与常用命令 - DDNS 服务商名称与自定义 IP,新增迁移 037/038/039 - CHANGELOG 与 VERSION 更新至 2.0.0
73 lines
2.0 KiB
Go
73 lines
2.0 KiB
Go
package terminal
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
// sshTerminal 把 x/crypto/ssh 的交互式会话包装成 Terminal。
|
|
// 申请了 PTY 之后,远端 shell 的 stdout/stderr 会合并到同一条输出流,无需分别读取。
|
|
type sshTerminal struct {
|
|
client *ssh.Client
|
|
session *ssh.Session
|
|
stdin io.WriteCloser
|
|
stdout io.Reader
|
|
|
|
closeOnce sync.Once
|
|
closeErr error
|
|
}
|
|
|
|
// NewSSHTerminal 在已认证的 SSH 连接上申请 PTY 并启动登录 shell。
|
|
func NewSSHTerminal(client *ssh.Client, cols, rows int) (Terminal, error) {
|
|
session, err := client.NewSession()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("创建 SSH 会话失败: %w", err)
|
|
}
|
|
modes := ssh.TerminalModes{ssh.ECHO: 1, ssh.TTY_OP_ISPEED: 14400, ssh.TTY_OP_OSPEED: 14400}
|
|
if err := session.RequestPty("xterm-256color", rows, cols, modes); err != nil {
|
|
_ = session.Close()
|
|
return nil, fmt.Errorf("申请 PTY 失败: %w", err)
|
|
}
|
|
stdin, err := session.StdinPipe()
|
|
if err != nil {
|
|
_ = session.Close()
|
|
return nil, fmt.Errorf("打开 SSH 输入流失败: %w", err)
|
|
}
|
|
stdout, err := session.StdoutPipe()
|
|
if err != nil {
|
|
_ = session.Close()
|
|
return nil, fmt.Errorf("打开 SSH 输出流失败: %w", err)
|
|
}
|
|
if err := session.Shell(); err != nil {
|
|
_ = session.Close()
|
|
return nil, fmt.Errorf("启动远端 shell 失败: %w", err)
|
|
}
|
|
return &sshTerminal{client: client, session: session, stdin: stdin, stdout: stdout}, nil
|
|
}
|
|
|
|
// Read 读取远端输出。
|
|
func (t *sshTerminal) Read(p []byte) (int, error) { return t.stdout.Read(p) }
|
|
|
|
// Write 把按键写入远端 PTY。
|
|
func (t *sshTerminal) Write(p []byte) (int, error) { return t.stdin.Write(p) }
|
|
|
|
// Resize 通知远端窗口尺寸变化。
|
|
func (t *sshTerminal) Resize(cols, rows int) error {
|
|
if cols <= 0 || rows <= 0 {
|
|
return nil
|
|
}
|
|
return t.session.WindowChange(rows, cols)
|
|
}
|
|
|
|
// Close 关闭 SSH 会话与底层连接;重复调用安全。
|
|
func (t *sshTerminal) Close() error {
|
|
t.closeOnce.Do(func() {
|
|
_ = t.session.Close()
|
|
t.closeErr = t.client.Close()
|
|
})
|
|
return t.closeErr
|
|
}
|