release v1.2.0

This commit is contained in:
dushixiang
2021-10-31 17:15:35 +08:00
parent 4665ab6f78
commit 6132a05786
173 changed files with 37928 additions and 9349 deletions

View File

@ -0,0 +1,103 @@
package term
import (
"bufio"
"io"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
type NextTerminal struct {
SshClient *ssh.Client
SshSession *ssh.Session
StdinPipe io.WriteCloser
SftpClient *sftp.Client
Recorder *Recorder
StdoutReader *bufio.Reader
}
func NewNextTerminal(ip string, port int, username, password, privateKey, passphrase string, rows, cols int, recording, term string, pipe bool) (*NextTerminal, error) {
sshClient, err := NewSshClient(ip, port, username, password, privateKey, passphrase)
if err != nil {
return nil, err
}
sshSession, err := sshClient.NewSession()
if err != nil {
return nil, err
}
var stdoutReader *bufio.Reader
if pipe {
stdoutPipe, err := sshSession.StdoutPipe()
if err != nil {
return nil, err
}
stdoutReader = bufio.NewReader(stdoutPipe)
}
var stdinPipe io.WriteCloser
if pipe {
stdinPipe, err = sshSession.StdinPipe()
if err != nil {
return nil, err
}
}
var recorder *Recorder
if recording != "" {
recorder, err = NewRecorder(recording, term, rows, cols)
if err != nil {
return nil, err
}
}
terminal := NextTerminal{
SshClient: sshClient,
SshSession: sshSession,
Recorder: recorder,
StdinPipe: stdinPipe,
StdoutReader: stdoutReader,
}
return &terminal, nil
}
func (ret *NextTerminal) Write(p []byte) (int, error) {
return ret.StdinPipe.Write(p)
}
func (ret *NextTerminal) Close() error {
if ret.SshSession != nil {
return ret.SshSession.Close()
}
if ret.SshClient != nil {
return ret.SshClient.Close()
}
if ret.Recorder != nil {
return ret.Close()
}
return nil
}
func (ret *NextTerminal) WindowChange(h int, w int) error {
return ret.SshSession.WindowChange(h, w)
}
func (ret *NextTerminal) RequestPty(term string, h, w int) error {
modes := ssh.TerminalModes{
ssh.ECHO: 1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
return ret.SshSession.RequestPty(term, h, w, modes)
}
func (ret *NextTerminal) Shell() error {
return ret.SshSession.Shell()
}

113
server/term/recorder.go Normal file
View File

@ -0,0 +1,113 @@
package term
import (
"encoding/json"
"os"
"time"
"next-terminal/server/utils"
)
type Env struct {
Shell string `json:"SHELL"`
Term string `json:"TERM"`
}
type Header struct {
Title string `json:"title"`
Version int `json:"version"`
Height int `json:"height"`
Width int `json:"width"`
Env Env `json:"env"`
Timestamp int `json:"Timestamp"`
}
type Recorder struct {
File *os.File
Timestamp int
}
func (recorder *Recorder) Close() {
if recorder.File != nil {
_ = recorder.File.Close()
}
}
func (recorder *Recorder) WriteHeader(header *Header) (err error) {
var p []byte
if p, err = json.Marshal(header); err != nil {
return
}
if _, err := recorder.File.Write(p); err != nil {
return err
}
if _, err := recorder.File.Write([]byte("\n")); err != nil {
return err
}
recorder.Timestamp = header.Timestamp
return
}
func (recorder *Recorder) WriteData(data string) (err error) {
now := int(time.Now().UnixNano())
delta := float64(now-recorder.Timestamp*1000*1000*1000) / 1000 / 1000 / 1000
row := make([]interface{}, 0)
row = append(row, delta)
row = append(row, "o")
row = append(row, data)
var s []byte
if s, err = json.Marshal(row); err != nil {
return
}
if _, err := recorder.File.Write(s); err != nil {
return err
}
if _, err := recorder.File.Write([]byte("\n")); err != nil {
return err
}
return
}
func NewRecorder(recordingPath, term string, h int, w int) (recorder *Recorder, err error) {
recorder = &Recorder{}
parentDirectory := utils.GetParentDirectory(recordingPath)
if utils.FileExists(parentDirectory) {
if err := os.RemoveAll(parentDirectory); err != nil {
return nil, err
}
}
if err = os.MkdirAll(parentDirectory, 0777); err != nil {
return
}
var file *os.File
file, err = os.Create(recordingPath)
if err != nil {
return nil, err
}
recorder.File = file
header := &Header{
Title: "",
Version: 2,
Height: h,
Width: w,
Env: Env{Shell: "/bin/bash", Term: term},
Timestamp: int(time.Now().Unix()),
}
if err := recorder.WriteHeader(header); err != nil {
return nil, err
}
return recorder, nil
}

54
server/term/ssh.go Normal file
View File

@ -0,0 +1,54 @@
package term
import (
"fmt"
"time"
"golang.org/x/crypto/ssh"
)
func NewSshClient(ip string, port int, username, password, privateKey, passphrase string) (*ssh.Client, error) {
var authMethod ssh.AuthMethod
if username == "-" || username == "" {
username = "root"
}
if password == "-" {
password = ""
}
if privateKey == "-" {
privateKey = ""
}
if passphrase == "-" {
passphrase = ""
}
var err error
if privateKey != "" {
var key ssh.Signer
if len(passphrase) > 0 {
key, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase))
if err != nil {
return nil, err
}
} else {
key, err = ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, err
}
}
authMethod = ssh.PublicKeys(key)
} else {
authMethod = ssh.Password(password)
}
config := &ssh.ClientConfig{
Timeout: 3 * time.Second,
User: username,
Auth: []ssh.AuthMethod{authMethod},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
addr := fmt.Sprintf("%s:%d", ip, port)
return ssh.Dial("tcp", addr, config)
}

