commit fdb5553bd777a65c3ca48914ff3a4d05933c3261 Author: Paulo Simão Date: Wed Sep 29 21:00:49 2021 -0300 1st ver diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d48c759 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +.vscode \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..98831b8 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# go-client +Core Implementation for Go based Replay features diff --git a/api/anticaptcha/cli.go b/api/anticaptcha/cli.go new file mode 100644 index 0000000..0a56d19 --- /dev/null +++ b/api/anticaptcha/cli.go @@ -0,0 +1,48 @@ +package anticaptcha + +import ( + "go.digitalcircle.com.br/open/replaycli-go/api" +) + +type Cli struct { + cli *api.Cli +} + +func (c *Cli) HttpCli() *api.Cli { + return c.cli +} + +type Req struct { + Site string `json:"site"` + Data string `json:"data"` + Img []byte `json:"img"` + To int `json:"to"` +} + +func (c *Cli) Recaptchav2(site string, data string) (string, error) { + ret := "" + req := &Req{ + Site: site, + Data: data, + Img: nil, + To: 300, + } + err := c.cli.HttpJsonPost("/ipc/anticaptcha/recaptchav2", req, &ret) + return ret, err +} + +func (c *Cli) Image2text(site string, data []byte) (string, error) { + ret := "" + req := &Req{ + Site: site, + Img: data, + To: 300, + } + err := c.cli.HttpJsonPost("/ipc/anticaptcha/image2text", req, &ret) + return ret, err +} + +func NewCli() *Cli { + ret := &Cli{cli: api.NewCli()} + return ret +} diff --git a/api/chrome/cli.go b/api/chrome/cli.go new file mode 100644 index 0000000..caf55bc --- /dev/null +++ b/api/chrome/cli.go @@ -0,0 +1,65 @@ +package chrome + +import ( + "encoding/json" + "fmt" + "go.digitalcircle.com.br/open/replaycli-go/api" +) + +type Cli struct { + cli *api.Cli +} + +func (c *Cli) HttpCli() *api.Cli { + return c.cli +} + +func (c *Cli) Start(to int) error { + err := c.cli.HttpJsonGet(fmt.Sprintf("/ipc/chrome/start?to=%d", to), nil) + return err +} +func (c *Cli) StartHeadless() error { + err := c.cli.HttpJsonGet("/ipc/chrome/startHeadless", nil) + return err +} +func (c *Cli) Stop() error { + err := c.cli.HttpJsonGet("/ipc/chrome/stop", nil) + return err +} +func (c *Cli) New(url string) (string, error) { + ret := "" + err := c.cli.HttpJsonGet(fmt.Sprintf("/ipc/chrome/new?url=%s", url), &ret) + return ret, err +} +func (c *Cli) Close(id string) error { + err := c.cli.HttpJsonGet("/ipc/chrome/close/"+id, nil) + return err +} +func (c *Cli) Eval(id string, s string) (map[string]interface{}, error) { + ret := make(map[string]interface{}) + bs, err := c.cli.HttpRawPost("/ipc/chrome/eval/"+id, []byte(s)) + json.Unmarshal(bs, &ret) + return ret, err +} + +func (c *Cli) Wait(id string, s string, to int) (string, error) { + ret := "" + bs, err := c.cli.HttpRawPost(fmt.Sprintf("/ipc/chrome/wait/%s?to=%d", id, to), []byte(s)) + json.Unmarshal(bs, &ret) + return ret, err +} + +func (c *Cli) Send(id string, m string, ps map[string]interface{}) (string, error) { + ret := "" + in := map[string]interface{}{ + "method": m, + "params": ps, + } + err := c.cli.HttpJsonPost("/ipc/chrome/eval/"+id, in, &ret) + return ret, err +} + +func NewCli() *Cli { + ret := &Cli{cli: api.NewCli()} + return ret +} diff --git a/api/cli.go b/api/cli.go new file mode 100644 index 0000000..860e853 --- /dev/null +++ b/api/cli.go @@ -0,0 +1,144 @@ +package api + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "os" + "strings" +) + +type Cli struct { + cli *http.Client + headers map[string]string +} + +func (c *Cli) Addr() string { + ret := os.Getenv("REPLAY_ADDR") + if ret == "" { + ret = "https://localhost:8443" + } else if strings.HasPrefix(ret, ":") { + ret = "https://localhost" + ret + } + + return ret +} +func (c *Cli) AddHeader(k string, v string) { + c.headers[k] = v +} +func (c *Cli) DelHeader(k string) { + delete(c.headers, k) +} +func (c *Cli) HttpDo(method string, strurl string, body []byte) (*http.Response, error) { + if !strings.HasPrefix(strurl, "http") { + addr := c.Addr() + if strings.HasSuffix(addr, "/") { + addr = addr[:len(addr)-1] + } + if !strings.HasPrefix(strurl, "") { + strurl = "/" + strurl + } + strurl = addr + strurl + } + req, err := http.NewRequest(method, strurl, bytes.NewReader(body)) + if err != nil { + return nil, err + } + if c.headers != nil { + for k, v := range c.headers { + req.Header.Set(k, v) + } + } + if body != nil && len(body) > 0 { + req, err = http.NewRequest(method, strurl, io.NopCloser(bytes.NewReader(body))) + + } else { + req, err = http.NewRequest(method, strurl, nil) + } + if err != nil { + return nil, err + } + req.Header.Set("X-API-KEY", apikey) + req.Header.Set("Content-Type", "application/json") + return c.cli.Do(req) +} +func (c *Cli) HttpDoJson(method string, strurl string, i interface{}, o interface{}) (err error) { + bs, err := json.Marshal(i) + if err != nil { + return err + } + res, err := c.HttpDo(method, strurl, bs) + if err != nil { + return + } + defer res.Body.Close() + if o != nil { + err = json.NewDecoder(res.Body).Decode(o) + } + return +} +func (c *Cli) HttpJsonGet(strurl string, o interface{}) error { + return c.HttpDoJson(http.MethodGet, strurl, nil, o) +} +func (c *Cli) HttpJsonDelete(strurl string, o interface{}) error { + return c.HttpDoJson(http.MethodDelete, strurl, nil, o) +} +func (c *Cli) HttpJsonHead(strurl string, o interface{}) error { + return c.HttpDoJson(http.MethodHead, strurl, nil, o) +} +func (c *Cli) HttpJsonPost(strurl string, i interface{}, o interface{}) error { + return c.HttpDoJson(http.MethodPost, strurl, i, o) +} +func (c *Cli) HttpJsonPut(strurl string, i interface{}, o interface{}) error { + return c.HttpDoJson(http.MethodPut, strurl, i, o) +} +func (c *Cli) HttpJsonPatch(strurl string, i interface{}, o interface{}) error { + return c.HttpDoJson(http.MethodPatch, strurl, i, o) +} +func (c *Cli) HttpRawGet(strurl string) ([]byte, error) { + res, err := c.HttpDo(http.MethodGet, strurl, nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + bs, _ := io.ReadAll(res.Body) + return bs, nil +} +func (c *Cli) HttpRawDelete(strurl string) ([]byte, error) { + res, err := c.HttpDo(http.MethodDelete, strurl, nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + bs, _ := io.ReadAll(res.Body) + return bs, nil +} +func (c *Cli) HttpRawPost(strurl string, i []byte) ([]byte, error) { + res, err := c.HttpDo(http.MethodPost, strurl, i) + if err != nil { + return nil, err + } + defer res.Body.Close() + bs, _ := io.ReadAll(res.Body) + return bs, nil +} +func (c *Cli) HttpRawPut(strurl string, i []byte) ([]byte, error) { + res, err := c.HttpDo(http.MethodPut, strurl, i) + if err != nil { + return nil, err + } + defer res.Body.Close() + bs, _ := io.ReadAll(res.Body) + return bs, nil +} + +func NewCli() *Cli { + apikey = os.Getenv("REPLAY_APIKEY") + ret := &Cli{ + cli: &http.Client{}, + headers: make(map[string]string), + } + ret.AddHeader("X-API-KEY", apikey) + return ret +} diff --git a/api/excel/cli.go b/api/excel/cli.go new file mode 100644 index 0000000..0057653 --- /dev/null +++ b/api/excel/cli.go @@ -0,0 +1,64 @@ +package excel + +import ( + "go.digitalcircle.com.br/open/replaycli-go/api" +) + +const ( + CELLTYPE_STRING = "s" + CELLTYPE_INT = "i" + CELLTYPE_BOOL = "b" +) + +type Cli struct { + cli *api.Cli +} + +func (c *Cli) HttpCli() *api.Cli { + return c.cli +} + +type Req struct { + File string `json:"file"` + Sheet string `json:"sheet"` + Cel string `json:"cel"` + Val string `json:"val"` + Celtype string `json:"celtype"` +} + +func (c *Cli) Read(fname string, sheet string) ([][]string, error) { + req := &Req{ + File: fname, + Sheet: sheet, + } + res := make([][]string, 0) + err := c.cli.HttpJsonPost("/ipc/excel/read", req, &res) + return res, err +} +func (c *Cli) Write(fname string, sheet string, cell string, val string, celtype string) error { + req := &Req{ + File: fname, + Sheet: sheet, + Cel: cell, + Val: val, + Celtype: celtype, + } + res := "" + err := c.cli.HttpJsonPost("/ipc/excel/write", req, &res) + return err +} + +func (c *Cli) New(fname string, sheet string) (string, error) { + req := &Req{ + File: fname, + Sheet: sheet, + } + res := "" + err := c.cli.HttpJsonPost("/ipc/excel/new", req, &res) + return res, err +} + +func NewCli() *Cli { + ret := &Cli{cli: api.NewCli()} + return ret +} diff --git a/api/lib.go b/api/lib.go new file mode 100644 index 0000000..c156acb --- /dev/null +++ b/api/lib.go @@ -0,0 +1,101 @@ +package api + +import ( + "bytes" + "encoding/json" + "fmt" + "go.digitalcircle.com.br/open/replaycli-go/types" + "io" + "net/http" + "os" + "strings" +) + +var apikey string + +func init() { + apikey = os.Getenv("REPLAY_APIKEY") +} + +func Addr() string { + ret := os.Getenv("REPLAY_ADDR") + if ret == "" { + ret = "http://localhost:8080" + } else if strings.HasPrefix(ret, ":") { + ret = "http://localhost" + ret + } + + return ret +} + +func HttpDo(method string, strurl string, body []byte) (*http.Response, error) { + cli := http.Client{} + var err error + var req *http.Request + + if body != nil && len(body) > 0 { + req, err = http.NewRequest(method, strurl, io.NopCloser(bytes.NewReader(body))) + + } else { + req, err = http.NewRequest(method, strurl, nil) + } + if err != nil { + return nil, err + } + req.Header.Set("X-API-KEY", apikey) + req.Header.Set("Content-Type", "application/json") + return cli.Do(req) +} + +func HttpDoJson(i interface{}, method string, strurl string, body []byte) (err error) { + res, err := HttpDo(method, strurl, body) + if err != nil { + return + } + err = json.NewDecoder(res.Body).Decode(i) + return +} + +type roboDAO struct { +} + +func (d roboDAO) GetAll() ([]*types.Robot, error) { + ret := make([]*types.Robot, 0) + err := HttpDoJson(&ret, http.MethodGet, Addr()+fmt.Sprintf("/api/v1/robots"), nil) + return ret, err +} +func (d roboDAO) Enqueue(id uint) error { + _, err := HttpDo(http.MethodGet, Addr()+fmt.Sprintf("/api/v1/robots/op/enqueue/%d", id), nil) + return err +} + +var RoboDAO roboDAO + +type sqlDAO struct { +} + +func (d sqlDAO) SQL(s string) (types.SQLResponse, error) { + ret := types.SQLResponse{} + req := types.SQLRequest{Sql: s} + bs, _ := json.Marshal(req) + err := HttpDoJson(&ret, http.MethodPost, Addr()+fmt.Sprintf("/api/v1/sql"), bs) + return ret, err +} + +var SQLDAO sqlDAO + +type appDAO struct { +} + +func (d appDAO) GetAll() ([]*types.App, error) { + ret := make([]*types.App, 0) + err := HttpDoJson(&ret, http.MethodGet, Addr()+fmt.Sprintf("/api/v1/apps"), nil) + return ret, err +} +func (d appDAO) Run(id interface{}) ([]*types.App, error) { + ret := make([]*types.App, 0) + err := HttpDoJson(&ret, http.MethodGet, Addr()+fmt.Sprintf("/api/v1/app/run/%v", id), nil) + return ret, err +} + +var AppDAO appDAO diff --git a/api/replay/cli.go b/api/replay/cli.go new file mode 100644 index 0000000..2a5f8a2 --- /dev/null +++ b/api/replay/cli.go @@ -0,0 +1,89 @@ +package replay + +import ( + "fmt" + "os" + + "go.digitalcircle.com.br/open/replaycli-go/api" + "go.digitalcircle.com.br/open/replaycli-go/types" + "go.digitalcircle.com.br/open/replaycli-go/util" +) + +type Cli struct { + cli *api.Cli +} + +func (c *Cli) HttpCli() *api.Cli { + return c.cli +} + +func (c *Cli) OpenApp(id uint) error { + err := c.cli.HttpJsonGet(fmt.Sprintf("/api/v1/app/run/%d", id), nil) + return err +} + +func (c *Cli) ConfigGet(k string) (ret string, err error) { + err = c.cli.HttpJsonGet("/api/v1/config/byrepo/"+util.Repo()+"/"+k, &ret) + return +} + +func (c *Cli) ConfigGetAll() (ret []types.Config, err error) { + ret = make([]types.Config, 0) + err = c.cli.HttpJsonGet("/api/v1/config/byrepo/"+util.Repo(), &ret) + return +} + +func (c *Cli) CronReload() error { + return c.cli.HttpJsonGet("/ipc/cron/reload", nil) +} + +func (c *Cli) Exit() error { + err := c.cli.HttpJsonGet("/api/v1/exit", nil) + return err +} + +func (c *Cli) MenuGetAllEnabled() (ret []types.Menu, err error) { + ret = make([]types.Menu, 0) + err = c.cli.HttpJsonGet("/api/v1/menu/enabled", nil) + return ret, err +} + +func (c *Cli) QueueAdd(job string, bs []byte) error { + return c.cli.HttpJsonGet("/api/v1/queue/add/"+job, bs) +} +func (c *Cli) QueueGetData(id string) (ret []byte, err error) { + ret, err = c.cli.HttpRawGet("/api/v1/queue/getrequest/" + id) + return +} +func (c *Cli) QueueGetMyData() ([]byte, error) { + return c.QueueGetData(os.Getenv("REPLAY_QUEUEID")) +} +func (c *Cli) QueueAbort() error { + return c.cli.HttpJsonGet("/api/v1/queue/abort", nil) +} +func (c *Cli) QueueEnqueue(id uint) error { + return c.cli.HttpJsonGet(fmt.Sprintf("/api/v1/robots/op/enqueue/%d", id), nil) +} + +func (c *Cli) ServiceStopAll() error { + return c.cli.HttpJsonGet("/api/v1/service/op/stopall", nil) +} + +type SQLReturn struct { + Data []map[string]interface{} `json:"data"` + Err string `json:"err"` +} + +func (c *Cli) SQL(s string) (*SQLReturn, error) { + in := &struct { + Sql string `json:"sql"` + }{s} + out := &SQLReturn{} + err := c.cli.HttpJsonPost("/api/v1/sql", in, &out) + return out, err +} + +func NewCli() *Cli { + ret := &Cli{cli: api.NewCli()} + return ret +} diff --git a/api/wingui/cli.go b/api/wingui/cli.go new file mode 100644 index 0000000..7efb4dc --- /dev/null +++ b/api/wingui/cli.go @@ -0,0 +1,80 @@ +package wingui + +import ( + "fmt" + "go.digitalcircle.com.br/open/replaycli-go/api" +) + +type Cli struct { + cli *api.Cli +} + +func (c *Cli) HttpCli() *api.Cli { + return c.cli +} + +func (c *Cli) ClipRead() (string, error) { + ret := "" + err := c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/clip/read"), &ret) + return ret, err +} + +func (c *Cli) ClipWrite(site string) error { + err := c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/clip/write?str=%s", site), nil) + return err +} + +func (c *Cli) MouseClick() error { + err := c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/mouse/click"), nil) + return err +} +func (c *Cli) MouseMove(x, y int) error { + err := c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/mouse/move?x=%d&y=%d", x, y), nil) + return err +} +func (c *Cli) MouseMoveRelative(x, y int) error { + err := c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/mouse/moverelative?x=%d&y=%d", x, y), nil) + return err +} +func (c *Cli) MouseClickRelative(x, y int) error { + err := c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/mouse/clickatrelative?x=%d&y=%d", x, y), nil) + return err +} +func (c *Cli) MouseClickAt(x, y int) error { + err := c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/mouse/clickat?x=%d&y=%d", x, y), nil) + return err +} +func (c *Cli) ScreenClick(f string) error { + err := c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/screen/click?f=%s", f), nil) + return err +} +func (c *Cli) ScreenClickCenter(f string) error { + err := c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/screen/clickcenter?f=%s", f), nil) + return err +} +func (c *Cli) ScreenFind(f string) (ret map[string]interface{}, err error) { + ret = make(map[string]interface{}) + err = c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/screen/find?f=%s", f), &ret) + return +} + +func (c *Cli) ScreenWait(f string, m int) (ret map[string]interface{}, err error) { + ret = make(map[string]interface{}) + err = c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/screen/wait?f=%s&m=%d", f, m), &ret) + return +} +func (c *Cli) ScreenWaitClick(f string, m int) (ret map[string]interface{}, err error) { + ret = make(map[string]interface{}) + err = c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/screen/waitclick?f=%s&m=%d", f, m), &ret) + return +} +func (c *Cli) ScreenWaitClickCenter(f string, m int) (ret map[string]interface{}, err error) { + ret = make(map[string]interface{}) + err = c.cli.HttpJsonGet(fmt.Sprintf("/ipc/wingui/screen/waitclickcenter?f=%s&m=%d", f, m), &ret) + return +} + +func NewCli() *Cli { + ret := &Cli{cli: api.NewCli()} + return ret +} diff --git a/constants/lib.go b/constants/lib.go new file mode 100644 index 0000000..8b2065c --- /dev/null +++ b/constants/lib.go @@ -0,0 +1,78 @@ +package constants + +const ( + FEATURE_ROBOT = "robot" + FEATURE_SERVICE = "service" + FEATURE_APP = "app" + FEATURE_RUNTIME = "runtime" + RUNNER_BIN = "bin" + RUNNER_PYTHON = "python" + RUNNER_SHELL = "shell" + ENV_ROOT = "ROOT" + ENV_IPC = "IPC" + ENV_ROOTPID = "ROOT_PID" + ENV_ADDR = "ADDR" + ENV_LISTEN_ADDR = "LISTENADDR" + ENV_DUMPENV = "DUMPENV" + ENV_IPC_TCP = "IPC_TCP" + ENV_LOG = "LOG" + ENV_DEBUG_PROTO = "DEBUG_PROTO" + ENV_PYTHON = "PYTHON" + ENV_HTTPBASE = "HTTPBASE" + ENV_STRAWID = "STRAWID" + + IPC_CORE = "core" + IPC_SVC_CLIP = "clip" + IPC_STATIC = "static" + IPC_DB = "db" + IPC_NOTIFY = "notify" + + IPC_CORE_reg = "reg" + IPC_CORE_regs = "regs" + IPC_CORE_unreg = "unreg" + IPC_CORE_db_ConfigGet = "db.ConfigGet" + IPC_CORE_db_MenuGet = "db.MenuGet" + IPC_CORE_db_QueueAdd = "db.QueueAdd" + + BASE_URL = "BASE_URL" + ADDR = "ADDR" + PYTHON = "PYTHON" + SHELL = "SHELL" + ROOT = "ROOT" + LOGGING = "LOGGING" + RESTART = "RESTART" + CHECK_URL = "CHECK_URL" + ROBOT_ID = "ROBOT_ID" + OWNER = "OWNER" + SHOWCONSOLE = "SHOWCONSOLE" + UID = "UID" + VSCODE = "VSCODE" + SCRIPTS_ROOT = "SCRIPTS_ROOT" + REPOS_ROOT = "REPOS_ROOT" + STATIC_ROOT = "STATIC_ROOT" + AUTO_OPEN_CONSOLE = "AUTO_OPEN_CONSOLE" + NOTIFY_ON_OPEN = "NOTIFY_ON_OPEN" + USE_TRAY = "USE_TRAY" + TEMP = "TEMP" + WORKINGIR = "WORKINGIR" + ADDONS_DIR = "ADDONS_DIR" + CHANNEL = "CHANNEL" + + API_VERCHECK = "/api/vercheck" + API_VERGET = "/api/verget" + + EVT_TYPE_JOBSTART = "jobstart" + EVT_TYPE_JOBFINISH = "jobfinish" + EVT_TYPE_APPSTART = "appstart" + EVT_TYPE_APPFINISH = "appfinish" + + ENV_REPLAY_INSTANCE_ALIAS = "REPLAY_INSTANCE_ALIAS" + ENV_ROOT_PID = "ROOT_PID" + ENV_REPLAY_ADDR = "REPLAY_ADDR" + ENV_REPLAY_DATADIR = "REPLAY_DATADIR" + ENV_REPLAY_REPO = "REPLAY_REPO" + ENV_REPLAY_APIKEY = "REPLAY_APIKEY" + ENV_REPLAY_ALIAS = "REPLAY_ALIAS" + ENV_REPLAY_VER = "REPLAY_VER" + ENV_REPLAY_QUEUEID = "REPLAY_QUEUEID" +) diff --git a/errmgr/lib.go b/errmgr/lib.go new file mode 100644 index 0000000..9755f40 --- /dev/null +++ b/errmgr/lib.go @@ -0,0 +1,11 @@ +package errmgr + +import "log" + +func Report(err error) bool { + if err != nil { + log.Printf(err.Error()) + return true + } + return false +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..42e89b5 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module go.digitalcircle.com.br/open/replaycli-go + +go 1.17 + +require ( + github.com/Microsoft/go-winio v0.5.0 + github.com/gorilla/websocket v1.4.2 + github.com/mitchellh/go-ps v1.0.0 + golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8faaf6d --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210921065528-437939a70204 h1:JJhkWtBuTQKyz2bd5WG9H8iUsJRU3En/KRfN8B2RnDs= +golang.org/x/sys v0.0.0-20210921065528-437939a70204/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/ipcmux/lib.go b/ipcmux/lib.go new file mode 100644 index 0000000..e8eb22f --- /dev/null +++ b/ipcmux/lib.go @@ -0,0 +1,94 @@ +package ipcmux + +import ( + "context" + "log" + "net" + "net/http" + "os" + "runtime" + "strings" +) + +const ( + ENV_IPC = "IPC" + ENV_ROOTPID = "ROOT_PID" +) + +var listenNet func(path string) (net.Listener, error) + +var dialNet func(path string) (net.Conn, error) + +func Dial(p string) (net.Conn, error) { + return dialNet(p) +} +func SetName(n string) { + os.Setenv(ENV_IPC, n) +} +func ResolveBinName() string { + + bin := os.Getenv(ENV_IPC) + if bin == "" { + bin = os.Args[0] + + if runtime.GOOS == "windows" { + bin = strings.Replace(bin, "\\", "/", -1) + } + parts := strings.Split(bin, "/") + bin = parts[len(parts)-1] + bin = strings.Replace(bin, ".exe", "", 1) + log.Printf("No IPC env var found, using %s to bind ipc", bin) + } + return bin +} + +func Listen(p string) (net.Listener, error) { + log.Printf("IPC:: Will listen on: [%s]", p) + return listenNet(p) +} + +func NewClient() *http.Client { + ret := &http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, b string) (net.Conn, error) { + ps := strings.Split(b, ":") + return dialNet(ps[0]) + }, + }, + } + return ret +} + +func Serve(mux *http.ServeMux) error { + go CheckRoot() + + bin := ResolveBinName() + + l, err := Listen(bin) + if err != nil { + return err + } + server := http.Server{ + Handler: mux, + } + err = server.Serve(l) + return err +} + +func ServeNamed(n string, mux *http.ServeMux) error { + go CheckRoot() + + l, err := Listen(n) + if err != nil { + return err + } + server := http.Server{ + Handler: mux, + } + err = server.Serve(l) + return err +} + +func ServeDefault() error { + return Serve(http.DefaultServeMux) +} diff --git a/ipcmux/lib_unix.go b/ipcmux/lib_unix.go new file mode 100644 index 0000000..af24449 --- /dev/null +++ b/ipcmux/lib_unix.go @@ -0,0 +1,76 @@ +//go:build !windows +// +build !windows + +package ipcmux + +import ( + "github.com/mitchellh/go-ps" + "log" + "net" + "os" + libpath "path" + "strconv" + "time" +) + +func init() { + + listenNet = func(path string) (net.Listener, error) { + ipc_root := os.Getenv("IPC_ROOT") + if ipc_root == "" { + ipc_root = os.TempDir() + } + + path = "replayme_" + path + path = libpath.Join(ipc_root, path) + err := os.Remove(path) + if err != nil { + if !os.IsNotExist(err) { + log.Printf("Err removing old uds: %s", err.Error()) + } + } + l, err := net.Listen("unix", path) + log.Printf("UnixSocket::%s=>%v", path, err) + return l, err + + } + dialNet = func(path string) (net.Conn, error) { + ipc_root := os.Getenv("IPC_ROOT") + if ipc_root == "" { + ipc_root = os.TempDir() + } + path = "replayme_" + path + path = libpath.Join(ipc_root, path) + conn, err := net.Dial("unix", path) + + return conn, err + } +} + +const ( + PRCTL_SYSCALL = 157 + PR_SET_PDEATHSIG = 1 +) + +func CheckRoot() { + + time.Sleep(time.Second) + rootpid := os.Getenv(ENV_ROOTPID) + rootpidi, err := strconv.Atoi(rootpid) + if err != nil { + log.Printf("No root process found") + return + } + if rootpidi == 0 || rootpidi == os.Getpid() { + log.Printf("%s is set to zero, no monitoring on root will take place", ENV_ROOTPID) + return + } + for { + p, err := ps.FindProcess(rootpidi) + if p == nil || err != nil { + log.Printf("aborting") + os.Exit(1) + } + time.Sleep(time.Second) + } +} diff --git a/ipcmux/lib_windows.go b/ipcmux/lib_windows.go new file mode 100644 index 0000000..5212a1c --- /dev/null +++ b/ipcmux/lib_windows.go @@ -0,0 +1,116 @@ +//go:build windows +// +build windows + +package ipcmux + +import ( + "errors" + "github.com/Microsoft/go-winio" + "log" + "net" + "os" + "strconv" + "strings" + "time" +) + +func init() { + listenNet = func(path string) (net.Listener, error) { + path = strings.Replace(path, "/", "\\", -1) + log.Printf("Listening to: " + path) + + chcon := make(chan bool) + var conn net.Listener + var err error + go func() { + path = "\\\\.\\pipe\\" + path + conn, err = winio.ListenPipe(path, &winio.PipeConfig{ + SecurityDescriptor: "", + MessageMode: false, + InputBufferSize: 0, + OutputBufferSize: 0, + }) + if err != nil { + log.Printf("ipcmux::listenNet: %s", err.Error()) + } + go func() { + go func() { + to := time.Second * time.Duration(3) + fconn, err := winio.DialPipe(path, &to) + if err != nil { + log.Printf("Error on pipe warm up: %s", err.Error()) + } else { + fconn.Close() + } + log.Printf("Pipe warm up done") + }() + }() + chcon <- true + }() + + select { + case <-chcon: + return conn, err + case <-time.After(time.Second * 5): + err = errors.New("Timeout exceeded for Listening: " + path) + return nil, err + } + + return conn, err + } + dialNet = func(path string) (net.Conn, error) { + path = strings.Replace(path, "/", "\\", -1) + path = "\\\\.\\pipe\\" + path + chcon := make(chan bool) + var conn net.Conn + var err error + //Log("Dialing to: " + path) + go func() { + to := time.Second * time.Duration(15) + conn, err = winio.DialPipe(path, &to) + //conn, err = npipe.Dial(path) + chcon <- true + }() + + select { + case <-chcon: + return conn, err + case <-time.After(time.Second * 5): + err = errors.New("Timeout exceeded for conn: " + path) + return nil, err + } + + return conn, err + } +} + +func CheckRoot() { + time.Sleep(time.Second) + rootpid := os.Getenv(ENV_ROOTPID) + rootpidi, err := strconv.Atoi(rootpid) + if err != nil { + log.Printf("No root process found") + } + if rootpidi == 0 || rootpidi == os.Getpid() { + log.Printf("%s is set to zero, no monitoring on root will take place", ENV_ROOTPID) + return + } + + p, err := os.FindProcess(rootpidi) + + if err != nil { + log.Printf("Error finding process: %s", err.Error()) + os.Exit(1) + } + if p == nil { + log.Printf("Process not found.") + os.Exit(1) + } + _, err = p.Wait() + if err != nil { + log.Printf(err.Error()) + } + log.Printf("Parent process %s is gone. Finishing here.", os.Getenv(ENV_ROOTPID)) + os.Exit(0) + +} diff --git a/pubsub/lib.go b/pubsub/lib.go new file mode 100644 index 0000000..38f8773 --- /dev/null +++ b/pubsub/lib.go @@ -0,0 +1,102 @@ +package pubsub + +import ( + "encoding/json" + "fmt" + "github.com/gorilla/websocket" + "go.digitalcircle.com.br/open/replaycli-go/ipcmux" + "log" + "net" + "os" + "os/user" + "strings" + "time" +) + +const XMSGTYPE = "X-MSGTYPE" +const XPUBSUBID = "X-PUBSUBID" + +var PSID = "" +var dialer *websocket.Dialer + +func init() { + dialer = &websocket.Dialer{} + hname, _ := os.Hostname() + pid := os.Getppid() + u, _ := user.Current() + PSID = fmt.Sprintf("psid_%s_%s_%d", hname, u.Username, pid) + + dialer.NetDial = func(network, addr string) (net.Conn, error) { + ps := strings.Split(addr, ":") + c, err := ipcmux.Dial(ps[0]) + return c, err + } +} + +type PSCli struct { + q string + con *websocket.Conn + ch chan []byte + closed bool +} + +func (p *PSCli) GetConn() *websocket.Conn { + if p.con == nil { + + var counter = 0 + for counter < 100 { + con, _, err := dialer.Dial("ws://pubsub/?q="+p.q, nil) + + if err != nil { + log.Printf("Error connecting WS: %s", err.Error()) + counter++ + time.Sleep(time.Second) + continue + } + p.con = con + p.con.SetCloseHandler(func(code int, text string) error { + log.Printf("Connection closed: %d: %s", code, text) + p.con = nil + return nil + }) + break + } + + } + return p.con +} + +func (p *PSCli) Send(i interface{}) error { + bs, err := json.Marshal(i) + if err != nil { + return err + } + err = p.GetConn().WriteMessage(websocket.TextMessage, bs) + if err != nil { + p.con.Close() + p.con = nil + } + return err +} + +func (p *PSCli) Chan() chan []byte { + return p.ch +} + +func (p *PSCli) Read(i interface{}) error { + _, bs, err := p.GetConn().ReadMessage() + if err != nil { + if p.con != nil { + p.con.Close() + p.con = nil + } + return err + } + return json.Unmarshal(bs, i) +} + +func NewCli(q string) (*PSCli, error) { + ret := &PSCli{} + ret.q = q + return ret, nil +} diff --git a/static/static.go b/static/static.go new file mode 100644 index 0000000..a0a8c76 --- /dev/null +++ b/static/static.go @@ -0,0 +1,66 @@ +package static + +import ( + "flag" + "go.digitalcircle.com.br/open/replaycli-go/ipcmux" + "mime" + "net/http" + "os" + "path/filepath" + "strings" +) + +var builtinMimeTypesLower = map[string]string{ + ".css": "text/css; charset=utf-8", + ".gif": "image/gif", + ".htm": "text/html; charset=utf-8", + ".html": "text/html; charset=utf-8", + ".jpg": "image/jpeg", + ".js": "application/javascript", + ".wasm": "application/wasm", + ".pdf": "application/pdf", + ".png": "image/png", + ".svg": "image/svg+xml", + ".xml": "text/xml; charset=utf-8", +} + +func Mime(ext string) string { + if v, ok := builtinMimeTypesLower[ext]; ok { + return v + } + return mime.TypeByExtension(ext) +} + +func ServeParse() { + addr := flag.String("ipc", "samplestatic", "IPC Addr") + root := flag.String("root", ".", "Root dir") + flag.Parse() + Serve(*addr, *root) +} + +func Serve(addr string, root string) { + + ipcmux.SetName(addr) + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + path := strings.Split(r.URL.Path, "?")[0] + if path == "" { + path = "index.html" + } + ext := filepath.Ext(path) + + bs, err := os.ReadFile(filepath.Join(root, path)) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Header().Set("Content-Type", Mime(ext)) + + w.Write(bs) + }) + ipcmux.ServeDefault() +} diff --git a/types/lib.go b/types/lib.go new file mode 100644 index 0000000..da5fb4c --- /dev/null +++ b/types/lib.go @@ -0,0 +1,349 @@ +package types + +import ( + "time" +) + +//region AUTH + +type SecUser struct { + Model + Username string `json:"username"` + Password string `json:"password"` + Email string `json:"email"` + Groups []*SecGroup `json:"groups" gorm:"many2many:sec_user_groups;"` +} + +type SecGroup struct { + Model + Code string `json:"code"` + Name string `json:"name"` + //Users []*SecUser `json:"users" gorm:"many2many:sec_user_groups;"` + Perms []*SecPerm `json:"perms" gorm:"many2many:sec_group_perms;"` +} + +type SecPerm struct { + Model + Code string `json:"code"` + Name string `json:"name"` + Value string `json:"value"` + //Groups []*SecGroup `json:"groups" gorm:"many2many:group_perms;"` +} + +type Session struct { + Model + Username string + SessionID string + Perms string +} + +//endregion AUTH + +type Feature struct { + Model + Name string `json:"name" yaml:"name"` + Desc string `json:"desc" yaml:"desc"` + Repo uint `json:"repo"` + Alias string `json:"alias"` +} + +type Robot struct { + Feature +} + +type App struct { + Feature + IsNative *bool +} + +type Service struct { + Feature + Ipcid string `json:"ipcid"` + Env string `json:"env" yaml:"env"` + Enabled *bool `yaml:"enabled" json:"enabled"` + Image string `yaml:"image" json:"image"` + Delay int `yaml:"delay" json:"delay"` + Pid int `json:"pid" yaml:"-"` + Hostname string `json:"hostname" yaml:"hostname"` + Gui bool `json:"gui" yaml:"gui"` +} + +type Runtime struct { + Feature + Path string `json:"path" yaml:"path"` +} + +type Repo struct { + Model + Feature string `json:"feature" yaml:"feature"` + Name string `json:"name" yaml:"name"` + Url string `json:"repo"` + Alias string `json:"alias"` + Cred uint `json:"cred"` + LocalDir string `json:"local_dir"` + Status string `json:"status"` +} + +type ReplayDescriptor struct { + Feature string `json:"feature" yaml:"feature"` + ReplayID string `json:"replay_id" yaml:"replay_id"` + Name string `json:"name" yaml:"name"` + Desc string `json:"desc" yaml:"desc"` + Runner string `json:"runner" yaml:"runner"` + Main string `json:"main" yaml:"main"` + Args []string `json:"args" yaml:"args"` + Env map[string]string `json:"env" yaml:"env"` + InstallScript string `json:"install_script" yaml:"install_script"` + UninstallScript string `json:"uninstall_script" yaml:"uninstall_script"` + AbortScript string `json:"abort_script" yaml:"abort_script"` + Id string `json:"id" yaml:"-"` + LocalDir string `json:"local_dir" yaml:"-"` + RepoID uint `json:"repo_id" yaml:"-"` + QueueID uint `json:"queue_id" yaml:"-"` + Config map[string]string `json:"config" yaml:"config"` + Alias string `json:"alias" yaml:"alias"` + Autostart bool `json:"autostart" yaml:"autostart"` + Hostname string `json:"hostname" yaml:"hostname"` + Ipcid string `json:"ipcid"` + Gui bool `json:"gui" yaml:"gui"` + Link string `json:"link" yaml:"link"` //This prop allows redirect of repos +} + +type NotifyRequest struct { + Title string + Msg string +} + +type Model struct { + ID uint `gorm:"primarykey" json:"id"` +} + +//type Model simpledb.SimplePersistent + +type Menu struct { + Model + Name string `json:"name"` + Label string `json:"label"` + Tooltip string `json:"tooltip"` + Target string `json:"target"` + Index int `json:"index" gorm:"column:menuindex"` + Enabled *bool `json:"enabled"` +} + +type Config struct { + Model + K string `json:"k"` + V string `json:"v"` + T string `json:"t"` + Repo uint `json:"repo"` +} + +type Credentials struct { + Model + Name string `json:"name"` + User string `json:"user"` + Password string `json:"password"` + Sshkey string `json:"sshkey"` +} + +type Log struct { + Model + Log []byte +} + +type Job struct { + Model + Name string `json:"name"` + Code string `json:"code"` + File string `json:"file"` + Notes string `json:"notes"` +} + +type Route struct { + Model + From string `json:"from"` + Host string `json:"host"` + To string `json:"to"` + Desc string `json:"desc"` + Enabled *bool `json:"enabled"` + Index string `json:"index"` + HeadersJson string `json:"headers_json"` + Permissions string `json:"permissions"` + PermArr []string `json:"-" gorm:"-"` +} + +type Queue struct { + Model + Job uint `json:"job"` + Ready bool `json:"ready"` + Processed bool `json:"processed"` + ProcessedAt time.Time `json:"processed_at"` + Request []byte +} + +//type QueueData struct { +// Model +// Qid uint +// Request []byte +//} + +type CryptoKey struct { + Model + Defkey bool + Name string + Pub []byte + Priv []byte +} + +type Options struct { + ID string `json:"id"` + Value string `json:"value"` +} + +//type Version struct { +// Model +// Job string `json:"job"` +// Ver string `json:"ver"` +// Notes string `json:"notes"` +//} +// +//type VersionData struct { +// Model +// Vid uint `json:"vid"` +// Data []byte `json:"data"` +//} + +type Cron struct { + Model + Cron string `json:"cron"` + JobCode uint `json:"job"` + Enabled *bool `json:"enabled"` +} + +//type Resource struct { +// Model +// Name string `json:"name"` +// Mime string `json:"mime"` +// Len int `json:"len"` +//} +// +//type ResourceContent struct { +// Model +// Resid uint `json:"resid"` +// Content []byte `json:"content"` +//} + +type License struct { + Model + Owner string `json:"owner"` + RobotID string `json:"robot_id"` + MachineID string `json:"machine_id"` + UID string `json:"uid"` + Alias string `json:"alias"` +} + +type KV struct { + ID string `gorm:"primarykey" json:"id"` + V string `json:"v"` +} + +/* +@API +*/ +type VerCheckRequest struct { + Chan string `json:"chan"` + Os string `json:"os"` + Arch string `json:"arch"` + Data map[string]string `json:"data"` +} + +/* +@API +*/ +type VerCheckResponse struct { + Data map[string]string `json:"data"` +} + +/* +@API +*/ +type VerGetRequest struct { + Os string `json:"os"` + Chan string `json:"chan"` + Arch string `json:"arch"` + Id string `json:"id"` +} + +/* +@API +*/ +type VerGetResponse struct { + Data []byte `json:"data"` +} + +type SQLRequest struct { + Sql string `json:"sql"` +} +type SQLResponse struct { + Data interface{} `json:"data"` + Err string `json:"err"` +} + +type SecUserChOwnPassRequest struct { + Oldpass string `json:"oldpass"` + Newpass string `json:"newpass"` +} + +type LoginRequest struct { + Username string + Password string +} + +type RecipeItem struct { + Url string `json:"url" yaml:"url"` + User string `json:"user" yaml:"user"` + Password string `json:"password" yaml:"password"` + Sshkey string `json:"sshkey" yaml:"sshkey"` +} + +type Recipe struct { + User string `json:"user" yaml:"user"` + Password string `json:"password" yaml:"password"` + Sshkey string `json:"sshkey" yaml:"sshkey"` + LicUser string `json:"licuser" yaml:"licuser"` + License string `json:"license" yaml:"license"` + Alias string `json:"alias" yaml:"alias"` + Items []RecipeItem `json:"items" yaml:"items"` +} + +type PubSubMsg struct { + Msg string + Data interface{} +} + +type PubSubMsgRunnerFinishData struct { + Err error + Desc *ReplayDescriptor +} + +type Series struct { + Serie string `json:"serie"` + When time.Time `json:"when"` + Value float64 `json:"value"` + Sec int `json:"sec"` + Min int `json:"min"` + Hour int `json:"hour"` + Day int `json:"day"` + Month int `json:"month"` + Year int `json:"year"` +} + +func (s *Series) SetTime(t time.Time) { + s.When = t + s.Day = t.Day() + s.Hour = t.Hour() + s.Min = t.Minute() + s.Sec = t.Second() + s.Month = int(t.Month()) + s.Year = t.Year() +} diff --git a/util/lib.go b/util/lib.go new file mode 100644 index 0000000..2daa477 --- /dev/null +++ b/util/lib.go @@ -0,0 +1,70 @@ +package util + +import ( + "go.digitalcircle.com.br/open/replaycli-go/constants" + "os" + "strings" +) + +var apikey string + +func init() { + apikey = os.Getenv("REPLAY_APIKEY") +} + +func Addr() string { + ret := os.Getenv("REPLAY_ADDR") + if ret == "" { + ret = "http://localhost:8080" + } else if strings.HasPrefix(ret, ":") { + ret = "http://localhost" + ret + } + + return ret +} + +func DataDir() string { + ret := os.Getenv(constants.ENV_REPLAY_DATADIR) + return ret +} + +func Repo() string { + repo := os.Getenv("REPLAY_REPO") + if repo == "" { + path, _ := os.Getwd() + path = strings.Replace(path, "\\", "/", -1) + pathparts := strings.Split(path, "/") + repo = pathparts[len(pathparts)-1] + + } + return repo +} + +// +//func HttpDo(method string, strurl string, body []byte) (*http.Response, error) { +// cli := http.Client{} +// var err error +// var req *http.Request +// +// if body != nil && len(body) > 0 { +// req, err = http.NewRequest(method, strurl, io.NopCloser(bytes.NewReader(body))) +// +// } else { +// req, err = http.NewRequest(method, strurl, nil) +// } +// if err != nil { +// return nil, err +// } +// req.Header.Set("X-API-KEY", apikey) +// req.Header.Set("Content-Type", "application/json") +// return cli.Do(req) +//} +// +//func HttpDoJson(i interface{}, method string, strurl string, body []byte) (err error) { +// res, err := HttpDo(method, strurl, body) +// if err != nil { +// return +// } +// err = json.NewDecoder(res.Body).Decode(i) +// return +//}