init
This commit is contained in:
parent
a3896541b7
commit
bdac69e881
12
go.mod
Normal file
12
go.mod
Normal file
@ -0,0 +1,12 @@
|
||||
module remote_exec_ssh_command
|
||||
|
||||
go 1.23.5
|
||||
|
||||
require golang.org/x/term v0.28.0
|
||||
|
||||
require (
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/pkg/sftp v1.13.7 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
)
|
58
go.sum
Normal file
58
go.sum
Normal file
@ -0,0 +1,58 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
|
||||
github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
35
local.go
Normal file
35
local.go
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
// localExecSSHCommand 执行本地命令并返回命令输出和错误信息
|
||||
func localExecSSHCommand(command string, timeout time.Duration) (string, error) {
|
||||
// 使用上下文设置超时时间
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
// 创建命令实例
|
||||
cmd := exec.CommandContext(ctx, "sh", "-c", command)
|
||||
|
||||
// 捕获命令输出
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
// 执行命令
|
||||
err := cmd.Run()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return "", fmt.Errorf("command timeout exceeded: %v", command)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("command failed: %v, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return stdout.String(), nil
|
||||
}
|
74
main.go
Normal file
74
main.go
Normal file
@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// 定义命令行参数
|
||||
var (
|
||||
h *string
|
||||
u *string
|
||||
p *int
|
||||
e *string
|
||||
k *string
|
||||
c *string
|
||||
l *string
|
||||
r *string
|
||||
)
|
||||
|
||||
// SSH 配置结构体
|
||||
type SSHConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
PrivateKey string
|
||||
Passphrase string
|
||||
}
|
||||
|
||||
func argv() {
|
||||
|
||||
h = flag.String("h", "127.0.0.1", "SSH server hostname or IP")
|
||||
p = flag.Int("p", 22, "SSH server port")
|
||||
u = flag.String("u", "root", "SSH username")
|
||||
e = flag.String("e", "", "SSH password")
|
||||
k = flag.String("k", "", "Path to SSH private key file")
|
||||
c = flag.String("c", ":", "Command to execute on the remote server")
|
||||
l = flag.String("l", "", "Local file path to upload")
|
||||
r = flag.String("r", "", "Remote file path to upload")
|
||||
|
||||
// 解析命令行参数
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
argv()
|
||||
var err error
|
||||
var output string
|
||||
|
||||
if *r == "" || *l == "" {
|
||||
fmt.Println("No file specified, no upload")
|
||||
} else {
|
||||
_sftp()
|
||||
}
|
||||
|
||||
output, err = _ssh()
|
||||
if err != nil {
|
||||
log.Fatalf("Error: %v", err)
|
||||
}
|
||||
fmt.Printf("Command output:\n%s", output)
|
||||
|
||||
/*
|
||||
// 本地执行命令
|
||||
command := "ls -l"
|
||||
output, err = localExecSSHCommand(command, 360*time.Second)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("Command output:\n%s", output)
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
BIN
remote_exec_ssh_command
Normal file
BIN
remote_exec_ssh_command
Normal file
Binary file not shown.
201
sftp.go
Normal file
201
sftp.go
Normal file
@ -0,0 +1,201 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// 创建 SFTP 客户端
|
||||
func connectSFTP(config SSHConfig) (*sftp.Client, *ssh.Client, error) {
|
||||
var authMethods []ssh.AuthMethod
|
||||
|
||||
// 使用密码认证
|
||||
if config.Password != "" {
|
||||
authMethods = append(authMethods, ssh.Password(config.Password))
|
||||
}
|
||||
|
||||
// 使用私钥认证
|
||||
if config.PrivateKey != "" {
|
||||
key, err := os.ReadFile(config.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read private key file: %w", err)
|
||||
}
|
||||
|
||||
var signer ssh.Signer
|
||||
if config.Passphrase != "" {
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase(key, []byte(config.Passphrase))
|
||||
} else {
|
||||
signer, err = ssh.ParsePrivateKey(key)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse private key: %w", err)
|
||||
}
|
||||
authMethods = append(authMethods, ssh.PublicKeys(signer))
|
||||
}
|
||||
|
||||
if len(authMethods) == 0 {
|
||||
return nil, nil, fmt.Errorf("no authentication method provided")
|
||||
}
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: config.User,
|
||||
Auth: authMethods,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
// 建立 SSH 连接
|
||||
address := fmt.Sprintf("%s:%d", config.Host, config.Port)
|
||||
sshClient, err := ssh.Dial("tcp", address, sshConfig)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to connect to SSH: %w", err)
|
||||
}
|
||||
|
||||
// 创建 SFTP 客户端
|
||||
sftpClient, err := sftp.NewClient(sshClient)
|
||||
if err != nil {
|
||||
sshClient.Close()
|
||||
return nil, nil, fmt.Errorf("failed to create SFTP client: %w", err)
|
||||
}
|
||||
|
||||
return sftpClient, sshClient, nil
|
||||
}
|
||||
|
||||
// downloadFile 通过 SFTP 协议从远程服务器下载文件到本地
|
||||
func downloadFile(sftpClient *sftp.Client, remotePath, localPath string) error {
|
||||
// 打开远程文件
|
||||
remoteFile, err := sftpClient.Open(remotePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open remote file: %w", err)
|
||||
}
|
||||
defer remoteFile.Close()
|
||||
|
||||
// 获取远程文件信息以确定文件大小
|
||||
fileInfo, err := remoteFile.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get remote file info: %w", err)
|
||||
}
|
||||
|
||||
// 创建本地文件(创建或覆盖)
|
||||
localFile, err := os.Create(localPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create local file: %w", err)
|
||||
}
|
||||
defer localFile.Close()
|
||||
|
||||
// 分块复制文件内容
|
||||
const bufferSize = 32 * 1024 // 32KB
|
||||
buf := make([]byte, bufferSize)
|
||||
var totalBytes int64 = 0
|
||||
for {
|
||||
n, err := remoteFile.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return fmt.Errorf("failed to read from remote file: %w", err)
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
if _, err := localFile.Write(buf[:n]); err != nil {
|
||||
return fmt.Errorf("failed to write to local file: %w", err)
|
||||
}
|
||||
totalBytes += int64(n)
|
||||
fmt.Printf("\rDownloaded %d / %d bytes", totalBytes, fileInfo.Size())
|
||||
}
|
||||
fmt.Println() // 换行
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
func uploadFile(sftpClient *sftp.Client, localPath, remotePath string) error {
|
||||
// 打开本地文件
|
||||
localFile, err := os.Open(localPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open local file: %w", err)
|
||||
}
|
||||
defer localFile.Close()
|
||||
|
||||
// 打开远程文件(创建或覆盖)
|
||||
RemoteFile, err := sftpClient.Create(remotePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create remote file: %w", err)
|
||||
}
|
||||
defer RemoteFile.Close()
|
||||
|
||||
// 分块复制文件内容
|
||||
const bufferSize = 32 * 1024 // 32KB
|
||||
buf := make([]byte, bufferSize)
|
||||
for {
|
||||
n, err := localFile.Read(buf)
|
||||
if err != nil && err.Error() != "EOF" {
|
||||
return fmt.Errorf("failed to read local file: %w", err)
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
if _, err := RemoteFile.Write(buf[:n]); err != nil {
|
||||
return fmt.Errorf("failed to write to remote file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func _sftp() error {
|
||||
|
||||
var err error
|
||||
|
||||
// 校验参数
|
||||
if *h == "" || *u == "" || *l == "" || *r == "" || (*e == "" && *k == "") {
|
||||
fmt.Println("Error: Missing required parameters")
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 如果使用私钥且需要密码解锁,则提示用户输入密码
|
||||
var passphrase string
|
||||
if *k != "" {
|
||||
fmt.Print("Enter passphrase for private key: ")
|
||||
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
log.Printf("Failed to read passphrase: %v", err)
|
||||
}
|
||||
passphrase = string(bytePassword)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// 构造 SSH 配置
|
||||
config := SSHConfig{
|
||||
Host: *h,
|
||||
Port: *p,
|
||||
User: *u,
|
||||
Password: *e,
|
||||
PrivateKey: *k,
|
||||
Passphrase: passphrase,
|
||||
}
|
||||
|
||||
// 连接 SFTP
|
||||
sftpClient, sshClient, err := connectSFTP(config)
|
||||
if err != nil {
|
||||
log.Printf("Failed to connect to SFTP: %v", err)
|
||||
}
|
||||
defer sftpClient.Close()
|
||||
defer sshClient.Close()
|
||||
|
||||
// 上传文件
|
||||
err = uploadFile(sftpClient, *l, *r)
|
||||
if err != nil {
|
||||
log.Printf("Failed to upload file: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("File uploaded successfully!")
|
||||
return err
|
||||
}
|
125
ssh.go
Normal file
125
ssh.go
Normal file
@ -0,0 +1,125 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// 通过 SSH 执行命令
|
||||
func runSSHCommand(config SSHConfig, command string) (string, error) {
|
||||
// 创建 SSH 配置
|
||||
var authMethods []ssh.AuthMethod
|
||||
|
||||
// 使用密码认证
|
||||
if config.Password != "" {
|
||||
authMethods = append(authMethods, ssh.Password(config.Password))
|
||||
}
|
||||
|
||||
// 使用私钥认证
|
||||
if config.PrivateKey != "" {
|
||||
key, err := os.ReadFile(config.PrivateKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read private key file: %w", err)
|
||||
}
|
||||
|
||||
var signer ssh.Signer
|
||||
if config.Passphrase != "" {
|
||||
// 使用密码解锁私钥
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase(key, []byte(config.Passphrase))
|
||||
} else {
|
||||
// 无密码的私钥
|
||||
signer, err = ssh.ParsePrivateKey(key)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse private key: %w", err)
|
||||
}
|
||||
|
||||
authMethods = append(authMethods, ssh.PublicKeys(signer))
|
||||
}
|
||||
|
||||
if len(authMethods) == 0 {
|
||||
return "", fmt.Errorf("no authentication method provided")
|
||||
}
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: config.User,
|
||||
Auth: authMethods,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 跳过主机密钥验证,生产环境中建议替换为可信的回调函数
|
||||
}
|
||||
|
||||
// 连接到远程服务器
|
||||
address := fmt.Sprintf("%s:%d", config.Host, config.Port)
|
||||
client, err := ssh.Dial("tcp", address, sshConfig)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to dial: %w", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// 创建会话
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create session: %w", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// 执行命令
|
||||
var stdout, stderr bytes.Buffer
|
||||
session.Stdout = &stdout
|
||||
session.Stderr = &stderr
|
||||
|
||||
if err := session.Run(command); err != nil {
|
||||
return "", fmt.Errorf("执行命令失败: %w. stderr: %s", err, stderr.String())
|
||||
}
|
||||
if stderr.Len() > 0 {
|
||||
log.Printf("stderr: %s", stderr.String())
|
||||
}
|
||||
return stdout.String(), nil
|
||||
|
||||
}
|
||||
|
||||
func _ssh() (string, error) {
|
||||
// 校验必需参数
|
||||
if *h == "" || *u == "" || *c == "" || (*e == "" && *k == "") {
|
||||
fmt.Println("Error: Missing required parameters")
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 如果使用私钥且需要密码解锁,则提示用户输入密码
|
||||
var passphrase string
|
||||
if *k != "" {
|
||||
fmt.Print("Enter passphrase for private key: ")
|
||||
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
log.Printf("Failed to read passphrase: %v", err)
|
||||
}
|
||||
passphrase = string(bytePassword)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// 构造 SSH 配置
|
||||
config := SSHConfig{
|
||||
Host: *h,
|
||||
Port: *p,
|
||||
User: *u,
|
||||
Password: *e,
|
||||
PrivateKey: *k,
|
||||
Passphrase: passphrase,
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
output, err := runSSHCommand(config, *c)
|
||||
if err != nil {
|
||||
log.Printf("Error: %v", err)
|
||||
}
|
||||
|
||||
return output, err
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user