View File

@ -0,0 +1,175 @@
package main
import (
"fmt"
"io"
"next-terminal/server/log"
"os"
"time"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
)
type SSHTerminal struct {
Session *ssh.Session
exitMsg string
stdout io.Reader
stdin io.Writer
stderr io.Reader
}
func main() {
sshConfig := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{
ssh.Password("root"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "172.16.101.32:22", sshConfig)
if err != nil {
log.Error(err)
}
defer client.Close()
err = New(client)
if err != nil {
fmt.Println(err)
}
}
func (t *SSHTerminal) updateTerminalSize() {
go func() {
// SIGWINCH is sent to the process when the window size of the terminal has
// changed.
sigwinchCh := make(chan os.Signal, 1)
//signal.Notify(sigwinchCh, syscall.SIN)
fd := int(os.Stdin.Fd())
termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil {
fmt.Println(err)
}
for {
select {
// The client updated the size of the local PTY. This change needs to occur
// on the server side PTY as well.
case sigwinch := <-sigwinchCh:
if sigwinch == nil {
return
}
currTermWidth, currTermHeight, err := terminal.GetSize(fd)
// Terminal size has not changed, don't do anything.
if currTermHeight == termHeight && currTermWidth == termWidth {
continue
}
err = t.Session.WindowChange(currTermHeight, currTermWidth)
if err != nil {
fmt.Printf("Unable to send window-change reqest: %s.", err)
continue
}
termWidth, termHeight = currTermWidth, currTermHeight
}
}
}()
}
func (t *SSHTerminal) interactiveSession() error {
defer func() {
if t.exitMsg == "" {
log.Info(os.Stdout, "the connection was closed on the remote side on ", time.Now().Format(time.RFC822))
} else {
log.Info(os.Stdout, t.exitMsg)
}
}()
fd := int(os.Stdin.Fd())
state, err := terminal.MakeRaw(fd)
if err != nil {
return err
}
defer terminal.Restore(fd, state)
termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil {
return err
}
termType := os.Getenv("TERM")
if termType == "" {
termType = "xterm-256color"
}
err = t.Session.RequestPty(termType, termHeight, termWidth, ssh.TerminalModes{})
if err != nil {
return err
}
t.updateTerminalSize()
t.stdin, err = t.Session.StdinPipe()
if err != nil {
return err
}
t.stdout, err = t.Session.StdoutPipe()
if err != nil {
return err
}
t.stderr, err = t.Session.StderrPipe()
go io.Copy(os.Stderr, t.stderr)
go io.Copy(os.Stdout, t.stdout)
go func() {
buf := make([]byte, 128)
for {
n, err := os.Stdin.Read(buf)
if err != nil {
fmt.Println(err)
return
}
if n > 0 {
_, err = t.stdin.Write(buf[:n])
if err != nil {
fmt.Println(err)
t.exitMsg = err.Error()
return
}
}
}
}()
err = t.Session.Shell()
if err != nil {
return err
}
err = t.Session.Wait()
if err != nil {
return err
}
return nil
}
func New(client *ssh.Client) error {
session, err := client.NewSession()
if err != nil {
return err
}
defer session.Close()
s := SSHTerminal{
Session: session,
}
return s.interactiveSession()
}