main.go 3.71 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package main

import (
	"crypto/md5"
	_ "embed"
	"encoding/base64"
	"encoding/binary"
	"errors"
	"fmt"
	"log"
	"math/rand/v2"
	"os"
	"strings"
	"syscall"

	"github.com/pkg/sftp"
	"github.com/spf13/pflag"
	"golang.org/x/crypto/ssh"
	"golang.org/x/term"
)

//go:embed hytop
var hytop_bin []byte

var (
	flagHost = pflag.String("host", "", "host to run hytop")
	flagPort = pflag.Uint16P("port", "p", 22, "ssh port")
)

func main() {
	pflag.Parse()
	if flagHost != nil {
		// 远程执行
		run_remote()
	} else {
		// 本地执行
		run_local()
	}
}

func run_local() {
	dir := fmt.Sprintf("/tmp/hytop-%s", get_rand_str())
	err := os.Mkdir(dir, 0777)
	if err != nil {
		log.Fatal(err)
	}
	fp := fmt.Sprintf("%s/%s", dir, "hytop")
	os.WriteFile(fp, hytop_bin, 0777)
	syscall.Exec(fp, []string{"hytop"}, os.Environ())
}

func run_remote() {

	home := os.Getenv("HOME")
	pk, err := os.ReadFile(home + "/.ssh/id_rsa")
	if err != nil {
		log.Fatalf("read file ${HOME}/.ssh/id_rsa error")
	}
	signer, err := ssh.ParsePrivateKey(pk)
	if err != nil {
		log.Fatalf("unable to parse private key: %v", err)
	}
	// 1. SSH 客户端配置
	config := &ssh.ClientConfig{
		User: os.Getenv("USER"),
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(signer),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	// 2. 连接服务器
	client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", *flagHost, *flagPort), config)
	if err != nil {
		log.Fatal("连接失败: ", err)
	}
	defer client.Close()

	binpath, err := send_file(client)
	if err != nil {
		log.Fatal("error send file: ", err)
	}

	// 3. 创建会话
	session, err := client.NewSession()
	if err != nil {
		log.Fatal("创建会话失败: ", err)
	}
	defer session.Close()

	// 4. 关键:请求 PTY (伪终端)
	// 这让远程进程认为它运行在一个真实的终端窗口中
	fd := int(os.Stdin.Fd())
	state, err := term.MakeRaw(fd) // 将本地终端设为 Raw 模式以透传按键
	if err != nil {
		log.Fatal(err)
	}
	defer term.Restore(fd, state)

	w, h, _ := term.GetSize(fd)
	if err := session.RequestPty("xterm-256color", h, w, ssh.TerminalModes{
		ssh.ECHO:          1,
		ssh.TTY_OP_ISPEED: 14400,
		ssh.TTY_OP_OSPEED: 14400,
	}); err != nil {
		log.Fatal("请求PTY失败: ", err)
	}

	// 5. 绑定输入输出
	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	session.Stdin = os.Stdin

	// 6. 启动持久命令(如 bash)
	if err := session.Start(fmt.Sprintf(`PATH="${PATH}:/opt/hyhal/bin"; %s`, binpath)); err != nil {
		log.Fatal("启动Shell失败: ", err)
	}

	// 等待命令结束
	session.Wait()

	del_file(client, binpath)
}

func get_rand_str() string {
	i := rand.Uint64()
	b := make([]byte, 8)
	binary.BigEndian.PutUint64(b, i)
	src := md5.Sum(b)
	return strings.Trim(strings.ReplaceAll(base64.StdEncoding.EncodeToString(src[:]), "/", "a"), "=")
}

func send_file(sshCli *ssh.Client) (string, error) {
	sftp_client, err := sftp.NewClient(sshCli)
	if err != nil {
		return "", err
	}
	tmp_path := get_rand_str()
	err = sftp_client.Mkdir(fmt.Sprintf("/tmp/hytop-%s", tmp_path))
	if err != nil {
		sftp_client.Close()
		return "", err
	}
	res := fmt.Sprintf("/tmp/hytop-%s/hytop", tmp_path)
	sftp_file, err := sftp_client.Create(res)
	if err != nil {
		sftp_client.Close()
		return "", err
	}
	s, err := sftp_file.Write(hytop_bin)
	if s != len(hytop_bin) || err != nil {
		sftp_file.Close()
		sftp_client.Close()
		if err != nil {
			return "", err
		} else {
			return "", errors.New("sftp send file size not equal with raw file")
		}
	}
	sftp_file.Chmod(0777)
	sftp_file.Close()
	sftp_client.Close()
	return res, nil
}

func del_file(sshCli *ssh.Client, filePath string) error {
	sftp_client, err := sftp.NewClient(sshCli)
	if err != nil {
		return err
	}
	defer sftp_client.Close()
	return sftp_client.Remove(filePath)
}