mirror of
https://github.com/XZB-1248/Spark
synced 2025-09-26 20:21:11 +08:00
add: log system
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
/releases
|
||||
/built
|
||||
/tools
|
||||
/logs
|
||||
/.idea
|
||||
/Config.json
|
||||
dist/
|
||||
|
31
API.ZH.md
31
API.ZH.md
@@ -133,13 +133,38 @@ Authorization: Basic WFpCOjEyNDg=
|
||||
|
||||
---
|
||||
|
||||
### 执行命令:`/device/exec`
|
||||
|
||||
参数:`cmd`、`args`以及`device`(设备ID)
|
||||
|
||||
示例:
|
||||
```http request
|
||||
POST http://localhost:8000/api/device/exec HTTP/1.1
|
||||
Host: localhost:8000
|
||||
Content-Length: 116
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.47
|
||||
Origin: http://localhost:8000
|
||||
Referer: http://localhost:8000/
|
||||
|
||||
cmd=taskkill&args=%2Ff%20%2Fim%20regedit.exe&device=bc7e49f8f794f80ffb0032a4ba516c86d76041bf2023e1be6c5dda3b1ee0cf4c
|
||||
```
|
||||
|
||||
```
|
||||
{
|
||||
"code": 0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 获取截屏:`/device/screenshot/get`
|
||||
|
||||
参数:`device`(设备ID)
|
||||
|
||||
如果截屏获取成功,则会直接以图片的形式输出。
|
||||
<br />
|
||||
如果截屏失败,如下响应会被输出(错误信息不一定是这一个)。
|
||||
如果截屏失败,如下响应会被输出(错误信息不唯一)。
|
||||
|
||||
```
|
||||
{
|
||||
@@ -199,7 +224,7 @@ Authorization: Basic WFpCOjEyNDg=
|
||||
<br />
|
||||
如果存在同名文件,则会被**覆盖**!
|
||||
|
||||
Example:
|
||||
示例:
|
||||
```http request
|
||||
POST http://localhost:8000/api/device/file/upload?path=D%3A%5C&file=Test.txt&device=bc7e49f8f794f80ffb0032a4ba516c86d76041bf2023e1be6c5dda3b1ee0cf4c HTTP/1.1
|
||||
Host: localhost:8000
|
||||
@@ -212,7 +237,7 @@ Referer: http://localhost:8000/
|
||||
Hello World.
|
||||
```
|
||||
|
||||
如果文件上传成功,则`code`为`1`。
|
||||
如果文件上传成功,则`code`为`0`。
|
||||
<br />
|
||||
文件`D:\Test.txt`会写入:`Hello World.`。
|
||||
|
||||
|
27
API.md
27
API.md
@@ -20,7 +20,7 @@ Example:
|
||||
Authorization: Basic WFpCOjEyNDg=
|
||||
```
|
||||
|
||||
After basic authentication, server will assign you a `Authorization` cookie.
|
||||
After basic authentication, server will assign you an `Authorization` cookie.
|
||||
<br />
|
||||
You can use this token cookie to authenticate rest of your requests.
|
||||
|
||||
@@ -130,6 +130,31 @@ For example, when you call `/device/restart`, your device will restart.
|
||||
|
||||
---
|
||||
|
||||
### Execute command: `/device/exec`
|
||||
|
||||
Parameters: `cmd`, `args` and `device` (device ID)
|
||||
|
||||
Example:
|
||||
```http request
|
||||
POST http://localhost:8000/api/device/exec HTTP/1.1
|
||||
Host: localhost:8000
|
||||
Content-Length: 116
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.47
|
||||
Origin: http://localhost:8000
|
||||
Referer: http://localhost:8000/
|
||||
|
||||
cmd=taskkill&args=%2Ff%20%2Fim%20regedit.exe&device=bc7e49f8f794f80ffb0032a4ba516c86d76041bf2023e1be6c5dda3b1ee0cf4c
|
||||
```
|
||||
|
||||
```
|
||||
{
|
||||
"code": 0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Take screenshot: `/device/screenshot/get`
|
||||
|
||||
Parameters: `device` (device ID)
|
||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,3 +1,15 @@
|
||||
## v0.1.6
|
||||
|
||||
* Optimize: potential crash problem of explorer.
|
||||
* Add: set config through command line arguments.
|
||||
* Add: log system.
|
||||
|
||||
* 优化:文件管理器可能导致服务崩溃。
|
||||
* 添加:支持通过命令行设置相关配置。
|
||||
* 新增:日志机制。
|
||||
|
||||
|
||||
|
||||
## v0.1.5
|
||||
|
||||
* Optimize: performance of desktop viewer on Windows.
|
||||
|
15
README.ZH.md
15
README.ZH.md
@@ -24,9 +24,11 @@
|
||||
|
||||
**本项目及其源代码和发行版,旨在用于学习和交流。**
|
||||
<br />
|
||||
**禁止用于任何非法用途!**
|
||||
<br />
|
||||
**使用本项目所带来的风险由使用者本人承担。**
|
||||
<br />
|
||||
**作者和开发者不会对你的错误使用而造成的损害承担任何责任。**
|
||||
**作者和开发者不会对你错误使用而造成的损害承担任何责任。**
|
||||
|
||||
**数据无价,在点击任何按钮、输入任何命令之前,请三思。**
|
||||
|
||||
@@ -40,14 +42,19 @@
|
||||
|
||||
* 从 [Releases](https://github.com/XZB-1248/Spark/releases) 页面下载对应系统的可执行文件。
|
||||
* 解压文件,**不要删除**`built`文件夹。
|
||||
* 在目录下创建一个名为`Config.json`的配置文件,修改其中的信息。
|
||||
* 在目录下创建一个名为`config.json`的配置文件,设置好相关的配置信息。
|
||||
|
||||
```json
|
||||
{
|
||||
"listen": ":8000",
|
||||
"salt": "some random string",
|
||||
"salt": "随机字符串(英文数字符号,小于24位)",
|
||||
"auth": {
|
||||
"username": "password"
|
||||
"用户名": "密码(英文数字符号)"
|
||||
},
|
||||
"log": {
|
||||
"level": "info",
|
||||
"path": "./logs",
|
||||
"days": 7
|
||||
}
|
||||
}
|
||||
```
|
||||
|
11
README.md
11
README.md
@@ -26,6 +26,8 @@ server forever.
|
||||
|
||||
**THIS PROJECT, ITS SOURCE CODE, AND ITS RELEASES SHOULD ONLY BE USED FOR EDUCATIONAL PURPOSES.**
|
||||
<br />
|
||||
**ALL ILLEGAL USAGE IS PROHIBITED!**
|
||||
<br />
|
||||
**YOU SHALL USE THIS PROJECT AT YOUR OWN RISK.**
|
||||
<br />
|
||||
**THE AUTHORS AND DEVELOPERS ARE NOT RESPONSIBLE FOR ANY DAMAGE CAUSED BY YOUR MISUSE OF THIS PROJECT.**
|
||||
@@ -41,14 +43,19 @@ Only local installation are available yet.
|
||||
### Local installation
|
||||
* Get prebuilt executable file from [Releases](https://github.com/XZB-1248/Spark/releases) page.
|
||||
* Extract all files and **do not** delete `built` directory.
|
||||
* Create a configuration file named `Config.json` and set your own salt.
|
||||
* Create a configuration file named `config.json` and set your own salt.
|
||||
|
||||
```json
|
||||
{
|
||||
"listen": ":8000",
|
||||
"salt": "some random string",
|
||||
"salt": "some random string length <= 24",
|
||||
"auth": {
|
||||
"username": "password"
|
||||
},
|
||||
"log": {
|
||||
"level": "info",
|
||||
"path": "./logs",
|
||||
"days": 7
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@@ -8,8 +8,6 @@ import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"math/big"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@@ -21,18 +19,18 @@ import (
|
||||
func init() {
|
||||
golog.SetTimeFormat(`2006/01/02 15:04:05`)
|
||||
|
||||
if len(strings.Trim(config.CfgBuffer, "\x19")) == 0 {
|
||||
if len(strings.Trim(config.ConfigBuffer, "\x19")) == 0 {
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert first 2 bytes to int, which is the length of the encrypted config.
|
||||
dataLen := int(big.NewInt(0).SetBytes([]byte(config.CfgBuffer[:2])).Uint64())
|
||||
if dataLen > len(config.CfgBuffer)-2 {
|
||||
dataLen := int(big.NewInt(0).SetBytes([]byte(config.ConfigBuffer[:2])).Uint64())
|
||||
if dataLen > len(config.ConfigBuffer)-2 {
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
cfgBytes := []byte(config.CfgBuffer[2 : 2+dataLen])
|
||||
cfgBytes := utils.StringToBytes(config.ConfigBuffer, 2, 2+dataLen)
|
||||
cfgBytes, err := decrypt(cfgBytes[16:], cfgBytes[:16])
|
||||
if err != nil {
|
||||
os.Exit(0)
|
||||
@@ -49,7 +47,6 @@ func init() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
go http.ListenAndServe(`:6060`, nil)
|
||||
update()
|
||||
core.Start()
|
||||
}
|
||||
|
@@ -18,12 +18,12 @@ type Conn struct {
|
||||
secretHex string
|
||||
}
|
||||
|
||||
const MaxMessageSize = (2 << 15) + 1024
|
||||
|
||||
var WSConn *Conn
|
||||
var Mutex = &sync.Mutex{}
|
||||
var HTTP = CreateClient()
|
||||
|
||||
const MaxMessageSize = 32768 + 1024
|
||||
|
||||
func CreateConn(wsConn *ws.Conn, secret []byte) *Conn {
|
||||
return &Conn{
|
||||
Conn: wsConn,
|
||||
@@ -42,7 +42,7 @@ func (wsConn *Conn) SendData(data []byte) error {
|
||||
if WSConn == nil {
|
||||
return errors.New(`${i18n|wsClosed}`)
|
||||
}
|
||||
wsConn.SetWriteDeadline(Now.Add(5 * time.Second))
|
||||
wsConn.SetWriteDeadline(utils.Now.Add(5 * time.Second))
|
||||
defer wsConn.SetWriteDeadline(time.Time{})
|
||||
return wsConn.WriteMessage(ws.BinaryMessage, data)
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func (wsConn *Conn) SendPack(pack any) error {
|
||||
if WSConn == nil {
|
||||
return errors.New(`${i18n|wsClosed}`)
|
||||
}
|
||||
wsConn.SetWriteDeadline(Now.Add(5 * time.Second))
|
||||
wsConn.SetWriteDeadline(utils.Now.Add(5 * time.Second))
|
||||
defer wsConn.SetWriteDeadline(time.Time{})
|
||||
return wsConn.WriteMessage(ws.BinaryMessage, data)
|
||||
}
|
||||
|
@@ -16,10 +16,10 @@ type Cfg struct {
|
||||
|
||||
// Localhost for my development only.
|
||||
// Shall be commented out when development is done.
|
||||
//var CfgBuffer = "\x00\xcd\xc6\x68\x5d\xf5\x83\x53\x1c\x49\xa2\x35\x7b\x5b\xaf\xf2\x9e\x6d\x74\x00\x95\x23\x73\x00\x77\xa0\xe1\x46\x64\xd2\x33\x2b\x04\xb2\xca\x70\xda\x4b\xed\xec\x43\x6b\xeb\x6e\x10\x53\x6e\x62\x13\x3c\xb1\x0a\xdd\xc0\x48\x2d\x77\xfa\x4a\x9b\x26\xb5\x1b\x50\x62\x05\xcc\xc9\x3b\x22\xf5\x19\x5b\xac\x41\x74\xc9\x9e\x02\x9f\xe8\x75\xce\x3a\xe0\x50\x67\x0f\x81\x01\xca\x47\x0d\xb2\x09\x8b\x74\x6c\xfd\xc5\x73\xf9\x2a\xf0\x13\x52\xb7\x79\xff\xeb\xab\xcd\x9f\xe8\xb7\xae\xff\xa9\x50\xb2\x90\x11\x35\x4d\x94\x6e\x67\x55\x37\x66\x58\x21\xc0\x0d\xab\x3b\x6f\xc4\x00\x56\xd6\x06\xa0\x7e\x73\xdf\x46\x76\xe0\xb3\x89\x0d\xa2\x33\x07\x39\x81\x2b\x59\x30\x24\xc7\x4f\xe9\xb9\xf6\x3c\xb6\x24\xc5\x44\xde\xe6\x66\x66\x92\x49\xe1\x38\x50\xff\xb5\xf3\x20\xb9\x15\x60\x4a\xdf\xba\xd5\xae\x85\x7e\x3f\x8a\xf0\xb8\xf5\x23\x39\xf0\x46\x11\x64\x42\x04\x8c\xf0\x8a\x5e\xc7\x43\xd2\x0c\x89\xd1\xc4\x14\x26\xb1\x67\x64\x28\x77\xf4\xc8\xf3\x51\x69\xba\xf2\xca\xfa\x2f\x11\xe0\x8d\x6c\x4e\x8c\xb7\x28\xf5\x2a\x67\xe3\x8f\xf0\x7f\x79\xc5\xa5\x1a\xb5\xa1\x22\xe9\x55\x61\xdd\xce\x39\x13\x4b\xdd\x19\xf1\x5c\x86\x9b\x16\x89\x45\xba\x16\x68\xfc\x88\x4b\xd5\x13\xa4\x7e\x26\xce\x35\x2d\x42\x4d\x21\xf1\xc3\x6d\xf5\x64\x16\xc9\x05\xed\x9b\x6c\xbf\x26\xe3\xad\x40\x1d\xc6\x64\x03\xb9\xcb\xca\x3c\x62\x5d\x07\x6b\x07\x8b\xa9\x86\x60\x27\x28\xe7\xa3\xc2\x8d\x6f\xc0\x3d\x8e\x14\xa6\xcc\xe0\x50\x51\x22\x20\x6b\x16\x10\xe9\xe0\x4a\xd2\x4e\x77\xc8\xd1\xf7\x60\x4c\xed\xca\x3f\x1e\x13\x0a\x2e\x84\x15\xd3\xf6\x3e\x13\x4e\x68\xaf\xfd\x7a\xd7\x5b\xaa\x5b\x28\x7c\x3f\xb3\xd0\xd0"
|
||||
//var ConfigBuffer = "\x00\xcd\xc6\x68\x5d\xf5\x83\x53\x1c\x49\xa2\x35\x7b\x5b\xaf\xf2\x9e\x6d\x74\x00\x95\x23\x73\x00\x77\xa0\xe1\x46\x64\xd2\x33\x2b\x04\xb2\xca\x70\xda\x4b\xed\xec\x43\x6b\xeb\x6e\x10\x53\x6e\x62\x13\x3c\xb1\x0a\xdd\xc0\x48\x2d\x77\xfa\x4a\x9b\x26\xb5\x1b\x50\x62\x05\xcc\xc9\x3b\x22\xf5\x19\x5b\xac\x41\x74\xc9\x9e\x02\x9f\xe8\x75\xce\x3a\xe0\x50\x67\x0f\x81\x01\xca\x47\x0d\xb2\x09\x8b\x74\x6c\xfd\xc5\x73\xf9\x2a\xf0\x13\x52\xb7\x79\xff\xeb\xab\xcd\x9f\xe8\xb7\xae\xff\xa9\x50\xb2\x90\x11\x35\x4d\x94\x6e\x67\x55\x37\x66\x58\x21\xc0\x0d\xab\x3b\x6f\xc4\x00\x56\xd6\x06\xa0\x7e\x73\xdf\x46\x76\xe0\xb3\x89\x0d\xa2\x33\x07\x39\x81\x2b\x59\x30\x24\xc7\x4f\xe9\xb9\xf6\x3c\xb6\x24\xc5\x44\xde\xe6\x66\x66\x92\x49\xe1\x38\x50\xff\xb5\xf3\x20\xb9\x15\x60\x4a\xdf\xba\xd5\xae\x85\x7e\x3f\x8a\xf0\xb8\xf5\x23\x39\xf0\x46\x11\x64\x42\x04\x8c\xf0\x8a\x5e\xc7\x43\xd2\x0c\x89\xd1\xc4\x14\x26\xb1\x67\x64\x28\x77\xf4\xc8\xf3\x51\x69\xba\xf2\xca\xfa\x2f\x11\xe0\x8d\x6c\x4e\x8c\xb7\x28\xf5\x2a\x67\xe3\x8f\xf0\x7f\x79\xc5\xa5\x1a\xb5\xa1\x22\xe9\x55\x61\xdd\xce\x39\x13\x4b\xdd\x19\xf1\x5c\x86\x9b\x16\x89\x45\xba\x16\x68\xfc\x88\x4b\xd5\x13\xa4\x7e\x26\xce\x35\x2d\x42\x4d\x21\xf1\xc3\x6d\xf5\x64\x16\xc9\x05\xed\x9b\x6c\xbf\x26\xe3\xad\x40\x1d\xc6\x64\x03\xb9\xcb\xca\x3c\x62\x5d\x07\x6b\x07\x8b\xa9\x86\x60\x27\x28\xe7\xa3\xc2\x8d\x6f\xc0\x3d\x8e\x14\xa6\xcc\xe0\x50\x51\x22\x20\x6b\x16\x10\xe9\xe0\x4a\xd2\x4e\x77\xc8\xd1\xf7\x60\x4c\xed\xca\x3f\x1e\x13\x0a\x2e\x84\x15\xd3\xf6\x3e\x13\x4e\x68\xaf\xfd\x7a\xd7\x5b\xaa\x5b\x28\x7c\x3f\xb3\xd0\xd0"
|
||||
|
||||
// None
|
||||
var CfgBuffer = "\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19"
|
||||
var ConfigBuffer = "\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19\x19"
|
||||
|
||||
// COMMIT means this commit hash, help to identify version and self upgrade.
|
||||
var COMMIT = ``
|
||||
|
@@ -91,7 +91,7 @@ func reportWS(wsConn *common.Conn) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
common.WSConn.SetReadDeadline(common.Now.Add(5 * time.Second))
|
||||
common.WSConn.SetReadDeadline(utils.Now.Add(5 * time.Second))
|
||||
_, data, err := common.WSConn.ReadMessage()
|
||||
common.WSConn.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
@@ -116,7 +116,7 @@ func checkUpdate(wsConn *common.Conn) error {
|
||||
return nil
|
||||
}
|
||||
resp, err := common.HTTP.R().
|
||||
SetBody(config.CfgBuffer).
|
||||
SetBody(config.ConfigBuffer).
|
||||
SetQueryParam(`os`, runtime.GOOS).
|
||||
SetQueryParam(`arch`, runtime.GOARCH).
|
||||
SetQueryParam(`commit`, config.COMMIT).
|
||||
|
@@ -11,7 +11,9 @@ import (
|
||||
"Spark/modules"
|
||||
"github.com/kataras/golog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var handlers = map[string]func(pack modules.Packet, wsConn *common.Conn){
|
||||
@@ -40,6 +42,7 @@ var handlers = map[string]func(pack modules.Packet, wsConn *common.Conn){
|
||||
`pingDesktop`: pingDesktop,
|
||||
`killDesktop`: killDesktop,
|
||||
`getDesktop`: getDesktop,
|
||||
`execCommand`: execCommand,
|
||||
}
|
||||
|
||||
func ping(pack modules.Packet, wsConn *common.Conn) {
|
||||
@@ -131,6 +134,8 @@ func initTerminal(pack modules.Packet, wsConn *common.Conn) {
|
||||
err := terminal.InitTerminal(pack)
|
||||
if err != nil {
|
||||
wsConn.SendCallback(modules.Packet{Act: `initTerminal`, Code: 1, Msg: err.Error()}, pack)
|
||||
} else {
|
||||
wsConn.SendCallback(modules.Packet{Act: `initTerminal`, Code: 0}, pack)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +323,8 @@ func initDesktop(pack modules.Packet, wsConn *common.Conn) {
|
||||
err := desktop.InitDesktop(pack)
|
||||
if err != nil {
|
||||
wsConn.SendCallback(modules.Packet{Act: `initDesktop`, Code: 1, Msg: err.Error()}, pack)
|
||||
} else {
|
||||
wsConn.SendCallback(modules.Packet{Act: `initDesktop`, Code: 0}, pack)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,3 +339,34 @@ func killDesktop(pack modules.Packet, wsConn *common.Conn) {
|
||||
func getDesktop(pack modules.Packet, wsConn *common.Conn) {
|
||||
desktop.GetDesktop(pack)
|
||||
}
|
||||
|
||||
func execCommand(pack modules.Packet, wsConn *common.Conn) {
|
||||
var proc *exec.Cmd
|
||||
var cmd, args string
|
||||
if val, ok := pack.Data[`cmd`]; !ok {
|
||||
wsConn.SendCallback(modules.Packet{Code: 1, Msg: `${i18n|invalidParameter}`}, pack)
|
||||
return
|
||||
} else {
|
||||
cmd = val.(string)
|
||||
}
|
||||
if val, ok := pack.Data[`args`]; !ok {
|
||||
wsConn.SendCallback(modules.Packet{Code: 1, Msg: `${i18n|invalidParameter}`}, pack)
|
||||
return
|
||||
} else {
|
||||
args = val.(string)
|
||||
}
|
||||
if len(args) == 0 {
|
||||
proc = exec.Command(cmd)
|
||||
} else {
|
||||
proc = exec.Command(cmd, strings.Split(args, ` `)...)
|
||||
}
|
||||
err := proc.Start()
|
||||
if err != nil {
|
||||
wsConn.SendCallback(modules.Packet{Code: 1, Msg: err.Error()}, pack)
|
||||
} else {
|
||||
wsConn.SendCallback(modules.Packet{Code: 0, Data: map[string]any{
|
||||
`pid`: proc.Process.Pid,
|
||||
}}, pack)
|
||||
proc.Process.Release()
|
||||
}
|
||||
}
|
||||
|
@@ -300,7 +300,7 @@ func InitDesktop(pack modules.Packet) error {
|
||||
desktop := &session{
|
||||
event: pack.Event,
|
||||
rawEvent: rawEvent,
|
||||
lastPack: common.Unix,
|
||||
lastPack: utils.Unix,
|
||||
escape: false,
|
||||
channel: make(chan message, 4),
|
||||
lock: &sync.Mutex{},
|
||||
@@ -345,7 +345,7 @@ func PingDesktop(pack modules.Packet) {
|
||||
return
|
||||
} else {
|
||||
desktop = val.(*session)
|
||||
desktop.lastPack = common.Unix
|
||||
desktop.lastPack = utils.Unix
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -115,17 +115,21 @@ func FetchFile(dir, file, bridge string) error {
|
||||
}
|
||||
|
||||
func getTempFile(dir, file string) (string, os.FileMode) {
|
||||
exists := true
|
||||
tempFile := ``
|
||||
for i := 0; exists; i++ {
|
||||
tempFile = path.Join(dir, file+`.tmp.`+strconv.Itoa(i))
|
||||
stat, err := os.Stat(tempFile)
|
||||
if os.IsNotExist(err) {
|
||||
exists = false
|
||||
fileMode := os.FileMode(0644)
|
||||
origin := path.Join(dir, file)
|
||||
stat, err := os.Stat(origin)
|
||||
if stat != nil {
|
||||
fileMode = stat.Mode()
|
||||
tempFile := ``
|
||||
for i := 0; i < 5; i++ {
|
||||
tempFile = path.Join(dir, file+`.tmp.`+strconv.Itoa(i))
|
||||
stat, err = os.Stat(tempFile)
|
||||
if os.IsNotExist(err) {
|
||||
return tempFile, fileMode
|
||||
}
|
||||
}
|
||||
return tempFile, stat.Mode()
|
||||
}
|
||||
return tempFile, 0644
|
||||
return origin, fileMode
|
||||
}
|
||||
|
||||
func RemoveFiles(files []string) error {
|
||||
|
@@ -5,6 +5,7 @@ package terminal
|
||||
import (
|
||||
"Spark/client/common"
|
||||
"Spark/modules"
|
||||
"Spark/utils"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/creack/pty"
|
||||
@@ -33,7 +34,7 @@ func InitTerminal(pack modules.Packet) error {
|
||||
termSession := &terminal{
|
||||
pty: ptySession,
|
||||
event: pack.Event,
|
||||
lastPack: common.Unix,
|
||||
lastPack: utils.Unix,
|
||||
}
|
||||
terminals.Set(pack.Data[`terminal`].(string), termSession)
|
||||
go func() {
|
||||
@@ -44,7 +45,7 @@ func InitTerminal(pack modules.Packet) error {
|
||||
common.WSConn.SendCallback(modules.Packet{Act: `outputTerminal`, Data: map[string]any{
|
||||
`output`: hex.EncodeToString(buffer),
|
||||
}}, pack)
|
||||
termSession.lastPack = common.Unix
|
||||
termSession.lastPack = utils.Unix
|
||||
if err != nil {
|
||||
common.WSConn.SendCallback(modules.Packet{Act: `quitTerminal`}, pack)
|
||||
break
|
||||
@@ -77,7 +78,7 @@ func InputTerminal(pack modules.Packet) error {
|
||||
}
|
||||
terminal := val.(*terminal)
|
||||
terminal.pty.Write(data)
|
||||
terminal.lastPack = common.Unix
|
||||
terminal.lastPack = utils.Unix
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -140,7 +141,7 @@ func PingTerminal(pack modules.Packet) {
|
||||
return
|
||||
} else {
|
||||
termSession = val.(*terminal)
|
||||
termSession.lastPack = common.Unix
|
||||
termSession.lastPack = utils.Unix
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ package terminal
|
||||
import (
|
||||
"Spark/client/common"
|
||||
"Spark/modules"
|
||||
"Spark/utils"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os/exec"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
type terminal struct {
|
||||
lastPack int64
|
||||
event string
|
||||
stop bool
|
||||
cmd *exec.Cmd
|
||||
stdout *io.ReadCloser
|
||||
stderr *io.ReadCloser
|
||||
@@ -26,7 +28,11 @@ func init() {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
syscall.NewLazyDLL(`kernel32.dll`).NewProc(`SetConsoleCP`).Call(65001)
|
||||
{
|
||||
kernel32 := syscall.NewLazyDLL(`kernel32.dll`)
|
||||
kernel32.NewProc(`SetConsoleCP`).Call(65001)
|
||||
kernel32.NewProc(`SetConsoleOutputCP`).Call(65001)
|
||||
}
|
||||
go healthCheck()
|
||||
}
|
||||
|
||||
@@ -34,34 +40,28 @@ func InitTerminal(pack modules.Packet) error {
|
||||
cmd := exec.Command(getTerminal())
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
cmd.Process.Kill()
|
||||
cmd.Process.Release()
|
||||
return err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
cmd.Process.Kill()
|
||||
cmd.Process.Release()
|
||||
return err
|
||||
}
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
cmd.Process.Kill()
|
||||
cmd.Process.Release()
|
||||
return err
|
||||
}
|
||||
termSession := &terminal{
|
||||
cmd: cmd,
|
||||
stop: false,
|
||||
event: pack.Event,
|
||||
stdout: &stdout,
|
||||
stderr: &stderr,
|
||||
stdin: &stdin,
|
||||
lastPack: common.Unix,
|
||||
lastPack: utils.Unix,
|
||||
}
|
||||
terminals.Set(pack.Data[`terminal`].(string), termSession)
|
||||
|
||||
readSender := func(rc io.ReadCloser) {
|
||||
for {
|
||||
for !termSession.stop {
|
||||
buffer := make([]byte, 512)
|
||||
n, err := rc.Read(buffer)
|
||||
buffer = buffer[:n]
|
||||
@@ -69,8 +69,9 @@ func InitTerminal(pack modules.Packet) error {
|
||||
common.WSConn.SendCallback(modules.Packet{Act: `outputTerminal`, Data: map[string]any{
|
||||
`output`: hex.EncodeToString(buffer),
|
||||
}}, pack)
|
||||
termSession.lastPack = common.Unix
|
||||
termSession.lastPack = utils.Unix
|
||||
if err != nil {
|
||||
termSession.stop = true
|
||||
common.WSConn.SendCallback(modules.Packet{Act: `quitTerminal`}, pack)
|
||||
break
|
||||
}
|
||||
@@ -79,7 +80,12 @@ func InitTerminal(pack modules.Packet) error {
|
||||
go readSender(stdout)
|
||||
go readSender(stderr)
|
||||
|
||||
cmd.Start()
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
termSession.stop = true
|
||||
return err
|
||||
}
|
||||
terminals.Set(pack.Data[`terminal`].(string), termSession)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -105,7 +111,7 @@ func InputTerminal(pack modules.Packet) error {
|
||||
}
|
||||
terminal := val.(*terminal)
|
||||
(*terminal.stdin).Write(data)
|
||||
terminal.lastPack = common.Unix
|
||||
terminal.lastPack = utils.Unix
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -142,7 +148,7 @@ func PingTerminal(pack modules.Packet) {
|
||||
return
|
||||
} else {
|
||||
termSession = val.(*terminal)
|
||||
termSession.lastPack = common.Unix
|
||||
termSession.lastPack = utils.Unix
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,19 +3,19 @@ package modules
|
||||
import "reflect"
|
||||
|
||||
type Packet struct {
|
||||
Code int `json:"code"`
|
||||
Act string `json:"act,omitempty"`
|
||||
Msg string `json:"msg,omitempty"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
Event string `json:"event,omitempty"`
|
||||
Code int `json:"code"`
|
||||
Act string `json:"act,omitempty"`
|
||||
Msg string `json:"msg,omitempty"`
|
||||
Data map[string]any `json:"data,omitempty"`
|
||||
Event string `json:"event,omitempty"`
|
||||
}
|
||||
|
||||
type CommonPack struct {
|
||||
Code int `json:"code"`
|
||||
Act string `json:"act,omitempty"`
|
||||
Msg string `json:"msg,omitempty"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Event string `json:"event,omitempty"`
|
||||
Code int `json:"code"`
|
||||
Act string `json:"act,omitempty"`
|
||||
Msg string `json:"msg,omitempty"`
|
||||
Data any `json:"data,omitempty"`
|
||||
Event string `json:"event,omitempty"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
@@ -55,7 +55,7 @@ type Net struct {
|
||||
Recv uint64 `json:"recv"`
|
||||
}
|
||||
|
||||
func (p *Packet) GetData(key string, t reflect.Kind) (interface{}, bool) {
|
||||
func (p *Packet) GetData(key string, t reflect.Kind) (any, bool) {
|
||||
if p.Data == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
@@ -14,6 +14,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const MaxMessageSize = (2 << 15) + 1024
|
||||
|
||||
var Melody = melody.New()
|
||||
var Devices = cmap.New()
|
||||
|
||||
@@ -67,6 +69,27 @@ func Decrypt(data []byte, session *melody.Session) ([]byte, bool) {
|
||||
return dec, true
|
||||
}
|
||||
|
||||
func GetAddrIP(addr net.Addr) string {
|
||||
switch addr.(type) {
|
||||
case *net.TCPAddr:
|
||||
return addr.(*net.TCPAddr).IP.String()
|
||||
case *net.UDPAddr:
|
||||
return addr.(*net.UDPAddr).IP.String()
|
||||
case *net.IPAddr:
|
||||
return addr.(*net.IPAddr).IP.String()
|
||||
default:
|
||||
return addr.String()
|
||||
}
|
||||
}
|
||||
|
||||
func GetRealIP(ctx *gin.Context) string {
|
||||
addr, ok := ctx.Request.Context().Value(`ClientIP`).(string)
|
||||
if !ok {
|
||||
return GetRemoteAddr(ctx)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
func GetRemoteAddr(ctx *gin.Context) string {
|
||||
if remote, ok := ctx.RemoteIP(); ok {
|
||||
if remote.IsLoopback() {
|
||||
|
123
server/common/log.go
Normal file
123
server/common/log.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"Spark/modules"
|
||||
"Spark/server/config"
|
||||
"Spark/utils"
|
||||
"Spark/utils/melody"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/kataras/golog"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var logWriter *os.File
|
||||
var disposed bool
|
||||
|
||||
func init() {
|
||||
setLogDst := func() {
|
||||
var err error
|
||||
if logWriter != nil {
|
||||
logWriter.Close()
|
||||
}
|
||||
if config.Config.Log.Level == `disable` || disposed {
|
||||
golog.SetOutput(os.Stdout)
|
||||
return
|
||||
}
|
||||
os.Mkdir(config.Config.Log.Path, 0666)
|
||||
now := utils.Now.Add(time.Second)
|
||||
logFile := fmt.Sprintf(`%s/%s.log`, config.Config.Log.Path, now.Format(`2006-01-02`))
|
||||
logWriter, err = os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
golog.Warn(`Failed to open log file: %v`, err)
|
||||
}
|
||||
golog.SetOutput(io.MultiWriter(os.Stdout, logWriter))
|
||||
|
||||
staleDate := time.Unix(now.Unix()-int64(config.Config.Log.Days*86400)-86400, 0)
|
||||
staleLog := fmt.Sprintf(`%s/%s.log`, config.Config.Log.Path, staleDate.Format(`2006-01-02`))
|
||||
os.Remove(staleLog)
|
||||
}
|
||||
setLogDst()
|
||||
go func() {
|
||||
waitSecs := 86400 - (utils.Now.Hour()*3600 + utils.Now.Minute()*60 + utils.Now.Second())
|
||||
<-time.After(time.Duration(waitSecs) * time.Second)
|
||||
setLogDst()
|
||||
for range time.NewTicker(time.Second * 86400).C {
|
||||
setLogDst()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func getLog(ctx any, event, status, msg string, args map[string]any) string {
|
||||
if args == nil {
|
||||
args = map[string]any{}
|
||||
}
|
||||
args[`event`] = event
|
||||
if len(msg) > 0 {
|
||||
args[`msg`] = msg
|
||||
}
|
||||
if len(status) > 0 {
|
||||
args[`status`] = status
|
||||
}
|
||||
if ctx != nil {
|
||||
var connUUID string
|
||||
var targetInfo bool
|
||||
switch ctx.(type) {
|
||||
case *gin.Context:
|
||||
c := ctx.(*gin.Context)
|
||||
args[`from`] = GetRealIP(c)
|
||||
connUUID, targetInfo = c.Request.Context().Value(`ConnUUID`).(string)
|
||||
case *melody.Session:
|
||||
s := ctx.(*melody.Session)
|
||||
args[`from`] = GetAddrIP(s.GetWSConn().UnderlyingConn().RemoteAddr())
|
||||
if deviceConn, ok := args[`deviceConn`]; ok {
|
||||
delete(args, `deviceConn`)
|
||||
connUUID = deviceConn.(*melody.Session).UUID
|
||||
targetInfo = true
|
||||
}
|
||||
}
|
||||
if targetInfo {
|
||||
val, ok := Devices.Get(connUUID)
|
||||
if ok {
|
||||
device := val.(*modules.Device)
|
||||
args[`target`] = map[string]any{
|
||||
`name`: device.Hostname,
|
||||
`ip`: device.WAN,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output, _ := utils.JSON.MarshalToString(args)
|
||||
return output
|
||||
}
|
||||
|
||||
func Info(ctx any, event, status, msg string, args map[string]any) {
|
||||
golog.Infof(getLog(ctx, event, status, msg, args))
|
||||
}
|
||||
|
||||
func Warn(ctx any, event, status, msg string, args map[string]any) {
|
||||
golog.Warnf(getLog(ctx, event, status, msg, args))
|
||||
}
|
||||
|
||||
func Error(ctx any, event, status, msg string, args map[string]any) {
|
||||
golog.Error(getLog(ctx, event, status, msg, args))
|
||||
}
|
||||
|
||||
func Fatal(ctx any, event, status, msg string, args map[string]any) {
|
||||
golog.Fatalf(getLog(ctx, event, status, msg, args))
|
||||
}
|
||||
|
||||
func Debug(ctx any, event, status, msg string, args map[string]any) {
|
||||
golog.Debugf(getLog(ctx, event, status, msg, args))
|
||||
}
|
||||
|
||||
func CloseLog() {
|
||||
disposed = true
|
||||
golog.SetOutput(os.Stdout)
|
||||
if logWriter != nil {
|
||||
logWriter.Close()
|
||||
logWriter = nil
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
package common
|
||||
|
||||
import "time"
|
||||
|
||||
var Now time.Time = time.Now()
|
||||
var Unix int64 = Now.Unix()
|
||||
|
||||
// To prevent call time.Now().Unix() too often.
|
||||
func init() {
|
||||
go func() {
|
||||
for now := range time.NewTicker(time.Second).C {
|
||||
Now = now
|
||||
Unix = now.Unix()
|
||||
}
|
||||
}()
|
||||
}
|
@@ -1,31 +1,112 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"Spark/utils"
|
||||
"bytes"
|
||||
"flag"
|
||||
"github.com/kataras/golog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Cfg struct {
|
||||
Debug struct {
|
||||
Pprof bool `json:"pprof"`
|
||||
Gin bool `json:"gin"`
|
||||
} `json:"debug,omitempty"`
|
||||
Listen string `json:"listen"`
|
||||
Salt string `json:"salt"`
|
||||
Auth map[string]string `json:"auth"`
|
||||
StdSalt []byte `json:"-"`
|
||||
type config struct {
|
||||
Listen string `json:"listen"`
|
||||
Salt string `json:"salt"`
|
||||
Auth map[string]string `json:"auth"`
|
||||
Log *log `json:"log"`
|
||||
SaltBytes []byte `json:"-"`
|
||||
}
|
||||
type log struct {
|
||||
Level string `json:"level"`
|
||||
Path string `json:"path"`
|
||||
Days uint `json:"days"`
|
||||
}
|
||||
|
||||
var Config Cfg
|
||||
var BuiltPath = getBuiltPath()
|
||||
|
||||
// COMMIT is hash of this commit, for auto upgrade.
|
||||
var COMMIT = ``
|
||||
var Config config
|
||||
var BuiltPath = `./built/%v_%v`
|
||||
|
||||
func getBuiltPath() string {
|
||||
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
if err != nil {
|
||||
return `./built/%v_%v`
|
||||
func init() {
|
||||
golog.SetTimeFormat(`2006/01/02 15:04:05`)
|
||||
|
||||
var (
|
||||
err error
|
||||
configData []byte
|
||||
configPath, listen, salt string
|
||||
username, password string
|
||||
logLevel, logPath string
|
||||
logDays uint
|
||||
)
|
||||
flag.StringVar(&configPath, `config`, `config.json`, `config file path, default: config.json`)
|
||||
flag.StringVar(&listen, `listen`, `:8000`, `required, listen address, default: :8000`)
|
||||
flag.StringVar(&salt, `salt`, ``, `required, salt of server`)
|
||||
flag.StringVar(&username, `username`, ``, `username of web interface`)
|
||||
flag.StringVar(&password, `password`, ``, `password of web interface`)
|
||||
flag.StringVar(&logLevel, `log-level`, `info`, `log level, default: info`)
|
||||
flag.StringVar(&logPath, `log-path`, `./logs`, `log file path, default: ./logs`)
|
||||
flag.UintVar(&logDays, `log-days`, 7, `max days of logs, default: 7`)
|
||||
flag.Parse()
|
||||
|
||||
if len(configPath) > 0 {
|
||||
configData, err = os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
configData, err = os.ReadFile(`Config.json`)
|
||||
if err != nil {
|
||||
fatal(map[string]any{
|
||||
`event`: `CONFIG_LOAD`,
|
||||
`status`: `fail`,
|
||||
`msg`: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
err = utils.JSON.Unmarshal(configData, &Config)
|
||||
if err != nil {
|
||||
fatal(map[string]any{
|
||||
`event`: `CONFIG_PARSE`,
|
||||
`status`: `fail`,
|
||||
`msg`: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if Config.Log == nil {
|
||||
Config.Log = &log{
|
||||
Level: `info`,
|
||||
Path: `./logs`,
|
||||
Days: 7,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Config = config{
|
||||
Listen: listen,
|
||||
Salt: salt,
|
||||
Auth: map[string]string{
|
||||
username: password,
|
||||
},
|
||||
Log: &log{
|
||||
Level: logLevel,
|
||||
Path: logPath,
|
||||
Days: logDays,
|
||||
},
|
||||
}
|
||||
}
|
||||
return filepath.Join(dir, `built/%v_%v`)
|
||||
|
||||
if len(Config.Salt) > 24 {
|
||||
fatal(map[string]any{
|
||||
`event`: `CONFIG_PARSE`,
|
||||
`status`: `fail`,
|
||||
`msg`: `length of salt should less than 24`,
|
||||
})
|
||||
return
|
||||
}
|
||||
Config.SaltBytes = []byte(Config.Salt)
|
||||
Config.SaltBytes = append(Config.SaltBytes, bytes.Repeat([]byte{25}, 24)...)
|
||||
Config.SaltBytes = Config.SaltBytes[:24]
|
||||
|
||||
golog.SetLevel(utils.If(len(Config.Log.Level) == 0, `info`, Config.Log.Level))
|
||||
}
|
||||
|
||||
func fatal(args map[string]any) {
|
||||
output, _ := utils.JSON.MarshalToString(args)
|
||||
golog.Fatal(output)
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ package bridge
|
||||
|
||||
import (
|
||||
"Spark/modules"
|
||||
"Spark/server/common"
|
||||
"Spark/utils"
|
||||
"Spark/utils/cmap"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/kataras/golog"
|
||||
@@ -98,7 +98,7 @@ func BridgePush(ctx *gin.Context) {
|
||||
for {
|
||||
eof := false
|
||||
buf := make([]byte, 2<<14)
|
||||
SrcConn.SetReadDeadline(common.Now.Add(5 * time.Second))
|
||||
SrcConn.SetReadDeadline(utils.Now.Add(5 * time.Second))
|
||||
n, err := bridge.Src.Request.Body.Read(buf)
|
||||
if n == 0 {
|
||||
break
|
||||
@@ -109,7 +109,7 @@ func BridgePush(ctx *gin.Context) {
|
||||
break
|
||||
}
|
||||
}
|
||||
DstConn.SetWriteDeadline(common.Now.Add(10 * time.Second))
|
||||
DstConn.SetWriteDeadline(utils.Now.Add(10 * time.Second))
|
||||
_, err = bridge.Dst.Writer.Write(buf[:n])
|
||||
if eof || err != nil {
|
||||
break
|
||||
@@ -152,7 +152,7 @@ func BridgePull(ctx *gin.Context) {
|
||||
for {
|
||||
eof := false
|
||||
buf := make([]byte, 2<<14)
|
||||
SrcConn.SetReadDeadline(common.Now.Add(5 * time.Second))
|
||||
SrcConn.SetReadDeadline(utils.Now.Add(5 * time.Second))
|
||||
n, err := bridge.Src.Request.Body.Read(buf)
|
||||
if n == 0 {
|
||||
break
|
||||
@@ -163,7 +163,7 @@ func BridgePull(ctx *gin.Context) {
|
||||
break
|
||||
}
|
||||
}
|
||||
DstConn.SetWriteDeadline(common.Now.Add(10 * time.Second))
|
||||
DstConn.SetWriteDeadline(utils.Now.Add(10 * time.Second))
|
||||
_, err = bridge.Dst.Writer.Write(buf[:n])
|
||||
if eof || err != nil {
|
||||
break
|
||||
@@ -183,7 +183,7 @@ func BridgePull(ctx *gin.Context) {
|
||||
|
||||
func AddBridge(ext any, uuid string) *Bridge {
|
||||
bridge := &Bridge{
|
||||
creation: common.Unix,
|
||||
creation: utils.Unix,
|
||||
uuid: uuid,
|
||||
using: false,
|
||||
lock: &sync.Mutex{},
|
||||
@@ -195,7 +195,7 @@ func AddBridge(ext any, uuid string) *Bridge {
|
||||
|
||||
func AddBridgeWithSrc(ext any, uuid string, Src *gin.Context) *Bridge {
|
||||
bridge := &Bridge{
|
||||
creation: common.Unix,
|
||||
creation: utils.Unix,
|
||||
uuid: uuid,
|
||||
using: false,
|
||||
lock: &sync.Mutex{},
|
||||
@@ -208,7 +208,7 @@ func AddBridgeWithSrc(ext any, uuid string, Src *gin.Context) *Bridge {
|
||||
|
||||
func AddBridgeWithDst(ext any, uuid string, Dst *gin.Context) *Bridge {
|
||||
bridge := &Bridge{
|
||||
creation: common.Unix,
|
||||
creation: utils.Unix,
|
||||
uuid: uuid,
|
||||
using: false,
|
||||
lock: &sync.Mutex{},
|
||||
|
@@ -13,16 +13,15 @@ import (
|
||||
|
||||
type desktop struct {
|
||||
uuid string
|
||||
event string
|
||||
device string
|
||||
targetConn *melody.Session
|
||||
srcConn *melody.Session
|
||||
deviceConn *melody.Session
|
||||
}
|
||||
|
||||
var desktopSessions = melody.New()
|
||||
|
||||
func init() {
|
||||
desktopSessions.Config.MaxMessageSize = 32768 + 1024
|
||||
desktopSessions.Config.MaxMessageSize = common.MaxMessageSize
|
||||
desktopSessions.HandleConnect(onDesktopConnect)
|
||||
desktopSessions.HandleMessage(onDesktopMessage)
|
||||
desktopSessions.HandleMessageBinary(onDesktopMessage)
|
||||
@@ -59,7 +58,7 @@ func InitDesktop(ctx *gin.Context) {
|
||||
desktopSessions.HandleRequestWithKeys(ctx.Writer, ctx.Request, nil, gin.H{
|
||||
`Secret`: secret,
|
||||
`Device`: device,
|
||||
`LastPack`: common.Unix,
|
||||
`LastPack`: utils.Unix,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -72,7 +71,7 @@ func desktopEventWrapper(desktop *desktop) common.EventCallback {
|
||||
return
|
||||
}
|
||||
if data, ok := pack.Data[`data`]; ok {
|
||||
desktop.targetConn.WriteBinary(*data.(*[]byte))
|
||||
desktop.srcConn.WriteBinary(*data.(*[]byte))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -84,9 +83,16 @@ func desktopEventWrapper(desktop *desktop) common.EventCallback {
|
||||
} else {
|
||||
msg += `${i18n|unknownError}`
|
||||
}
|
||||
sendPack(modules.Packet{Act: `quit`, Msg: msg}, desktop.targetConn)
|
||||
common.RemoveEvent(desktop.event)
|
||||
desktop.targetConn.Close()
|
||||
sendPack(modules.Packet{Act: `quit`, Msg: msg}, desktop.srcConn)
|
||||
common.RemoveEvent(desktop.uuid)
|
||||
desktop.srcConn.Close()
|
||||
common.Warn(desktop.srcConn, `DESKTOP_INIT`, `fail`, msg, map[string]any{
|
||||
`deviceConn`: desktop.deviceConn,
|
||||
})
|
||||
} else {
|
||||
common.Info(desktop.srcConn, `DESKTOP_INIT`, `success`, ``, map[string]any{
|
||||
`deviceConn`: desktop.deviceConn,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -95,9 +101,12 @@ func desktopEventWrapper(desktop *desktop) common.EventCallback {
|
||||
if len(pack.Msg) > 0 {
|
||||
msg = pack.Msg
|
||||
}
|
||||
sendPack(modules.Packet{Act: `quit`, Msg: msg}, desktop.targetConn)
|
||||
common.RemoveEvent(desktop.event)
|
||||
desktop.targetConn.Close()
|
||||
sendPack(modules.Packet{Act: `quit`, Msg: msg}, desktop.srcConn)
|
||||
common.RemoveEvent(desktop.uuid)
|
||||
desktop.srcConn.Close()
|
||||
common.Info(desktop.srcConn, `DESKTOP_QUIT`, `success`, ``, map[string]any{
|
||||
`deviceConn`: desktop.deviceConn,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -122,20 +131,21 @@ func onDesktopConnect(session *melody.Session) {
|
||||
session.Close()
|
||||
return
|
||||
}
|
||||
eventUUID := utils.GetStrUUID()
|
||||
desktopUUID := utils.GetStrUUID()
|
||||
desktop := &desktop{
|
||||
uuid: desktopUUID,
|
||||
event: eventUUID,
|
||||
device: device.(string),
|
||||
targetConn: session,
|
||||
srcConn: session,
|
||||
deviceConn: deviceConn,
|
||||
}
|
||||
session.Set(`Desktop`, desktop)
|
||||
common.AddEvent(desktopEventWrapper(desktop), connUUID, eventUUID)
|
||||
common.AddEvent(desktopEventWrapper(desktop), connUUID, desktopUUID)
|
||||
common.SendPack(modules.Packet{Act: `initDesktop`, Data: gin.H{
|
||||
`desktop`: desktopUUID,
|
||||
}, Event: eventUUID}, deviceConn)
|
||||
}, Event: desktopUUID}, deviceConn)
|
||||
common.Info(desktop.srcConn, `DESKTOP_CONN`, `success`, ``, map[string]any{
|
||||
`deviceConn`: desktop.deviceConn,
|
||||
})
|
||||
}
|
||||
|
||||
func onDesktopMessage(session *melody.Session, data []byte) {
|
||||
@@ -146,7 +156,7 @@ func onDesktopMessage(session *melody.Session, data []byte) {
|
||||
desktop := val.(*desktop)
|
||||
common.SendPack(modules.Packet{Act: `killDesktop`, Data: gin.H{
|
||||
`desktop`: desktop.uuid,
|
||||
}, Event: desktop.event}, desktop.deviceConn)
|
||||
}, Event: desktop.uuid}, desktop.deviceConn)
|
||||
}
|
||||
sendPack(modules.Packet{Code: -1}, session)
|
||||
session.Close()
|
||||
@@ -157,29 +167,33 @@ func onDesktopMessage(session *melody.Session, data []byte) {
|
||||
return
|
||||
}
|
||||
desktop := val.(*desktop)
|
||||
session.Set(`LastPack`, common.Unix)
|
||||
session.Set(`LastPack`, utils.Unix)
|
||||
if pack.Act == `pingDesktop` {
|
||||
common.SendPack(modules.Packet{Act: `pingDesktop`, Data: gin.H{
|
||||
`desktop`: desktop.uuid,
|
||||
}, Event: desktop.event}, desktop.deviceConn)
|
||||
}, Event: desktop.uuid}, desktop.deviceConn)
|
||||
return
|
||||
}
|
||||
if pack.Act == `killDesktop` {
|
||||
common.Info(desktop.srcConn, `DESKTOP_KILL`, `success`, ``, map[string]any{
|
||||
`deviceConn`: desktop.deviceConn,
|
||||
})
|
||||
common.SendPack(modules.Packet{Act: `killDesktop`, Data: gin.H{
|
||||
`desktop`: desktop.uuid,
|
||||
}, Event: desktop.event}, desktop.deviceConn)
|
||||
}, Event: desktop.uuid}, desktop.deviceConn)
|
||||
return
|
||||
}
|
||||
if pack.Act == `getDesktop` {
|
||||
common.SendPack(modules.Packet{Act: `getDesktop`, Data: gin.H{
|
||||
`desktop`: desktop.uuid,
|
||||
}, Event: desktop.event}, desktop.deviceConn)
|
||||
}, Event: desktop.uuid}, desktop.deviceConn)
|
||||
return
|
||||
}
|
||||
session.Close()
|
||||
}
|
||||
|
||||
func onDesktopDisconnect(session *melody.Session) {
|
||||
common.Info(session, `DESKTOP_CLOSE`, `success`, ``, nil)
|
||||
val, ok := session.Get(`Desktop`)
|
||||
if !ok {
|
||||
return
|
||||
@@ -190,8 +204,8 @@ func onDesktopDisconnect(session *melody.Session) {
|
||||
}
|
||||
common.SendPack(modules.Packet{Act: `killDesktop`, Data: gin.H{
|
||||
`desktop`: desktop.uuid,
|
||||
}, Event: desktop.event}, desktop.deviceConn)
|
||||
common.RemoveEvent(desktop.event)
|
||||
}, Event: desktop.uuid}, desktop.deviceConn)
|
||||
common.RemoveEvent(desktop.uuid)
|
||||
session.Set(`Desktop`, nil)
|
||||
desktop = nil
|
||||
}
|
||||
@@ -224,7 +238,7 @@ func CloseSessionsByDevice(deviceID string) {
|
||||
return true
|
||||
}
|
||||
if desktop.device == deviceID {
|
||||
sendPack(modules.Packet{Act: `quit`, Msg: `${i18n|desktopSessionClosed}`}, desktop.targetConn)
|
||||
sendPack(modules.Packet{Act: `quit`, Msg: `${i18n|desktopSessionClosed}`}, desktop.srcConn)
|
||||
queue = append(queue, session)
|
||||
return false
|
||||
}
|
||||
|
@@ -27,16 +27,29 @@ func RemoveDeviceFiles(ctx *gin.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if len(form.Files) == 0 {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||
return
|
||||
}
|
||||
trigger := utils.GetStrUUID()
|
||||
common.SendPackByUUID(modules.Packet{Code: 0, Act: `removeFiles`, Data: gin.H{`files`: form.Files}, Event: trigger}, target)
|
||||
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
||||
if p.Code != 0 {
|
||||
common.Warn(ctx, `REMOVE_FILES`, `fail`, p.Msg, map[string]any{
|
||||
`files`: form.Files,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||
} else {
|
||||
common.Info(ctx, `REMOVE_FILES`, `success`, ``, map[string]any{
|
||||
`files`: form.Files,
|
||||
})
|
||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||
}
|
||||
}, target, trigger, 5*time.Second)
|
||||
if !ok {
|
||||
common.Warn(ctx, `REMOVE_FILES`, `fail`, `timeout`, map[string]any{
|
||||
`files`: form.Files,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||
}
|
||||
}
|
||||
@@ -124,11 +137,14 @@ func GetDeviceFiles(ctx *gin.Context) {
|
||||
wait := make(chan bool)
|
||||
called := false
|
||||
common.AddEvent(func(p modules.Packet, _ *melody.Session) {
|
||||
wait <- false
|
||||
called = true
|
||||
bridge.RemoveBridge(bridgeID)
|
||||
common.RemoveEvent(trigger)
|
||||
common.Warn(ctx, `READ_FILES`, `fail`, p.Msg, map[string]any{
|
||||
`files`: form.Files,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||
wait <- false
|
||||
}, target, trigger)
|
||||
instance := bridge.AddBridgeWithDst(nil, bridgeID, ctx)
|
||||
instance.OnPush = func(bridge *bridge.Bridge) {
|
||||
@@ -177,6 +193,11 @@ func GetDeviceFiles(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
instance.OnFinish = func(bridge *bridge.Bridge) {
|
||||
if called {
|
||||
common.Info(ctx, `READ_FILES`, `success`, ``, map[string]any{
|
||||
`files`: form.Files,
|
||||
})
|
||||
}
|
||||
wait <- false
|
||||
}
|
||||
select {
|
||||
@@ -185,6 +206,9 @@ func GetDeviceFiles(ctx *gin.Context) {
|
||||
if !called {
|
||||
bridge.RemoveBridge(bridgeID)
|
||||
common.RemoveEvent(trigger)
|
||||
common.Warn(ctx, `READ_FILES`, `fail`, `timeout`, map[string]any{
|
||||
`files`: form.Files,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||
} else {
|
||||
<-wait
|
||||
@@ -203,6 +227,10 @@ func GetDeviceTextFile(ctx *gin.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if len(form.File) == 0 {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||
return
|
||||
}
|
||||
bridgeID := utils.GetStrUUID()
|
||||
trigger := utils.GetStrUUID()
|
||||
common.SendPackByUUID(modules.Packet{Code: 0, Act: `uploadTextFile`, Data: gin.H{
|
||||
@@ -212,11 +240,14 @@ func GetDeviceTextFile(ctx *gin.Context) {
|
||||
wait := make(chan bool)
|
||||
called := false
|
||||
common.AddEvent(func(p modules.Packet, _ *melody.Session) {
|
||||
wait <- false
|
||||
called = true
|
||||
bridge.RemoveBridge(bridgeID)
|
||||
common.RemoveEvent(trigger)
|
||||
common.Warn(ctx, `READ_TEXT_FILE`, `fail`, p.Msg, map[string]any{
|
||||
`file`: form.File,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||
wait <- false
|
||||
}, target, trigger)
|
||||
instance := bridge.AddBridgeWithDst(nil, bridgeID, ctx)
|
||||
instance.OnPush = func(bridge *bridge.Bridge) {
|
||||
@@ -239,6 +270,11 @@ func GetDeviceTextFile(ctx *gin.Context) {
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
instance.OnFinish = func(bridge *bridge.Bridge) {
|
||||
if called {
|
||||
common.Info(ctx, `READ_TEXT_FILE`, `success`, ``, map[string]any{
|
||||
`file`: form.File,
|
||||
})
|
||||
}
|
||||
wait <- false
|
||||
}
|
||||
select {
|
||||
@@ -247,6 +283,9 @@ func GetDeviceTextFile(ctx *gin.Context) {
|
||||
if !called {
|
||||
bridge.RemoveBridge(bridgeID)
|
||||
common.RemoveEvent(trigger)
|
||||
common.Warn(ctx, `READ_TEXT_FILE`, `fail`, `timeout`, map[string]any{
|
||||
`file`: form.File,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||
} else {
|
||||
<-wait
|
||||
@@ -266,18 +305,28 @@ func UploadToDevice(ctx *gin.Context) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if len(form.File) == 0 || len(form.Path) == 0 {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||
return
|
||||
}
|
||||
bridgeID := utils.GetStrUUID()
|
||||
trigger := utils.GetStrUUID()
|
||||
wait := make(chan bool)
|
||||
called := false
|
||||
response := false
|
||||
fileDest := path.Join(form.Path, form.File)
|
||||
fileSize := ctx.Request.ContentLength
|
||||
common.AddEvent(func(p modules.Packet, _ *melody.Session) {
|
||||
wait <- false
|
||||
called = true
|
||||
response = true
|
||||
bridge.RemoveBridge(bridgeID)
|
||||
common.RemoveEvent(trigger)
|
||||
common.Warn(ctx, `UPLOAD_FILE`, `fail`, p.Msg, map[string]any{
|
||||
`dest`: fileDest,
|
||||
`size`: fileSize,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||
wait <- false
|
||||
}, target, trigger)
|
||||
instance := bridge.AddBridgeWithSrc(nil, bridgeID, ctx)
|
||||
instance.OnPull = func(bridge *bridge.Bridge) {
|
||||
@@ -293,6 +342,12 @@ func UploadToDevice(ctx *gin.Context) {
|
||||
dst.Header(`Content-Disposition`, fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, form.File, url.PathEscape(form.File)))
|
||||
}
|
||||
instance.OnFinish = func(bridge *bridge.Bridge) {
|
||||
if called {
|
||||
common.Info(ctx, `UPLOAD_FILE`, `success`, ``, map[string]any{
|
||||
`dest`: fileDest,
|
||||
`size`: fileSize,
|
||||
})
|
||||
}
|
||||
wait <- false
|
||||
}
|
||||
common.SendPackByUUID(modules.Packet{Code: 0, Act: `fetchFile`, Data: gin.H{
|
||||
@@ -310,6 +365,10 @@ func UploadToDevice(ctx *gin.Context) {
|
||||
bridge.RemoveBridge(bridgeID)
|
||||
common.RemoveEvent(trigger)
|
||||
if !response {
|
||||
common.Warn(ctx, `UPLOAD_FILE`, `fail`, `timeout`, map[string]any{
|
||||
`dest`: fileDest,
|
||||
`size`: fileSize,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||
}
|
||||
} else {
|
||||
|
@@ -86,7 +86,7 @@ func GenerateClient(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
clientUUID := utils.GetUUID()
|
||||
clientKey, err := common.EncAES(clientUUID, config.Config.StdSalt)
|
||||
clientKey, err := common.EncAES(clientUUID, config.Config.SaltBytes)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: `${i18n|configGenerateFailed}`})
|
||||
return
|
||||
|
@@ -29,6 +29,7 @@ func InitRouter(ctx *gin.RouterGroup) {
|
||||
group.POST(`/device/file/list`, file.ListDeviceFiles)
|
||||
group.POST(`/device/file/text`, file.GetDeviceTextFile)
|
||||
group.POST(`/device/file/get`, file.GetDeviceFiles)
|
||||
group.POST(`/device/exec`, utility.ExecDeviceCmd)
|
||||
group.POST(`/device/list`, utility.GetDevices)
|
||||
group.POST(`/device/:act`, utility.CallDevice)
|
||||
group.POST(`/client/check`, generate.CheckClient)
|
||||
|
@@ -46,11 +46,20 @@ func KillDeviceProcess(ctx *gin.Context) {
|
||||
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
||||
if p.Code != 0 {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||
common.Warn(ctx, `KILL_PROCESS`, `fail`, p.Msg, map[string]any{
|
||||
`pid`: form.Pid,
|
||||
})
|
||||
} else {
|
||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||
common.Info(ctx, `KILL_PROCESS`, `success`, ``, map[string]any{
|
||||
`pid`: form.Pid,
|
||||
})
|
||||
}
|
||||
}, target, trigger, 5*time.Second)
|
||||
if !ok {
|
||||
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||
common.Warn(ctx, `KILL_PROCESS`, `fail`, `timeout`, map[string]any{
|
||||
`pid`: form.Pid,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -24,11 +24,12 @@ func GetScreenshot(ctx *gin.Context) {
|
||||
called := false
|
||||
common.SendPackByUUID(modules.Packet{Code: 0, Act: `screenshot`, Data: gin.H{`bridge`: bridgeID}, Event: trigger}, target)
|
||||
common.AddEvent(func(p modules.Packet, _ *melody.Session) {
|
||||
wait <- false
|
||||
called = true
|
||||
bridge.RemoveBridge(bridgeID)
|
||||
common.RemoveEvent(trigger)
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||
common.Warn(ctx, `TAKE_SCREENSHOT`, `fail`, p.Msg, nil)
|
||||
wait <- false
|
||||
}, target, trigger)
|
||||
instance := bridge.AddBridgeWithDst(nil, bridgeID, ctx)
|
||||
instance.OnPush = func(bridge *bridge.Bridge) {
|
||||
@@ -37,6 +38,9 @@ func GetScreenshot(ctx *gin.Context) {
|
||||
ctx.Header(`Content-Type`, `image/png`)
|
||||
}
|
||||
instance.OnFinish = func(bridge *bridge.Bridge) {
|
||||
if called {
|
||||
common.Info(ctx, `TAKE_SCREENSHOT`, `success`, ``, nil)
|
||||
}
|
||||
wait <- false
|
||||
}
|
||||
select {
|
||||
@@ -46,6 +50,7 @@ func GetScreenshot(ctx *gin.Context) {
|
||||
bridge.RemoveBridge(bridgeID)
|
||||
common.RemoveEvent(trigger)
|
||||
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||
common.Warn(ctx, `TAKE_SCREENSHOT`, `fail`, `timeout`, nil)
|
||||
} else {
|
||||
<-wait
|
||||
}
|
||||
|
@@ -9,11 +9,11 @@ import (
|
||||
"encoding/hex"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type terminal struct {
|
||||
uuid string
|
||||
event string
|
||||
device string
|
||||
session *melody.Session
|
||||
deviceConn *melody.Session
|
||||
@@ -58,7 +58,7 @@ func InitTerminal(ctx *gin.Context) {
|
||||
terminalSessions.HandleRequestWithKeys(ctx.Writer, ctx.Request, nil, gin.H{
|
||||
`Secret`: secret,
|
||||
`Device`: device,
|
||||
`LastPack`: common.Unix,
|
||||
`LastPack`: utils.Unix,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -75,8 +75,15 @@ func terminalEventWrapper(terminal *terminal) common.EventCallback {
|
||||
msg += `${i18n|unknownError}`
|
||||
}
|
||||
sendPack(modules.Packet{Act: `warn`, Msg: msg}, terminal.session)
|
||||
common.RemoveEvent(terminal.event)
|
||||
common.RemoveEvent(terminal.uuid)
|
||||
terminal.session.Close()
|
||||
common.Warn(terminal.session, `TERMINAL_INIT`, `fail`, msg, map[string]any{
|
||||
`deviceConn`: terminal.deviceConn,
|
||||
})
|
||||
} else {
|
||||
common.Info(terminal.session, `TERMINAL_INIT`, `success`, ``, map[string]any{
|
||||
`deviceConn`: terminal.deviceConn,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -86,8 +93,11 @@ func terminalEventWrapper(terminal *terminal) common.EventCallback {
|
||||
msg = pack.Msg
|
||||
}
|
||||
sendPack(modules.Packet{Act: `warn`, Msg: msg}, terminal.session)
|
||||
common.RemoveEvent(terminal.event)
|
||||
common.RemoveEvent(terminal.uuid)
|
||||
terminal.session.Close()
|
||||
common.Info(terminal.session, `TERMINAL_QUIT`, ``, msg, map[string]any{
|
||||
`deviceConn`: terminal.deviceConn,
|
||||
})
|
||||
return
|
||||
}
|
||||
if pack.Act == `outputTerminal` {
|
||||
@@ -123,19 +133,20 @@ func onTerminalConnect(session *melody.Session) {
|
||||
return
|
||||
}
|
||||
termUUID := utils.GetStrUUID()
|
||||
eventUUID := utils.GetStrUUID()
|
||||
terminal := &terminal{
|
||||
uuid: termUUID,
|
||||
event: eventUUID,
|
||||
device: device.(string),
|
||||
session: session,
|
||||
deviceConn: deviceConn,
|
||||
}
|
||||
session.Set(`Terminal`, terminal)
|
||||
common.AddEvent(terminalEventWrapper(terminal), connUUID, eventUUID)
|
||||
common.AddEvent(terminalEventWrapper(terminal), connUUID, termUUID)
|
||||
common.SendPack(modules.Packet{Act: `initTerminal`, Data: gin.H{
|
||||
`terminal`: termUUID,
|
||||
}, Event: eventUUID}, deviceConn)
|
||||
}, Event: termUUID}, deviceConn)
|
||||
common.Info(terminal.session, `TERMINAL_CONN`, `success`, ``, map[string]any{
|
||||
`deviceConn`: terminal.deviceConn,
|
||||
})
|
||||
}
|
||||
|
||||
func onTerminalMessage(session *melody.Session, data []byte) {
|
||||
@@ -151,16 +162,21 @@ func onTerminalMessage(session *melody.Session, data []byte) {
|
||||
return
|
||||
}
|
||||
terminal := val.(*terminal)
|
||||
session.Set(`LastPack`, common.Unix)
|
||||
session.Set(`LastPack`, utils.Unix)
|
||||
if pack.Act == `inputTerminal` {
|
||||
if pack.Data == nil {
|
||||
return
|
||||
}
|
||||
if input, ok := pack.Data[`input`]; ok {
|
||||
if input, ok := pack.GetData(`input`, reflect.String); ok {
|
||||
rawInput, _ := hex.DecodeString(input.(string))
|
||||
common.Info(terminal.session, `TERMINAL_INPUT`, ``, ``, map[string]any{
|
||||
`deviceConn`: terminal.deviceConn,
|
||||
`input`: utils.BytesToString(rawInput),
|
||||
})
|
||||
common.SendPack(modules.Packet{Act: `inputTerminal`, Data: gin.H{
|
||||
`input`: input,
|
||||
`terminal`: terminal.uuid,
|
||||
}, Event: terminal.event}, terminal.deviceConn)
|
||||
}, Event: terminal.uuid}, terminal.deviceConn)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -174,7 +190,7 @@ func onTerminalMessage(session *melody.Session, data []byte) {
|
||||
`width`: width,
|
||||
`height`: height,
|
||||
`terminal`: terminal.uuid,
|
||||
}, Event: terminal.event}, terminal.deviceConn)
|
||||
}, Event: terminal.uuid}, terminal.deviceConn)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -183,9 +199,12 @@ func onTerminalMessage(session *melody.Session, data []byte) {
|
||||
if pack.Data == nil {
|
||||
return
|
||||
}
|
||||
common.Info(terminal.session, `TERMINAL_KILL`, `success`, ``, map[string]any{
|
||||
`deviceConn`: terminal.deviceConn,
|
||||
})
|
||||
common.SendPack(modules.Packet{Act: `killTerminal`, Data: gin.H{
|
||||
`terminal`: terminal.uuid,
|
||||
}, Event: terminal.event}, terminal.deviceConn)
|
||||
}, Event: terminal.uuid}, terminal.deviceConn)
|
||||
return
|
||||
}
|
||||
if pack.Act == `ping` {
|
||||
@@ -194,13 +213,14 @@ func onTerminalMessage(session *melody.Session, data []byte) {
|
||||
}
|
||||
common.SendPack(modules.Packet{Act: `pingTerminal`, Data: gin.H{
|
||||
`terminal`: terminal.uuid,
|
||||
}, Event: terminal.event}, terminal.deviceConn)
|
||||
}, Event: terminal.uuid}, terminal.deviceConn)
|
||||
return
|
||||
}
|
||||
session.Close()
|
||||
}
|
||||
|
||||
func onTerminalDisconnect(session *melody.Session) {
|
||||
common.Info(session, `TERMINAL_CLOSE`, `success`, ``, nil)
|
||||
val, ok := session.Get(`Terminal`)
|
||||
if !ok {
|
||||
return
|
||||
@@ -211,8 +231,8 @@ func onTerminalDisconnect(session *melody.Session) {
|
||||
}
|
||||
common.SendPack(modules.Packet{Act: `killTerminal`, Data: gin.H{
|
||||
`terminal`: terminal.uuid,
|
||||
}, Event: terminal.event}, terminal.deviceConn)
|
||||
common.RemoveEvent(terminal.event)
|
||||
}, Event: terminal.uuid}, terminal.deviceConn)
|
||||
common.RemoveEvent(terminal.uuid)
|
||||
session.Set(`Terminal`, nil)
|
||||
terminal = nil
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"Spark/utils"
|
||||
"Spark/utils/melody"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
@@ -40,6 +41,7 @@ func CheckForm(ctx *gin.Context, form any) (string, bool) {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadGateway, modules.Packet{Code: 1, Msg: `${i18n|deviceNotExists}`})
|
||||
return ``, false
|
||||
}
|
||||
ctx.Request = ctx.Request.WithContext(context.WithValue(ctx.Request.Context(), `ConnUUID`, connUUID))
|
||||
return connUUID, true
|
||||
}
|
||||
|
||||
@@ -88,6 +90,12 @@ func OnDevicePack(data []byte, session *melody.Session) error {
|
||||
common.Devices.Remove(exSession)
|
||||
}
|
||||
common.Devices.Set(session.UUID, &pack.Device)
|
||||
common.Info(nil, `CLIENT_ONLINE`, ``, ``, map[string]any{
|
||||
`device`: map[string]any{
|
||||
`name`: pack.Device.Hostname,
|
||||
`ip`: pack.Device.WAN,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
val, ok := common.Devices.Get(session.UUID)
|
||||
if ok {
|
||||
@@ -111,36 +119,84 @@ func CheckUpdate(ctx *gin.Context) {
|
||||
Commit string `form:"commit" binding:"required"`
|
||||
}
|
||||
if err := ctx.ShouldBind(&form); err != nil {
|
||||
golog.Error(err)
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||
return
|
||||
}
|
||||
if form.Commit == config.COMMIT {
|
||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||
common.Warn(ctx, `CLIENT_UPDATE`, `success`, `latest`, map[string]any{
|
||||
`client`: map[string]any{
|
||||
`os`: form.OS,
|
||||
`arch`: form.Arch,
|
||||
`commit`: form.Commit,
|
||||
},
|
||||
`server`: config.COMMIT,
|
||||
})
|
||||
return
|
||||
}
|
||||
tpl, err := os.Open(fmt.Sprintf(config.BuiltPath, form.OS, form.Arch))
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `${i18n|osOrArchNotPrebuilt}`})
|
||||
common.Warn(ctx, `CLIENT_UPDATE`, `fail`, `no prebuild asset`, map[string]any{
|
||||
`client`: map[string]any{
|
||||
`os`: form.OS,
|
||||
`arch`: form.Arch,
|
||||
`commit`: form.Commit,
|
||||
},
|
||||
`server`: config.COMMIT,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const MaxBodySize = 384 // This is size of client config buffer.
|
||||
if ctx.Request.ContentLength > MaxBodySize {
|
||||
ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1})
|
||||
common.Warn(ctx, `CLIENT_UPDATE`, `fail`, `config too large`, map[string]any{
|
||||
`client`: map[string]any{
|
||||
`os`: form.OS,
|
||||
`arch`: form.Arch,
|
||||
`commit`: form.Commit,
|
||||
},
|
||||
`server`: config.COMMIT,
|
||||
})
|
||||
return
|
||||
}
|
||||
body, err := ctx.GetRawData()
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: 1})
|
||||
common.Warn(ctx, `CLIENT_UPDATE`, `fail`, `read config fail`, map[string]any{
|
||||
`client`: map[string]any{
|
||||
`os`: form.OS,
|
||||
`arch`: form.Arch,
|
||||
`commit`: form.Commit,
|
||||
},
|
||||
`server`: config.COMMIT,
|
||||
})
|
||||
return
|
||||
}
|
||||
session := common.CheckClientReq(ctx)
|
||||
if session == nil {
|
||||
ctx.AbortWithStatusJSON(http.StatusUnauthorized, modules.Packet{Code: 1})
|
||||
common.Warn(ctx, `CLIENT_UPDATE`, `fail`, `check config fail`, map[string]any{
|
||||
`client`: map[string]any{
|
||||
`os`: form.OS,
|
||||
`arch`: form.Arch,
|
||||
`commit`: form.Commit,
|
||||
},
|
||||
`server`: config.COMMIT,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
common.Info(ctx, `CLIENT_UPDATE`, `success`, `updating`, map[string]any{
|
||||
`client`: map[string]any{
|
||||
`os`: form.OS,
|
||||
`arch`: form.Arch,
|
||||
`commit`: form.Commit,
|
||||
},
|
||||
`server`: config.COMMIT,
|
||||
})
|
||||
|
||||
ctx.Header(`Spark-Commit`, config.COMMIT)
|
||||
ctx.Header(`Accept-Ranges`, `none`)
|
||||
ctx.Header(`Content-Transfer-Encoding`, `binary`)
|
||||
@@ -171,6 +227,46 @@ func CheckUpdate(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// ExecDeviceCmd execute command on device.
|
||||
func ExecDeviceCmd(ctx *gin.Context) {
|
||||
var form struct {
|
||||
Cmd string `json:"cmd" yaml:"cmd" form:"cmd" binding:"required"`
|
||||
Args string `json:"args" yaml:"args" form:"args"`
|
||||
}
|
||||
target, ok := CheckForm(ctx, &form)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if len(form.Cmd) == 0 {
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||
return
|
||||
}
|
||||
trigger := utils.GetStrUUID()
|
||||
common.SendPackByUUID(modules.Packet{Code: 0, Act: `execCommand`, Data: gin.H{`cmd`: form.Cmd, `args`: form.Args}, Event: trigger}, target)
|
||||
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
||||
if p.Code != 0 {
|
||||
common.Warn(ctx, `EXEC_COMMAND`, `fail`, p.Msg, map[string]any{
|
||||
`cmd`: form.Cmd,
|
||||
`args`: form.Args,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||
} else {
|
||||
common.Info(ctx, `EXEC_COMMAND`, `success`, ``, map[string]any{
|
||||
`cmd`: form.Cmd,
|
||||
`args`: form.Args,
|
||||
})
|
||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||
}
|
||||
}, target, trigger, 5*time.Second)
|
||||
if !ok {
|
||||
common.Warn(ctx, `EXEC_COMMAND`, `fail`, `timeout`, map[string]any{
|
||||
`cmd`: form.Cmd,
|
||||
`args`: form.Args,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusGatewayTimeout, modules.Packet{Code: 1, Msg: `${i18n|responseTimeout}`})
|
||||
}
|
||||
}
|
||||
|
||||
// GetDevices will return all info about all clients.
|
||||
func GetDevices(ctx *gin.Context) {
|
||||
devices := map[string]any{}
|
||||
@@ -199,6 +295,9 @@ func CallDevice(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
common.Warn(ctx, `CALL_DEVICE`, `fail`, `invalid act`, map[string]any{
|
||||
`act`: act,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `${i18n|invalidParameter}`})
|
||||
return
|
||||
}
|
||||
@@ -211,14 +310,23 @@ func CallDevice(ctx *gin.Context) {
|
||||
common.SendPackByUUID(modules.Packet{Act: act, Event: trigger}, connUUID)
|
||||
ok = common.AddEventOnce(func(p modules.Packet, _ *melody.Session) {
|
||||
if p.Code != 0 {
|
||||
common.Warn(ctx, `CALL_DEVICE`, `fail`, p.Msg, map[string]any{
|
||||
`act`: act,
|
||||
})
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError, modules.Packet{Code: 1, Msg: p.Msg})
|
||||
} else {
|
||||
common.Info(ctx, `CALL_DEVICE`, `success`, ``, map[string]any{
|
||||
`act`: act,
|
||||
})
|
||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||
}
|
||||
}, connUUID, trigger, 5*time.Second)
|
||||
if !ok {
|
||||
//This means the client is offline.
|
||||
//So we take this as a success.
|
||||
common.Info(ctx, `CALL_DEVICE`, `success`, ``, map[string]any{
|
||||
`act`: act,
|
||||
})
|
||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||
}
|
||||
}
|
||||
|
133
server/main.go
133
server/main.go
@@ -28,51 +28,24 @@ import (
|
||||
"Spark/utils/melody"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-contrib/pprof"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/kataras/golog"
|
||||
)
|
||||
|
||||
var blocked = cmap.New()
|
||||
var lastRequest = time.Now().Unix()
|
||||
|
||||
func main() {
|
||||
golog.SetTimeFormat(`2006/01/02 15:04:05`)
|
||||
|
||||
data, err := os.ReadFile(`./Config.json`)
|
||||
if err != nil {
|
||||
golog.Fatal(`Failed to read config file: `, err)
|
||||
return
|
||||
}
|
||||
err = utils.JSON.Unmarshal(data, &config.Config)
|
||||
if err != nil {
|
||||
golog.Fatal(`Failed to parse config file: `, err)
|
||||
return
|
||||
}
|
||||
if len(config.Config.Salt) > 24 {
|
||||
golog.Fatal(`Length of Salt should be less than 24.`)
|
||||
return
|
||||
}
|
||||
config.Config.StdSalt = []byte(config.Config.Salt)
|
||||
config.Config.StdSalt = append(config.Config.StdSalt, bytes.Repeat([]byte{25}, 24)...)
|
||||
config.Config.StdSalt = config.Config.StdSalt[:24]
|
||||
|
||||
webFS, err := fs.NewWithNamespace(`web`)
|
||||
if err != nil {
|
||||
golog.Fatal(`Failed to load static resources: `, err)
|
||||
common.Fatal(nil, `LOAD_STATIC_RES`, `fail`, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
if config.Config.Debug.Gin {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
} else {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
app := gin.New()
|
||||
app.Use(gin.Recovery())
|
||||
if config.Config.Debug.Pprof {
|
||||
pprof.Register(app)
|
||||
}
|
||||
{
|
||||
handler.AuthHandler = authCheck()
|
||||
handler.AuthHandler = checkAuth()
|
||||
handler.InitRouter(app.Group(`/api`))
|
||||
app.Any(`/ws`, wsHandshake)
|
||||
app.NoRoute(handler.AuthHandler, func(ctx *gin.Context) {
|
||||
@@ -82,7 +55,7 @@ func main() {
|
||||
})
|
||||
}
|
||||
|
||||
common.Melody.Config.MaxMessageSize = 32768 + 1024
|
||||
common.Melody.Config.MaxMessageSize = common.MaxMessageSize
|
||||
common.Melody.HandleConnect(wsOnConnect)
|
||||
common.Melody.HandleMessage(wsOnMessage)
|
||||
common.Melody.HandleMessageBinary(wsOnMessageBinary)
|
||||
@@ -93,33 +66,43 @@ func main() {
|
||||
Addr: config.Config.Listen,
|
||||
Handler: app,
|
||||
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
|
||||
return context.WithValue(ctx, `Conn`, c)
|
||||
ctx = context.WithValue(ctx, `Conn`, c)
|
||||
ctx = context.WithValue(ctx, `ClientIP`, common.GetAddrIP(c.RemoteAddr()))
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
go func() {
|
||||
if err = srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
golog.Fatal(`Failed to bind address: `, err)
|
||||
{
|
||||
go func() {
|
||||
err = srv.ListenAndServe()
|
||||
}()
|
||||
if err != nil {
|
||||
common.Fatal(nil, `SERVICE_INIT`, `fail`, err.Error(), nil)
|
||||
} else {
|
||||
common.Info(nil, `SERVICE_INIT`, ``, ``, map[string]any{
|
||||
`listen`: config.Config.Listen,
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
||||
quit := make(chan os.Signal, 3)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
golog.Warn(`Server is shutting down ...`)
|
||||
common.Warn(nil, `SERVICE_EXITING`, ``, ``, nil)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
golog.Fatal(`Server shutdown: `, err)
|
||||
common.Warn(nil, `SERVICE_EXIT`, `error`, err.Error(), nil)
|
||||
}
|
||||
<-ctx.Done()
|
||||
golog.Info(`Server exited.`)
|
||||
common.Warn(nil, `SERVICE_EXIT`, `success`, ``, nil)
|
||||
common.CloseLog()
|
||||
}
|
||||
|
||||
func wsHandshake(ctx *gin.Context) {
|
||||
if !ctx.IsWebsocket() {
|
||||
// When message is too large to transport via websocket,
|
||||
// client will try to send these data via http.
|
||||
const MaxBodySize = 2 << 18 //524288 512KB
|
||||
const MaxBodySize = 2 << 18 // 524288 512KB
|
||||
if ctx.Request.ContentLength > MaxBodySize {
|
||||
ctx.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1})
|
||||
return
|
||||
@@ -145,7 +128,7 @@ func wsHandshake(ctx *gin.Context) {
|
||||
ctx.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
decrypted, err := common.DecAES(clientKey, config.Config.StdSalt)
|
||||
decrypted, err := common.DecAES(clientKey, config.Config.SaltBytes)
|
||||
if err != nil || !bytes.Equal(decrypted, clientUUID) {
|
||||
ctx.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
@@ -155,7 +138,7 @@ func wsHandshake(ctx *gin.Context) {
|
||||
`Secret`: []string{hex.EncodeToString(secret)},
|
||||
}, gin.H{
|
||||
`Secret`: secret,
|
||||
`LastPack`: common.Unix,
|
||||
`LastPack`: utils.Unix,
|
||||
`Address`: common.GetRemoteAddr(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -169,7 +152,7 @@ func wsOnConnect(session *melody.Session) {
|
||||
pingDevice(session)
|
||||
}
|
||||
|
||||
func wsOnMessage(session *melody.Session, bytes []byte) {
|
||||
func wsOnMessage(session *melody.Session, _ []byte) {
|
||||
session.Close()
|
||||
}
|
||||
|
||||
@@ -200,7 +183,7 @@ func wsOnMessageBinary(session *melody.Session, data []byte) {
|
||||
return
|
||||
}
|
||||
if pack.Act == `report` || pack.Act == `setDevice` {
|
||||
session.Set(`LastPack`, common.Unix)
|
||||
session.Set(`LastPack`, utils.Unix)
|
||||
utility.OnDevicePack(data, session)
|
||||
return
|
||||
}
|
||||
@@ -209,7 +192,7 @@ func wsOnMessageBinary(session *melody.Session, data []byte) {
|
||||
return
|
||||
}
|
||||
common.CallEvent(pack, session)
|
||||
session.Set(`LastPack`, common.Unix)
|
||||
session.Set(`LastPack`, utils.Unix)
|
||||
}
|
||||
|
||||
func wsOnDisconnect(session *melody.Session) {
|
||||
@@ -217,6 +200,18 @@ func wsOnDisconnect(session *melody.Session) {
|
||||
deviceInfo := val.(*modules.Device)
|
||||
terminal.CloseSessionsByDevice(deviceInfo.ID)
|
||||
desktop.CloseSessionsByDevice(deviceInfo.ID)
|
||||
common.Info(nil, `CLIENT_OFFLINE`, ``, ``, map[string]any{
|
||||
`device`: map[string]any{
|
||||
`name`: deviceInfo.Hostname,
|
||||
`ip`: deviceInfo.WAN,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
common.Info(nil, `CLIENT_OFFLINE`, ``, ``, map[string]any{
|
||||
`device`: map[string]any{
|
||||
`ip`: common.GetAddrIP(session.GetWSConn().UnderlyingConn().RemoteAddr()),
|
||||
},
|
||||
})
|
||||
}
|
||||
common.Devices.Remove(session.UUID)
|
||||
}
|
||||
@@ -231,7 +226,7 @@ func wsHealthCheck(container *melody.Melody) {
|
||||
var pingInterval int64 = 3
|
||||
for range time.NewTicker(3 * time.Second).C {
|
||||
tick += 3
|
||||
if tick >= common.Unix-lastRequest {
|
||||
if tick >= utils.Unix-lastRequest {
|
||||
pingInterval = 3
|
||||
}
|
||||
if tick >= 3 && (tick >= pingInterval || tick >= MaxPingInterval) {
|
||||
@@ -286,7 +281,7 @@ func pingDevice(s *melody.Session) {
|
||||
}, s.UUID, trigger, 3*time.Second)
|
||||
}
|
||||
|
||||
func authCheck() gin.HandlerFunc {
|
||||
func checkAuth() gin.HandlerFunc {
|
||||
// Token as key and update timestamp as value.
|
||||
// Stores authenticated tokens.
|
||||
tokens := cmap.New()
|
||||
@@ -300,19 +295,30 @@ func authCheck() gin.HandlerFunc {
|
||||
return true
|
||||
})
|
||||
tokens.Remove(queue...)
|
||||
queue = nil
|
||||
|
||||
blocked.IterCb(func(addr string, v any) bool {
|
||||
if now.Unix() > v.(int64) {
|
||||
queue = append(queue, addr)
|
||||
}
|
||||
return true
|
||||
})
|
||||
blocked.Remove(queue...)
|
||||
}
|
||||
}()
|
||||
|
||||
if config.Config.Auth == nil || len(config.Config.Auth) == 0 {
|
||||
return func(ctx *gin.Context) {
|
||||
lastRequest = common.Unix
|
||||
lastRequest = utils.Unix
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
auth := gin.BasicAuth(config.Config.Auth)
|
||||
return func(ctx *gin.Context) {
|
||||
now := common.Unix
|
||||
now := utils.Unix
|
||||
passed := false
|
||||
|
||||
if token, err := ctx.Cookie(`Authorization`); err == nil {
|
||||
if tokens.Has(token) {
|
||||
lastRequest = now
|
||||
@@ -321,11 +327,32 @@ func authCheck() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !passed {
|
||||
addr := common.GetRealIP(ctx)
|
||||
if expire, ok := blocked.Get(addr); ok {
|
||||
if now < expire.(int64) {
|
||||
ctx.AbortWithStatusJSON(http.StatusTooManyRequests, modules.Packet{Code: 1})
|
||||
return
|
||||
}
|
||||
blocked.Remove(addr)
|
||||
}
|
||||
|
||||
auth(ctx)
|
||||
user, _, _ := ctx.Request.BasicAuth()
|
||||
|
||||
if ctx.IsAborted() {
|
||||
blocked.Set(addr, now+1)
|
||||
user = utils.If(len(user) == 0, `EMPTY`, user)
|
||||
common.Warn(ctx, `LOGIN_ATTEMPT`, `fail`, ``, map[string]any{
|
||||
`user`: user,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
common.Warn(ctx, `LOGIN_ATTEMPT`, `success`, ``, map[string]any{
|
||||
`user`: user,
|
||||
})
|
||||
token := utils.GetStrUUID()
|
||||
tokens.Set(token, now)
|
||||
ctx.Header(`Set-Cookie`, fmt.Sprintf(`Authorization=%s; Path=/; HttpOnly`, token))
|
||||
@@ -366,7 +393,7 @@ func serveGzip(ctx *gin.Context, statikFS http.FileSystem) bool {
|
||||
}
|
||||
ctx.Header(`Cache-Control`, `max-age=604800`)
|
||||
ctx.Header(`ETag`, etag)
|
||||
ctx.Header(`Expires`, common.Now.Add(7*24*time.Hour).Format(`Mon, 02 Jan 2006 15:04:05 GMT`))
|
||||
ctx.Header(`Expires`, utils.Now.Add(7*24*time.Hour).Format(`Mon, 02 Jan 2006 15:04:05 GMT`))
|
||||
|
||||
ctx.Writer.Header().Del(`Content-Length`)
|
||||
ctx.Header(`Content-Encoding`, `gzip`)
|
||||
@@ -386,7 +413,7 @@ func serveGzip(ctx *gin.Context, statikFS http.FileSystem) bool {
|
||||
break
|
||||
}
|
||||
}
|
||||
conn.SetWriteDeadline(common.Now.Add(10 * time.Second))
|
||||
conn.SetWriteDeadline(utils.Now.Add(10 * time.Second))
|
||||
_, err = ctx.Writer.Write(buf[:n])
|
||||
if eof || err != nil {
|
||||
break
|
||||
@@ -408,6 +435,6 @@ func checkCache(ctx *gin.Context, _ http.FileSystem) bool {
|
||||
}
|
||||
ctx.Header(`ETag`, etag)
|
||||
ctx.Header(`Cache-Control`, `max-age=604800`)
|
||||
ctx.Header(`Expires`, common.Now.Add(7*24*time.Hour).Format(`Mon, 02 Jan 2006 15:04:05 GMT`))
|
||||
ctx.Header(`Expires`, utils.Now.Add(7*24*time.Hour).Format(`Mon, 02 Jan 2006 15:04:05 GMT`))
|
||||
return false
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package utils
|
||||
|
||||
import "time"
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
@@ -107,6 +108,57 @@ func Decrypt(data []byte, key []byte) ([]byte, error) {
|
||||
return decBuffer, nil
|
||||
}
|
||||
|
||||
func FormatSize(size int64) string {
|
||||
sizes := []string{`B`, `KB`, `MB`, `GB`, `TB`, `PB`, `EB`, `ZB`, `YB`}
|
||||
i := 0
|
||||
for size >= 1024 && i < len(sizes)-1 {
|
||||
size /= 1024
|
||||
i++
|
||||
}
|
||||
return fmt.Sprintf(`%d%s`, size, sizes[i])
|
||||
}
|
||||
|
||||
func BytesToString(b []byte, r ...int) string {
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
bytesPtr := sh.Data
|
||||
bytesLen := sh.Len
|
||||
switch len(r) {
|
||||
case 1:
|
||||
r[0] = If(r[0] > bytesLen, bytesLen, r[0])
|
||||
bytesLen -= r[0]
|
||||
bytesPtr += uintptr(r[0])
|
||||
case 2:
|
||||
r[0] = If(r[0] > bytesLen, bytesLen, r[0])
|
||||
bytesLen = If(r[1] > bytesLen, bytesLen, r[1]) - r[0]
|
||||
bytesPtr += uintptr(r[0])
|
||||
}
|
||||
return *(*string)(unsafe.Pointer(&reflect.StringHeader{
|
||||
Data: bytesPtr,
|
||||
Len: bytesLen,
|
||||
}))
|
||||
}
|
||||
|
||||
func StringToBytes(s string, r ...int) []byte {
|
||||
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
strPtr := sh.Data
|
||||
strLen := sh.Len
|
||||
switch len(r) {
|
||||
case 1:
|
||||
r[0] = If(r[0] > strLen, strLen, r[0])
|
||||
strLen -= r[0]
|
||||
strPtr += uintptr(r[0])
|
||||
case 2:
|
||||
r[0] = If(r[0] > strLen, strLen, r[0])
|
||||
strLen = If(r[1] > strLen, strLen, r[1]) - r[0]
|
||||
strPtr += uintptr(r[0])
|
||||
}
|
||||
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: strPtr,
|
||||
Len: strLen,
|
||||
Cap: strLen,
|
||||
}))
|
||||
}
|
||||
|
||||
func GetSlicePrefix[T any](data *[]T, n int) *[]T {
|
||||
sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(data))
|
||||
return (*[]T)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
|
@@ -9,7 +9,7 @@
|
||||
<script src="./ext-modelist.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -14,7 +14,7 @@ import {
|
||||
Spin
|
||||
} from "antd";
|
||||
import ProTable, {TableDropdown} from "@ant-design/pro-table";
|
||||
import {catchBlobReq, formatSize, orderCompare, post, preventClose, request, translate, waitTime} from "../utils/utils";
|
||||
import {catchBlobReq, formatSize, orderCompare, post, preventClose, request, waitTime} from "../utils/utils";
|
||||
import dayjs from "dayjs";
|
||||
import i18n from "../locale/locale";
|
||||
import {VList} from "virtuallist-antd";
|
||||
|
@@ -15,13 +15,11 @@ function Generate(props) {
|
||||
}
|
||||
form.secure = location.protocol === 'https:' ? 'true' : 'false';
|
||||
let basePath = location.origin + location.pathname + 'api/client/';
|
||||
request(basePath + 'check', form)
|
||||
.then((res) => {
|
||||
if (res.data.code === 0) {
|
||||
post(basePath += 'generate', form);
|
||||
}
|
||||
})
|
||||
.catch()
|
||||
request(basePath + 'check', form).then(res => {
|
||||
if (res.data.code === 0) {
|
||||
post(basePath += 'generate', form);
|
||||
}
|
||||
}).catch();
|
||||
}
|
||||
|
||||
function getInitValues() {
|
||||
|
52
web/src/components/runner.js
Normal file
52
web/src/components/runner.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import {ModalForm, ProFormText} from '@ant-design/pro-form';
|
||||
import {request} from "../utils/utils";
|
||||
import i18n from "../locale/locale";
|
||||
import {message} from "antd";
|
||||
|
||||
function Runner(props) {
|
||||
async function onFinish(form) {
|
||||
form.device = props.device.id;
|
||||
let basePath = location.origin + location.pathname + 'api/device/';
|
||||
request(basePath + 'exec', form).then(res => {
|
||||
if (res.data.code === 0) {
|
||||
message.success(i18n.t('executionSuccess'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalForm
|
||||
modalProps={{
|
||||
destroyOnClose: true,
|
||||
maskClosable: false,
|
||||
}}
|
||||
title={i18n.t('run')}
|
||||
width={380}
|
||||
onFinish={onFinish}
|
||||
onVisibleChange={visible => {
|
||||
if (!visible) props.onCancel();
|
||||
}}
|
||||
submitter={{
|
||||
render: (_, elems) => elems.pop()
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<ProFormText
|
||||
width="md"
|
||||
name="cmd"
|
||||
label={i18n.t('cmdPlaceholder')}
|
||||
rules={[{
|
||||
required: true
|
||||
}]}
|
||||
/>
|
||||
<ProFormText
|
||||
width="md"
|
||||
name="args"
|
||||
label={i18n.t('argsPlaceholder')}
|
||||
/>
|
||||
</ModalForm>
|
||||
)
|
||||
}
|
||||
|
||||
export default Runner;
|
@@ -25,6 +25,7 @@
|
||||
"terminal": "Terminal",
|
||||
"procMgr": "ProcMgr",
|
||||
"fileMgr": "Explorer",
|
||||
"run": "Run",
|
||||
"screenshot": "Screenshot",
|
||||
"desktop": "Desktop",
|
||||
"lock": "Lock",
|
||||
@@ -116,5 +117,9 @@
|
||||
"screenshotObtainFailed": "Failed to obtain screenshot",
|
||||
"noDisplayFound": "No display found",
|
||||
|
||||
"executionSuccess": "Execution success",
|
||||
"cmdPlaceholder": "Command",
|
||||
"argsPlaceholder": "Arguments (separated by space)",
|
||||
|
||||
"colon": ": "
|
||||
}
|
@@ -25,6 +25,7 @@
|
||||
"terminal": "终端",
|
||||
"procMgr": "进程",
|
||||
"fileMgr": "文件",
|
||||
"run": "运行",
|
||||
"screenshot": "截屏",
|
||||
"desktop": "桌面",
|
||||
"lock": "锁屏",
|
||||
@@ -116,5 +117,9 @@
|
||||
"screenshotObtainFailed": "截屏读取失败",
|
||||
"noDisplayFound": "设备未连接显示器",
|
||||
|
||||
"executionSuccess": "执行成功",
|
||||
"cmdPlaceholder": "命令",
|
||||
"argsPlaceholder": "参数(以空格分隔)",
|
||||
|
||||
"colon": ":"
|
||||
}
|
@@ -7,6 +7,7 @@ import Explorer from "../components/explorer";
|
||||
import Terminal from "../components/terminal";
|
||||
import ProcMgr from "../components/procmgr";
|
||||
import Desktop from "../components/desktop";
|
||||
import Runner from "../components/runner";
|
||||
import {QuestionCircleOutlined} from "@ant-design/icons";
|
||||
import i18n from "../locale/locale";
|
||||
|
||||
@@ -38,6 +39,7 @@ function UsageBar(props) {
|
||||
}
|
||||
|
||||
function overview(props) {
|
||||
const [runner, setRunner] = useState(false);
|
||||
const [desktop, setDesktop] = useState(false);
|
||||
const [procMgr, setProcMgr] = useState(false);
|
||||
const [explorer, setExplorer] = useState(false);
|
||||
@@ -271,8 +273,9 @@ function overview(props) {
|
||||
}
|
||||
function renderOperation(device) {
|
||||
let menus = [
|
||||
{key: 'screenshot', name: i18n.t('screenshot')},
|
||||
{key: 'run', name: i18n.t('run')},
|
||||
{key: 'desktop', name: i18n.t('desktop')},
|
||||
{key: 'screenshot', name: i18n.t('screenshot')},
|
||||
{key: 'lock', name: i18n.t('lock')},
|
||||
{key: 'logoff', name: i18n.t('logoff')},
|
||||
{key: 'hibernate', name: i18n.t('hibernate')},
|
||||
@@ -299,6 +302,14 @@ function overview(props) {
|
||||
}
|
||||
|
||||
function callDevice(act, device) {
|
||||
if (act === 'run') {
|
||||
setRunner(device);
|
||||
return;
|
||||
}
|
||||
if (act === 'desktop') {
|
||||
setDesktop(device);
|
||||
return;
|
||||
}
|
||||
if (act === 'screenshot') {
|
||||
request('/api/device/screenshot/get', {device: device.id}, {}, {
|
||||
responseType: 'blob'
|
||||
@@ -312,10 +323,6 @@ function overview(props) {
|
||||
}).catch(catchBlobReq);
|
||||
return;
|
||||
}
|
||||
if (act === 'desktop') {
|
||||
setDesktop(device);
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: i18n.t('operationConfirm').replace('{0}', i18n.t(act).toUpperCase()),
|
||||
icon: <QuestionCircleOutlined/>,
|
||||
@@ -409,6 +416,11 @@ function overview(props) {
|
||||
device={procMgr}
|
||||
onCancel={setProcMgr.bind(null, false)}
|
||||
/>
|
||||
<Runner
|
||||
visible={runner}
|
||||
device={runner}
|
||||
onCancel={setRunner.bind(null, false)}
|
||||
/>
|
||||
<Desktop
|
||||
visible={desktop}
|
||||
device={desktop}
|
||||
|
Reference in New Issue
Block a user