├── test_loop.sh
├── cmdsFile
├── readme_source
└── demo.gif
├── test_go_exec.sh
├── config.json
├── log.go
├── pid.go
├── webpage.go
├── README.md
├── main.go
└── cmdsocket.go
/test_loop.sh:
--------------------------------------------------------------------------------
1 | while [ 1 ]; do
2 | echo 1 1>/dev/null
3 | done
--------------------------------------------------------------------------------
/cmdsFile:
--------------------------------------------------------------------------------
1 | pp:ps
2 | hi:echo hihi
3 | testloop:sh test_loop.sh
4 | errorsh:asdfff
5 | errorparams:ls --error
--------------------------------------------------------------------------------
/readme_source/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spxvszero/go_shell_socket/HEAD/readme_source/demo.gif
--------------------------------------------------------------------------------
/test_go_exec.sh:
--------------------------------------------------------------------------------
1 | echo "shell: try to test go"
2 | while [[ 1 ]]; do
3 | #statements
4 | read a
5 | echo "shell: Your INPUT is $a"
6 | done
7 | echo "shell: finished"
8 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "port":18080,
3 | "Cmd_config": {
4 | "web_url": "/cmdsocket",
5 | "socket_url": "/ws",
6 | "cmds_file": "cmdsFile",
7 | "cmds": [
8 | {
9 | "exec_alias": "ll",
10 | "cmd": "ls",
11 | "param": "-l"
12 | },
13 | {
14 | "exec_alias": "shtest",
15 | "cmd": "sh",
16 | "param": "test_go_exec.sh"
17 | },
18 | {
19 | "exec_alias": "pinggithub",
20 | "cmd": "ping",
21 | "param": "github.com"
22 | },
23 | {
24 | "exec_alias": "cshow",
25 | "cmd": "cat",
26 | "param": "config.json"
27 | }
28 | ],
29 | "exit_string": "exit"
30 | }
31 | }
--------------------------------------------------------------------------------
/log.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "io/ioutil"
6 | "log"
7 | "os"
8 | )
9 |
10 | var (
11 | Trace *log.Logger
12 | Info *log.Logger
13 | Warning *log.Logger
14 | Error *log.Logger
15 | Debug *log.Logger
16 | )
17 |
18 | type LogMode int
19 | const (
20 | DebugMode LogMode = iota
21 | ReleaseMode
22 | )
23 |
24 | var Mode = DebugMode
25 |
26 | func init() {
27 | defaultLogger(ioutil.Discard, os.Stdout, os.Stdout, os.Stderr,os.Stdout)
28 | }
29 |
30 | func defaultLogger(
31 | traceHandle io.Writer,
32 | infoHandle io.Writer,
33 | warningHandle io.Writer,
34 | errorHandle io.Writer,
35 | debugHandle io.Writer) {
36 |
37 | Trace = log.New(traceHandle,
38 | "TRACE: ",
39 | log.Ldate|log.Ltime|log.Lshortfile)
40 |
41 | Info = log.New(infoHandle,
42 | "INFO: ",
43 | log.Ldate|log.Ltime|log.Lshortfile)
44 |
45 | Warning = log.New(warningHandle,
46 | "WARNING: ",
47 | log.Ldate|log.Ltime|log.Lshortfile)
48 |
49 | Error = log.New(errorHandle,
50 | "ERROR: ",
51 | log.Ldate|log.Ltime|log.Lshortfile)
52 |
53 | Debug = log.New(debugHandle,
54 | "DEBUG: ",
55 | log.Ldate|log.Ltime|log.Lshortfile)
56 | }
57 |
58 | func SetLogMode(mode LogMode) {
59 | Mode = mode
60 | }
61 |
62 | func SetWriter(w io.Writer) {
63 | Trace.SetOutput(w)
64 | Info.SetOutput(w)
65 | Warning.SetOutput(w)
66 | Error.SetOutput(w)
67 | if Mode == DebugMode {
68 | Debug.SetOutput(w)
69 | }else {
70 | Debug.SetOutput(ioutil.Discard)
71 | }
72 | }
--------------------------------------------------------------------------------
/pid.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "io/ioutil"
7 | "os"
8 | "os/signal"
9 | "strconv"
10 | "syscall"
11 | )
12 |
13 | var pid_file_name = "cmdsocket.pid"
14 |
15 | var pid_log_prefix = "PID : "
16 |
17 | func setupPidModule() {
18 | writePidFile()
19 | signalReceive()
20 | }
21 |
22 | func writePidFile() {
23 | pid := os.Getpid()
24 | file, err := os.Create(pid_file_name)
25 | if err != nil {
26 | Error.Println("create pid file :", "error : ",err)
27 | return
28 | }
29 | defer file.Close()
30 |
31 | pidstr := fmt.Sprint(pid)
32 | _,err = io.WriteString(file,pidstr)
33 | if err != nil {
34 | Error.Println("write config file error : ",err)
35 | return
36 | }
37 | Info.Println("Finished! Pid ",pidstr," successfully generated.")
38 | }
39 |
40 | func readPidFromFile() int{
41 | buf,err := ioutil.ReadFile(pid_file_name)
42 | if err != nil {
43 | logPid("read pid file error",err)
44 | return -1
45 | }
46 | pid,err := strconv.Atoi(string(buf))
47 | if err !=nil {
48 | logPid("string convert err :",err)
49 | return -1
50 | }
51 | return pid
52 | }
53 |
54 | func signalReceive() {
55 | sigc := make(chan os.Signal, 1)
56 | signal.Notify(sigc,
57 | syscall.SIGHUP)
58 | go func() {
59 | for s := range sigc {
60 | // reload cmd from cmd file
61 | logPid("get signal ",s)
62 | serializeCmds(UserConfig.Cmd)
63 | }
64 | }()
65 |
66 | }
67 |
68 | func sendSignal() {
69 | pid := readPidFromFile()
70 | if pid <= 0{
71 | logPid("pid num small ")
72 | return
73 | }
74 | proc,err := os.FindProcess(pid)
75 | if err != nil {
76 | logPid("process not found ",err)
77 | return
78 | }
79 | err = proc.Signal(syscall.SIGHUP)
80 | if err != nil {
81 | logPid("send signal err ",err)
82 | }else {
83 | logPid("send signal success")
84 | }
85 | }
86 |
87 | func logPid(v ...interface{}) {
88 | Debug.Println(pid_log_prefix,v)
89 | }
--------------------------------------------------------------------------------
/webpage.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | const CmdSocketHTMLPage = `
4 |
5 |
6 |
7 |
8 |
9 | Cmd Socket
10 |
11 |
25 |
26 |
27 |
28 | Cmd Socket Go
29 |
30 |
31 |
36 |
37 |
78 |
79 |
80 |
81 |
82 |
83 | `
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go_shell_socket
2 |
3 | run shell scripts by websocket with go lauguage
4 |
5 | 一个在 web 端执行服务器指令的小工具。
6 |
7 | ## Usage
8 | #### Build yourself
9 |
10 | * pull project
11 | * get [gin](https://github.com/gin-gonic/gin) and [websocket](https://github.com/gorilla/websocket) with `go get`
12 | * config `config.json` file
13 | * build it with `go build`
14 | * open browser with config url and have fun
15 |
16 |
17 |
18 | * 下载代码或者直接用 git 拉取过来
19 | * 通过 go get 装好 [gin](https://github.com/gin-gonic/gin) 和 [websocket](https://github.com/gorilla/websocket)
20 | * 配置 config.json 配置文件
21 | * 然后就可以通过 go build 编译了,跨平台编译根据官网的说明来就好了,没有特殊配置
22 | * 然后 go run 执行,就可以打开网页测试了
23 |
24 |
25 | #### Use Release File
26 |
27 | Sample For Linux :
28 | ```
29 | $ curl -O -L https://github.com/spxvszero/go_shell_socket/releases/download/v1.0/go_shell_socket_linux
30 | $ chmod +x go_shell_socket_linux
31 |
32 | #generate config.json file
33 | $ ./go_shell_socket_linux --generate config.json
34 |
35 | #edit config.json
36 | #run in background if config.json when in same path
37 | $ ./go_shell_socket_linux &
38 |
39 | ```
40 |
41 |
42 | ## Example
43 |
44 | 
45 |
46 | ## Command For Application
47 |
48 | * `--help` to see what commands are supported.
49 | * `--generate [file_path]` to generate a sample config.json file.
50 | * `--signal reload` to reload command file which config in parameter `cmds_file` while server is running.
51 |
52 | 其实总共就两个指令
53 |
54 | * help 指令是 golang 的 flag 包自带的。
55 | * generate 指令会生成一个 config.json 文件,方便配置。
56 | * signal 指令只接受 reload 参数,可以重新加载 cmds_file 配置好的文本文件。
57 |
58 | ## Config
59 |
60 | This is config.json which example used. Don't copy to your own config.json directly.
61 |
62 | 这个配置文件仅用做说明,不要直接拷贝使用。
63 |
64 | ```
65 | {
66 | //web page open port
67 | //网页打开的端口
68 | "port":18080,
69 |
70 | "Cmd_config": {
71 |
72 | //web socket page url
73 | //自带的网页地址
74 | "web_url": "/cmdsocket",
75 |
76 | //socket url
77 | //websocket的地址
78 | "socket_url": "/ws",
79 |
80 | //addition command files on server which can be reloaded without restart go program
81 | //本地定义的一个文本文件,与 cmds 区别就是,这个文件可以通过此程序的 --signal reload 重载配置,而不需要重启程序
82 | "cmds_file": "cmdsFile",
83 |
84 | //inner cmds which loaded on first boot
85 | //这个字段的内容在启动的时候加载,不支持 reload 重载
86 | "cmds": [
87 | {
88 |
89 | //alias for actual command
90 | //指令的别名,web中仅能通过这个访问指令
91 | "exec_alias": "ll",
92 |
93 | //command
94 | //实际运行的指令
95 | "cmd": "ls",
96 |
97 | //param not supported long params or pipe, use script file instead
98 | //指令的参数,不支持管道和过长的指令,如果有需要,建议放在脚本中执行,这里只执行脚本
99 | "param": "-l"
100 | },
101 | {
102 | "exec_alias": "shtest",
103 | "cmd": "sh",
104 | "param": "test_go_exec.sh"
105 | },
106 | {
107 | "exec_alias": "pinggithub",
108 | "cmd": "ping",
109 | "param": "github.com"
110 | },
111 | {
112 | "exec_alias": "cshow",
113 | "cmd": "cat",
114 | "param": "config.json"
115 | }
116 | ],
117 |
118 | //exit current running cmd
119 | //用来中止当前运行的指令
120 | "exit_string": "exit"
121 | }
122 | }
123 | ```
124 |
125 | ## Format of cmds_file
126 |
127 | `[exec_alias]:[cmd] [param]`
128 |
129 | It easy to add new command.
130 |
131 | Look how to add a `ps` command.
132 |
133 | ```
134 | #server is running in background, and there is a cmdsFile which i add path to config.json.
135 | #add cmd
136 | $ echo "ppps:ps aux" >> cmdsFile
137 |
138 | #send signal to server
139 | $ ./go_shell_socket_linux --signal reload
140 | ```
141 |
142 | ## Something more
143 |
144 | * This program does not support any text editor such as `vim`.
145 | * It works fin in my macOS Catalina and CentOS 8, and i am not sure about other system.
146 | * `param` in config file will go through to `exec.Command`, so it does not support complicated command. If you want ,put command in shell script and run with `sh`.
147 | * ssh command will stuck in goroutine which cannot be kill,but why use it.
148 | * nginx do not support websocket default, if using nginx, see how to config it in this [site](https://nginx.org/en/docs/http/websocket.html).
149 | * This program will generate a log file and a pid file while running.
150 |
151 |
152 |
153 | * 这个小工具不支持文本编辑了,不要把它当作一个终端来用,写这个的目的只是为了方便某些不适合登录终端的场景了,要做复杂的任务还是老老实实登入终端吧。
154 | * 这个程序我在 mac 上和 centos8 上测试是正常的,windows 我也试了,交互正常,不过命令我用的少就没怎么测试了。
155 | * 不支持多参数主要自己不太需要,就没有写了,如果有需要可以自己修改 `exec` 执行的形式。
156 | * 还有 ssh 命令是会有问题的,开启的进程没办法正常 kill 掉,不晓得什么原因,不过通过一些方式修改之后,应该有办法实现的吧。
157 | * 通过 nginx 反向代理需要一些特殊配置才能支持 websocket 了,可以看[官网](https://nginx.org/en/docs/http/websocket.html)的描述。
158 | * 这个工具在运行的时候会生成一个 log 文件和一个 pid 文件。
159 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "flag"
6 | "github.com/gin-gonic/gin"
7 | "html/template"
8 | "io"
9 | "io/ioutil"
10 | "net/http"
11 | "os"
12 | "path/filepath"
13 | "strconv"
14 | )
15 |
16 | var generateConfig = flag.String("generate","",
17 | "generate sample config.json file, Example '--generate config.json' will generate config file in current directory ")
18 | var signalFlag = flag.String("signal","","reload")
19 |
20 | type ConfigFileStruct struct {
21 | Port int `json:"port"`
22 | Cmd CmdRoute `json:"cmd_config"`
23 | }
24 |
25 | var UserConfig = ConfigFileStruct{}
26 |
27 | type OUTPUT_MODE int
28 | const (
29 | ModeDebug OUTPUT_MODE = iota
30 | ModeRelease
31 | )
32 |
33 | var GO_OUTPUT_MODE OUTPUT_MODE
34 | var logFile = "go_log.txt"
35 |
36 | func main() {
37 | GO_OUTPUT_MODE = ModeRelease
38 |
39 | checkFlag()
40 |
41 | //pid listen module
42 | setupPidModule()
43 | logConfig()
44 |
45 | readConfig()
46 | ginSetup()
47 | }
48 |
49 | func checkFlag() {
50 | flag.Parse()
51 |
52 | if len(*generateConfig) > 0 {
53 | defer os.Exit(0)
54 | configPath := *generateConfig
55 |
56 | file, err := os.Create(configPath)
57 | if err != nil {
58 | Error.Println("create config file : ", configPath,"error : ",err)
59 | return
60 | }
61 | defer file.Close()
62 |
63 | _,err = io.WriteString(file,getSampleConfigJsonString())
64 | if err != nil {
65 | Error.Println("write config file error : ",err)
66 | return
67 | }
68 | Info.Println("Finished! ",configPath," file successfully generated.")
69 | }
70 | if len(*signalFlag) > 0 {
71 | if *signalFlag == "reload" {
72 | sendSignal()
73 | os.Exit(0)
74 | }
75 | }
76 | }
77 |
78 | func ginSetup() {
79 |
80 | if GO_OUTPUT_MODE == ModeDebug {
81 | gin.SetMode(gin.DebugMode)
82 | }else {
83 | gin.SetMode(gin.ReleaseMode)
84 | }
85 |
86 | route := gin.Default()
87 | //pprof.Register(route,"debug/pprof")
88 |
89 | port := 8888
90 | configRoute(route)
91 |
92 | if &UserConfig != nil && UserConfig.Port > 0 {
93 | port = UserConfig.Port
94 | }
95 | portStr := ":"+strconv.Itoa(port)
96 | Info.Println("Server Open on Port ",port)
97 | err := route.Run(portStr)
98 | if err != nil {
99 | Error.Println("Server Open Failed :",err)
100 | }
101 | }
102 |
103 | func configRoute(route *gin.Engine) {
104 |
105 | //play routes config
106 | if &UserConfig == nil {
107 | Error.Println("Read Route Config Failed")
108 | return;
109 | }
110 | Info.Println("Read Route Config Success")
111 |
112 | //config cmd websocket route
113 | configRoute_cmd_websocket(route)
114 | }
115 |
116 | func configRoute_cmd_websocket(route gin.IRouter){
117 | if len(UserConfig.Cmd.Web_url) > 0 {
118 | //download web page path
119 | route.GET(UserConfig.Cmd.Web_url, func(c *gin.Context) {
120 |
121 | cmdInfoPath := struct {
122 | Socket_Path string
123 | }{UserConfig.Cmd.Socket_url}
124 |
125 | tmpl, err := template.New("socket_page").Parse(CmdSocketHTMLPage)
126 |
127 | if err != nil {
128 | Error.Println("Cmd Socket Web Page Tmpl Err ", err);
129 | c.String(http.StatusNotFound,"something is wrong in this page")
130 | }else {
131 | c.Status(200)
132 | tmpl.Execute(c.Writer,cmdInfoPath)
133 | }
134 | })
135 |
136 | Info.Println("Build Cmd Socket")
137 | route.GET(UserConfig.Cmd.Socket_url, func(c *gin.Context) {
138 | cmdWebSocketHandler(c.Writer,c.Request)
139 | })
140 | }
141 | }
142 |
143 | func readConfig() {
144 | filePossiblePath := "config.json"
145 | UserConfig = *readConfigFile(filePossiblePath)
146 | if &UserConfig == nil {
147 | filePossiblePath = "config/config.json"
148 | UserConfig = *readConfigFile(filePossiblePath)
149 | }
150 |
151 | //serailize cmd config
152 | serializeCmds(UserConfig.Cmd)
153 | }
154 |
155 | func readConfigFile(filePath string) *ConfigFileStruct {
156 |
157 | res := ConfigFileStruct{}
158 |
159 | fileBlob,fileErr := ioutil.ReadFile(filePath)
160 |
161 | if fileErr != nil {
162 | Error.Println("Config Read Error : ",fileErr)
163 | return nil
164 | }
165 |
166 | json.Unmarshal(fileBlob,&res)
167 | return &res
168 | }
169 |
170 | func logConfig() {
171 | f,err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
172 | if err != nil {
173 | Error.Println("Open Log File Failed : ",err);
174 | return;
175 | }
176 | w := io.MultiWriter(os.Stdout,f)
177 | gin.DefaultWriter = w
178 |
179 | if GO_OUTPUT_MODE == ModeDebug {
180 | SetLogMode(DebugMode)
181 | }else {
182 | SetLogMode(ReleaseMode)
183 | }
184 |
185 | SetWriter(w)
186 |
187 | Info.Println("Log File Path : ",filepath.Dir(os.Args[0]) + "/" + logFile)
188 | }
189 |
190 | func getSampleConfigJsonString() string {
191 | return `
192 | {
193 | "port":18080,
194 | "Cmd_config": {
195 | "web_url": "/cmdsocket",
196 | "socket_url": "/ws",
197 | "cmds_file": "cmdsFile",
198 | "cmds": [
199 | {
200 | "exec_alias": "pinggithub",
201 | "cmd": "ping",
202 | "param": "github.com"
203 | }
204 | ],
205 | "exit_string": "exit"
206 | }
207 | }
208 |
209 |
210 |
211 | `
212 | }
--------------------------------------------------------------------------------
/cmdsocket.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "context"
6 | "github.com/gorilla/websocket"
7 | "io"
8 | "net/http"
9 | "os"
10 | "os/exec"
11 | "strings"
12 | "sync"
13 | )
14 |
15 | type CmdRoute struct {
16 | Web_url string `json:"web_url"`
17 | Socket_url string `json:"socket_url"`
18 | Cmds []CmdConfig `json:"cmds"`
19 | CmdsFile string `json:"cmds_file"`
20 | Exit string `json:"exit_string"`
21 | }
22 | type CmdConfig struct {
23 | Alias string `json:"exec_alias"`
24 | Cmd string `json:"cmd"`
25 | Param string `json:"param"`
26 | }
27 |
28 | type SocketSafeWriter struct {
29 | Conn *websocket.Conn
30 | mu sync.Mutex
31 | }
32 | func (sw *SocketSafeWriter) SendMsg(msg []byte) {
33 | sw.mu.Lock()
34 | defer sw.mu.Unlock()
35 | if sw.Conn != nil {
36 | sw.Conn.WriteMessage(websocket.TextMessage,msg)
37 | }
38 | }
39 |
40 | var cmdsocket_log_prefix = "CMD Socket : "
41 |
42 | var cmds_map map[string]CmdConfig
43 |
44 | func serializeCmds(route CmdRoute) {
45 | cmds_map = make(map[string]CmdConfig)
46 | //scan file
47 | if len(route.CmdsFile) > 0 {
48 | file,err := os.Open(route.CmdsFile)
49 | if err != nil {
50 | Error.Println(cmdsocket_log_prefix,"Open cmdsfile error : ",err)
51 | }else {
52 | scanner := bufio.NewScanner(file)
53 | for scanner.Scan() {
54 | str := scanner.Text()
55 | splitIdx := strings.Index(str,":")
56 | if splitIdx > 0 {
57 | alias := str[0:splitIdx]
58 | blankSplitIdx := strings.Index(str," ")
59 | if blankSplitIdx > 0{
60 | cmdStr := str[splitIdx+1:blankSplitIdx]
61 | paramStr := str[blankSplitIdx+1:]
62 | cmds_map[alias] = CmdConfig{Alias: alias,Cmd: cmdStr,Param: paramStr}
63 | logCmdsocket("Add cmd ",alias," ",cmdStr," ",paramStr)
64 | }else {
65 | cmdStr := str[splitIdx+1:]
66 | if len(cmdStr) > 0 {
67 | cmds_map[alias] = CmdConfig{Alias: alias,Cmd: cmdStr,Param: ""}
68 | logCmdsocket("Add cmd ",alias," ",cmdStr)
69 | }
70 | }
71 | }
72 | }
73 | }
74 | }
75 | //config file
76 | for _,cmd := range route.Cmds {
77 | cmds_map[cmd.Alias] = cmd
78 | logCmdsocket("Add cmd ",cmd.Alias," ",cmd)
79 | }
80 | }
81 |
82 | var wsupgrader = websocket.Upgrader{
83 | ReadBufferSize: 1024,
84 | WriteBufferSize: 1024,
85 | CheckOrigin: func(r *http.Request) bool {
86 | return true
87 | },
88 | }
89 | func cmdWebSocketHandler(w http.ResponseWriter, r *http.Request) {
90 | conn, err := wsupgrader.Upgrade(w, r, nil)
91 | if err != nil {
92 | Error.Println(cmdsocket_log_prefix,"Failed to set websocket upgrade: %+v", err)
93 | return
94 | }
95 | safeSocket := SocketSafeWriter{Conn: conn}
96 |
97 | remoteChan := make(chan string);
98 | okChan := make(chan bool)
99 | getCmd := false
100 | ctx,cancel := context.WithCancel(context.Background())
101 | defer close(remoteChan)
102 | defer close(okChan)
103 | for {
104 | _,msg, err := conn.ReadMessage()
105 | if err != nil {
106 | break
107 | }
108 | select {
109 | case c:= <-okChan:
110 | getCmd = c
111 | default:
112 | }
113 | if len(string(msg)) > 0 {
114 | //if reading cmd, socket string will gothrought cmd interactive without make a new cmd.
115 | if getCmd {
116 | remoteChan <- string(msg)
117 | continue
118 | }
119 | logCmdsocket("recieve ",string(msg))
120 | //get str
121 | if cmd := seekCmdFromConfig(string(msg));cmd!=nil {
122 | excmd(ctx,*cmd,&safeSocket,remoteChan,&okChan)
123 | safeSocket.SendMsg([]byte("CMD open success, put any key go on"))
124 | } else {
125 | safeSocket.SendMsg( msg)
126 | }
127 | }
128 | }
129 |
130 | //read last okchan which still waiting to receive before closed
131 | select {
132 | case c:= <-okChan:
133 | logCmdsocket("no use ",c)
134 | default:
135 | }
136 | okChan = nil
137 | remoteChan = nil
138 | safeSocket.Conn = nil
139 | cancel()
140 | logCmdsocket("Socket Handle Closed !")
141 | }
142 |
143 | func seekCmdFromConfig(alias string) *CmdConfig {
144 | cmd := cmds_map[alias]
145 | if len(cmd.Alias) > 0 {
146 | return &cmd
147 | }
148 | return nil
149 | }
150 |
151 | func writeInputToCmd(cmd *exec.Cmd,file *io.WriteCloser,stringChan chan string) {
152 |
153 | for puts := range stringChan{
154 | logCmdsocket(">>> ",puts)
155 | if puts == UserConfig.Cmd.Exit {
156 | err := cmd.Process.Kill()
157 | if err!=nil {
158 | logCmdsocket("kill process error :",err)
159 | }
160 | break;
161 | }
162 | wNum,err := io.WriteString(*file, puts + "\n")
163 | logCmdsocket("write length ",wNum)
164 | if err!=nil {
165 | logCmdsocket("write failed : ",err)
166 | break;
167 | }
168 | }
169 | //check cmd if still running
170 | logCmdsocket("cmd process ",cmd.ProcessState)
171 | if cmd.ProcessState == nil && cmd.Process != nil {
172 | logCmdsocket("cmd still running, close it")
173 | cmd.Process.Kill()
174 | }
175 | logCmdsocket("goroutine write loop closed ")
176 | }
177 | func readOutputFromCmd(cmd *exec.Cmd,file *io.PipeReader, safeSocket *SocketSafeWriter) {
178 | logCmdsocket("output pipe file :",*file)
179 |
180 | //bufio.NewScanner is more easier than bufio.NewReader(),and won't cause loop leak
181 | scanner := bufio.NewScanner(file)
182 | for scanner.Scan() {
183 | str := scanner.Text()
184 | logCmdsocket("read cmd script msg : ",str)
185 | if len(str) > 0 {
186 | if safeSocket.Conn == nil {
187 | break
188 | }
189 | safeSocket.SendMsg([]byte(str))
190 | logCmdsocket("socket : ",safeSocket.Conn)
191 | }
192 | }
193 | //check cmd if still running
194 | logCmdsocket("cmd process ",cmd.ProcessState)
195 | if cmd.ProcessState == nil && cmd.Process != nil {
196 | logCmdsocket("cmd still running, close it")
197 | cmd.Process.Kill()
198 | }
199 | logCmdsocket("goroutine read loop closed ")
200 | }
201 | func startCmdScripts(ctx context.Context, config CmdConfig,safeSocket *SocketSafeWriter, inputChan chan string, okChan *chan bool) {
202 | logCmdsocket("ready for send cmd ",config.Cmd)
203 | *okChan <- true
204 |
205 | var cmd *exec.Cmd
206 | if len(config.Param) > 0{
207 | cmd = exec.Command(config.Cmd,config.Param)
208 | }else {
209 | cmd = exec.Command(config.Cmd)
210 | }
211 |
212 | pf,perr := cmd.StdinPipe()
213 | if perr != nil {
214 | logCmdsocket("pipe err ",perr)
215 | }
216 | defer pf.Close()
217 |
218 | go writeInputToCmd(cmd,&pf,inputChan)
219 |
220 | r,w := io.Pipe()
221 | cmd.Stdout = w
222 | cmd.Stderr = w
223 | defer r.Close()
224 | defer w.Close()
225 | go readOutputFromCmd(cmd,r,safeSocket)
226 |
227 | select {
228 | case <-ctx.Done():
229 | logCmdsocket("cmd context forcus finished, skip cmd ",config.Cmd)
230 | default:
231 | err := cmd.Run()
232 | if err!=nil {
233 | logCmdsocket("cmd failed ",err)
234 | safeSocket.SendMsg([]byte("CMD Error: "+err.Error()))
235 | }
236 |
237 | safeSocket.SendMsg([]byte("CMD Finished!"))
238 | }
239 |
240 | logCmdsocket("goroutine cmd exit")
241 | //without goroutine, okchan will stuck this routine until next okchan read
242 | go func() {
243 | if *okChan != nil{
244 | *okChan <- false
245 | }
246 | logCmdsocket("goroutine okchan finish!")
247 | }()
248 | }
249 | func excmd(ctx context.Context,config CmdConfig, safeSocket *SocketSafeWriter, cmdChan chan string, okChan *chan bool) {
250 | go startCmdScripts(ctx,config,safeSocket,cmdChan,okChan)
251 | }
252 |
253 | func logCmdsocket(v ...interface{}) {
254 | Debug.Println(cmdsocket_log_prefix,v)
255 | }
--------------------------------------------------------------------------------