diff --git a/.gitignore b/.gitignore
index 50e5136..affef48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@
/data
/dist
.DS_Store
+/main
diff --git a/README.md b/README.md
index 650a26d..8de6a6e 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
LOGO designed by 熊大 .
-
+
:trollface: 哪吒监控 一站式轻监控轻运维系统。支持系统状态、HTTP(SSL 证书变更、即将到期、到期)、TCP、Ping 监控报警,命令批量执行和计划任务。
diff --git a/cmd/agent/main.go b/cmd/agent/main.go index b8e043e..45e3296 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -14,20 +14,18 @@ import ( "net/url" "os" "os/exec" - "runtime" - "syscall" "time" - "unsafe" "github.com/blang/semver" "github.com/genkiroid/cert" "github.com/go-ping/ping" "github.com/gorilla/websocket" - "github.com/kr/pty" "github.com/p14yground/go-github-selfupdate/selfupdate" "google.golang.org/grpc" "github.com/naiba/nezha/cmd/agent/monitor" + "github.com/naiba/nezha/cmd/agent/processgroup" + "github.com/naiba/nezha/cmd/agent/pty" "github.com/naiba/nezha/model" "github.com/naiba/nezha/pkg/utils" pb "github.com/naiba/nezha/proto" @@ -97,6 +95,8 @@ func run() { ClientSecret: clientSecret, } + go pty.DownloadDependency() + // 上报服务器信息 go reportState() // 更新IP信息 @@ -311,7 +311,7 @@ func handleCommandTask(task *pb.Task, result *pb.TaskResult) { startedAt := time.Now() var cmd *exec.Cmd var endCh = make(chan struct{}) - pg, err := utils.NewProcessExitGroup() + pg, err := processgroup.NewProcessExitGroup() if err != nil { // 进程组创建失败,直接退出 result.Data = err.Error() @@ -345,6 +345,11 @@ func handleCommandTask(task *pb.Task, result *pb.TaskResult) { result.Delay = float32(time.Since(startedAt).Seconds()) } +type WindowSize struct { + Cols uint32 + Rows uint32 +} + func handleTerminalTask(task *pb.Task) { var terminal model.TerminalTask err := json.Unmarshal([]byte(task.GetData()), &terminal) @@ -365,36 +370,18 @@ func handleTerminalTask(task *pb.Task) { } defer conn.Close() - var cmd *exec.Cmd - var shellPath string - if runtime.GOOS == "windows" { - shellPath, err = exec.LookPath("powershell.exe") - if err != nil || shellPath == "" { - shellPath = "cmd.exe" - } - } else { - shellPath = os.Getenv("SHELL") - if shellPath == "" { - shellPath = "sh" - } - } - cmd = exec.Command(shellPath) - cmd.Env = append(os.Environ(), "TERM=xterm") - - tty, err := pty.Start(cmd) + tty, err := pty.Start() if err != nil { println("Terminal pty.Start失败:", err) return } defer func() { - cmd.Process.Kill() - cmd.Process.Wait() tty.Close() conn.Close() println("terminal exit", terminal.Session) }() - println("terminal init", terminal.Session, shellPath) + println("terminal init", terminal.Session) go func() { for { @@ -434,17 +421,12 @@ func handleTerminalTask(task *pb.Task) { io.Copy(tty, reader) case 1: decoder := json.NewDecoder(reader) - resizeMessage := windowSize{} + var resizeMessage WindowSize err := decoder.Decode(&resizeMessage) if err != nil { continue } - syscall.Syscall( - syscall.SYS_IOCTL, - tty.Fd(), - syscall.TIOCSWINSZ, - uintptr(unsafe.Pointer(&resizeMessage)), - ) + tty.Setsize(resizeMessage.Cols, resizeMessage.Rows) } } } diff --git a/pkg/utils/proccess_group.go b/cmd/agent/processgroup/process_group.go similarity index 96% rename from pkg/utils/proccess_group.go rename to cmd/agent/processgroup/process_group.go index c7a24ed..83195c9 100644 --- a/pkg/utils/proccess_group.go +++ b/cmd/agent/processgroup/process_group.go @@ -1,6 +1,6 @@ // +build !windows -package utils +package processgroup import ( "os/exec" diff --git a/pkg/utils/proccess_group_windows.go b/cmd/agent/processgroup/process_group_windows.go similarity index 96% rename from pkg/utils/proccess_group_windows.go rename to cmd/agent/processgroup/process_group_windows.go index 55bfe30..87cc47c 100644 --- a/pkg/utils/proccess_group_windows.go +++ b/cmd/agent/processgroup/process_group_windows.go @@ -1,6 +1,6 @@ // +build windows -package utils +package processgroup import ( "fmt" diff --git a/cmd/agent/pty/pty.go b/cmd/agent/pty/pty.go new file mode 100644 index 0000000..c9d578c --- /dev/null +++ b/cmd/agent/pty/pty.go @@ -0,0 +1,52 @@ +//go:build !windows +//+build !windows + +package pty + +import ( + "os" + "os/exec" + + opty "github.com/creack/pty" +) + +type Pty struct { + tty *os.File + cmd *exec.Cmd +} + +func DownloadDependency() { +} + +func Start() (*Pty, error) { + shellPath := os.Getenv("SHELL") + if shellPath == "" { + shellPath = "sh" + } + cmd := exec.Command(shellPath) + cmd.Env = append(os.Environ(), "TERM=xterm") + tty, err := opty.Start(cmd) + return &Pty{tty: tty, cmd: cmd}, err +} + +func (pty *Pty) Write(p []byte) (n int, err error) { + return pty.tty.Write(p) +} + +func (pty *Pty) Read(p []byte) (n int, err error) { + return pty.tty.Read(p) +} + +func (pty *Pty) Setsize(cols, rows uint32) error { + return opty.Setsize(pty.tty, &opty.Winsize{ + Cols: uint16(cols), + Rows: uint16(rows), + }) +} + +func (pty *Pty) Close() error { + if err := pty.tty.Close(); err != nil { + return err + } + return pty.cmd.Process.Kill() +} diff --git a/cmd/agent/pty/pty_windows.go b/cmd/agent/pty/pty_windows.go new file mode 100644 index 0000000..19b4c94 --- /dev/null +++ b/cmd/agent/pty/pty_windows.go @@ -0,0 +1,96 @@ +// go:build windows +// +build windows + +package pty + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + + "github.com/artdarek/go-unzip" + "github.com/iamacarpet/go-winpty" +) + +type Pty struct { + tty *winpty.WinPTY +} + +func DownloadDependency() { + resp, err := http.Get("https://dn-dao-github-mirror.daocloud.io/rprichard/winpty/releases/download/0.4.3/winpty-0.4.3-msvc2015.zip") + if err != nil { + log.Println("wintty 下载失败", err) + return + } + defer resp.Body.Close() + content, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println("wintty 下载失败", err) + return + } + if err := ioutil.WriteFile("./wintty.zip", content, os.FileMode(0777)); err != nil { + log.Println("wintty 写入失败", err) + return + } + if err := unzip.New("./wintty.zip", "./wintty").Extract(); err != nil { + fmt.Println("wintty 解压失败", err) + return + } + arch := "x64" + if runtime.GOARCH != "amd64" { + arch = "ia32" + } + executablePath, err := getExecutableFilePath() + if err != nil { + fmt.Println("wintty 获取文件路径失败", err) + return + } + os.Rename("./wintty/"+arch+"/bin/winpty-agent.exe", filepath.Join(executablePath, "winpty-agent.exe")) + os.Rename("./wintty/"+arch+"/bin/winpty.dll", filepath.Join(executablePath, "winpty.dll")) + os.RemoveAll("./wintty") + os.RemoveAll("./wintty.zip") +} + +func getExecutableFilePath() (string, error) { + ex, err := os.Executable() + if err != nil { + return "", err + } + return filepath.Dir(ex), nil +} + +func Start() (*Pty, error) { + shellPath, err := exec.LookPath("powershell.exe") + if err != nil || shellPath == "" { + shellPath = "cmd.exe" + } + path, err := getExecutableFilePath() + if err != nil { + return nil, err + } + tty, err := winpty.Open(path, shellPath) + return &Pty{tty: tty}, err +} + +func (pty *Pty) Write(p []byte) (n int, err error) { + return pty.tty.StdIn.Read(p) +} + +func (pty *Pty) Read(p []byte) (n int, err error) { + return pty.tty.StdOut.Read(p) +} + +func (pty *Pty) Setsize(cols, rows uint32) error { + pty.tty.SetSize(cols, rows) + return nil +} + +func (pty *Pty) Close() error { + pty.tty.Close() + return nil +} diff --git a/cmd/playground/main.go b/cmd/playground/main.go index 39903a7..57b18b9 100644 --- a/cmd/playground/main.go +++ b/cmd/playground/main.go @@ -1,191 +1,7 @@ package main -import ( - "context" - "crypto/tls" - "fmt" - "io" - "log" - "net" - "net/http" - "os" - "os/exec" - "strings" - "time" - - "github.com/genkiroid/cert" - "github.com/go-ping/ping" - "github.com/shirou/gopsutil/v3/cpu" - "github.com/shirou/gopsutil/v3/disk" - "github.com/shirou/gopsutil/v3/host" - - "github.com/naiba/nezha/pkg/utils" -) +import "github.com/naiba/nezha/cmd/agent/pty" func main() { - // icmp() - // tcpping() - // httpWithSSLInfo() - // sysinfo() - // cmdExec() - // resolveIP("ipapi.co", true) - // resolveIP("ipapi.co", false) - log.Println(exec.LookPath("powershell.exe")) - defaultShell := os.Getenv("SHELL") - if defaultShell == "" { - defaultShell = "sh" - } - cmd := exec.Command(defaultShell) - cmd.Stdin = os.Stdin - stdoutReader, err := cmd.StdoutPipe() - if err != nil { - println("Terminal StdoutPipe:", err) - return - } - stderrReader, err := cmd.StderrPipe() - if err != nil { - println("Terminal StderrPipe: ", err) - return - } - - readers := []io.Reader{stdoutReader, stderrReader} - for i := 0; i < len(readers); i++ { - go func(j int) { - data := make([]byte, 2048) - for { - count, err := readers[j].Read(data) - if err != nil { - panic(err) - } - os.Stdout.Write(data[:count]) - } - }(i) - } - - cmd.Run() -} - -func resolveIP(addr string, ipv6 bool) { - url := strings.Split(addr, ":") - - dnsServers := []string{"2606:4700:4700::1001", "2001:4860:4860::8844", "2400:3200::1", "2400:3200:baba::1"} - if !ipv6 { - dnsServers = []string{"1.0.0.1", "8.8.4.4", "223.5.5.5", "223.6.6.6"} - } - - log.Println(net.LookupIP(url[0])) - for i := 0; i < len(dnsServers); i++ { - dnsServer := dnsServers[i] - if ipv6 { - dnsServer = "[" + dnsServer + "]" - } - r := &net.Resolver{ - PreferGo: true, - Dial: func(ctx context.Context, network, address string) (net.Conn, error) { - d := net.Dialer{ - Timeout: time.Second * 10, - } - return d.DialContext(ctx, "udp", dnsServer+":53") - }, - } - log.Println(r.LookupIP(context.Background(), "ip", url[0])) - } -} - -func tcpping() { - start := time.Now() - conn, err := net.DialTimeout("tcp", "example.com:80", time.Second*10) - if err != nil { - panic(err) - } - conn.Write([]byte("ping\n")) - conn.Close() - fmt.Println(time.Since(start).Microseconds(), float32(time.Since(start).Microseconds())/1000.0) -} - -func sysinfo() { - hi, _ := host.Info() - var cpuType string - if hi.VirtualizationSystem != "" { - cpuType = "Virtual" - } else { - cpuType = "Physical" - } - cpuModelCount := make(map[string]int) - ci, _ := cpu.Info() - for i := 0; i < len(ci); i++ { - cpuModelCount[ci[i].ModelName]++ - } - var cpus []string - for model, count := range cpuModelCount { - cpus = append(cpus, fmt.Sprintf("%s %d %s Core", model, count, cpuType)) - } - os.Exit(0) - // 硬盘信息,不使用的原因是会重复统计 Linux、Mac - dparts, _ := disk.Partitions(false) - for _, part := range dparts { - u, _ := disk.Usage(part.Mountpoint) - if u != nil { - log.Printf("%s %d %d", part.Device, u.Total, u.Used) - } - } -} - -func httpWithSSLInfo() { - // 跳过 SSL 检查 - transCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - httpClient := &http.Client{Transport: transCfg, CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }} - url := "https://ops.naibahq.com" - resp, err := httpClient.Get(url) - fmt.Println(err, resp) - // SSL 证书信息获取 - c := cert.NewCert(url[8:]) - fmt.Println(c.Error) -} - -func icmp() { - pinger, err := ping.NewPinger("10.10.10.2") - if err != nil { - panic(err) // Blocks until finished. - } - pinger.Count = 3000 - pinger.Timeout = 10 * time.Second - if err = pinger.Run(); err != nil { - panic(err) - } - fmt.Println(pinger.PacketsRecv, float32(pinger.Statistics().AvgRtt.Microseconds())/1000.0) -} - -func cmdExec() { - execFrom, err := os.Getwd() - if err != nil { - panic(err) - } - var cmd *exec.Cmd - pg, err := utils.NewProcessExitGroup() - if err != nil { - panic(err) - } - if utils.IsWindows() { - cmd = exec.Command("cmd", "/c", os.Args[1]) - // cmd = exec.Command("cmd", "/c", execFrom+"/cmd/playground/example.sh hello asd") - } else { - cmd = exec.Command("sh", "-c", execFrom+`/cmd/playground/example.sh hello && \ - echo world!`) - } - pg.AddProcess(cmd) - go func() { - time.Sleep(time.Second * 10) - if err = pg.Dispose(); err != nil { - panic(err) - } - fmt.Println("killed") - }() - output, err := cmd.Output() - log.Println("output:", string(output)) - log.Println("err:", err) + pty.DownloadDependency() } diff --git a/go.mod b/go.mod index 4b052f8..079e7ee 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.13 require ( code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 + github.com/artdarek/go-unzip v1.0.0 github.com/blang/semver v3.5.1+incompatible + github.com/creack/pty v1.1.14 github.com/fsnotify/fsnotify v1.4.9 github.com/genkiroid/cert v0.0.0-20191007122723-897560fbbe50 github.com/gin-contrib/pprof v1.3.0 @@ -14,7 +16,7 @@ require ( github.com/google/go-github v17.0.0+incompatible github.com/gorilla/websocket v1.4.2 github.com/hashicorp/go-uuid v1.0.1 - github.com/kr/pty v1.1.1 + github.com/iamacarpet/go-winpty v1.0.2 github.com/onsi/ginkgo v1.7.0 // indirect github.com/onsi/gomega v1.4.3 // indirect github.com/ory/graceful v0.1.1 diff --git a/go.sum b/go.sum index 0fc1a1e..569a236 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/artdarek/go-unzip v1.0.0 h1:Ja9wfhiXyl67z5JT37rWjTSb62KXDP+9jHRkdSREUvg= +github.com/artdarek/go-unzip v1.0.0/go.mod h1:KhX4LV7e4UwWCTo7orBYnJ6LJ/dZTI6jXxUg69hO/C8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -63,6 +65,8 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/creack/pty v1.1.14 h1:55VbUWoBxE1iTAh3B6JztD6xyQ06CvW/31oD6rYwrtY= +github.com/creack/pty v1.1.14/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -193,6 +197,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iamacarpet/go-winpty v1.0.2 h1:jwPVTYrjAHZx6Mcm6K5i9G4opMp5TblEHH5EQCl/Gzw= +github.com/iamacarpet/go-winpty v1.0.2/go.mod h1:/GHKJicG/EVRQIK1IQikMYBakBkhj/3hTjLgdzYsmpI= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8= github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg= @@ -216,7 +222,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= diff --git a/resource/template/dashboard/terminal.html b/resource/template/dashboard/terminal.html index e707475..ce1081b 100644 --- a/resource/template/dashboard/terminal.html +++ b/resource/template/dashboard/terminal.html @@ -6,7 +6,7 @@ -