mirror of
https://github.com/XZB-1248/Spark
synced 2025-10-26 17:20:21 +08:00
support client self-upgrade
This commit is contained in:
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -133,6 +133,4 @@ jobs:
|
|||||||
releases/server_windows_arm.zip
|
releases/server_windows_arm.zip
|
||||||
releases/server_windows_arm64.zip
|
releases/server_windows_arm64.zip
|
||||||
releases/server_windows_i386.zip
|
releases/server_windows_i386.zip
|
||||||
releases/server_windows_amd64.zip
|
releases/server_windows_amd64.zip
|
||||||
env:
|
|
||||||
GITHUB_REPOSITORY: XZB-1248/Spark
|
|
||||||
23
CHANGELOG.md
Normal file
23
CHANGELOG.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
## v0.0.3
|
||||||
|
|
||||||
|
* Add: network IO speed monitoring.
|
||||||
|
* Add: support client self-upgrade.
|
||||||
|
* Fix: garbled characters when display Chinese on Unix-like OS.
|
||||||
|
* BREAKING-CHANGE: module `Device` has changed.
|
||||||
|
* THIS RELEASE IS **NOT** COMPATIBLE WITH LAST RELEASE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## v0.0.2
|
||||||
|
|
||||||
|
* Add: latency check.
|
||||||
|
* Add: progress bar of cpu usage, memory usage and disk usage.
|
||||||
|
* BREAKING-CHANGE: module `Device` has changed.
|
||||||
|
* THIS RELEASE IS **NOT** COMPATIBLE WITH LAST RELEASE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## v0.0.1
|
||||||
|
|
||||||
|
* First release.
|
||||||
|
*
|
||||||
53
LICENSE
53
LICENSE
@@ -23,3 +23,56 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
-------
|
||||||
|
|
||||||
|
utils/melody are copied and modified from olahol/melody.
|
||||||
|
|
||||||
|
Copyright (c) 2015 Ola Holmström. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
-------
|
||||||
|
|
||||||
|
utils/cmap are copied and modified from orcaman/concurrent-map.
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 streamrail
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
40
README.ZH.md
40
README.ZH.md
@@ -130,6 +130,46 @@ $ go build -ldflags "-s -w" -o Spark Spark/Server
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 项目依赖
|
||||||
|
|
||||||
|
Spark使用了许多第三方的开源项目。
|
||||||
|
|
||||||
|
依赖列表可以在 `go.mod` 和 `package.json` 里面找到。
|
||||||
|
|
||||||
|
一些主要的依赖项如下列所示。
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
* [Go](https://github.com/golang/go) ([License](https://github.com/golang/go/blob/master/LICENSE))
|
||||||
|
|
||||||
|
* [gin-gonic/gin](https://github.com/gin-gonic/gin) (MIT License)
|
||||||
|
|
||||||
|
* [imroc/req](https://github.com/imroc/req) (MIT License)
|
||||||
|
|
||||||
|
* [kbinani/screenshot](https://github.com/kbinani/screenshot) (MIT License)
|
||||||
|
|
||||||
|
* [shirou/gopsutil](https://github.com/shirou/gopsutil) ([License](https://github.com/shirou/gopsutil/blob/master/LICENSE))
|
||||||
|
|
||||||
|
* [gorilla/websocket](https://github.com/gorilla/websocket) (BSD-2-Clause License)
|
||||||
|
|
||||||
|
* [olahol/melody](https://github.com/olahol/melody) (BSD-2-Clause License)
|
||||||
|
|
||||||
|
* [orcaman/concurrent-map](https://github.com/orcaman/concurrent-map) (MIT License)
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
* [React](https://github.com/facebook/react) (MIT License)
|
||||||
|
|
||||||
|
* [Ant-Design](https://github.com/ant-design/ant-design) (MIT License)
|
||||||
|
|
||||||
|
* [axios](https://github.com/axios/axios) (MIT License)
|
||||||
|
|
||||||
|
* [xterm.js](https://github.com/xtermjs/xterm.js) (MIT License)
|
||||||
|
|
||||||
|
* [crypto-js](https://github.com/brix/crypto-js) (MIT License)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 开源协议
|
## 开源协议
|
||||||
|
|
||||||
本项目基于 [BSD-2 协议](./LICENSE) 。
|
本项目基于 [BSD-2 协议](./LICENSE) 。
|
||||||
38
README.md
38
README.md
@@ -125,6 +125,44 @@ $ go build -ldflags "-s -w" -o Spark Spark/Server
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Spark contains many third-party open-source projects.
|
||||||
|
|
||||||
|
Lists of dependencies can be found at `go.mod` and `package.json`.
|
||||||
|
|
||||||
|
Some major dependencies are listed below.
|
||||||
|
|
||||||
|
### Back-end
|
||||||
|
|
||||||
|
* [Go](https://github.com/golang/go) ([License](https://github.com/golang/go/blob/master/LICENSE))
|
||||||
|
|
||||||
|
* [gin-gonic/gin](https://github.com/gin-gonic/gin) (MIT License)
|
||||||
|
|
||||||
|
* [imroc/req](https://github.com/imroc/req) (MIT License)
|
||||||
|
|
||||||
|
* [kbinani/screenshot](https://github.com/kbinani/screenshot) (MIT License)
|
||||||
|
|
||||||
|
* [shirou/gopsutil](https://github.com/shirou/gopsutil) ([License](https://github.com/shirou/gopsutil/blob/master/LICENSE))
|
||||||
|
|
||||||
|
* [gorilla/websocket](https://github.com/gorilla/websocket) (BSD-2-Clause License)
|
||||||
|
|
||||||
|
* [orcaman/concurrent-map](https://github.com/orcaman/concurrent-map) (MIT License)
|
||||||
|
|
||||||
|
### Front-end
|
||||||
|
|
||||||
|
* [React](https://github.com/facebook/react) (MIT License)
|
||||||
|
|
||||||
|
* [Ant-Design](https://github.com/ant-design/ant-design) (MIT License)
|
||||||
|
|
||||||
|
* [axios](https://github.com/axios/axios) (MIT License)
|
||||||
|
|
||||||
|
* [xterm.js](https://github.com/xtermjs/xterm.js) (MIT License)
|
||||||
|
|
||||||
|
* [crypto-js](https://github.com/brix/crypto-js) (MIT License)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[BSD-2 License](./LICENSE)
|
[BSD-2 License](./LICENSE)
|
||||||
@@ -1,30 +1,32 @@
|
|||||||
set GO111MODULE=auto
|
set GO111MODULE=auto
|
||||||
mkdir .\built
|
mkdir .\built
|
||||||
|
for /F %%i in ('git rev-parse HEAD') do ( set COMMIT=%%i)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set GOOS=linux
|
set GOOS=linux
|
||||||
|
|
||||||
set GOARCH=arm
|
set GOARCH=arm
|
||||||
go build -ldflags "-s -w" -o ./built/linux_arm Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_arm Spark/client
|
||||||
set GOARCH=arm64
|
set GOARCH=arm64
|
||||||
go build -ldflags "-s -w" -o ./built/linux_arm64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_arm64 Spark/client
|
||||||
set GOARCH=386
|
set GOARCH=386
|
||||||
go build -ldflags "-s -w" -o ./built/linux_i386 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_i386 Spark/client
|
||||||
set GOARCH=amd64
|
set GOARCH=amd64
|
||||||
go build -ldflags "-s -w" -o ./built/linux_amd64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/linux_amd64 Spark/client
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set GOOS=windows
|
set GOOS=windows
|
||||||
|
|
||||||
set GOARCH=arm
|
set GOARCH=arm
|
||||||
go build -ldflags "-s -w" -o ./built/windows_arm Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_arm Spark/client
|
||||||
set GOARCH=arm64
|
set GOARCH=arm64
|
||||||
go build -ldflags "-s -w" -o ./built/windows_arm64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_arm64 Spark/client
|
||||||
set GOARCH=386
|
set GOARCH=386
|
||||||
go build -ldflags "-s -w" -o ./built/windows_i386 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_i386 Spark/client
|
||||||
set GOARCH=amd64
|
set GOARCH=amd64
|
||||||
go build -ldflags "-s -w" -o ./built/windows_amd64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/windows_amd64 Spark/client
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -34,23 +36,19 @@ go build -ldflags "-s -w" -o ./built/windows_amd64 Spark/client
|
|||||||
@REM set GOARCH=arm
|
@REM set GOARCH=arm
|
||||||
@REM set CC=armv7a-linux-androideabi21-clang
|
@REM set CC=armv7a-linux-androideabi21-clang
|
||||||
@REM set CXX=armv7a-linux-androideabi21-clang++
|
@REM set CXX=armv7a-linux-androideabi21-clang++
|
||||||
@REM go build -ldflags "-s -w" -o ./built/android_armv7a Spark/client
|
@REM go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_arm Spark/client
|
||||||
|
|
||||||
@REM set GOARCH=arm64
|
@REM set GOARCH=arm64
|
||||||
@REM set CC=aarch64-linux-android21-clang
|
@REM set CC=aarch64-linux-android21-clang
|
||||||
@REM set CXX=aarch64-linux-android21-clang++
|
@REM set CXX=aarch64-linux-android21-clang++
|
||||||
@REM go build -ldflags "-s -w" -o ./built/android_aarch64 Spark/client
|
@REM go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_arm64 Spark/client
|
||||||
|
|
||||||
@REM set GOARCH=386
|
@REM set GOARCH=386
|
||||||
@REM set CC=i686-linux-android21-clang
|
@REM set CC=i686-linux-android21-clang
|
||||||
@REM set CXX=i686-linux-android21-clang++
|
@REM set CXX=i686-linux-android21-clang++
|
||||||
@REM go build -ldflags "-s -w" -o ./built/android_i686 Spark/client
|
@REM go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_i386 Spark/client
|
||||||
|
|
||||||
@REM set GOARCH=amd64
|
@REM set GOARCH=amd64
|
||||||
@REM set CC=x86_64-linux-android21-clang
|
@REM set CC=x86_64-linux-android21-clang
|
||||||
@REM set CXX=x86_64-linux-android21-clang++
|
@REM set CXX=x86_64-linux-android21-clang++
|
||||||
@REM go build -ldflags "-s -w" -o ./built/android_x86_64 Spark/client
|
@REM go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=%COMMIT%'" -o ./built/android_amd64 Spark/client
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
statik -m -src="./built" -f -dest="./server/embed" -include=* -p built -ns built
|
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
export GO111MODULE=auto
|
export GO111MODULE=auto
|
||||||
mkdir ./built
|
mkdir ./built
|
||||||
|
export COMMIT=`git rev-parse HEAD`
|
||||||
|
|
||||||
|
|
||||||
export GOOS=linux
|
export GOOS=linux
|
||||||
|
|
||||||
export GOARCH=arm
|
export GOARCH=arm
|
||||||
go build -ldflags "-s -w" -o ./built/linux_arm Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_arm Spark/client
|
||||||
export GOARCH=arm64
|
export GOARCH=arm64
|
||||||
go build -ldflags "-s -w" -o ./built/linux_arm64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_arm64 Spark/client
|
||||||
export GOARCH=386
|
export GOARCH=386
|
||||||
go build -ldflags "-s -w" -o ./built/linux_i386 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_i386 Spark/client
|
||||||
export GOARCH=amd64
|
export GOARCH=amd64
|
||||||
go build -ldflags "-s -w" -o ./built/linux_amd64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/linux_amd64 Spark/client
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export GOOS=windows
|
export GOOS=windows
|
||||||
|
|
||||||
export GOARCH=arm
|
export GOARCH=arm
|
||||||
go build -ldflags "-s -w" -o ./built/windows_arm Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_arm Spark/client
|
||||||
export GOARCH=arm64
|
export GOARCH=arm64
|
||||||
go build -ldflags "-s -w" -o ./built/windows_arm64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_arm64 Spark/client
|
||||||
export GOARCH=386
|
export GOARCH=386
|
||||||
go build -ldflags "-s -w" -o ./built/windows_i386 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_i386 Spark/client
|
||||||
export GOARCH=amd64
|
export GOARCH=amd64
|
||||||
go build -ldflags "-s -w" -o ./built/windows_amd64 Spark/client
|
go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/windows_amd64 Spark/client
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -34,23 +35,19 @@ go build -ldflags "-s -w" -o ./built/windows_amd64 Spark/client
|
|||||||
# export GOARCH=arm
|
# export GOARCH=arm
|
||||||
# export CC=armv7a-linux-androideabi21-clang
|
# export CC=armv7a-linux-androideabi21-clang
|
||||||
# export CXX=armv7a-linux-androideabi21-clang++
|
# export CXX=armv7a-linux-androideabi21-clang++
|
||||||
# go build -ldflags "-s -w" -o ./built/android_armv7a Spark/client
|
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_arm Spark/client
|
||||||
|
|
||||||
# export GOARCH=arm64
|
# export GOARCH=arm64
|
||||||
# export CC=aarch64-linux-android21-clang
|
# export CC=aarch64-linux-android21-clang
|
||||||
# export CXX=aarch64-linux-android21-clang++
|
# export CXX=aarch64-linux-android21-clang++
|
||||||
# go build -ldflags "-s -w" -o ./built/android_aarch64 Spark/client
|
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_arm64 Spark/client
|
||||||
|
|
||||||
# export GOARCH=386
|
# export GOARCH=386
|
||||||
# export CC=i686-linux-android21-clang
|
# export CC=i686-linux-android21-clang
|
||||||
# export CXX=i686-linux-android21-clang++
|
# export CXX=i686-linux-android21-clang++
|
||||||
# go build -ldflags "-s -w" -o ./built/android_i686 Spark/client
|
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_i386 Spark/client
|
||||||
|
|
||||||
# export GOARCH=amd64
|
# export GOARCH=amd64
|
||||||
# export CC=x86_64-linux-android21-clang
|
# export CC=x86_64-linux-android21-clang
|
||||||
# export CXX=x86_64-linux-android21-clang++
|
# export CXX=x86_64-linux-android21-clang++
|
||||||
# go build -ldflags "-s -w" -o ./built/android_x86_64 Spark/client
|
# go build -ldflags "-s -w -X 'Spark/client/config.COMMIT=$COMMIT'" -o ./built/android_amd64 Spark/client
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
statik -m -src="./built" -f -dest="./server/embed" -include=* -p built -ns built
|
|
||||||
|
|||||||
@@ -1,28 +1,29 @@
|
|||||||
statik -m -src="./web/dist" -f -dest="./server/embed" -p web -ns web
|
export GO111MODULE=auto
|
||||||
mkdir ./releases
|
mkdir ./releases
|
||||||
|
export COMMIT=`git rev-parse HEAD`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export GOOS=linux
|
export GOOS=linux
|
||||||
|
|
||||||
export GOARCH=arm
|
export GOARCH=arm
|
||||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_linux_arm Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_arm Spark/server
|
||||||
export GOARCH=arm64
|
export GOARCH=arm64
|
||||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_linux_arm64 Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_arm64 Spark/server
|
||||||
export GOARCH=386
|
export GOARCH=386
|
||||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_linux_i386 Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_i386 Spark/server
|
||||||
export GOARCH=amd64
|
export GOARCH=amd64
|
||||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_linux_amd64 Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_linux_amd64 Spark/server
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export GOOS=windows
|
export GOOS=windows
|
||||||
|
|
||||||
export GOARCH=arm
|
export GOARCH=arm
|
||||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_windows_arm.exe Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_arm.exe Spark/server
|
||||||
export GOARCH=arm64
|
export GOARCH=arm64
|
||||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_windows_arm64.exe Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_arm64.exe Spark/server
|
||||||
export GOARCH=386
|
export GOARCH=386
|
||||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_windows_i386.exe Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_i386.exe Spark/server
|
||||||
export GOARCH=amd64
|
export GOARCH=amd64
|
||||||
go build -ldflags "-s -w" -tags=jsoniter -o ./releases/server_windows_amd64.exe Spark/server
|
go build -ldflags "-s -w -X 'Spark/server/config.COMMIT=$COMMIT'" -tags=jsoniter -o ./releases/server_windows_amd64.exe Spark/server
|
||||||
|
|||||||
@@ -7,32 +7,29 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/kataras/golog"
|
"github.com/kataras/golog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// localhost
|
|
||||||
//var cfgBuffer = "\x00\xcd\x90\x50\x43\xfc\x3d\x36\x56\x6d\xf6\x01\xd1\xcd\x81\xc3\x1b\x80\xc9\x61\xd8\xdf\x5b\x76\x48\x88\xc5\xb1\x74\x22\x23\xab\x3b\xfc\x8b\xbe\x98\x27\xed\x05\xec\xbb\x40\x4f\xe9\xe7\xe5\xe0\x84\xaa\xb7\xfd\x4a\x30\x71\x08\x6c\x02\x50\xe9\xc5\x22\xcf\xcb\x89\x16\x0a\x89\x08\xd4\x26\xdc\x5c\xc1\xc9\xbf\xc4\xac\x0d\x92\x2f\x34\x7f\x45\xeb\x55\xa0\x6d\xf6\x64\xbc\xd5\x15\x40\x96\x43\x64\xe0\x24\x51\xfb\xe8\xc9\x7f\x48\x60\xcd\x30\x5e\x5e\x78\xba\xb6\x6f\x07\x64\xe8\x59\x81\x0b\x91\x13\x92\x1a\xdd\x49\x8f\x28\xe7\x74\xea\xff\x5b\x45\x0e\x4a\x2d\x60\x4e\xc9\xde\x9c\xbe\x50\xc6\x12\xc7\x45\xa2\x15\xa0\x58\x62\x45\x86\x74\x9f\xa5\x14\x5c\x17\x8a\xcc\x56\x73\xa7\x75\xb7\xf6\x6d\x52\x0f\xb8\xc1\xff\x9c\x39\x39\x00\x74\xe1\x4d\x65\x73\x9c\x02\x57\x8b\xcf\xdf\x0a\x20\x4c\xed\xe2\x25\xea\x01\x36\x12\x37\x12\x2e\x1a\x03\x41\x19\x2e\xc9\xdd\x71\xac\x73\x90\xfa\x5e\x60\x08\x43\x35\xef\x61\x45\xf9\xe3\xba\xcb\xb1\xc5\x7c\xf0\x11\xcd\x47\x57\x53\xdc\x35\x6b\x9f\xac\xad\x43\x4a\xc7\x54\x20\xb8\xd0\xf8\xb5\x0c\x45\x76\x57\xb9\xee\x4a\x3f\xd2\xda\xf7\x94\x54\x74\xf3\x91\xf3\x4d\x49\x98\xc6\xf8\x60\x80\xad\x84\x04\xef\x35\xca\x3a\xcf\xd3\x7e\x74\xc2\x4b\xb8\xb3\x9f\xb2\x83\xb8\xbd\x29\x13\x9f\x2b\xaa\x60\x47\x24\x7e\x20\xb2\x85\xdc\x47\xfe\x8f\x68\xb6\xc3\x43\xad\x61\x3d\x9b\x35\x60\x2e\x6c\x44\xf0\xaf\xb2\xf3\xdb\xe2\x1b\x8a\xec\x0a\x48\x5e\x43\xa9\xb3\x3a\x5e\xb6\x90\xa9\x3d\xee\x4f\xa1\x57\x7c\x94\xf4\xb1\x36\xda\x04\xa8\x5e\x48\x2a\xc3\xa1\xf0\x97\xf0\xe0\x10\x46\x32\x10\xe5\xd8\x36\x5a\x56\xa5\xbb\x37\x3c\x9f\xbd\xef\xf5\x2f"
|
|
||||||
|
|
||||||
// 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"
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
golog.SetTimeFormat(`2006/01/02 15:04:05`)
|
golog.SetTimeFormat(`2006/01/02 15:04:05`)
|
||||||
|
|
||||||
if len(strings.Trim(cfgBuffer, "\x19")) == 0 {
|
if len(strings.Trim(config.CfgBuffer, "\x19")) == 0 {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dataLen := int(big.NewInt(0).SetBytes([]byte(cfgBuffer[:2])).Uint64())
|
dataLen := int(big.NewInt(0).SetBytes([]byte(config.CfgBuffer[:2])).Uint64())
|
||||||
if dataLen > len(cfgBuffer)-2 {
|
if dataLen > len(config.CfgBuffer)-2 {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfgBytes := []byte(cfgBuffer[2 : 2+dataLen])
|
cfgBytes := []byte(config.CfgBuffer[2 : 2+dataLen])
|
||||||
cfgBytes, err := decrypt(cfgBytes[16:], cfgBytes[:16])
|
cfgBytes, err := decrypt(cfgBytes[16:], cfgBytes[:16])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@@ -49,6 +46,27 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == `--update` {
|
||||||
|
thisPath := os.Args[0]
|
||||||
|
destPath := thisPath[:len(thisPath)-4]
|
||||||
|
if len(thisPath) <= 4 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
thisFile, err := ioutil.ReadFile(thisPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ioutil.WriteFile(destPath, thisFile, 0755)
|
||||||
|
cmd := exec.Command(destPath, `--clean`)
|
||||||
|
if cmd.Start() == nil {
|
||||||
|
os.Exit(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == `--clean` {
|
||||||
|
<-time.After(time.Second)
|
||||||
|
os.Remove(os.Args[0] + `.tmp`)
|
||||||
|
}
|
||||||
core.Start()
|
core.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func SendPack(pack interface{}, wsConn *Conn) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(data) > 1024 {
|
if len(data) > 1024 {
|
||||||
_, err = req.C().R().
|
_, err = req.R().
|
||||||
SetBody(data).
|
SetBody(data).
|
||||||
SetHeader(`Secret`, hex.EncodeToString(wsConn.Secret)).
|
SetHeader(`Secret`, hex.EncodeToString(wsConn.Secret)).
|
||||||
Send(`POST`, config.GetBaseURL(false)+`/ws`)
|
Send(`POST`, config.GetBaseURL(false)+`/ws`)
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ type Cfg struct {
|
|||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// localhost
|
||||||
|
//var CfgBuffer = "\x00\xcd\x90\x50\x43\xfc\x3d\x36\x56\x6d\xf6\x01\xd1\xcd\x81\xc3\x1b\x80\xc9\x61\xd8\xdf\x5b\x76\x48\x88\xc5\xb1\x74\x22\x23\xab\x3b\xfc\x8b\xbe\x98\x27\xed\x05\xec\xbb\x40\x4f\xe9\xe7\xe5\xe0\x84\xaa\xb7\xfd\x4a\x30\x71\x08\x6c\x02\x50\xe9\xc5\x22\xcf\xcb\x89\x16\x0a\x89\x08\xd4\x26\xdc\x5c\xc1\xc9\xbf\xc4\xac\x0d\x92\x2f\x34\x7f\x45\xeb\x55\xa0\x6d\xf6\x64\xbc\xd5\x15\x40\x96\x43\x64\xe0\x24\x51\xfb\xe8\xc9\x7f\x48\x60\xcd\x30\x5e\x5e\x78\xba\xb6\x6f\x07\x64\xe8\x59\x81\x0b\x91\x13\x92\x1a\xdd\x49\x8f\x28\xe7\x74\xea\xff\x5b\x45\x0e\x4a\x2d\x60\x4e\xc9\xde\x9c\xbe\x50\xc6\x12\xc7\x45\xa2\x15\xa0\x58\x62\x45\x86\x74\x9f\xa5\x14\x5c\x17\x8a\xcc\x56\x73\xa7\x75\xb7\xf6\x6d\x52\x0f\xb8\xc1\xff\x9c\x39\x39\x00\x74\xe1\x4d\x65\x73\x9c\x02\x57\x8b\xcf\xdf\x0a\x20\x4c\xed\xe2\x25\xea\x01\x36\x12\x37\x12\x2e\x1a\x03\x41\x19\x2e\xc9\xdd\x71\xac\x73\x90\xfa\x5e\x60\x08\x43\x35\xef\x61\x45\xf9\xe3\xba\xcb\xb1\xc5\x7c\xf0\x11\xcd\x47\x57\x53\xdc\x35\x6b\x9f\xac\xad\x43\x4a\xc7\x54\x20\xb8\xd0\xf8\xb5\x0c\x45\x76\x57\xb9\xee\x4a\x3f\xd2\xda\xf7\x94\x54\x74\xf3\x91\xf3\x4d\x49\x98\xc6\xf8\x60\x80\xad\x84\x04\xef\x35\xca\x3a\xcf\xd3\x7e\x74\xc2\x4b\xb8\xb3\x9f\xb2\x83\xb8\xbd\x29\x13\x9f\x2b\xaa\x60\x47\x24\x7e\x20\xb2\x85\xdc\x47\xfe\x8f\x68\xb6\xc3\x43\xad\x61\x3d\x9b\x35\x60\x2e\x6c\x44\xf0\xaf\xb2\xf3\xdb\xe2\x1b\x8a\xec\x0a\x48\x5e\x43\xa9\xb3\x3a\x5e\xb6\x90\xa9\x3d\xee\x4f\xa1\x57\x7c\x94\xf4\xb1\x36\xda\x04\xa8\x5e\x48\x2a\xc3\xa1\xf0\x97\xf0\xe0\x10\x46\x32\x10\xe5\xd8\x36\x5a\x56\xa5\xbb\x37\x3c\x9f\xbd\xef\xf5\x2f"
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
|
||||||
|
// COMMIT means this commit hash, for auto upgrade.
|
||||||
|
var COMMIT = ``
|
||||||
var Config Cfg
|
var Config Cfg
|
||||||
|
|
||||||
func GetBaseURL(ws bool) string {
|
func GetBaseURL(ws bool) string {
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
ws "github.com/gorilla/websocket"
|
ws "github.com/gorilla/websocket"
|
||||||
|
"github.com/imroc/req/v3"
|
||||||
"github.com/kataras/golog"
|
"github.com/kataras/golog"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -48,6 +52,8 @@ func Start() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkUpdate(common.WSConn)
|
||||||
|
|
||||||
go heartbeat(common.WSConn)
|
go heartbeat(common.WSConn)
|
||||||
|
|
||||||
err = handleWS(common.WSConn)
|
err = handleWS(common.WSConn)
|
||||||
@@ -106,6 +112,44 @@ func reportWS(wsConn *common.Conn) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkUpdate(wsConn *common.Conn) error {
|
||||||
|
if len(config.COMMIT) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
resp, err := req.R().
|
||||||
|
SetBody(config.CfgBuffer).
|
||||||
|
SetQueryParam(`os`, runtime.GOOS).
|
||||||
|
SetQueryParam(`arch`, runtime.GOARCH).
|
||||||
|
SetQueryParam(`commit`, config.COMMIT).
|
||||||
|
SetHeader(`Secret`, hex.EncodeToString(wsConn.Secret)).
|
||||||
|
Send(`POST`, config.GetBaseURL(false)+`/api/client/update`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
return errors.New(`unknown error occurred`)
|
||||||
|
}
|
||||||
|
if resp.GetContentType() == `application/octet-stream` {
|
||||||
|
body := resp.Bytes()
|
||||||
|
if len(body) > 0 {
|
||||||
|
err = ioutil.WriteFile(os.Args[0]+`.tmp`, body, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd := exec.Command(os.Args[0]+`.tmp`, `--update`)
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stop = true
|
||||||
|
wsConn.Close()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func handleWS(wsConn *common.Conn) error {
|
func handleWS(wsConn *common.Conn) error {
|
||||||
errCount := 0
|
errCount := 0
|
||||||
for {
|
for {
|
||||||
@@ -223,16 +267,21 @@ func handleAct(pack modules.Packet, wsConn *common.Conn) {
|
|||||||
common.SendCb(modules.Packet{Code: 0, Data: smap{`files`: files}}, pack, wsConn)
|
common.SendCb(modules.Packet{Code: 0, Data: smap{`files`: files}}, pack, wsConn)
|
||||||
}
|
}
|
||||||
case `removeFile`:
|
case `removeFile`:
|
||||||
path, ok := pack.Data[`path`]
|
val, ok := pack.Data[`path`]
|
||||||
if !ok {
|
if !ok {
|
||||||
common.SendCb(modules.Packet{Code: 1, Msg: `can not find such a file or directory`}, pack, wsConn)
|
common.SendCb(modules.Packet{Code: 1, Msg: `can not find such a file or directory`}, pack, wsConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if path == `\` || path == `/` || len(path.(string)) == 0 {
|
path, ok := val.(string)
|
||||||
|
if !ok {
|
||||||
common.SendCb(modules.Packet{Code: 1, Msg: `can not find such a file or directory`}, pack, wsConn)
|
common.SendCb(modules.Packet{Code: 1, Msg: `can not find such a file or directory`}, pack, wsConn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := os.RemoveAll(path.(string))
|
if path == `\` || path == `/` || len(path) == 0 {
|
||||||
|
common.SendCb(modules.Packet{Code: 1, Msg: `can not find such a file or directory`}, pack, wsConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := os.RemoveAll(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.SendCb(modules.Packet{Code: 1, Msg: err.Error()}, pack, wsConn)
|
common.SendCb(modules.Packet{Code: 1, Msg: err.Error()}, pack, wsConn)
|
||||||
} else {
|
} else {
|
||||||
@@ -307,15 +356,16 @@ func handleAct(pack modules.Packet, wsConn *common.Conn) {
|
|||||||
|
|
||||||
func heartbeat(wsConn *common.Conn) error {
|
func heartbeat(wsConn *common.Conn) error {
|
||||||
t := 0
|
t := 0
|
||||||
for range time.NewTicker(5 * time.Second).C {
|
for range time.NewTicker(2 * time.Second).C {
|
||||||
t++
|
t++
|
||||||
// Get disk info every 30*5 seconds.
|
// GetPartialInfo always costs more than 1 second.
|
||||||
device, err := GetPartialInfo(t >= 30)
|
// So it is actually get disk info every 200*3 seconds (10 minutes).
|
||||||
|
device, err := GetPartialInfo(t >= 200)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
golog.Error(err)
|
golog.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if t >= 30 {
|
if t >= 200 {
|
||||||
t = 0
|
t = 0
|
||||||
}
|
}
|
||||||
err = common.SendPack(modules.CommonPack{Act: `setDevice`, Data: device}, wsConn)
|
err = common.SendPack(modules.CommonPack{Act: `setDevice`, Data: device}, wsConn)
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import (
|
|||||||
"github.com/shirou/gopsutil/v3/disk"
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
"github.com/shirou/gopsutil/v3/host"
|
"github.com/shirou/gopsutil/v3/host"
|
||||||
"github.com/shirou/gopsutil/v3/mem"
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
"net"
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
|
_net "net"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -18,8 +19,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isPrivateIP(ip net.IP) bool {
|
func isPrivateIP(ip _net.IP) bool {
|
||||||
var privateIPBlocks []*net.IPNet
|
var privateIPBlocks []*_net.IPNet
|
||||||
for _, cidr := range []string{
|
for _, cidr := range []string{
|
||||||
//"127.0.0.0/8", // IPv4 loopback
|
//"127.0.0.0/8", // IPv4 loopback
|
||||||
//"::1/128", // IPv6 loopback
|
//"::1/128", // IPv6 loopback
|
||||||
@@ -28,7 +29,7 @@ func isPrivateIP(ip net.IP) bool {
|
|||||||
"172.16.0.0/12", // RFC1918
|
"172.16.0.0/12", // RFC1918
|
||||||
"192.168.0.0/16", // RFC1918
|
"192.168.0.0/16", // RFC1918
|
||||||
} {
|
} {
|
||||||
_, block, _ := net.ParseCIDR(cidr)
|
_, block, _ := _net.ParseCIDR(cidr)
|
||||||
privateIPBlocks = append(privateIPBlocks, block)
|
privateIPBlocks = append(privateIPBlocks, block)
|
||||||
}
|
}
|
||||||
for _, block := range privateIPBlocks {
|
for _, block := range privateIPBlocks {
|
||||||
@@ -40,7 +41,7 @@ func isPrivateIP(ip net.IP) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetLocalIP() (string, error) {
|
func GetLocalIP() (string, error) {
|
||||||
ifaces, err := net.Interfaces()
|
ifaces, err := _net.Interfaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return `Unknown`, err
|
return `Unknown`, err
|
||||||
}
|
}
|
||||||
@@ -51,11 +52,11 @@ func GetLocalIP() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
var ip net.IP
|
var ip _net.IP
|
||||||
switch v := addr.(type) {
|
switch v := addr.(type) {
|
||||||
case *net.IPNet:
|
case *_net.IPNet:
|
||||||
ip = v.IP
|
ip = v.IP
|
||||||
case *net.IPAddr:
|
case *_net.IPAddr:
|
||||||
ip = v.IP
|
ip = v.IP
|
||||||
}
|
}
|
||||||
if isPrivateIP(ip) {
|
if isPrivateIP(ip) {
|
||||||
@@ -71,7 +72,7 @@ func GetLocalIP() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetMacAddress() (string, error) {
|
func GetMacAddress() (string, error) {
|
||||||
interfaces, err := net.Interfaces()
|
interfaces, err := _net.Interfaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ``, err
|
return ``, err
|
||||||
}
|
}
|
||||||
@@ -88,6 +89,28 @@ func GetMacAddress() (string, error) {
|
|||||||
return strings.ToUpper(address[0]), nil
|
return strings.ToUpper(address[0]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetNetIOInfo() (modules.Net, error) {
|
||||||
|
result := modules.Net{}
|
||||||
|
first, err := net.IOCounters(false)
|
||||||
|
if err != nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if len(first) == 0 {
|
||||||
|
return result, errors.New(`failed to read network io counters`)
|
||||||
|
}
|
||||||
|
<-time.After(time.Second)
|
||||||
|
second, err := net.IOCounters(false)
|
||||||
|
if err != nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if len(second) == 0 {
|
||||||
|
return result, errors.New(`failed to read network io counters`)
|
||||||
|
}
|
||||||
|
result.Recv = second[0].BytesRecv - first[0].BytesRecv
|
||||||
|
result.Sent = second[0].BytesSent - first[0].BytesSent
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetCPUInfo() (modules.CPU, error) {
|
func GetCPUInfo() (modules.CPU, error) {
|
||||||
result := modules.CPU{}
|
result := modules.CPU{}
|
||||||
info, err := cpu.Info()
|
info, err := cpu.Info()
|
||||||
@@ -109,8 +132,8 @@ func GetCPUInfo() (modules.CPU, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMemInfo() (modules.Mem, error) {
|
func GetMemInfo() (modules.IO, error) {
|
||||||
result := modules.Mem{}
|
result := modules.IO{}
|
||||||
stat, err := mem.VirtualMemory()
|
stat, err := mem.VirtualMemory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -121,8 +144,9 @@ func GetMemInfo() (modules.Mem, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDiskInfo() (modules.Disk, error) {
|
func GetDiskInfo() (modules.IO, error) {
|
||||||
result := modules.Disk{}
|
result := modules.IO{}
|
||||||
|
disk.IOCounters()
|
||||||
disks, err := disk.Partitions(true)
|
disks, err := disk.Partitions(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -163,9 +187,16 @@ func GetDevice() (*modules.Device, error) {
|
|||||||
Usage: 0,
|
Usage: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
netInfo, err := GetNetIOInfo()
|
||||||
|
if err != nil {
|
||||||
|
netInfo = modules.Net{
|
||||||
|
Sent: 0,
|
||||||
|
Recv: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
memInfo, err := GetMemInfo()
|
memInfo, err := GetMemInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
memInfo = modules.Mem{
|
memInfo = modules.IO{
|
||||||
Total: 0,
|
Total: 0,
|
||||||
Used: 0,
|
Used: 0,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
@@ -173,7 +204,7 @@ func GetDevice() (*modules.Device, error) {
|
|||||||
}
|
}
|
||||||
diskInfo, err := GetDiskInfo()
|
diskInfo, err := GetDiskInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diskInfo = modules.Disk{
|
diskInfo = modules.IO{
|
||||||
Total: 0,
|
Total: 0,
|
||||||
Used: 0,
|
Used: 0,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
@@ -203,6 +234,7 @@ func GetDevice() (*modules.Device, error) {
|
|||||||
LAN: localIP,
|
LAN: localIP,
|
||||||
Mac: macAddr,
|
Mac: macAddr,
|
||||||
CPU: cpuInfo,
|
CPU: cpuInfo,
|
||||||
|
Net: netInfo,
|
||||||
Mem: memInfo,
|
Mem: memInfo,
|
||||||
Disk: diskInfo,
|
Disk: diskInfo,
|
||||||
Uptime: uptime,
|
Uptime: uptime,
|
||||||
@@ -211,7 +243,7 @@ func GetDevice() (*modules.Device, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPartialInfo(getDisk bool) (*modules.Device, error) {
|
func GetPartialInfo(getDisk bool) (modules.Device, error) {
|
||||||
cpuInfo, err := GetCPUInfo()
|
cpuInfo, err := GetCPUInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cpuInfo = modules.CPU{
|
cpuInfo = modules.CPU{
|
||||||
@@ -219,9 +251,16 @@ func GetPartialInfo(getDisk bool) (*modules.Device, error) {
|
|||||||
Usage: 0,
|
Usage: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
netInfo, err := GetNetIOInfo()
|
||||||
|
if err != nil {
|
||||||
|
netInfo = modules.Net{
|
||||||
|
Recv: 0,
|
||||||
|
Sent: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
memInfo, err := GetMemInfo()
|
memInfo, err := GetMemInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
memInfo = modules.Mem{
|
memInfo = modules.IO{
|
||||||
Total: 0,
|
Total: 0,
|
||||||
Used: 0,
|
Used: 0,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
@@ -229,7 +268,7 @@ func GetPartialInfo(getDisk bool) (*modules.Device, error) {
|
|||||||
}
|
}
|
||||||
diskInfo, err := GetDiskInfo()
|
diskInfo, err := GetDiskInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diskInfo = modules.Disk{
|
diskInfo = modules.IO{
|
||||||
Total: 0,
|
Total: 0,
|
||||||
Used: 0,
|
Used: 0,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
@@ -239,8 +278,9 @@ func GetPartialInfo(getDisk bool) (*modules.Device, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
uptime = 0
|
uptime = 0
|
||||||
}
|
}
|
||||||
return &modules.Device{
|
return modules.Device{
|
||||||
CPU: cpuInfo,
|
CPU: cpuInfo,
|
||||||
|
Net: netInfo,
|
||||||
Mem: memInfo,
|
Mem: memInfo,
|
||||||
Disk: diskInfo,
|
Disk: diskInfo,
|
||||||
Uptime: uptime,
|
Uptime: uptime,
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func InitTerminal(pack modules.Packet) error {
|
|||||||
buffer := make([]byte, 512)
|
buffer := make([]byte, 512)
|
||||||
n, err := stdout.Read(buffer)
|
n, err := stdout.Read(buffer)
|
||||||
buffer = buffer[:n]
|
buffer = buffer[:n]
|
||||||
buffer, _ = gbkToUtf8(buffer)
|
buffer, _ = encodeUTF8(buffer)
|
||||||
common.SendCb(modules.Packet{Act: `outputTerminal`, Data: map[string]interface{}{
|
common.SendCb(modules.Packet{Act: `outputTerminal`, Data: map[string]interface{}{
|
||||||
`output`: hex.EncodeToString(buffer),
|
`output`: hex.EncodeToString(buffer),
|
||||||
}}, pack, common.WSConn)
|
}}, pack, common.WSConn)
|
||||||
@@ -74,7 +74,7 @@ func InitTerminal(pack modules.Packet) error {
|
|||||||
buffer := make([]byte, 512)
|
buffer := make([]byte, 512)
|
||||||
n, err := stderr.Read(buffer)
|
n, err := stderr.Read(buffer)
|
||||||
buffer = buffer[:n]
|
buffer = buffer[:n]
|
||||||
buffer, _ = gbkToUtf8(buffer)
|
buffer, _ = encodeUTF8(buffer)
|
||||||
common.SendCb(modules.Packet{Act: `outputTerminal`, Data: map[string]interface{}{
|
common.SendCb(modules.Packet{Act: `outputTerminal`, Data: map[string]interface{}{
|
||||||
`output`: hex.EncodeToString(buffer),
|
`output`: hex.EncodeToString(buffer),
|
||||||
}}, pack, common.WSConn)
|
}}, pack, common.WSConn)
|
||||||
@@ -138,7 +138,7 @@ func InputTerminal(pack modules.Packet) error {
|
|||||||
terminal.cmd.Process.Signal(os.Interrupt)
|
terminal.cmd.Process.Signal(os.Interrupt)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
data, _ = utf8ToGbk(data)
|
data, _ = decodeUTF8(data)
|
||||||
(*terminal.stdin).Write(data)
|
(*terminal.stdin).Write(data)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -180,15 +180,33 @@ func doKillTerminal(terminal *terminal) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getTerminal() string {
|
func getTerminal() string {
|
||||||
switch runtime.GOOS {
|
if runtime.GOOS == `windows` {
|
||||||
case `windows`:
|
|
||||||
return `cmd.exe`
|
return `cmd.exe`
|
||||||
case `linux`:
|
}
|
||||||
return `sh`
|
sh := []string{`bash`, `zsh`, `sh`}
|
||||||
case `darwin`:
|
for i := 0; i < len(sh); i++ {
|
||||||
return `sh`
|
f, err := os.Open(sh[i])
|
||||||
default:
|
if err == nil {
|
||||||
return `sh`
|
f.Close()
|
||||||
|
return sh[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `sh`
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeUTF8(s []byte) ([]byte, error) {
|
||||||
|
if runtime.GOOS == `windows` {
|
||||||
|
return gbkToUtf8(s)
|
||||||
|
} else {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUTF8(s []byte) ([]byte, error) {
|
||||||
|
if runtime.GOOS == `windows` {
|
||||||
|
return utf8ToGbk(s)
|
||||||
|
} else {
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,28 +23,28 @@ type Device struct {
|
|||||||
LAN string `json:"lan"`
|
LAN string `json:"lan"`
|
||||||
WAN string `json:"wan"`
|
WAN string `json:"wan"`
|
||||||
Mac string `json:"mac"`
|
Mac string `json:"mac"`
|
||||||
|
Net Net `json:"net"`
|
||||||
CPU CPU `json:"cpu"`
|
CPU CPU `json:"cpu"`
|
||||||
Mem Mem `json:"mem"`
|
Mem IO `json:"mem"`
|
||||||
Disk Disk `json:"disk"`
|
Disk IO `json:"disk"`
|
||||||
Uptime uint64 `json:"uptime"`
|
Uptime uint64 `json:"uptime"`
|
||||||
Latency uint `json:"latency"`
|
Latency uint `json:"latency"`
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IO struct {
|
||||||
|
Total uint64 `json:"total"`
|
||||||
|
Used uint64 `json:"used"`
|
||||||
|
Usage float64 `json:"usage"`
|
||||||
|
}
|
||||||
|
|
||||||
type CPU struct {
|
type CPU struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Usage float64 `json:"usage"`
|
Usage float64 `json:"usage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mem struct {
|
type Net struct {
|
||||||
Total uint64 `json:"total"`
|
Sent uint64 `json:"sent"`
|
||||||
Used uint64 `json:"used"`
|
Recv uint64 `json:"recv"`
|
||||||
Usage float64 `json:"usage"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Disk struct {
|
|
||||||
Total uint64 `json:"total"`
|
|
||||||
Used uint64 `json:"used"`
|
|
||||||
Usage float64 `json:"usage"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 32 KiB |
@@ -8,6 +8,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"encoding/hex"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -117,6 +119,28 @@ func WSHealthCheck(container *melody.Melody) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckClientReq(ctx *gin.Context, cb func(*melody.Session)) bool {
|
||||||
|
secret, err := hex.DecodeString(ctx.GetHeader(`Secret`))
|
||||||
|
if err != nil || len(secret) != 32 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
find := false
|
||||||
|
Melody.IterSessions(func(uuid string, s *melody.Session) bool {
|
||||||
|
if val, ok := s.Get(`Secret`); ok {
|
||||||
|
// Check if there's a connection matches this secret.
|
||||||
|
if b, ok := val.([]byte); ok && bytes.Equal(b, secret) {
|
||||||
|
find = true
|
||||||
|
if cb != nil {
|
||||||
|
cb(s)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return find
|
||||||
|
}
|
||||||
|
|
||||||
func CheckDevice(deviceID string) (string, bool) {
|
func CheckDevice(deviceID string) (string, bool) {
|
||||||
connUUID := ``
|
connUUID := ``
|
||||||
Devices.IterCb(func(uuid string, v interface{}) bool {
|
Devices.IterCb(func(uuid string, v interface{}) bool {
|
||||||
|
|||||||
@@ -8,3 +8,6 @@ type Cfg struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var Config Cfg
|
var Config Cfg
|
||||||
|
|
||||||
|
// COMMIT means this commit hash, for auto upgrade.
|
||||||
|
var COMMIT = ``
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ func getDeviceFile(ctx *gin.Context) {
|
|||||||
ctx.Header(`Content-Length`, strconv.FormatInt(req.ContentLength, 10))
|
ctx.Header(`Content-Length`, strconv.FormatInt(req.ContentLength, 10))
|
||||||
}
|
}
|
||||||
ctx.Header(`Accept-Ranges`, `bytes`)
|
ctx.Header(`Accept-Ranges`, `bytes`)
|
||||||
|
ctx.Header(`Content-Transfer-Encoding`, `binary`)
|
||||||
ctx.Header(`Content-Type`, `application/octet-stream`)
|
ctx.Header(`Content-Type`, `application/octet-stream`)
|
||||||
filename := ctx.GetHeader(`FileName`)
|
filename := ctx.GetHeader(`FileName`)
|
||||||
if len(filename) == 0 {
|
if len(filename) == 0 {
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ func generateClient(ctx *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
ctx.Header(`Content-Disposition`, `attachment; filename=client;`)
|
ctx.Header(`Content-Disposition`, `attachment; filename=client;`)
|
||||||
}
|
}
|
||||||
|
// Find and replace plain buffer with encrypted configuration.
|
||||||
cfgBuffer := bytes.Repeat([]byte{'\x19'}, 384)
|
cfgBuffer := bytes.Repeat([]byte{'\x19'}, 384)
|
||||||
prevBuffer := make([]byte, 0)
|
prevBuffer := make([]byte, 0)
|
||||||
for {
|
for {
|
||||||
@@ -173,6 +174,7 @@ func genConfig(cfg clientCfg) ([]byte, error) {
|
|||||||
if len(final) > 384-2 {
|
if len(final) > 384-2 {
|
||||||
return nil, errTooLargeEntity
|
return nil, errTooLargeEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
dataLen := big.NewInt(int64(len(final))).Bytes()
|
dataLen := big.NewInt(int64(len(final))).Bytes()
|
||||||
dataLen = append(bytes.Repeat([]byte{'\x00'}, 2-len(dataLen)), dataLen...)
|
dataLen = append(bytes.Repeat([]byte{'\x00'}, 2-len(dataLen)), dataLen...)
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,24 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"Spark/modules"
|
"Spark/modules"
|
||||||
"Spark/server/common"
|
"Spark/server/common"
|
||||||
|
"Spark/server/config"
|
||||||
"Spark/utils"
|
"Spark/utils"
|
||||||
"Spark/utils/melody"
|
"Spark/utils/melody"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/kataras/golog"
|
"github.com/kataras/golog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIRouter 负责分配各种API接口
|
// APIRouter will initialize http and websocket routers.
|
||||||
func APIRouter(ctx *gin.RouterGroup, auth gin.HandlerFunc) {
|
func APIRouter(ctx *gin.RouterGroup, auth gin.HandlerFunc) {
|
||||||
ctx.PUT(`/device/screenshot/put`, putScreenshot)
|
ctx.PUT(`/device/screenshot/put`, putScreenshot) // Client, upload screenshot and forward to browser.
|
||||||
ctx.PUT(`/device/file/put`, putDeviceFile)
|
ctx.PUT(`/device/file/put`, putDeviceFile) // Client, to upload file and forward to browser.
|
||||||
ctx.Any(`/device/terminal`, initTerminal)
|
ctx.Any(`/device/terminal`, initTerminal) // Browser, handle websocket events for web terminal.
|
||||||
|
ctx.Any(`/client/update`, checkUpdate) // Client, for update.
|
||||||
group := ctx.Group(`/`, auth)
|
group := ctx.Group(`/`, auth)
|
||||||
{
|
{
|
||||||
group.POST(`/device/screenshot/get`, getScreenshot)
|
group.POST(`/device/screenshot/get`, getScreenshot)
|
||||||
@@ -32,7 +36,73 @@ func APIRouter(ctx *gin.RouterGroup, auth gin.HandlerFunc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// putScreenshot 负责获取client发送过来的屏幕截图
|
// checkUpdate will check if client need update and return latest client if so.
|
||||||
|
func checkUpdate(ctx *gin.Context) {
|
||||||
|
var form struct {
|
||||||
|
OS string `form:"os" binding:"required"`
|
||||||
|
Arch string `form:"arch" binding:"required"`
|
||||||
|
Commit string `form:"commit" binding:"required"`
|
||||||
|
}
|
||||||
|
if err := ctx.ShouldBind(&form); err != nil {
|
||||||
|
golog.Error(err)
|
||||||
|
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: -1, Msg: `参数不完整`})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if form.Commit == config.COMMIT {
|
||||||
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tpl, err := common.BuiltFS.Open(fmt.Sprintf(`/%v_%v`, form.OS, form.Arch))
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(http.StatusNotFound, modules.Packet{Code: 1, Msg: `该系统或架构的客户端尚未编译`})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const MaxBodySize = 384 // This is size of client config buffer.
|
||||||
|
if ctx.Request.ContentLength > MaxBodySize {
|
||||||
|
ctx.JSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body, err := ctx.GetRawData()
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: 1})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
auth := common.CheckClientReq(ctx, nil)
|
||||||
|
if !auth {
|
||||||
|
ctx.JSON(http.StatusUnauthorized, modules.Packet{Code: 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Header(`Accept-Ranges`, `none`)
|
||||||
|
ctx.Header(`Content-Transfer-Encoding`, `binary`)
|
||||||
|
ctx.Header(`Content-Type`, `application/octet-stream`)
|
||||||
|
if stat, err := tpl.Stat(); err == nil {
|
||||||
|
ctx.Header(`Content-Length`, strconv.FormatInt(stat.Size(), 10))
|
||||||
|
}
|
||||||
|
cfgBuffer := bytes.Repeat([]byte{'\x19'}, 384)
|
||||||
|
prevBuffer := make([]byte, 0)
|
||||||
|
for {
|
||||||
|
thisBuffer := make([]byte, 1024)
|
||||||
|
n, err := tpl.Read(thisBuffer)
|
||||||
|
thisBuffer = thisBuffer[:n]
|
||||||
|
tempBuffer := append(prevBuffer, thisBuffer...)
|
||||||
|
bufIndex := bytes.Index(tempBuffer, cfgBuffer)
|
||||||
|
if bufIndex > -1 {
|
||||||
|
tempBuffer = bytes.Replace(tempBuffer, cfgBuffer, body, -1)
|
||||||
|
}
|
||||||
|
ctx.Writer.Write(tempBuffer[:len(prevBuffer)])
|
||||||
|
prevBuffer = tempBuffer[len(prevBuffer):]
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(prevBuffer) > 0 {
|
||||||
|
ctx.Writer.Write(prevBuffer)
|
||||||
|
prevBuffer = []byte{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// putScreenshot will forward screenshot image from client to browser.
|
||||||
func putScreenshot(ctx *gin.Context) {
|
func putScreenshot(ctx *gin.Context) {
|
||||||
errMsg := ctx.GetHeader(`Error`)
|
errMsg := ctx.GetHeader(`Error`)
|
||||||
trigger := ctx.GetHeader(`Trigger`)
|
trigger := ctx.GetHeader(`Trigger`)
|
||||||
@@ -76,7 +146,7 @@ func putScreenshot(ctx *gin.Context) {
|
|||||||
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||||
}
|
}
|
||||||
|
|
||||||
// getScreenshot 负责发送指令给client,让其截图
|
// getScreenshot will call client to screenshot.
|
||||||
func getScreenshot(ctx *gin.Context) {
|
func getScreenshot(ctx *gin.Context) {
|
||||||
var form struct {
|
var form struct {
|
||||||
Conn string `json:"uuid" yaml:"uuid" form:"uuid"`
|
Conn string `json:"uuid" yaml:"uuid" form:"uuid"`
|
||||||
@@ -125,7 +195,7 @@ func getScreenshot(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDevices 负责获取所有device的基本信息
|
// getDevices will return all info about all clients.
|
||||||
func getDevices(ctx *gin.Context) {
|
func getDevices(ctx *gin.Context) {
|
||||||
devices := make(map[string]modules.Device)
|
devices := make(map[string]modules.Device)
|
||||||
common.Devices.IterCb(func(uuid string, v interface{}) bool {
|
common.Devices.IterCb(func(uuid string, v interface{}) bool {
|
||||||
@@ -138,7 +208,7 @@ func getDevices(ctx *gin.Context) {
|
|||||||
ctx.JSON(http.StatusOK, modules.CommonPack{Code: 0, Data: devices})
|
ctx.JSON(http.StatusOK, modules.CommonPack{Code: 0, Data: devices})
|
||||||
}
|
}
|
||||||
|
|
||||||
// callDevice 负责把HTTP网关发送的请求转发给client
|
// callDevice will call client with command from browser.
|
||||||
func callDevice(ctx *gin.Context) {
|
func callDevice(ctx *gin.Context) {
|
||||||
var form struct {
|
var form struct {
|
||||||
Conn string `json:"uuid" yaml:"uuid" form:"uuid"`
|
Conn string `json:"uuid" yaml:"uuid" form:"uuid"`
|
||||||
@@ -178,7 +248,8 @@ func callDevice(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WSDevice 负责处理client设备信息上报的事件
|
// WSDevice handles events about device info.
|
||||||
|
// Such as websocket handshake and update device info.
|
||||||
func WSDevice(data []byte, session *melody.Session) error {
|
func WSDevice(data []byte, session *melody.Session) error {
|
||||||
var pack struct {
|
var pack struct {
|
||||||
Code int `json:"code,omitempty"`
|
Code int `json:"code,omitempty"`
|
||||||
@@ -230,6 +301,7 @@ func WSDevice(data []byte, session *melody.Session) error {
|
|||||||
deviceInfo, ok := val.(*modules.Device)
|
deviceInfo, ok := val.(*modules.Device)
|
||||||
if ok {
|
if ok {
|
||||||
deviceInfo.CPU = pack.Device.CPU
|
deviceInfo.CPU = pack.Device.CPU
|
||||||
|
deviceInfo.Net = pack.Device.Net
|
||||||
deviceInfo.Mem = pack.Device.Mem
|
deviceInfo.Mem = pack.Device.Mem
|
||||||
if pack.Device.Disk.Total > 0 {
|
if pack.Device.Disk.Total > 0 {
|
||||||
deviceInfo.Disk = pack.Device.Disk
|
deviceInfo.Disk = pack.Device.Disk
|
||||||
@@ -243,8 +315,7 @@ func WSDevice(data []byte, session *melody.Session) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WSRouter 负责处理client回复的packet
|
// WSRouter handles all packets from client.
|
||||||
func WSRouter(pack modules.Packet, session *melody.Session) {
|
func WSRouter(pack modules.Packet, session *melody.Session) {
|
||||||
|
|
||||||
common.CallEvent(pack, session)
|
common.CallEvent(pack, session)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,11 +62,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
app := gin.New()
|
app := gin.New()
|
||||||
auth := gin.BasicAuth(config.Config.Auth)
|
auth := gin.BasicAuth(config.Config.Auth)
|
||||||
|
handler.APIRouter(app.Group(`/api`), auth)
|
||||||
|
app.Any(`/ws`, wsHandshake)
|
||||||
app.NoRoute(auth, func(ctx *gin.Context) {
|
app.NoRoute(auth, func(ctx *gin.Context) {
|
||||||
http.FileServer(webFS).ServeHTTP(ctx.Writer, ctx.Request)
|
http.FileServer(webFS).ServeHTTP(ctx.Writer, ctx.Request)
|
||||||
})
|
})
|
||||||
handler.APIRouter(app.Group(`/api`), auth)
|
|
||||||
app.Any(`/ws`, wsHandshake)
|
|
||||||
|
|
||||||
common.Melody.Config.MaxMessageSize = 1024
|
common.Melody.Config.MaxMessageSize = 1024
|
||||||
common.Melody.HandleConnect(wsOnConnect)
|
common.Melody.HandleConnect(wsOnConnect)
|
||||||
@@ -126,26 +126,23 @@ func wsHandshake(ctx *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
// When message is too large to transport via websocket,
|
// When message is too large to transport via websocket,
|
||||||
// client will try to send these data via http.
|
// client will try to send these data via http.
|
||||||
// Here is the data validator.
|
const MaxBodySize = 2 << 18 //524288 512KB
|
||||||
const MaxBufferSize = 2 << 18 //524288 512KB
|
if ctx.Request.ContentLength > MaxBodySize {
|
||||||
secret, err := hex.DecodeString(ctx.GetHeader(`Secret`))
|
ctx.JSON(http.StatusRequestEntityTooLarge, modules.Packet{Code: 1})
|
||||||
if err != nil || len(secret) != 32 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
body, err := ctx.GetRawData()
|
body, err := ctx.GetRawData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
ctx.JSON(http.StatusBadRequest, modules.Packet{Code: 1})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
common.Melody.IterSessions(func(uuid string, s *melody.Session) bool {
|
auth := common.CheckClientReq(ctx, func(s *melody.Session) {
|
||||||
if val, ok := s.Get(`Secret`); ok {
|
wsOnMessageBinary(s, body)
|
||||||
// Check if there's the connection with the secret.
|
|
||||||
if b, ok := val.([]byte); ok && bytes.Equal(b, secret) {
|
|
||||||
wsOnMessageBinary(s, body)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
})
|
||||||
|
if !auth {
|
||||||
|
ctx.JSON(http.StatusUnauthorized, modules.Packet{Code: 1})
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, modules.Packet{Code: 0})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
npm run build-prod
|
npm run build-prod
|
||||||
|
statik -m -src="../web/dist" -f -dest="../server/embed" -p web -ns web
|
||||||
@@ -13,7 +13,6 @@ import 'dayjs/locale/zh-cn';
|
|||||||
import Overview from "./pages/overview";
|
import Overview from "./pages/overview";
|
||||||
|
|
||||||
dayjs.locale('zh-cn');
|
dayjs.locale('zh-cn');
|
||||||
console.log("%c By XZB", 'font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:64px;color:#00bbee;-webkit-text-fill-color:#00bbee;-webkit-text-stroke:1px#00bbee;');
|
|
||||||
|
|
||||||
axios.defaults.baseURL = '.';
|
axios.defaults.baseURL = '.';
|
||||||
axios.interceptors.response.use(async (res) => {
|
axios.interceptors.response.use(async (res) => {
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import {QuestionCircleOutlined} from "@ant-design/icons";
|
|||||||
|
|
||||||
import defaultColumnsState from "../config/columnsState.json";
|
import defaultColumnsState from "../config/columnsState.json";
|
||||||
|
|
||||||
|
// DO NOT EDIT OR DELETE THIS COPYRIGHT MESSAGE.
|
||||||
|
console.log("%c By XZB %c https://github.com/XZB-1248/Spark", 'font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:64px;color:#00bbee;-webkit-text-fill-color:#00bbee;-webkit-text-stroke:1px#00bbee;', 'font-size:12px;');
|
||||||
|
|
||||||
function overview(props) {
|
function overview(props) {
|
||||||
const [procMgr, setProcMgr] = useState(false);
|
const [procMgr, setProcMgr] = useState(false);
|
||||||
const [browser, setBrowser] = useState(false);
|
const [browser, setBrowser] = useState(false);
|
||||||
@@ -33,7 +36,7 @@ function overview(props) {
|
|||||||
title: 'Username',
|
title: 'Username',
|
||||||
dataIndex: 'username',
|
dataIndex: 'username',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
width: 100
|
width: 90
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'ping',
|
key: 'ping',
|
||||||
@@ -48,7 +51,7 @@ function overview(props) {
|
|||||||
title: 'CPU Usage',
|
title: 'CPU Usage',
|
||||||
dataIndex: 'cpu_usage',
|
dataIndex: 'cpu_usage',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (_, v) => <Progress percent={v.cpu_usage} showInfo={false} strokeWidth={12} />,
|
render: (_, v) => <Progress percent={v.cpu_usage} showInfo={false} strokeWidth={12} trailColor='#FFECFF'/>,
|
||||||
width: 100
|
width: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -56,7 +59,7 @@ function overview(props) {
|
|||||||
title: 'Mem Usage',
|
title: 'Mem Usage',
|
||||||
dataIndex: 'mem_usage',
|
dataIndex: 'mem_usage',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (_, v) => <Progress percent={v.mem_usage} showInfo={false} strokeWidth={12} />,
|
render: (_, v) => <Progress percent={v.mem_usage} showInfo={false} strokeWidth={12} trailColor='#FFECFF'/>,
|
||||||
width: 100
|
width: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -64,9 +67,17 @@ function overview(props) {
|
|||||||
title: 'Disk Usage',
|
title: 'Disk Usage',
|
||||||
dataIndex: 'disk_usage',
|
dataIndex: 'disk_usage',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (_, v) => <Progress percent={v.disk_usage} showInfo={false} strokeWidth={12} />,
|
render: (_, v) => <Progress percent={v.disk_usage} showInfo={false} strokeWidth={12} trailColor='#FFECFF'/>,
|
||||||
width: 100
|
width: 100
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'mem_total',
|
||||||
|
title: 'Mem',
|
||||||
|
dataIndex: 'mem_total',
|
||||||
|
ellipsis: true,
|
||||||
|
renderText: formatSize,
|
||||||
|
width: 70
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'os',
|
key: 'os',
|
||||||
title: 'OS',
|
title: 'OS',
|
||||||
@@ -102,14 +113,6 @@ function overview(props) {
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
width: 100
|
width: 100
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'mem_total',
|
|
||||||
title: 'Mem',
|
|
||||||
dataIndex: 'mem_total',
|
|
||||||
ellipsis: true,
|
|
||||||
renderText: formatSize,
|
|
||||||
width: 70
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'uptime',
|
key: 'uptime',
|
||||||
title: 'Uptime',
|
title: 'Uptime',
|
||||||
@@ -118,14 +121,21 @@ function overview(props) {
|
|||||||
renderText: tsToTime,
|
renderText: tsToTime,
|
||||||
width: 100
|
width: 100
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'net_stat',
|
||||||
|
title: 'Network IO',
|
||||||
|
ellipsis: true,
|
||||||
|
renderText: (_, v) => renderNetworkIO(v),
|
||||||
|
width: 170
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'option',
|
key: 'option',
|
||||||
width: 180,
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
valueType: 'option',
|
valueType: 'option',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (_, device) => renderOperation(device)
|
render: (_, device) => renderOperation(device),
|
||||||
|
width: 170
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const options = {
|
const options = {
|
||||||
@@ -165,6 +175,22 @@ function overview(props) {
|
|||||||
localStorage.setItem(`columnsState`, JSON.stringify(stateMap));
|
localStorage.setItem(`columnsState`, JSON.stringify(stateMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderNetworkIO(device) {
|
||||||
|
// Make unit starts with Kbps.
|
||||||
|
let sent = device.net_sent * 8 / 1024;
|
||||||
|
let recv = device.net_recv * 8 / 1024;
|
||||||
|
return `${format(sent)} ↑ / ${format(recv)} ↓`;
|
||||||
|
|
||||||
|
function format(size) {
|
||||||
|
if (size <= 1) return '0 Kbps';
|
||||||
|
// Units array is large enough.
|
||||||
|
let k = 1024,
|
||||||
|
i = Math.floor(Math.log(size) / Math.log(k)),
|
||||||
|
units = ['Kbps', 'Mbps', 'Gbps', 'Tbps'];
|
||||||
|
return (size / Math.pow(k, i)).toFixed(1) + ' ' + units[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderOperation(device) {
|
function renderOperation(device) {
|
||||||
return [
|
return [
|
||||||
<a key='terminal' onClick={setTerminal.bind(null, device.id)}>终端</a>,
|
<a key='terminal' onClick={setTerminal.bind(null, device.id)}>终端</a>,
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ function waitTime(time) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatSize(bytes) {
|
function formatSize(size) {
|
||||||
if (bytes === 0) return 'Unknown';
|
if (size === 0) return 'Unknown';
|
||||||
let k = 1024,
|
let k = 1024,
|
||||||
i = Math.floor(Math.log(bytes) / Math.log(k)),
|
i = Math.floor(Math.log(size) / Math.log(k)),
|
||||||
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
|
return (size / Math.pow(k, i)).toFixed(2) + ' ' + units[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
function tsToTime(ts) {
|
function tsToTime(ts) {
|
||||||
|
|||||||
Reference in New Issue
Block a user