65 Star 396 Fork 128

admpub/nging

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
cmd.go 8.79 KB
一键复制 编辑 原始数据 按行查看 历史
admpub 提交于 2019-05-10 23:35 . v2.0.0
/*
Nging is a toolbox for webmasters
Copyright (C) 2018-present Wenhui Shen <swh@admpub.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package server
import (
"context"
"errors"
"io"
"os"
"os/exec"
"strings"
"time"
"github.com/admpub/log"
"github.com/admpub/nging/application/dbschema"
"github.com/admpub/nging/application/handler"
"github.com/admpub/nging/application/library/charset"
"github.com/admpub/nging/application/library/config"
"github.com/admpub/nging/application/library/cron"
"github.com/admpub/nging/application/model"
"github.com/admpub/sockjs-go/sockjs"
"github.com/admpub/websocket"
"github.com/webx-top/com"
"github.com/webx-top/db"
"github.com/webx-top/echo"
"github.com/webx-top/echo/param"
)
func Cmd(ctx echo.Context) error {
var err error
id := ctx.Formx(`id`).Uint()
m := model.NewCommand(ctx)
if id > 0 {
err := m.Get(nil, `id`, id)
if err != nil {
handler.SendFail(ctx, err.Error())
return ctx.Redirect(handler.URLFor(`/manager/command`))
}
}
ctx.Set(`id`, id)
ctx.Set(`cmd`, m.Command)
return ctx.Render(`server/cmd`, err)
}
func CmdSendBySockJS(c sockjs.Session) error {
send := make(chan string)
//push(writer)
go func() {
for {
message := <-send
handler.WebSocketLogger.Debug(`Push message: `, message)
if err := c.Send(message); err != nil {
handler.WebSocketLogger.Error(`Push error: `, err.Error())
return
}
}
}()
timeout := c.Request().URL.Query().Get(`timeout`)
duration := config.ParseTimeDuration(timeout)
//echo
exec := func(session sockjs.Session) error {
var (
w io.WriteCloser
cmd *exec.Cmd
)
for {
command, err := session.Recv()
if err != nil {
return err
}
if len(command) == 0 {
continue
}
var (
workdir string
env []string
)
if command[0] == '>' {
id := param.String(command[1:]).Uint()
if id > 0 {
m, result, err := ExecCommand(id)
if err != nil {
send <- err.Error()
continue
}
if m.Remote == `Y` {
send <- result
continue
}
workdir = m.WorkDirectory
env = config.ParseEnvSlice(m.Env)
command = m.Command
} else {
return errors.New(`Invalid ID: ` + command[1:])
}
} else {
}
if w == nil {
w, cmd, err = cmdRunner(workdir, env, command, send, func() {
w.Close()
w = nil
}, duration, c.Request().Context())
if err != nil {
return err
}
continue
}
err = cmdContinue(command, w, cmd)
if err != nil {
return err
}
}
}
err := exec(c)
if err != nil {
handler.WebSocketLogger.Error(err)
}
close(send)
return nil
}
func cmdRunner(workdir string, env []string, command string, send chan string, onEnd func(), timeout time.Duration, ctx context.Context) (w io.WriteCloser, cmd *exec.Cmd, err error) {
params := cron.CmdParams(command)
cmd = com.CreateCmd(params, func(b []byte) (e error) {
if com.IsWindows {
b, e = charset.Convert(`gbk`, `utf-8`, b)
if e != nil {
return e
}
}
send <- string(b)
return nil
})
if len(workdir) > 0 {
cmd.Dir = workdir
}
if len(env) > 0 {
cmd.Env = env
}
w, err = cmd.StdinPipe()
if err != nil {
return
}
done := make(chan struct{})
go func() {
log.Info(`[command] running: `, command)
if e := cmd.Run(); e != nil {
cmd.Stderr.Write([]byte(e.Error()))
}
done <- struct{}{}
onEnd()
}()
cmdMaxTimeout := config.DefaultConfig.Sys.CmdTimeoutDuration
if timeout <= 0 {
timeout = time.Minute * 5
}
if timeout > cmdMaxTimeout {
timeout = cmdMaxTimeout
}
go func() {
ticker := time.NewTicker(timeout)
defer ticker.Stop()
for {
select {
case <-done:
log.Info(`[command] exited: `, command)
return
case <-ticker.C:
if cmd == nil {
return
}
if cmd.Process == nil {
return
}
cmd.Stderr.Write([]byte(`timeout`))
log.Info(`[command] timeout: `, command)
err := cmd.Process.Kill()
if err != nil {
log.Error(err)
}
return
case <-ctx.Done():
if cmd == nil {
return
}
if cmd.Process == nil {
return
}
cmd.Stderr.Write([]byte(`request is cancelled`))
log.Info(`[command] request is cancelled: `, command)
err := cmd.Process.Kill()
if err != nil {
log.Error(err)
}
return
}
}
}()
return
}
func cmdContinue(command string, w io.WriteCloser, cmd *exec.Cmd) (err error) {
if cmd == nil {
return nil
}
switch command {
case `^C`:
err = cmd.Process.Signal(os.Interrupt)
if err != nil {
if !strings.HasPrefix(err.Error(), `not supported by `) {
handler.WebSocketLogger.Error(err)
}
err = cmd.Process.Kill()
if err != nil {
handler.WebSocketLogger.Error(err)
}
}
default:
w.Write([]byte(command + "\n"))
}
return nil
}
func CmdSendByWebsocket(c *websocket.Conn, ctx echo.Context) error {
check, _ := ctx.Funcs()[`CheckPerm`].(func(string) error)
send := make(chan string)
//push(writer)
go func() {
for {
message := <-send
handler.WebSocketLogger.Debug(`Push message: `, message)
if err := c.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {
handler.WebSocketLogger.Error(`Push error: `, err.Error())
return
}
}
}()
timeout := ctx.Query(`timeout`)
duration := config.ParseTimeDuration(timeout)
//echo
exec := func(conn *websocket.Conn) error {
var (
w io.WriteCloser
cmd *exec.Cmd
)
for {
_, message, err := conn.ReadMessage()
if err != nil {
return err
}
command := string(message)
if len(command) == 0 {
continue
}
//TEST:
//notice.OpenMessage(`test`, ``)
//notice.Send(`test`, notice.NewMessageWithValue(``, `from: admin`, `test user message`))
var (
workdir string
env []string
)
if command[0] == '>' {
id := param.String(command[1:]).Uint()
if id > 0 {
if check != nil {
err := check(command[1:])
if err != nil {
send <- err.Error()
continue
}
}
m, result, err := ExecCommand(id)
if err != nil {
send <- err.Error()
continue
}
if m.Remote == `Y` {
send <- result
continue
}
workdir = m.WorkDirectory
env = config.ParseEnvSlice(m.Env)
command = m.Command
} else {
err := errors.New(`Invalid ID: ` + command[1:])
send <- err.Error()
continue
}
} else {
if check != nil {
err := check(``)
if err != nil {
return err
}
}
}
if w == nil {
w, cmd, err = cmdRunner(workdir, env, command, send, func() {
w.Close()
w = nil
}, duration, ctx.Request().StdRequest().Context())
if err != nil {
return err
}
continue
}
err = cmdContinue(command, w, cmd)
if err != nil {
return err
}
}
}
err := exec(c)
if err != nil {
handler.WebSocketLogger.Error(err)
}
close(send)
return nil
}
func ExecCommand(id uint) (*dbschema.Command, string, error) {
m := model.NewCommand(nil)
err := m.Get(nil, `id`, id)
if err != nil {
return m.Command, "", err
}
if m.Command.Disabled == `Y` {
return m.Command, "", errors.New(echo.T(`该命令已禁用`))
}
//m.Command.Remote = `Y`
//m.Command.SshAccountId = 4
if m.Command.Remote == `Y` {
if m.Command.SshAccountId < 1 {
return m.Command, "", errors.New("Error, you did not choose ssh account")
}
sshUser := model.NewSshUser(nil)
err = sshUser.Get(nil, `id`, m.Command.SshAccountId)
if err != nil {
if err == db.ErrNoMoreRows {
return m.Command, "", errors.New("The specified ssh account does not exist")
}
return m.Command, "", err
}
sshUser.Passphrase = config.DefaultConfig.Decode(sshUser.Passphrase)
sshUser.Password = config.DefaultConfig.Decode(sshUser.Password)
cmdList := []string{}
if len(m.WorkDirectory) > 0 {
cmdList = append(cmdList, `cd `+m.WorkDirectory)
}
if len(m.Env) > 0 {
for _, env := range strings.Split(m.Env, "\n") {
env = strings.TrimSpace(env)
if len(env) < 1 {
continue
}
cmdList = append(cmdList, `export `+env)
}
}
cmdList = append(cmdList, m.Command.Command)
w := cron.NewCmdRec(1000)
err = sshUser.ExecMultiCMD(w, cmdList...)
if err != nil {
return m.Command, "", err
}
//panic(echo.Dump(w.String(), false))
return m.Command, w.String(), nil
}
return m.Command, "", err
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/admpub/nging.git
git@gitee.com:admpub/nging.git
admpub
nging
nging
v2.0.5

搜索帮助

0d507c66 1850385 C8b1a773 1850385