Initial commit (code only without large binaries)
This commit is contained in:
12
EdgeAdmin/.gitignore
vendored
Normal file
12
EdgeAdmin/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
部署启动手册.md
|
||||
|
||||
# Runtime Data
|
||||
data/
|
||||
logs/
|
||||
|
||||
# Local Configs
|
||||
configs/api_admin.yaml
|
||||
configs/server.yaml
|
||||
|
||||
# Build Artifacts
|
||||
bin/
|
||||
74
EdgeAdmin/.golangci.yaml
Normal file
74
EdgeAdmin/.golangci.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
# https://golangci-lint.run/usage/configuration/
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- ifshort
|
||||
- exhaustivestruct
|
||||
- golint
|
||||
- nosnakecase
|
||||
- scopelint
|
||||
- varcheck
|
||||
- structcheck
|
||||
- interfacer
|
||||
- maligned
|
||||
- deadcode
|
||||
- dogsled
|
||||
- wrapcheck
|
||||
- wastedassign
|
||||
- varnamelen
|
||||
- testpackage
|
||||
- thelper
|
||||
- nilerr
|
||||
- sqlclosecheck
|
||||
- paralleltest
|
||||
- nonamedreturns
|
||||
- nlreturn
|
||||
- nakedret
|
||||
- ireturn
|
||||
- interfacebloat
|
||||
- gosmopolitan
|
||||
- gomnd
|
||||
- goerr113
|
||||
- gochecknoglobals
|
||||
- exhaustruct
|
||||
- errorlint
|
||||
- depguard
|
||||
- exhaustive
|
||||
- containedctx
|
||||
- wsl
|
||||
- cyclop
|
||||
- dupword
|
||||
- errchkjson
|
||||
- contextcheck
|
||||
- tagalign
|
||||
- dupl
|
||||
- forbidigo
|
||||
- funlen
|
||||
- goconst
|
||||
- godox
|
||||
- gosec
|
||||
- lll
|
||||
- nestif
|
||||
- revive
|
||||
- unparam
|
||||
- stylecheck
|
||||
- gocritic
|
||||
- gofumpt
|
||||
- gomoddirectives
|
||||
- godot
|
||||
- gofmt
|
||||
- gocognit
|
||||
- mirror
|
||||
- gocyclo
|
||||
- gochecknoinits
|
||||
- gci
|
||||
- maintidx
|
||||
- prealloc
|
||||
- goimports
|
||||
- errname
|
||||
- musttag
|
||||
- forcetypeassert
|
||||
- whitespace
|
||||
- noctx
|
||||
- reassign
|
||||
29
EdgeAdmin/LICENSE
Normal file
29
EdgeAdmin/LICENSE
Normal file
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2020, LiuXiangChao
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
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.
|
||||
63
EdgeAdmin/README.md
Normal file
63
EdgeAdmin/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# GoEdge目标
|
||||
做一款人人用得起的CDN & WAF系统。
|
||||
|
||||

|
||||
|
||||
## 特性
|
||||
* `免费` - 开源、免费、自由、开放
|
||||
* `简单` - 架构简单清晰,安装简单,使用简单,运维简单
|
||||
* `高扩展性` - 可以自由扩展新的节点,支持亿级数据
|
||||
|
||||
## 功能介绍
|
||||
* 多用户
|
||||
* 日志审计
|
||||
* 集群管理
|
||||
* HTTP/HTTPS/TCP/UDP等协议支持
|
||||
* WAF
|
||||
* 缓存
|
||||
* DNS自动解析
|
||||
* 多域名绑定
|
||||
* 免费证书申请
|
||||
* IP黑白名单
|
||||
* 访问日志
|
||||
* 统计
|
||||
* 内容压缩
|
||||
* Protocol Proxy协议
|
||||
* 本地静态文件
|
||||
* URL跳转
|
||||
* 路由规则
|
||||
* 重写规则
|
||||
* 访问控制
|
||||
* 字符编码
|
||||
* 自定义页面
|
||||
* 自定义HTTP Header
|
||||
* Websocket
|
||||
* WebP自动转换
|
||||
* Fastcgi
|
||||
* 请求限制
|
||||
* 流量限制
|
||||
|
||||
## 在线演示
|
||||
* [http://demo.goedge.cn](http://demo.goedge.cn)
|
||||
|
||||
## 文档
|
||||
* [新手指南](https://goedge.cn/docs/QuickStart/Index.md)
|
||||
* [完整文档](https://goedge.cn/docs)
|
||||
* [开发者指南](https://goedge.cn/docs/Developer/Build.md)
|
||||
|
||||
## 架构
|
||||

|
||||
|
||||
其中的组件源码地址如下:
|
||||
* [边缘节点](https://github.com/TeaOSLab/EdgeNode)
|
||||
* [API节点](https://github.com/TeaOSLab/EdgeAPI)
|
||||
* [管理平台](https://github.com/TeaOSLab/EdgeAdmin)
|
||||
|
||||
## 联系我们
|
||||
有什么问题和建议都可以加入 [Telegram群](https://t.me/+5kVCMGxQhZxiODY9)
|
||||
|
||||
## 企业版
|
||||
* [GoEdge企业版](https://goedge.cn/commercial) - 功能更强大的CDN系统
|
||||
|
||||
## 感谢
|
||||
* 感谢 [Gitee](https://gitee.com/) 提供国内源代码托管平台
|
||||
2
EdgeAdmin/build/.gitignore
vendored
Normal file
2
EdgeAdmin/build/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
edge-api/
|
||||
build-all-test.sh
|
||||
28
EdgeAdmin/build/build-all-plus.sh
Normal file
28
EdgeAdmin/build/build-all-plus.sh
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ROOT=$(dirname $0)
|
||||
|
||||
# build all nodes
|
||||
if [ -f $ROOT"/../../EdgeNode/build/build-all-plus.sh" ]; then
|
||||
echo "=============================="
|
||||
echo "build all edge-node"
|
||||
echo "=============================="
|
||||
cd $ROOT"/../../EdgeNode/build"
|
||||
./build-all-plus.sh
|
||||
cd -
|
||||
fi
|
||||
|
||||
# build admin
|
||||
echo "=============================="
|
||||
echo "build all edge-admin"
|
||||
echo "=============================="
|
||||
./build.sh linux amd64 plus
|
||||
#./build.sh linux 386 plus
|
||||
#./build.sh linux arm64 plus
|
||||
#./build.sh linux mips64 plus
|
||||
#./build.sh linux mips64le plus
|
||||
#./build.sh darwin amd64 plus
|
||||
#./build.sh darwin arm64 plus
|
||||
|
||||
|
||||
|
||||
21
EdgeAdmin/build/build-all.sh
Normal file
21
EdgeAdmin/build/build-all.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ROOT=$(dirname $0)
|
||||
|
||||
# build all nodes
|
||||
if [ -f $ROOT"/../../EdgeNode/build/build-all-plus.sh" ]; then
|
||||
echo "=============================="
|
||||
echo "build all edge-node"
|
||||
echo "=============================="
|
||||
cd $ROOT"/../../EdgeNode/build"
|
||||
./build-all-plus.sh
|
||||
cd -
|
||||
fi
|
||||
|
||||
./build.sh linux amd64
|
||||
./build.sh linux 386
|
||||
#./build.sh linux arm64
|
||||
./build.sh linux mips64
|
||||
./build.sh linux mips64le
|
||||
./build.sh darwin amd64
|
||||
#./build.sh darwin arm64
|
||||
243
EdgeAdmin/build/build.sh
Normal file
243
EdgeAdmin/build/build.sh
Normal file
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function build() {
|
||||
ROOT=$(dirname "$0")
|
||||
JS_ROOT=$ROOT/../web/public/js
|
||||
NAME="edge-admin"
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
OS=${1}
|
||||
ARCH=${2}
|
||||
TAG=${3}
|
||||
|
||||
if [ -z "$OS" ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z "$ARCH" ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="community"
|
||||
fi
|
||||
|
||||
# checking environment
|
||||
echo "checking required commands ..."
|
||||
commands=("zip" "unzip" "go" "find" "sed")
|
||||
for cmd in "${commands[@]}"; do
|
||||
if [ "$(which "${cmd}")" ]; then
|
||||
echo "checking ${cmd}: ok"
|
||||
else
|
||||
echo "checking ${cmd}: not found"
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
|
||||
# 鐢熸垚 zip 鏂囦欢鍚嶆椂涓嶅寘鍚?plus 鏍囪
|
||||
if [ "${TAG}" = "plus" ]; then
|
||||
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
|
||||
else
|
||||
ZIP="${NAME}-${OS}-${ARCH}-${TAG}-v${VERSION}.zip"
|
||||
fi
|
||||
|
||||
# build edge-api
|
||||
APINodeVersion=$(lookup-version "$ROOT""/../../EdgeAPI/internal/const/const.go")
|
||||
echo "building edge-api v${APINodeVersion} ..."
|
||||
EDGE_API_BUILD_SCRIPT=$ROOT"/../../EdgeAPI/build/build.sh"
|
||||
if [ ! -f "$EDGE_API_BUILD_SCRIPT" ]; then
|
||||
echo "unable to find edge-api build script 'EdgeAPI/build/build.sh'"
|
||||
exit
|
||||
fi
|
||||
|
||||
cd "$ROOT""/../../EdgeAPI/build" || exit
|
||||
echo "=============================="
|
||||
./build.sh "$OS" "$ARCH" $TAG
|
||||
echo "=============================="
|
||||
cd - || exit
|
||||
|
||||
# generate files
|
||||
echo "generating files ..."
|
||||
env CGO_ENABLED=0 go run -tags $TAG "$ROOT"/../cmd/edge-admin/main.go generate
|
||||
if [ "$(which uglifyjs)" ]; then
|
||||
echo "compress to component.js ..."
|
||||
uglifyjs --compress --mangle -- "${JS_ROOT}"/components.src.js > "${JS_ROOT}"/components.js
|
||||
uglifyjs --compress --mangle -- "${JS_ROOT}"/utils.js > "${JS_ROOT}"/utils.min.js
|
||||
else
|
||||
echo "copy to component.js ..."
|
||||
cp "${JS_ROOT}"/components.src.js "${JS_ROOT}"/components.js
|
||||
cp "${JS_ROOT}"/utils.js "${JS_ROOT}"/utils.min.js
|
||||
fi
|
||||
|
||||
# create dir & copy files
|
||||
echo "copying ..."
|
||||
if [ ! -d "$DIST" ]; then
|
||||
mkdir "$DIST"
|
||||
mkdir "$DIST"/bin
|
||||
mkdir "$DIST"/configs
|
||||
mkdir "$DIST"/logs
|
||||
fi
|
||||
|
||||
cp -R "$ROOT"/../web "$DIST"/
|
||||
rm -f "$DIST"/web/tmp/*
|
||||
rm -rf "$DIST"/web/public/js/components
|
||||
rm -f "$DIST"/web/public/js/components.src.js
|
||||
cp "$ROOT"/configs/server.template.yaml "$DIST"/configs/
|
||||
|
||||
# change _plus.[ext] to .[ext]
|
||||
if [ "${TAG}" = "plus" ]; then
|
||||
echo "converting filenames ..."
|
||||
exts=("html" "js" "css")
|
||||
for ext in "${exts[@]}"; do
|
||||
pattern="*_plus."${ext}
|
||||
find "$DIST"/web/views -type f -name "$pattern" | \
|
||||
while read filename; do
|
||||
mv "${filename}" "${filename/_plus."${ext}"/."${ext}"}"
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
# 鏌ユ壘 EdgeAPI zip 鏂囦欢鏃朵笉鍖呭惈 plus 鏍囪
|
||||
if [ "${TAG}" = "plus" ]; then
|
||||
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-v${APINodeVersion}.zip"
|
||||
else
|
||||
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-${TAG}-v${APINodeVersion}.zip"
|
||||
fi
|
||||
cp "$EDGE_API_ZIP_FILE" "$DIST"/
|
||||
cd "$DIST"/ || exit
|
||||
unzip -q "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
rm -f "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
|
||||
# ensure edge-api package always contains fluent-bit runtime assets/packages
|
||||
FLUENT_ROOT="$ROOT/../../deploy/fluent-bit"
|
||||
FLUENT_DIST="$DIST/edge-api/deploy/fluent-bit"
|
||||
if [ -d "$FLUENT_ROOT" ]; then
|
||||
verify_fluent_bit_package_matrix "$FLUENT_ROOT" "$ARCH" || exit 1
|
||||
rm -rf "$FLUENT_DIST"
|
||||
mkdir -p "$FLUENT_DIST"
|
||||
|
||||
FLUENT_FILES=(
|
||||
"parsers.conf"
|
||||
)
|
||||
for file in "${FLUENT_FILES[@]}"; do
|
||||
if [ -f "$FLUENT_ROOT/$file" ]; then
|
||||
cp "$FLUENT_ROOT/$file" "$FLUENT_DIST/"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "$FLUENT_ROOT/packages" ]; then
|
||||
cp -R "$FLUENT_ROOT/packages" "$FLUENT_DIST/"
|
||||
fi
|
||||
|
||||
rm -f "$FLUENT_DIST/.gitignore"
|
||||
rm -f "$FLUENT_DIST"/logs.db*
|
||||
rm -rf "$FLUENT_DIST/storage"
|
||||
fi
|
||||
|
||||
# 鍒犻櫎 MaxMind 鏁版嵁搴撴枃浠讹紙浣跨敤宓屽叆鐨勬暟鎹簱锛屼笉闇€瑕佸閮ㄦ枃浠讹級
|
||||
find . -name "*.mmdb" -type f -delete
|
||||
find . -type d -name "iplibrary" -empty -delete
|
||||
cd - || exit
|
||||
|
||||
# find gcc
|
||||
GCC_DIR=""
|
||||
CC_PATH=""
|
||||
CXX_PATH=""
|
||||
if [ "${ARCH}" == "amd64" ]; then
|
||||
GCC_DIR="/usr/local/gcc/x86_64-unknown-linux-gnu/bin"
|
||||
CC_PATH="x86_64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="x86_64-unknown-linux-gnu-g++"
|
||||
fi
|
||||
if [ "${ARCH}" == "arm64" ]; then
|
||||
GCC_DIR="/usr/local/gcc/aarch64-unknown-linux-gnu/bin"
|
||||
CC_PATH="aarch64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="aarch64-unknown-linux-gnu-g++"
|
||||
fi
|
||||
|
||||
# build
|
||||
echo "building ${NAME} ..."
|
||||
if [ -f "${GCC_DIR}/${CC_PATH}" ]; then
|
||||
echo " building ${NAME} with gcc ..."
|
||||
env CC="${GCC_DIR}/${CC_PATH}" \
|
||||
CXX="${GCC_DIR}/${CXX_PATH}" \
|
||||
CGO_ENABLED=1 \
|
||||
GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags "${TAG} gcc" -ldflags="-linkmode external -extldflags -static -s -w" -o "$DIST"/bin/${NAME} "$ROOT"/../cmd/edge-admin/main.go
|
||||
else
|
||||
GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags $TAG -ldflags="-s -w" -o "$DIST"/bin/${NAME} "$ROOT"/../cmd/edge-admin/main.go
|
||||
fi
|
||||
if [ ! -f "${DIST}/bin/${NAME}" ]; then
|
||||
echo "build '${NAME}' failed!"
|
||||
exit
|
||||
fi
|
||||
|
||||
# delete hidden files
|
||||
find "$DIST" -name ".DS_Store" -delete
|
||||
find "$DIST" -name ".gitignore" -delete
|
||||
find "$DIST" -name "*.less" -delete
|
||||
#find "$DIST" -name "*.css.map" -delete
|
||||
#find "$DIST" -name "*.js.map" -delete
|
||||
|
||||
# 鍒犻櫎 MaxMind 鏁版嵁搴撴枃浠讹紙浣跨敤宓屽叆鐨勬暟鎹簱锛屼笉闇€瑕佸閮ㄦ枃浠讹級
|
||||
find "$DIST" -name "*.mmdb" -type f -delete
|
||||
find "$DIST" -type d -name "iplibrary" -empty -delete
|
||||
|
||||
# zip
|
||||
echo "zip files ..."
|
||||
cd "${DIST}/../" || exit
|
||||
if [ -f "${ZIP}" ]; then
|
||||
rm -f "${ZIP}"
|
||||
fi
|
||||
zip -r -X -q "${ZIP}" ${NAME}/
|
||||
rm -rf ${NAME}
|
||||
cd - || exit
|
||||
|
||||
echo "[done]"
|
||||
}
|
||||
|
||||
function verify_fluent_bit_package_matrix() {
|
||||
FLUENT_ROOT=$1
|
||||
ARCH=$2
|
||||
REQUIRED_FILES=()
|
||||
if [ "$ARCH" = "amd64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-amd64/fluent-bit_4.2.2_amd64.deb"
|
||||
"packages/linux-amd64/fluent-bit-4.2.2-1.x86_64.rpm"
|
||||
)
|
||||
elif [ "$ARCH" = "arm64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-arm64/fluent-bit_4.2.2_arm64.deb"
|
||||
"packages/linux-arm64/fluent-bit-4.2.2-1.aarch64.rpm"
|
||||
)
|
||||
else
|
||||
echo "[error] unsupported arch for fluent-bit package validation: $ARCH"
|
||||
return 1
|
||||
fi
|
||||
|
||||
MISSING=0
|
||||
for FILE in "${REQUIRED_FILES[@]}"; do
|
||||
if [ ! -f "$FLUENT_ROOT/$FILE" ]; then
|
||||
echo "[error] fluent-bit matrix package missing: $FLUENT_ROOT/$FILE"
|
||||
MISSING=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$MISSING" -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat "$FILE")
|
||||
re="Version[ ]+=[ ]+\"([0-9.]+)\""
|
||||
if [[ $VERSION_DATA =~ $re ]]; then
|
||||
VERSION=${BASH_REMATCH[1]}
|
||||
echo "$VERSION"
|
||||
else
|
||||
echo "could not match version"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
build "$1" "$2" "$3"
|
||||
6
EdgeAdmin/build/configs/.gitignore
vendored
Normal file
6
EdgeAdmin/build/configs/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
api.yaml
|
||||
api_admin.yaml
|
||||
server.yaml
|
||||
api_db.yaml
|
||||
*.pem
|
||||
*.cache.json
|
||||
3
EdgeAdmin/build/configs/api_admin.template.yaml
Normal file
3
EdgeAdmin/build/configs/api_admin.template.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
rpc.endpoints: [ "http://127.0.0.1:8003" ]
|
||||
nodeId: ""
|
||||
secret: ""
|
||||
11
EdgeAdmin/build/configs/api_db.template.yaml
Normal file
11
EdgeAdmin/build/configs/api_db.template.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
default:
|
||||
db: "prod"
|
||||
prefix: ""
|
||||
|
||||
dbs:
|
||||
prod:
|
||||
driver: "mysql"
|
||||
dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s"
|
||||
prefix: "edge"
|
||||
models:
|
||||
package: internal/web/models
|
||||
16
EdgeAdmin/build/configs/server.template.yaml
Normal file
16
EdgeAdmin/build/configs/server.template.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
# environment code
|
||||
env: prod
|
||||
|
||||
# http
|
||||
http:
|
||||
"on": true
|
||||
listen: [ "0.0.0.0:7788" ]
|
||||
|
||||
# https
|
||||
https:
|
||||
"on": false
|
||||
listen: [ "0.0.0.0:443"]
|
||||
cert: ""
|
||||
key: ""
|
||||
|
||||
|
||||
22
EdgeAdmin/build/generate.sh
Normal file
22
EdgeAdmin/build/generate.sh
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
JS_ROOT=../web/public/js
|
||||
|
||||
echo "generating component.src.js ..."
|
||||
env CGO_ENABLED=0 go run -tags=community ../cmd/edge-admin/main.go generate
|
||||
|
||||
if [ "$(which uglifyjs)" ]; then
|
||||
echo "compress to component.js ..."
|
||||
uglifyjs --compress --mangle -- ${JS_ROOT}/components.src.js > ${JS_ROOT}/components.js
|
||||
|
||||
echo "compress to utils.min.js ..."
|
||||
uglifyjs --compress --mangle -- ${JS_ROOT}/utils.js > ${JS_ROOT}/utils.min.js
|
||||
else
|
||||
echo "copy to component.js ..."
|
||||
cp ${JS_ROOT}/components.src.js ${JS_ROOT}/components.js
|
||||
|
||||
echo "copy to utils.min.js ..."
|
||||
cp ${JS_ROOT}/utils.js ${JS_ROOT}/utils.min.js
|
||||
fi
|
||||
|
||||
echo "ok"
|
||||
1
EdgeAdmin/build/licenses/README.md
Normal file
1
EdgeAdmin/build/licenses/README.md
Normal file
@@ -0,0 +1 @@
|
||||
这个目录下我们列举了所有需要公开声明的第三方License,如果有遗漏,烦请告知 goedge.cdn@gmail.com。再次感谢这些开源软件项目和贡献人员!
|
||||
30
EdgeAdmin/build/licenses/miekg-dns.md
Normal file
30
EdgeAdmin/build/licenses/miekg-dns.md
Normal file
@@ -0,0 +1,30 @@
|
||||
Copyright (c) 2009 The Go Authors. 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.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
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
|
||||
OWNER 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.
|
||||
|
||||
As this is fork of the official Go code the same license applies.
|
||||
Extensions of the original work are copyright (c) 2011 Miek Gieben
|
||||
1
EdgeAdmin/build/logs/.gitignore
vendored
Normal file
1
EdgeAdmin/build/logs/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.log
|
||||
3
EdgeAdmin/build/search-hans-plus.sh
Normal file
3
EdgeAdmin/build/search-hans-plus.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
go run ../../EdgeCommon/cmd/langs/main.go search /Users/WorkSpace/EdgeProject/EdgeAdmin/internal
|
||||
216
EdgeAdmin/cmd/edge-admin/main.go
Normal file
216
EdgeAdmin/cmd/edge-admin/main.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/apps"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/gen"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/updates/updateutils"
|
||||
_ "github.com/TeaOSLab/EdgeCommon/pkg/langs/messages"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var app = apps.NewAppCmd().
|
||||
Version(teaconst.Version).
|
||||
Product(teaconst.ProductName).
|
||||
Usage(teaconst.ProcessName+" [-h|-v|start|stop|restart|service|daemon|reset|recover|demo|upgrade]").
|
||||
Usage(teaconst.ProcessName+" [dev|prod]").
|
||||
Option("-h", "show this help").
|
||||
Option("-v", "show version").
|
||||
Option("start", "start the service").
|
||||
Option("stop", "stop the service").
|
||||
Option("restart", "restart the service").
|
||||
Option("service", "register service into systemd").
|
||||
Option("daemon", "start the service with daemon").
|
||||
Option("reset", "reset configs").
|
||||
Option("recover", "enter recovery mode").
|
||||
Option("demo", "switch to demo mode").
|
||||
Option("dev", "switch to 'dev' mode").
|
||||
Option("prod", "switch to 'prod' mode").
|
||||
Option("upgrade [--url=URL]", "upgrade from official site or an url").
|
||||
Option("install-local-node", "install a local node").
|
||||
Option("security.reset", "reset security config")
|
||||
|
||||
app.On("daemon", func() {
|
||||
nodes.NewAdminNode().Daemon()
|
||||
})
|
||||
app.On("service", func() {
|
||||
err := nodes.NewAdminNode().InstallSystemService()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]install failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("done")
|
||||
})
|
||||
app.On("reset", func() {
|
||||
err := configs.ResetAPIConfig()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]reset failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// reset local api
|
||||
var apiNodeExe = Tea.Root + "/edge-api/bin/edge-api"
|
||||
_, err = os.Stat(apiNodeExe)
|
||||
if err == nil {
|
||||
var cmd = exec.Command(apiNodeExe, "reset")
|
||||
var stderr = &bytes.Buffer{}
|
||||
cmd.Stderr = stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println("reset api node failed: " + stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("done")
|
||||
})
|
||||
app.On("recover", func() {
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
if !sock.IsListening() {
|
||||
fmt.Println("[ERROR]the service not started yet, you should start the service first")
|
||||
return
|
||||
}
|
||||
_, err := sock.Send(&gosock.Command{Code: "recover"})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]enter recovery mode failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("enter recovery mode successfully")
|
||||
})
|
||||
app.On("demo", func() {
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
if !sock.IsListening() {
|
||||
fmt.Println("[ERROR]the service not started yet, you should start the service first")
|
||||
return
|
||||
}
|
||||
reply, err := sock.Send(&gosock.Command{Code: "demo"})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]change demo mode failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
var isDemo = maps.NewMap(reply.Params).GetBool("isDemo")
|
||||
if isDemo {
|
||||
fmt.Println("change demo mode to: on")
|
||||
} else {
|
||||
fmt.Println("change demo mode to: off")
|
||||
}
|
||||
})
|
||||
app.On("generate", func() {
|
||||
err := gen.Generate()
|
||||
if err != nil {
|
||||
fmt.Println("generate failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
})
|
||||
app.On("dev", func() {
|
||||
var env = "dev"
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
_, err := sock.Send(&gosock.Command{
|
||||
Code: env,
|
||||
Params: nil,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("failed to switch to '" + env + "': " + err.Error())
|
||||
} else {
|
||||
fmt.Println("switch to '" + env + "' ok")
|
||||
}
|
||||
})
|
||||
app.On("prod", func() {
|
||||
var env = "prod"
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
_, err := sock.Send(&gosock.Command{
|
||||
Code: env,
|
||||
Params: nil,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("failed to switch to '" + env + "': " + err.Error())
|
||||
} else {
|
||||
fmt.Println("switch to '" + env + "' ok")
|
||||
}
|
||||
})
|
||||
app.On("upgrade", func() {
|
||||
var downloadURL = ""
|
||||
var flagSet = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
flagSet.StringVar(&downloadURL, "url", "", "new version download url")
|
||||
_ = flagSet.Parse(os.Args[2:])
|
||||
|
||||
var manager = utils.NewUpgradeManager("admin", downloadURL)
|
||||
log.Println("checking latest version ...")
|
||||
var ticker = time.NewTicker(1 * time.Second)
|
||||
go func() {
|
||||
var lastProgress float32 = 0
|
||||
var isStarted = false
|
||||
for range ticker.C {
|
||||
if manager.IsDownloading() {
|
||||
if !isStarted {
|
||||
if len(manager.NewVersion()) == 0 {
|
||||
continue
|
||||
}
|
||||
log.Println("start downloading v" + manager.NewVersion() + " ...")
|
||||
isStarted = true
|
||||
}
|
||||
var progress = manager.Progress()
|
||||
if progress >= 0 {
|
||||
if progress == 0 || progress == 1 || progress-lastProgress >= 0.1 {
|
||||
lastProgress = progress
|
||||
log.Printf("%.2f%%", manager.Progress()*100)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
err := manager.Start()
|
||||
if err != nil {
|
||||
log.Println("upgrade failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// try to exec local 'edge-api upgrade'
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err == nil {
|
||||
exePath, ok := updateutils.CheckLocalAPINode(rpcClient, rpcClient.Context(0))
|
||||
if ok && len(exePath) > 0 {
|
||||
log.Println("upgrading database ...")
|
||||
var cmd = executils.NewCmd(exePath, "upgrade")
|
||||
_ = cmd.Run()
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("finished!")
|
||||
log.Println("restarting ...")
|
||||
app.RunRestart()
|
||||
})
|
||||
app.On("security.reset", func() {
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
if !sock.IsListening() {
|
||||
fmt.Println("[ERROR]the service not started yet, you should start the service first")
|
||||
return
|
||||
}
|
||||
_, _ = sock.Send(&gosock.Command{
|
||||
Code: "security.reset",
|
||||
})
|
||||
fmt.Println("ok")
|
||||
})
|
||||
app.Run(func() {
|
||||
var adminNode = nodes.NewAdminNode()
|
||||
adminNode.Run()
|
||||
})
|
||||
}
|
||||
2
EdgeAdmin/dist/.gitignore
vendored
Normal file
2
EdgeAdmin/dist/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.zip
|
||||
edge-admin
|
||||
BIN
EdgeAdmin/doc/architect-zh.png
Normal file
BIN
EdgeAdmin/doc/architect-zh.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
BIN
EdgeAdmin/doc/screenshot.png
Normal file
BIN
EdgeAdmin/doc/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 225 KiB |
1
EdgeAdmin/docker/.gitignore
vendored
Normal file
1
EdgeAdmin/docker/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.zip
|
||||
41
EdgeAdmin/docker/Dockerfile
Normal file
41
EdgeAdmin/docker/Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
||||
FROM --platform=linux/amd64 alpine:latest
|
||||
LABEL maintainer="goedge.cdn@gmail.com"
|
||||
ENV TZ "Asia/Shanghai"
|
||||
ENV VERSION 1.4.7
|
||||
ENV ROOT_DIR /usr/local/goedge
|
||||
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip
|
||||
|
||||
# remote official repository
|
||||
ENV TAR_URL "https://dl.goedge.cn/edge/v${VERSION}/edge-admin-linux-amd64-plus-v${VERSION}.zip"
|
||||
|
||||
# your local repository
|
||||
#ENV TAR_URL "http://192.168.2.61:8080/edge-admin-linux-amd64-plus-v${VERSION}.zip"
|
||||
|
||||
RUN apk add --no-cache tzdata
|
||||
|
||||
RUN apk add wget
|
||||
RUN mkdir ${ROOT_DIR}; \
|
||||
cd ${ROOT_DIR}; \
|
||||
wget ${TAR_URL} -O ${TAR_FILE}; \
|
||||
apk add unzip; \
|
||||
unzip ${TAR_FILE}; \
|
||||
rm -f ${TAR_FILE}
|
||||
|
||||
RUN apk add mysql mysql-client; \
|
||||
sed -e "s/\[mysqld\]/\[mysqld\]\n\ndatadir=\/var\/lib\/mysql\nport=3306\ninnodb_flush_log_at_trx_commit=2\nmax_connections=256\nmax_prepared_stmt_count=65535\nbinlog_cache_size=1M\nbinlog_stmt_cache_size=1M\nthread_cache_size=32\nbinlog_expire_logs_seconds=1209600\n\n/" /etc/my.cnf > /tmp/my.cnf; \
|
||||
cp /tmp/my.cnf /etc/my.cnf; \
|
||||
sed -e "s/skip-networking/#skip-networking/" /etc/my.cnf.d/mariadb-server.cnf > /tmp/mariadb-server.cnf; \
|
||||
cp /tmp/mariadb-server.cnf /etc/my.cnf.d/mariadb-server.cnf; \
|
||||
mysql_install_db --user=mysql
|
||||
RUN mysqld_safe --user=mysql & \
|
||||
sleep 5; \
|
||||
mysql -uroot -hlocalhost --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';"
|
||||
|
||||
RUN echo -e "#!/usr/bin/env sh\n\nmysqld_safe --user=mysql &\n/usr/local/goedge/edge-admin/bin/edge-admin\n" > ${ROOT_DIR}/run.sh; \
|
||||
chmod u+x ${ROOT_DIR}/run.sh
|
||||
|
||||
EXPOSE 7788
|
||||
EXPOSE 8001
|
||||
EXPOSE 3306
|
||||
|
||||
ENTRYPOINT [ "/usr/local/goedge/run.sh" ]
|
||||
5
EdgeAdmin/docker/build.sh
Normal file
5
EdgeAdmin/docker/build.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION=latest
|
||||
|
||||
docker build --no-cache -t goedge/edge-admin:${VERSION} .
|
||||
5
EdgeAdmin/docker/run.sh
Normal file
5
EdgeAdmin/docker/run.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION=latest
|
||||
|
||||
docker run -d -p 7788:7788 -p 8001:8001 -p 3306:3306 --name edge-admin goedge/edge-admin:${VERSION}
|
||||
62
EdgeAdmin/go.mod
Normal file
62
EdgeAdmin/go.mod
Normal file
@@ -0,0 +1,62 @@
|
||||
module github.com/TeaOSLab/EdgeAdmin
|
||||
|
||||
go 1.25
|
||||
|
||||
replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
|
||||
|
||||
replace github.com/TeaOSLab/EdgePlus => ../EdgePlus
|
||||
|
||||
require (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/TeaOSLab/EdgePlus v0.0.0-00010101000000-000000000000
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/iwind/TeaGo v0.0.0-20240429060313-31a7bc8e9cc9
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/quic-go/quic-go v0.42.0
|
||||
github.com/shirou/gopsutil/v3 v3.22.5
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tealeg/xlsx/v3 v3.2.3
|
||||
github.com/xlzd/gotp v0.1.0
|
||||
github.com/xuri/excelize/v2 v2.9.0
|
||||
golang.org/x/crypto v0.44.0
|
||||
golang.org/x/sys v0.38.0
|
||||
google.golang.org/grpc v1.78.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/frankban/quicktest v1.11.3 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 // indirect
|
||||
github.com/oschwald/geoip2-golang v1.13.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
165
EdgeAdmin/go.sum
Normal file
165
EdgeAdmin/go.sum
Normal file
@@ -0,0 +1,165 @@
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f h1:f00RU+zOX+B3rLAmMMkzHUF2h1z4DeYR9tTCvEq2REY=
|
||||
github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/iwind/TeaGo v0.0.0-20240429060313-31a7bc8e9cc9 h1:lqSd+OeAMzPlejoVtsmHJEeQ6/MWXCr+Ws2eX9/Rewg=
|
||||
github.com/iwind/TeaGo v0.0.0-20240429060313-31a7bc8e9cc9/go.mod h1:SfqVbWyIPdVflyA6lMgicZzsoGS8pyeLiTRe8/CIpGI=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI=
|
||||
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
|
||||
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
|
||||
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
|
||||
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
|
||||
github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJQoJA=
|
||||
github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tealeg/xlsx/v3 v3.2.3 h1:MXnVh+9Y8cUglowItTy2HL3Kv6z+q/0aNjeKuTsVqZQ=
|
||||
github.com/tealeg/xlsx/v3 v3.2.3/go.mod h1:0hGmAEoZ48SS1ZAE6eqZJkJVXgOMY+8a33vjXa8S8HA=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
|
||||
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
|
||||
github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=
|
||||
github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE=
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
|
||||
github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
302
EdgeAdmin/internal/apps/app_cmd.go
Normal file
302
EdgeAdmin/internal/apps/app_cmd.go
Normal file
@@ -0,0 +1,302 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AppCmd App命令帮助
|
||||
type AppCmd struct {
|
||||
product string
|
||||
version string
|
||||
usages []string
|
||||
options []*CommandHelpOption
|
||||
appendStrings []string
|
||||
|
||||
directives []*Directive
|
||||
|
||||
sock *gosock.Sock
|
||||
}
|
||||
|
||||
func NewAppCmd() *AppCmd {
|
||||
return &AppCmd{
|
||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||
}
|
||||
}
|
||||
|
||||
type CommandHelpOption struct {
|
||||
Code string
|
||||
Description string
|
||||
}
|
||||
|
||||
// Product 产品
|
||||
func (this *AppCmd) Product(product string) *AppCmd {
|
||||
this.product = product
|
||||
return this
|
||||
}
|
||||
|
||||
// Version 版本
|
||||
func (this *AppCmd) Version(version string) *AppCmd {
|
||||
this.version = version
|
||||
return this
|
||||
}
|
||||
|
||||
// Usage 使用方法
|
||||
func (this *AppCmd) Usage(usage string) *AppCmd {
|
||||
this.usages = append(this.usages, usage)
|
||||
return this
|
||||
}
|
||||
|
||||
// Option 选项
|
||||
func (this *AppCmd) Option(code string, description string) *AppCmd {
|
||||
this.options = append(this.options, &CommandHelpOption{
|
||||
Code: code,
|
||||
Description: description,
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
// Append 附加内容
|
||||
func (this *AppCmd) Append(appendString string) *AppCmd {
|
||||
this.appendStrings = append(this.appendStrings, appendString)
|
||||
return this
|
||||
}
|
||||
|
||||
// Print 打印
|
||||
func (this *AppCmd) Print() {
|
||||
fmt.Println(this.product + " v" + this.version)
|
||||
|
||||
fmt.Println("Usage:")
|
||||
for _, usage := range this.usages {
|
||||
fmt.Println(" " + usage)
|
||||
}
|
||||
|
||||
if len(this.options) > 0 {
|
||||
fmt.Println("")
|
||||
fmt.Println("Options:")
|
||||
|
||||
spaces := 20
|
||||
max := 40
|
||||
for _, option := range this.options {
|
||||
l := len(option.Code)
|
||||
if l < max && l > spaces {
|
||||
spaces = l + 4
|
||||
}
|
||||
}
|
||||
|
||||
for _, option := range this.options {
|
||||
if len(option.Code) > max {
|
||||
fmt.Println("")
|
||||
fmt.Println(" " + option.Code)
|
||||
option.Code = ""
|
||||
}
|
||||
|
||||
fmt.Printf(" %-"+strconv.Itoa(spaces)+"s%s\n", option.Code, ": "+option.Description)
|
||||
}
|
||||
}
|
||||
|
||||
if len(this.appendStrings) > 0 {
|
||||
fmt.Println("")
|
||||
for _, s := range this.appendStrings {
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On 添加指令
|
||||
func (this *AppCmd) On(arg string, callback func()) {
|
||||
this.directives = append(this.directives, &Directive{
|
||||
Arg: arg,
|
||||
Callback: callback,
|
||||
})
|
||||
}
|
||||
|
||||
// Run 运行
|
||||
func (this *AppCmd) Run(main func()) {
|
||||
// 获取参数
|
||||
var args = os.Args[1:]
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "-v", "version", "-version", "--version":
|
||||
this.runVersion()
|
||||
return
|
||||
case "?", "help", "-help", "h", "-h":
|
||||
this.runHelp()
|
||||
return
|
||||
case "start":
|
||||
this.runStart()
|
||||
return
|
||||
case "stop":
|
||||
this.runStop()
|
||||
return
|
||||
case "restart":
|
||||
this.RunRestart()
|
||||
return
|
||||
case "status":
|
||||
this.runStatus()
|
||||
return
|
||||
}
|
||||
|
||||
// 查找指令
|
||||
for _, directive := range this.directives {
|
||||
if directive.Arg == args[0] {
|
||||
directive.Callback()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("unknown command '" + args[0] + "'")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 日志
|
||||
var writer = new(LogWriter)
|
||||
writer.Init()
|
||||
logs.SetWriter(writer)
|
||||
|
||||
// 运行主函数
|
||||
main()
|
||||
}
|
||||
|
||||
// 版本号
|
||||
func (this *AppCmd) runVersion() {
|
||||
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH, teaconst.Tag+")")
|
||||
}
|
||||
|
||||
// 帮助
|
||||
func (this *AppCmd) runHelp() {
|
||||
this.Print()
|
||||
}
|
||||
|
||||
// 启动
|
||||
func (this *AppCmd) runStart() {
|
||||
var pid = this.getPID()
|
||||
if pid > 0 {
|
||||
fmt.Println(this.product+" already started, pid:", pid)
|
||||
return
|
||||
}
|
||||
|
||||
var cmd = exec.Command(this.exe())
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
fmt.Println(this.product+" start failed:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// create symbolic links
|
||||
_ = this.createSymLinks()
|
||||
|
||||
fmt.Println(this.product+" started ok, pid:", cmd.Process.Pid)
|
||||
}
|
||||
|
||||
// 停止
|
||||
func (this *AppCmd) runStop() {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = this.sock.Send(&gosock.Command{Code: "stop"})
|
||||
|
||||
fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
|
||||
}
|
||||
|
||||
// RunRestart 重启
|
||||
func (this *AppCmd) RunRestart() {
|
||||
this.runStop()
|
||||
time.Sleep(1 * time.Second)
|
||||
this.runStart()
|
||||
}
|
||||
|
||||
// 状态
|
||||
func (this *AppCmd) runStatus() {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(this.product + " is running, pid: " + types.String(pid))
|
||||
}
|
||||
|
||||
// 获取当前的PID
|
||||
func (this *AppCmd) getPID() int {
|
||||
if !this.sock.IsListening() {
|
||||
return 0
|
||||
}
|
||||
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return maps.NewMap(reply.Params).GetInt("pid")
|
||||
}
|
||||
|
||||
func (this *AppCmd) exe() string {
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
exe = os.Args[0]
|
||||
}
|
||||
return exe
|
||||
}
|
||||
|
||||
// 创建软链接
|
||||
func (this *AppCmd) createSymLinks() error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errorList = []string{}
|
||||
|
||||
// bin
|
||||
{
|
||||
var target = "/usr/bin/" + teaconst.ProcessName
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != exe {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(exe, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log
|
||||
{
|
||||
var realPath = filepath.Dir(filepath.Dir(exe)) + "/logs/run.log"
|
||||
var target = "/var/log/" + teaconst.ProcessName + ".log"
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != realPath {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(realPath, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorList) > 0 {
|
||||
return errors.New(strings.Join(errorList, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
6
EdgeAdmin/internal/apps/directive.go
Normal file
6
EdgeAdmin/internal/apps/directive.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package apps
|
||||
|
||||
type Directive struct {
|
||||
Arg string
|
||||
Callback func()
|
||||
}
|
||||
108
EdgeAdmin/internal/apps/log_writer.go
Normal file
108
EdgeAdmin/internal/apps/log_writer.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LogWriter struct {
|
||||
fp *os.File
|
||||
c chan string
|
||||
}
|
||||
|
||||
func (this *LogWriter) Init() {
|
||||
// 创建目录
|
||||
var dir = files.NewFile(Tea.LogDir())
|
||||
if !dir.Exists() {
|
||||
err := dir.Mkdir()
|
||||
if err != nil {
|
||||
log.Println("[LOG]create log dir failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 打开要写入的日志文件
|
||||
var logPath = Tea.LogFile("run.log")
|
||||
fp, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Println("[LOG]open log file failed: " + err.Error())
|
||||
} else {
|
||||
this.fp = fp
|
||||
}
|
||||
|
||||
this.c = make(chan string, 1024)
|
||||
|
||||
// 异步写入文件
|
||||
var maxFileSize = 2 * sizes.G // 文件最大尺寸,超出此尺寸则清空
|
||||
if fp != nil {
|
||||
goman.New(func() {
|
||||
var totalSize int64 = 0
|
||||
stat, err := fp.Stat()
|
||||
if err == nil {
|
||||
totalSize = stat.Size()
|
||||
}
|
||||
|
||||
for message := range this.c {
|
||||
totalSize += int64(len(message))
|
||||
_, err := fp.WriteString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
|
||||
if err != nil {
|
||||
log.Println("[LOG]write log failed: " + err.Error())
|
||||
} else {
|
||||
// 如果太大则Truncate
|
||||
if totalSize > maxFileSize {
|
||||
_ = fp.Truncate(0)
|
||||
totalSize = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (this *LogWriter) Write(message string) {
|
||||
backgroundEnv, _ := os.LookupEnv("EdgeBackground")
|
||||
if backgroundEnv != "on" {
|
||||
// 文件和行号
|
||||
var file string
|
||||
var line int
|
||||
if Tea.IsTesting() {
|
||||
var callDepth = 3
|
||||
var ok bool
|
||||
_, file, line, ok = runtime.Caller(callDepth)
|
||||
if ok {
|
||||
file = this.packagePath(file)
|
||||
}
|
||||
}
|
||||
|
||||
if len(file) > 0 {
|
||||
log.Println(message + " (" + file + ":" + strconv.Itoa(line) + ")")
|
||||
} else {
|
||||
log.Println(message)
|
||||
}
|
||||
}
|
||||
|
||||
this.c <- message
|
||||
}
|
||||
|
||||
func (this *LogWriter) Close() {
|
||||
if this.fp != nil {
|
||||
_ = this.fp.Close()
|
||||
}
|
||||
|
||||
close(this.c)
|
||||
}
|
||||
|
||||
func (this *LogWriter) packagePath(path string) string {
|
||||
var pieces = strings.Split(path, "/")
|
||||
if len(pieces) >= 2 {
|
||||
return strings.Join(pieces[len(pieces)-2:], "/")
|
||||
}
|
||||
return path
|
||||
}
|
||||
283
EdgeAdmin/internal/configloaders/admin_module.go
Normal file
283
EdgeAdmin/internal/configloaders/admin_module.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type AdminModuleCode = string
|
||||
|
||||
const (
|
||||
AdminModuleCodeDashboard AdminModuleCode = "dashboard" // 看板
|
||||
AdminModuleCodeServer AdminModuleCode = "server" // 网站
|
||||
AdminModuleCodeNode AdminModuleCode = "node" // 节点
|
||||
AdminModuleCodeDNS AdminModuleCode = "dns" // DNS
|
||||
AdminModuleCodeNS AdminModuleCode = "ns" // 域名服务
|
||||
AdminModuleCodeAdmin AdminModuleCode = "admin" // 系统用户
|
||||
AdminModuleCodeUser AdminModuleCode = "user" // 平台用户
|
||||
AdminModuleCodeFinance AdminModuleCode = "finance" // 财务
|
||||
AdminModuleCodePlan AdminModuleCode = "plan" // 套餐
|
||||
AdminModuleCodeLog AdminModuleCode = "log" // 日志
|
||||
AdminModuleCodeSetting AdminModuleCode = "setting" // 设置
|
||||
AdminModuleCodeTicket AdminModuleCode = "ticket" // 工单
|
||||
AdminModuleCodeCommon AdminModuleCode = "common" // 只要登录就可以访问的模块
|
||||
)
|
||||
|
||||
var sharedAdminModuleMapping = map[int64]*AdminModuleList{} // adminId => AdminModuleList
|
||||
|
||||
func loadAdminModuleMapping() (map[int64]*AdminModuleList, error) {
|
||||
if len(sharedAdminModuleMapping) > 0 {
|
||||
return sharedAdminModuleMapping, nil
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modulesResp, err := rpcClient.AdminRPC().FindAllAdminModules(rpcClient.Context(0), &pb.FindAllAdminModulesRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapping := map[int64]*AdminModuleList{}
|
||||
for _, m := range modulesResp.AdminModules {
|
||||
list := &AdminModuleList{
|
||||
IsSuper: m.IsSuper,
|
||||
Fullname: m.Fullname,
|
||||
Theme: m.Theme,
|
||||
Lang: m.Lang,
|
||||
}
|
||||
|
||||
for _, pbModule := range m.Modules {
|
||||
list.Modules = append(list.Modules, &systemconfigs.AdminModule{
|
||||
Code: pbModule.Code,
|
||||
AllowAll: pbModule.AllowAll,
|
||||
Actions: pbModule.Actions,
|
||||
})
|
||||
}
|
||||
|
||||
mapping[m.AdminId] = list
|
||||
}
|
||||
|
||||
sharedAdminModuleMapping = mapping
|
||||
|
||||
return sharedAdminModuleMapping, nil
|
||||
}
|
||||
|
||||
func NotifyAdminModuleMappingChange() error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
sharedAdminModuleMapping = map[int64]*AdminModuleList{}
|
||||
_, err := loadAdminModuleMapping()
|
||||
return err
|
||||
}
|
||||
|
||||
// CheckAdmin 检查用户是否存在
|
||||
func CheckAdmin(adminId int64) bool {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
// 如果还没有数据,则尝试加载
|
||||
if len(sharedAdminModuleMapping) == 0 {
|
||||
_, _ = loadAdminModuleMapping()
|
||||
}
|
||||
|
||||
_, ok := sharedAdminModuleMapping[adminId]
|
||||
return ok
|
||||
}
|
||||
|
||||
// AllowModule 检查模块是否允许访问
|
||||
func AllowModule(adminId int64, module string) bool {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
if module == AdminModuleCodeCommon {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(sharedAdminModuleMapping) == 0 {
|
||||
_, _ = loadAdminModuleMapping()
|
||||
}
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Allow(module)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// FindFirstAdminModule 获取管理员第一个可访问模块
|
||||
func FindFirstAdminModule(adminId int64) (module AdminModuleCode, ok bool) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
list, ok2 := sharedAdminModuleMapping[adminId]
|
||||
if ok2 {
|
||||
if list.IsSuper {
|
||||
return AdminModuleCodeDashboard, true
|
||||
} else if len(list.Modules) > 0 {
|
||||
return list.Modules[0].Code, true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindAdminFullname 查找某个管理员名称
|
||||
func FindAdminFullname(adminId int64) string {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Fullname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// FindAdminTheme 查找某个管理员选择的风格
|
||||
func FindAdminTheme(adminId int64) string {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Theme
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UpdateAdminTheme 设置某个管理员的风格
|
||||
func UpdateAdminTheme(adminId int64, theme string) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
list.Theme = theme
|
||||
}
|
||||
}
|
||||
|
||||
// FindAdminLang 查找某个管理员选择的语言
|
||||
func FindAdminLang(adminId int64) string {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Lang
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// UpdateAdminLang 修改某个管理员选择的语言
|
||||
func UpdateAdminLang(adminId int64, langCode string) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
list.Lang = langCode
|
||||
}
|
||||
}
|
||||
|
||||
func FindAdminLangForAction(actionPtr actions.ActionWrapper) (langCode langs.LangCode) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var adminId = actionPtr.Object().Session().GetInt64(teaconst.SessionAdminId)
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
var result = ""
|
||||
if ok {
|
||||
result = list.Lang
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
result = langs.ParseLangFromAction(actionPtr)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// AllModuleMaps 所有权限列表
|
||||
func AllModuleMaps(langCode string) []maps.Map {
|
||||
var m = []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Dashboard),
|
||||
"code": AdminModuleCodeDashboard,
|
||||
"url": "/dashboard",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Servers),
|
||||
"code": AdminModuleCodeServer,
|
||||
"url": "/servers",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Nodes),
|
||||
"code": AdminModuleCodeNode,
|
||||
"url": "/clusters",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_DNS),
|
||||
"code": AdminModuleCodeDNS,
|
||||
"url": "/dns",
|
||||
},
|
||||
}
|
||||
if teaconst.IsPlus {
|
||||
m = append(m, maps.Map{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_NS),
|
||||
"code": AdminModuleCodeNS,
|
||||
"url": "/ns",
|
||||
})
|
||||
}
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Users),
|
||||
"code": AdminModuleCodeUser,
|
||||
"url": "/users",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Admins),
|
||||
"code": AdminModuleCodeAdmin,
|
||||
"url": "/admins",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Finance),
|
||||
"code": AdminModuleCodeFinance,
|
||||
"url": "/finance",
|
||||
},
|
||||
}...)
|
||||
|
||||
if teaconst.IsPlus {
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Plans),
|
||||
"code": AdminModuleCodePlan,
|
||||
"url": "/plans",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Tickets),
|
||||
"code": AdminModuleCodeTicket,
|
||||
"url": "/tickets",
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
m = append(m, []maps.Map{
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Logs),
|
||||
"code": AdminModuleCodeLog,
|
||||
"url": "/log",
|
||||
},
|
||||
{
|
||||
"name": langs.Message(langCode, codes.AdminMenu_Settings),
|
||||
"code": AdminModuleCodeSetting,
|
||||
"url": "/settings",
|
||||
},
|
||||
}...)
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package configloaders
|
||||
23
EdgeAdmin/internal/configloaders/admin_module_list.go
Normal file
23
EdgeAdmin/internal/configloaders/admin_module_list.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package configloaders
|
||||
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
|
||||
type AdminModuleList struct {
|
||||
IsSuper bool
|
||||
Modules []*systemconfigs.AdminModule
|
||||
Fullname string
|
||||
Theme string
|
||||
Lang string
|
||||
}
|
||||
|
||||
func (this *AdminModuleList) Allow(module string) bool {
|
||||
if this.IsSuper {
|
||||
return true
|
||||
}
|
||||
for _, m := range this.Modules {
|
||||
if m.Code == module {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
14
EdgeAdmin/internal/configloaders/admin_module_test.go
Normal file
14
EdgeAdmin/internal/configloaders/admin_module_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadAdminModuleMapping(t *testing.T) {
|
||||
m, err := loadAdminModuleMapping()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
logs.PrintAsJSON(m, t)
|
||||
}
|
||||
133
EdgeAdmin/internal/configloaders/admin_ui_config.go
Normal file
133
EdgeAdmin/internal/configloaders/admin_ui_config.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sharedAdminUIConfig *systemconfigs.AdminUIConfig = nil
|
||||
|
||||
func LoadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadAdminUIConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.AdminUIConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func ReloadAdminUIConfig() error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
sharedAdminUIConfig = nil
|
||||
_, err := loadAdminUIConfig()
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(uiConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeAdminUIConfig,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedAdminUIConfig = uiConfig
|
||||
|
||||
// timezone
|
||||
updateTimeZone(uiConfig)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowFinance 是否显示财务信息
|
||||
func ShowFinance() bool {
|
||||
config, _ := LoadAdminUIConfig()
|
||||
if config != nil && !config.ShowFinance {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
|
||||
if sharedAdminUIConfig != nil {
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeAdminUIConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedAdminUIConfig = defaultAdminUIConfig()
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
var config = &systemconfigs.AdminUIConfig{}
|
||||
config.DNSResolver.Type = nodeconfigs.DNSResolverTypeDefault // 默认值
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[UI_MANAGER]" + err.Error())
|
||||
sharedAdminUIConfig = defaultAdminUIConfig()
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
// timezone
|
||||
updateTimeZone(config)
|
||||
|
||||
sharedAdminUIConfig = config
|
||||
return sharedAdminUIConfig, nil
|
||||
}
|
||||
|
||||
func defaultAdminUIConfig() *systemconfigs.AdminUIConfig {
|
||||
var config = &systemconfigs.AdminUIConfig{
|
||||
ProductName: langs.DefaultMessage(codes.AdminUI_DefaultProductName),
|
||||
AdminSystemName: langs.DefaultMessage(codes.AdminUI_DefaultSystemName),
|
||||
ShowOpenSourceInfo: true,
|
||||
ShowVersion: true,
|
||||
ShowFinance: true,
|
||||
DefaultPageSize: 10,
|
||||
TimeZone: nodeconfigs.DefaultTimeZoneLocation,
|
||||
}
|
||||
config.DNSResolver.Type = nodeconfigs.DNSResolverTypeDefault
|
||||
return config
|
||||
}
|
||||
|
||||
// 修改时区
|
||||
func updateTimeZone(config *systemconfigs.AdminUIConfig) {
|
||||
if len(config.TimeZone) > 0 {
|
||||
location, err := time.LoadLocation(config.TimeZone)
|
||||
if err == nil && time.Local != location {
|
||||
time.Local = location
|
||||
}
|
||||
}
|
||||
}
|
||||
29
EdgeAdmin/internal/configloaders/admin_ui_config_test.go
Normal file
29
EdgeAdmin/internal/configloaders/admin_ui_config_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLoadUIConfig(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
before := time.Now()
|
||||
config, err := LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
t.Logf("%p", config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadUIConfig2(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
config, err := LoadAdminUIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(config)
|
||||
}
|
||||
}
|
||||
5
EdgeAdmin/internal/configloaders/locker.go
Normal file
5
EdgeAdmin/internal/configloaders/locker.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package configloaders
|
||||
|
||||
import "sync"
|
||||
|
||||
var locker sync.Mutex
|
||||
83
EdgeAdmin/internal/configloaders/log_config.go
Normal file
83
EdgeAdmin/internal/configloaders/log_config.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var sharedLogConfig *systemconfigs.LogConfig = nil
|
||||
|
||||
const (
|
||||
LogSettingName = "adminLogConfig"
|
||||
)
|
||||
|
||||
func LoadLogConfig() (*systemconfigs.LogConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadLogConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.LogConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func UpdateLogConfig(logConfig *systemconfigs.LogConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(logConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: LogSettingName,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedLogConfig = logConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadLogConfig() (*systemconfigs.LogConfig, error) {
|
||||
if sharedLogConfig != nil {
|
||||
return sharedLogConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: LogSettingName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedLogConfig = systemconfigs.DefaultLogConfig()
|
||||
return sharedLogConfig, nil
|
||||
}
|
||||
|
||||
config := &systemconfigs.LogConfig{}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[LOG_MANAGER]" + err.Error())
|
||||
sharedLogConfig = systemconfigs.DefaultLogConfig()
|
||||
return sharedLogConfig, nil
|
||||
}
|
||||
sharedLogConfig = config
|
||||
return sharedLogConfig, nil
|
||||
}
|
||||
118
EdgeAdmin/internal/configloaders/security_config.go
Normal file
118
EdgeAdmin/internal/configloaders/security_config.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
SecuritySettingName = "adminSecurityConfig"
|
||||
|
||||
FrameNone = ""
|
||||
FrameDeny = "DENY"
|
||||
FrameSameOrigin = "SAMEORIGIN"
|
||||
)
|
||||
|
||||
var sharedSecurityConfig *systemconfigs.SecurityConfig = nil
|
||||
|
||||
func LoadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadSecurityConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var v = reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func UpdateSecurityConfig(securityConfig *systemconfigs.SecurityConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(securityConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: SecuritySettingName,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = securityConfig.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedSecurityConfig = securityConfig
|
||||
|
||||
// 通知更新
|
||||
events.Notify(events.EventSecurityConfigChanged)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
||||
if sharedSecurityConfig != nil {
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: SecuritySettingName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedSecurityConfig = NewSecurityConfig()
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
|
||||
var config = &systemconfigs.SecurityConfig{
|
||||
Frame: FrameSameOrigin,
|
||||
AllowLocal: true,
|
||||
CheckClientFingerprint: false,
|
||||
CheckClientRegion: true,
|
||||
DenySearchEngines: true,
|
||||
DenySpiders: true,
|
||||
}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[SECURITY_MANAGER]" + err.Error())
|
||||
sharedSecurityConfig = NewSecurityConfig()
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sharedSecurityConfig = config
|
||||
return sharedSecurityConfig, nil
|
||||
}
|
||||
|
||||
// NewSecurityConfig create new security config
|
||||
func NewSecurityConfig() *systemconfigs.SecurityConfig {
|
||||
return &systemconfigs.SecurityConfig{
|
||||
Frame: FrameSameOrigin,
|
||||
AllowLocal: true,
|
||||
CheckClientFingerprint: false,
|
||||
CheckClientRegion: true,
|
||||
DenySearchEngines: true,
|
||||
DenySpiders: true,
|
||||
}
|
||||
}
|
||||
32
EdgeAdmin/internal/configloaders/security_config_test.go
Normal file
32
EdgeAdmin/internal/configloaders/security_config_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLoadSecurityConfig(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
before := time.Now()
|
||||
config, err := LoadSecurityConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
t.Logf("%p", config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSecurityConfig2(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
config, err := LoadSecurityConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if i == 0 {
|
||||
config.Frame = "DENY"
|
||||
}
|
||||
t.Log(config.Frame)
|
||||
}
|
||||
}
|
||||
79
EdgeAdmin/internal/configloaders/user_price_config_plus.go
Normal file
79
EdgeAdmin/internal/configloaders/user_price_config_plus.go
Normal file
@@ -0,0 +1,79 @@
|
||||
//go:build plus
|
||||
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var sharedUserPriceConfig *userconfigs.UserPriceConfig = nil
|
||||
|
||||
func LoadUserPriceConfig() (*userconfigs.UserPriceConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadUserPriceConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var v = reflect.Indirect(reflect.ValueOf(config)).Interface().(userconfigs.UserPriceConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func ResetUserPriceConfig(priceConfig *userconfigs.UserPriceConfig) {
|
||||
sharedUserPriceConfig = priceConfig
|
||||
}
|
||||
|
||||
func UpdateUserPriceConfig(priceConfig *userconfigs.UserPriceConfig) error {
|
||||
priceConfigJSON, err := json.Marshal(priceConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeUserPriceConfig,
|
||||
ValueJSON: priceConfigJSON,
|
||||
})
|
||||
sharedUserPriceConfig = priceConfig
|
||||
return err
|
||||
}
|
||||
|
||||
func loadUserPriceConfig() (*userconfigs.UserPriceConfig, error) {
|
||||
if sharedUserPriceConfig != nil {
|
||||
return sharedUserPriceConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeUserPriceConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedUserPriceConfig = userconfigs.DefaultUserPriceConfig()
|
||||
return sharedUserPriceConfig, nil
|
||||
}
|
||||
|
||||
var config = userconfigs.DefaultUserPriceConfig()
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("loadUserPriceConfig: " + err.Error())
|
||||
sharedUserPriceConfig = userconfigs.DefaultUserPriceConfig()
|
||||
return sharedUserPriceConfig, nil
|
||||
}
|
||||
sharedUserPriceConfig = config
|
||||
return sharedUserPriceConfig, nil
|
||||
}
|
||||
35
EdgeAdmin/internal/configloaders/user_sender_config_plus.go
Normal file
35
EdgeAdmin/internal/configloaders/user_sender_config_plus.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
)
|
||||
|
||||
// LoadUserSenderConfig 加载用户媒介发送配置
|
||||
func LoadUserSenderConfig() (*userconfigs.UserSenderConfig, error) {
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configResp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserSenderConfig})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data = configResp.ValueJSON
|
||||
if len(data) == 0 {
|
||||
return userconfigs.DefaultUserSenderConfig(), nil
|
||||
}
|
||||
|
||||
var config = userconfigs.DefaultUserSenderConfig()
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
81
EdgeAdmin/internal/configloaders/user_ui_config_plus.go
Normal file
81
EdgeAdmin/internal/configloaders/user_ui_config_plus.go
Normal file
@@ -0,0 +1,81 @@
|
||||
//go:build plus
|
||||
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var sharedUserUIConfig *systemconfigs.UserUIConfig = nil
|
||||
|
||||
func LoadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
config, err := loadUserUIConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.UserUIConfig)
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func UpdateUserUIConfig(uiConfig *systemconfigs.UserUIConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(uiConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeUserUIConfig,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedUserUIConfig = uiConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
|
||||
if sharedUserUIConfig != nil {
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
var rpcClient, err = rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeUserUIConfig,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedUserUIConfig = systemconfigs.NewUserUIConfig()
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
|
||||
var config = systemconfigs.NewUserUIConfig()
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
logs.Println("[UI_MANAGER]" + err.Error())
|
||||
sharedUserUIConfig = systemconfigs.NewUserUIConfig()
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
sharedUserUIConfig = config
|
||||
return sharedUserUIConfig, nil
|
||||
}
|
||||
226
EdgeAdmin/internal/configs/api_config.go
Normal file
226
EdgeAdmin/internal/configs/api_config.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const ConfigFileName = "api_admin.yaml"
|
||||
const oldConfigFileName = "api.yaml"
|
||||
|
||||
// APIConfig API配置
|
||||
type APIConfig struct {
|
||||
OldRPC struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
} `yaml:"rpc,omitempty"`
|
||||
|
||||
RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"`
|
||||
RPCDisableUpdate bool `yaml:"rpc.disableUpdate" json:"rpc.disableUpdate"`
|
||||
|
||||
NodeId string `yaml:"nodeId"`
|
||||
Secret string `yaml:"secret"`
|
||||
}
|
||||
|
||||
// LoadAPIConfig 加载API配置
|
||||
func LoadAPIConfig() (*APIConfig, error) {
|
||||
// 候选文件
|
||||
var realFile = Tea.ConfigFile(ConfigFileName)
|
||||
var oldRealFile = Tea.ConfigFile(oldConfigFileName)
|
||||
var isFromLocal = false
|
||||
var paths = []string{realFile, oldRealFile}
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
paths = append(paths, homeDir+"/."+teaconst.ProcessName+"/"+ConfigFileName)
|
||||
}
|
||||
paths = append(paths, "/etc/"+teaconst.ProcessName+"/"+ConfigFileName)
|
||||
|
||||
var data []byte
|
||||
var err error
|
||||
var isFromOld = false
|
||||
for _, path := range paths {
|
||||
data, err = os.ReadFile(path)
|
||||
if err == nil {
|
||||
if path == realFile || path == oldRealFile {
|
||||
isFromLocal = true
|
||||
}
|
||||
|
||||
// 自动生成新的配置文件
|
||||
isFromOld = path == oldRealFile
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config = &APIConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
return nil, errors.New("init error: " + err.Error())
|
||||
}
|
||||
|
||||
if !isFromLocal {
|
||||
// 恢复文件
|
||||
_ = os.WriteFile(realFile, data, 0666)
|
||||
}
|
||||
|
||||
// 自动生成新配置文件
|
||||
if isFromOld {
|
||||
config.OldRPC.Endpoints = nil
|
||||
_ = config.WriteFile(Tea.ConfigFile(ConfigFileName))
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// ResetAPIConfig 重置配置
|
||||
func ResetAPIConfig() error {
|
||||
var filename = ConfigFileName
|
||||
|
||||
// 重置 configs/api_admin.yaml
|
||||
{
|
||||
var configFile = Tea.ConfigFile(filename)
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重置 ~/.edge-admin/api_admin.yaml
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
var configFile = homeDir + "/." + teaconst.ProcessName + "/" + filename
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重置 /etc/edge-admin/api_admin.yaml
|
||||
{
|
||||
var configFile = "/etc/" + teaconst.ProcessName + "/" + filename
|
||||
stat, err := os.Stat(configFile)
|
||||
if err == nil && !stat.IsDir() {
|
||||
err = os.Remove(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsNewInstalled() bool {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, filename := range []string{ConfigFileName, oldConfigFileName} {
|
||||
_, err = os.Stat(homeDir + "/." + teaconst.ProcessName + "/" + filename)
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// WriteFile 写入API配置
|
||||
func (this *APIConfig) WriteFile(path string) error {
|
||||
data, err := yaml.Marshal(this)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(path, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入 ~/ 和 /etc/ 目录,因为是备份需要,所以不需要提示错误信息
|
||||
// 写入 ~/.edge-admin/
|
||||
// 这个用来判断用户是否为重装,所以比较重要
|
||||
var filename = filepath.Base(path)
|
||||
homeDir, homeErr := os.UserHomeDir()
|
||||
if homeErr == nil {
|
||||
dir := homeDir + "/." + teaconst.ProcessName
|
||||
stat, err := os.Stat(dir)
|
||||
if err == nil && stat.IsDir() {
|
||||
err = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil && os.IsNotExist(err) {
|
||||
err = os.Mkdir(dir, 0777)
|
||||
if err == nil {
|
||||
err = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写入 /etc/edge-admin
|
||||
{
|
||||
var dir = "/etc/" + teaconst.ProcessName
|
||||
stat, err := os.Stat(dir)
|
||||
if err == nil && stat.IsDir() {
|
||||
_ = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
} else if err != nil && os.IsNotExist(err) {
|
||||
err = os.Mkdir(dir, 0777)
|
||||
if err == nil {
|
||||
_ = os.WriteFile(dir+"/"+filename, data, 0666)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone 克隆当前配置
|
||||
func (this *APIConfig) Clone() *APIConfig {
|
||||
return &APIConfig{
|
||||
NodeId: this.NodeId,
|
||||
Secret: this.Secret,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *APIConfig) Init() error {
|
||||
// compatible with old
|
||||
if len(this.RPCEndpoints) == 0 && len(this.OldRPC.Endpoints) > 0 {
|
||||
this.RPCEndpoints = this.OldRPC.Endpoints
|
||||
this.RPCDisableUpdate = this.OldRPC.DisableUpdate
|
||||
}
|
||||
|
||||
if len(this.RPCEndpoints) == 0 {
|
||||
return errors.New("no valid 'rpc.endpoints'")
|
||||
}
|
||||
|
||||
if len(this.NodeId) == 0 {
|
||||
return errors.New("'nodeId' required")
|
||||
}
|
||||
if len(this.Secret) == 0 {
|
||||
return errors.New("'secret' required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
30
EdgeAdmin/internal/configs/api_config_test.go
Normal file
30
EdgeAdmin/internal/configs/api_config_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadAPIConfig(t *testing.T) {
|
||||
config, err := LoadAPIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(config)
|
||||
|
||||
configData, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(configData))
|
||||
}
|
||||
|
||||
func TestAPIConfig_WriteFile(t *testing.T) {
|
||||
var config = &APIConfig{}
|
||||
err := config.WriteFile("/tmp/api_config.yaml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
6
EdgeAdmin/internal/configs/nodes/node_config.go
Normal file
6
EdgeAdmin/internal/configs/nodes/node_config.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package nodes
|
||||
|
||||
type NodeConfig struct {
|
||||
Id string `json:"id" yaml:"id"`
|
||||
Secret string `json:"secret" yaml:"secret"`
|
||||
}
|
||||
44
EdgeAdmin/internal/configs/plus_config_plus.go
Normal file
44
EdgeAdmin/internal/configs/plus_config_plus.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package configs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"os"
|
||||
)
|
||||
|
||||
var plusConfigFile = "plus.cache.json"
|
||||
|
||||
type PlusConfig struct {
|
||||
IsPlus bool `json:"isPlus"`
|
||||
Edition string `json:"edition"`
|
||||
Components []string `json:"components"`
|
||||
DayTo string `json:"dayTo"`
|
||||
}
|
||||
|
||||
func ReadPlusConfig() *PlusConfig {
|
||||
data, err := os.ReadFile(Tea.ConfigFile(plusConfigFile))
|
||||
if err != nil {
|
||||
return &PlusConfig{IsPlus: false}
|
||||
}
|
||||
var config = &PlusConfig{IsPlus: false}
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return config
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func WritePlusConfig(config *PlusConfig) error {
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(Tea.ConfigFile(plusConfigFile), configJSON, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
3
EdgeAdmin/internal/configs/secret.go
Normal file
3
EdgeAdmin/internal/configs/secret.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package configs
|
||||
|
||||
var Secret = ""
|
||||
51
EdgeAdmin/internal/configs/simple_db_config.go
Normal file
51
EdgeAdmin/internal/configs/simple_db_config.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package configs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
"gopkg.in/yaml.v3"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
type SimpleDBConfig struct {
|
||||
User string `yaml:"user"`
|
||||
Password string `yaml:"password"`
|
||||
Database string `yaml:"database"`
|
||||
Host string `yaml:"host"`
|
||||
BoolFields []string `yaml:"boolFields,omitempty"`
|
||||
}
|
||||
|
||||
func (this *SimpleDBConfig) GenerateOldConfig(targetFile string) error {
|
||||
var dbConfig = &dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: url.QueryEscape(this.User) + ":" + url.QueryEscape(this.Password) + "@tcp(" + this.Host + ")/" + url.PathEscape(this.Database) + "?charset=utf8mb4&timeout=30s&multiStatements=true",
|
||||
Prefix: "edge",
|
||||
}
|
||||
dbConfig.Models.Package = "internal/db/models"
|
||||
|
||||
var config = &dbs.Config{
|
||||
DBs: map[string]*dbs.DBConfig{
|
||||
Tea.Env: dbConfig,
|
||||
},
|
||||
}
|
||||
config.Default.DB = Tea.Env
|
||||
config.Fields = map[string][]string{
|
||||
"bool": this.BoolFields,
|
||||
}
|
||||
|
||||
oldConfigYAML, encodeErr := yaml.Marshal(config)
|
||||
if encodeErr != nil {
|
||||
return encodeErr
|
||||
}
|
||||
|
||||
err := os.WriteFile(targetFile, oldConfigYAML, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create database config file failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
8
EdgeAdmin/internal/const/build.go
Normal file
8
EdgeAdmin/internal/const/build.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus
|
||||
|
||||
package teaconst
|
||||
|
||||
const BuildCommunity = true
|
||||
const BuildPlus = false
|
||||
const Tag = "community"
|
||||
8
EdgeAdmin/internal/const/build_plus.go
Normal file
8
EdgeAdmin/internal/const/build_plus.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package teaconst
|
||||
|
||||
const BuildCommunity = false
|
||||
const BuildPlus = true
|
||||
const Tag = "plus"
|
||||
21
EdgeAdmin/internal/const/const.go
Normal file
21
EdgeAdmin/internal/const/const.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.7" //1.3.9
|
||||
|
||||
APINodeVersion = "1.4.7" //1.3.9
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
|
||||
Role = "admin"
|
||||
|
||||
EncryptMethod = "aes-256-cfb"
|
||||
|
||||
CookieSID = "geadsid"
|
||||
SessionAdminId = "adminId"
|
||||
|
||||
SystemdServiceName = "edge-admin"
|
||||
UpdatesURL = "https://goedge.cn/api/boot/versions?os=${os}&arch=${arch}&version=${version}"
|
||||
)
|
||||
|
||||
6
EdgeAdmin/internal/const/plus.go
Normal file
6
EdgeAdmin/internal/const/plus.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus
|
||||
|
||||
package teaconst
|
||||
|
||||
var IsPlus = false
|
||||
6
EdgeAdmin/internal/const/plus_plus.go
Normal file
6
EdgeAdmin/internal/const/plus_plus.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package teaconst
|
||||
|
||||
var IsPlus = false
|
||||
32
EdgeAdmin/internal/const/vars.go
Normal file
32
EdgeAdmin/internal/const/vars.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package teaconst
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
IsRecoverMode = false
|
||||
|
||||
IsDemoMode = false
|
||||
ErrorDemoOperation = "DEMO模式下无法进行创建、修改、删除等操作"
|
||||
|
||||
NewVersionCode = "" // 有新的版本
|
||||
NewVersionDownloadURL = "" // 新版本下载地址
|
||||
|
||||
IsMain = checkMain()
|
||||
)
|
||||
|
||||
// 检查是否为主程序
|
||||
func checkMain() bool {
|
||||
if len(os.Args) == 1 ||
|
||||
(len(os.Args) >= 2 && os.Args[1] == "pprof") {
|
||||
return true
|
||||
}
|
||||
exe, _ := os.Executable()
|
||||
return strings.HasSuffix(exe, ".test") ||
|
||||
strings.HasSuffix(exe, ".test.exe") ||
|
||||
strings.Contains(exe, "___")
|
||||
}
|
||||
58
EdgeAdmin/internal/csrf/token_manager.go
Normal file
58
EdgeAdmin/internal/csrf/token_manager.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package csrf
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sharedTokenManager = NewTokenManager()
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(1 * time.Hour)
|
||||
for range ticker.C {
|
||||
sharedTokenManager.Clean()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
type TokenManager struct {
|
||||
tokenMap map[string]int64 // token => timestamp
|
||||
|
||||
locker sync.Mutex
|
||||
}
|
||||
|
||||
func NewTokenManager() *TokenManager {
|
||||
return &TokenManager{
|
||||
tokenMap: map[string]int64{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *TokenManager) Put(token string) {
|
||||
this.locker.Lock()
|
||||
this.tokenMap[token] = time.Now().Unix()
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *TokenManager) Exists(token string) bool {
|
||||
this.locker.Lock()
|
||||
_, ok := this.tokenMap[token]
|
||||
this.locker.Unlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
func (this *TokenManager) Delete(token string) {
|
||||
this.locker.Lock()
|
||||
delete(this.tokenMap, token)
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *TokenManager) Clean() {
|
||||
this.locker.Lock()
|
||||
for token, timestamp := range this.tokenMap {
|
||||
if time.Now().Unix()-timestamp > 3600 { // 删除一个小时前的
|
||||
delete(this.tokenMap, token)
|
||||
}
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
66
EdgeAdmin/internal/csrf/utils.go
Normal file
66
EdgeAdmin/internal/csrf/utils.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package csrf
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Generate 生成Token
|
||||
func Generate() string {
|
||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(configs.Secret))
|
||||
h.Write([]byte(timestamp))
|
||||
s := h.Sum(nil)
|
||||
token := base64.StdEncoding.EncodeToString([]byte(timestamp + fmt.Sprintf("%x", s)))
|
||||
sharedTokenManager.Put(token)
|
||||
return token
|
||||
}
|
||||
|
||||
// Validate 校验Token
|
||||
func Validate(token string) (b bool) {
|
||||
if len(token) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !sharedTokenManager.Exists(token) {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
sharedTokenManager.Delete(token)
|
||||
}()
|
||||
|
||||
data, err := base64.StdEncoding.DecodeString(token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
hashString := string(data)
|
||||
if len(hashString) < 10+32 {
|
||||
return
|
||||
}
|
||||
|
||||
timestampString := hashString[:10]
|
||||
hashString = hashString[10:]
|
||||
|
||||
h := sha256.New()
|
||||
h.Write([]byte(configs.Secret))
|
||||
h.Write([]byte(timestampString))
|
||||
hashData := h.Sum(nil)
|
||||
if hashString != fmt.Sprintf("%x", hashData) {
|
||||
return
|
||||
}
|
||||
|
||||
timestamp := types.Int64(timestampString)
|
||||
if timestamp < time.Now().Unix()-1800 { // 有效期半个小时
|
||||
return
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
41
EdgeAdmin/internal/encrypt/magic_key.go
Normal file
41
EdgeAdmin/internal/encrypt/magic_key.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
)
|
||||
|
||||
const (
|
||||
MagicKey = "f1c8eafb543f03023e97b7be864a4e9b"
|
||||
)
|
||||
|
||||
// 加密特殊信息
|
||||
func MagicKeyEncode(data []byte) []byte {
|
||||
method, err := NewMethodInstance("aes-256-cfb", MagicKey, MagicKey[:16])
|
||||
if err != nil {
|
||||
logs.Println("[MagicKeyEncode]" + err.Error())
|
||||
return data
|
||||
}
|
||||
|
||||
dst, err := method.Encrypt(data)
|
||||
if err != nil {
|
||||
logs.Println("[MagicKeyEncode]" + err.Error())
|
||||
return data
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// 解密特殊信息
|
||||
func MagicKeyDecode(data []byte) []byte {
|
||||
method, err := NewMethodInstance("aes-256-cfb", MagicKey, MagicKey[:16])
|
||||
if err != nil {
|
||||
logs.Println("[MagicKeyEncode]" + err.Error())
|
||||
return data
|
||||
}
|
||||
|
||||
src, err := method.Decrypt(data)
|
||||
if err != nil {
|
||||
logs.Println("[MagicKeyEncode]" + err.Error())
|
||||
return data
|
||||
}
|
||||
return src
|
||||
}
|
||||
11
EdgeAdmin/internal/encrypt/magic_key_test.go
Normal file
11
EdgeAdmin/internal/encrypt/magic_key_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package encrypt
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMagicKeyEncode(t *testing.T) {
|
||||
dst := MagicKeyEncode([]byte("Hello,World"))
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src := MagicKeyDecode(dst)
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
12
EdgeAdmin/internal/encrypt/method.go
Normal file
12
EdgeAdmin/internal/encrypt/method.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package encrypt
|
||||
|
||||
type MethodInterface interface {
|
||||
// 初始化
|
||||
Init(key []byte, iv []byte) error
|
||||
|
||||
// 加密
|
||||
Encrypt(src []byte) (dst []byte, err error)
|
||||
|
||||
// 解密
|
||||
Decrypt(dst []byte) (src []byte, err error)
|
||||
}
|
||||
73
EdgeAdmin/internal/encrypt/method_aes_128_cfb.go
Normal file
73
EdgeAdmin/internal/encrypt/method_aes_128_cfb.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
)
|
||||
|
||||
type AES128CFBMethod struct {
|
||||
iv []byte
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
func (this *AES128CFBMethod) Init(key, iv []byte) error {
|
||||
// 判断key是否为32长度
|
||||
l := len(key)
|
||||
if l > 16 {
|
||||
key = key[:16]
|
||||
} else if l < 16 {
|
||||
key = append(key, bytes.Repeat([]byte{' '}, 16-l)...)
|
||||
}
|
||||
|
||||
// 判断iv长度
|
||||
l2 := len(iv)
|
||||
if l2 > aes.BlockSize {
|
||||
iv = iv[:aes.BlockSize]
|
||||
} else if l2 < aes.BlockSize {
|
||||
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
|
||||
}
|
||||
|
||||
this.iv = iv
|
||||
|
||||
// block
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.block = block
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AES128CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
dst = make([]byte, len(src))
|
||||
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
|
||||
encrypter.XORKeyStream(dst, src)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *AES128CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
|
||||
if len(dst) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
src = make([]byte, len(dst))
|
||||
encrypter := cipher.NewCFBDecrypter(this.block, this.iv)
|
||||
encrypter.XORKeyStream(src, dst)
|
||||
|
||||
return
|
||||
}
|
||||
90
EdgeAdmin/internal/encrypt/method_aes_128_cfb_test.go
Normal file
90
EdgeAdmin/internal/encrypt/method_aes_128_cfb_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAES128CFBMethod_Encrypt(t *testing.T) {
|
||||
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := []byte("Hello, World")
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dst = dst[:len(src)]
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
|
||||
func TestAES128CFBMethod_Encrypt2(t *testing.T) {
|
||||
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sources := [][]byte{}
|
||||
|
||||
{
|
||||
a := []byte{1}
|
||||
_, err = method.Encrypt(a)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
src := []byte(strings.Repeat("Hello", 1))
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sources = append(sources, dst)
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
a := []byte{1}
|
||||
_, err = method.Decrypt(a)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, dst := range sources {
|
||||
dst2 := append([]byte{}, dst...)
|
||||
src2, err := method.Decrypt(dst2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(src2))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAES128CFBMethod_Encrypt(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
src := []byte(strings.Repeat("Hello", 1024))
|
||||
for i := 0; i < b.N; i++ {
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = dst
|
||||
}
|
||||
}
|
||||
74
EdgeAdmin/internal/encrypt/method_aes_192_cfb.go
Normal file
74
EdgeAdmin/internal/encrypt/method_aes_192_cfb.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
)
|
||||
|
||||
type AES192CFBMethod struct {
|
||||
block cipher.Block
|
||||
iv []byte
|
||||
}
|
||||
|
||||
func (this *AES192CFBMethod) Init(key, iv []byte) error {
|
||||
// 判断key是否为24长度
|
||||
l := len(key)
|
||||
if l > 24 {
|
||||
key = key[:24]
|
||||
} else if l < 24 {
|
||||
key = append(key, bytes.Repeat([]byte{' '}, 24-l)...)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.block = block
|
||||
|
||||
// 判断iv长度
|
||||
l2 := len(iv)
|
||||
if l2 > aes.BlockSize {
|
||||
iv = iv[:aes.BlockSize]
|
||||
} else if l2 < aes.BlockSize {
|
||||
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
|
||||
}
|
||||
|
||||
this.iv = iv
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AES192CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
dst = make([]byte, len(src))
|
||||
|
||||
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
|
||||
encrypter.XORKeyStream(dst, src)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *AES192CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
|
||||
if len(dst) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
src = make([]byte, len(dst))
|
||||
|
||||
decrypter := cipher.NewCFBDecrypter(this.block, this.iv)
|
||||
decrypter.XORKeyStream(src, dst)
|
||||
|
||||
return
|
||||
}
|
||||
45
EdgeAdmin/internal/encrypt/method_aes_192_cfb_test.go
Normal file
45
EdgeAdmin/internal/encrypt/method_aes_192_cfb_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAES192CFBMethod_Encrypt(t *testing.T) {
|
||||
method, err := NewMethodInstance("aes-192-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := []byte("Hello, World")
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dst = dst[:len(src)]
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
|
||||
func BenchmarkAES192CFBMethod_Encrypt(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
method, err := NewMethodInstance("aes-192-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
src := []byte(strings.Repeat("Hello", 1024))
|
||||
for i := 0; i < b.N; i++ {
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = dst
|
||||
}
|
||||
}
|
||||
72
EdgeAdmin/internal/encrypt/method_aes_256_cfb.go
Normal file
72
EdgeAdmin/internal/encrypt/method_aes_256_cfb.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
)
|
||||
|
||||
type AES256CFBMethod struct {
|
||||
block cipher.Block
|
||||
iv []byte
|
||||
}
|
||||
|
||||
func (this *AES256CFBMethod) Init(key, iv []byte) error {
|
||||
// 判断key是否为32长度
|
||||
l := len(key)
|
||||
if l > 32 {
|
||||
key = key[:32]
|
||||
} else if l < 32 {
|
||||
key = append(key, bytes.Repeat([]byte{' '}, 32-l)...)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.block = block
|
||||
|
||||
// 判断iv长度
|
||||
l2 := len(iv)
|
||||
if l2 > aes.BlockSize {
|
||||
iv = iv[:aes.BlockSize]
|
||||
} else if l2 < aes.BlockSize {
|
||||
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
|
||||
}
|
||||
this.iv = iv
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AES256CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
dst = make([]byte, len(src))
|
||||
|
||||
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
|
||||
encrypter.XORKeyStream(dst, src)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *AES256CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
|
||||
if len(dst) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
src = make([]byte, len(dst))
|
||||
decrypter := cipher.NewCFBDecrypter(this.block, this.iv)
|
||||
decrypter.XORKeyStream(src, dst)
|
||||
|
||||
return
|
||||
}
|
||||
42
EdgeAdmin/internal/encrypt/method_aes_256_cfb_test.go
Normal file
42
EdgeAdmin/internal/encrypt/method_aes_256_cfb_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package encrypt
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAES256CFBMethod_Encrypt(t *testing.T) {
|
||||
method, err := NewMethodInstance("aes-256-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := []byte("Hello, World")
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dst = dst[:len(src)]
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
|
||||
func TestAES256CFBMethod_Encrypt2(t *testing.T) {
|
||||
method, err := NewMethodInstance("aes-256-cfb", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := []byte("Hello, World")
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
26
EdgeAdmin/internal/encrypt/method_raw.go
Normal file
26
EdgeAdmin/internal/encrypt/method_raw.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package encrypt
|
||||
|
||||
type RawMethod struct {
|
||||
}
|
||||
|
||||
func (this *RawMethod) Init(key, iv []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *RawMethod) Encrypt(src []byte) (dst []byte, err error) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
dst = make([]byte, len(src))
|
||||
copy(dst, src)
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RawMethod) Decrypt(dst []byte) (src []byte, err error) {
|
||||
if len(dst) == 0 {
|
||||
return
|
||||
}
|
||||
src = make([]byte, len(dst))
|
||||
copy(src, dst)
|
||||
return
|
||||
}
|
||||
23
EdgeAdmin/internal/encrypt/method_raw_test.go
Normal file
23
EdgeAdmin/internal/encrypt/method_raw_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package encrypt
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRawMethod_Encrypt(t *testing.T) {
|
||||
method, err := NewMethodInstance("raw", "abc", "123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := []byte("Hello, World")
|
||||
dst, err := method.Encrypt(src)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dst = dst[:len(src)]
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("src:", string(src))
|
||||
}
|
||||
43
EdgeAdmin/internal/encrypt/method_utils.go
Normal file
43
EdgeAdmin/internal/encrypt/method_utils.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var methods = map[string]reflect.Type{
|
||||
"raw": reflect.TypeOf(new(RawMethod)).Elem(),
|
||||
"aes-128-cfb": reflect.TypeOf(new(AES128CFBMethod)).Elem(),
|
||||
"aes-192-cfb": reflect.TypeOf(new(AES192CFBMethod)).Elem(),
|
||||
"aes-256-cfb": reflect.TypeOf(new(AES256CFBMethod)).Elem(),
|
||||
}
|
||||
|
||||
func NewMethodInstance(method string, key string, iv string) (MethodInterface, error) {
|
||||
valueType, ok := methods[method]
|
||||
if !ok {
|
||||
return nil, errors.New("method '" + method + "' not found")
|
||||
}
|
||||
instance, ok := reflect.New(valueType).Interface().(MethodInterface)
|
||||
if !ok {
|
||||
return nil, errors.New("method '" + method + "' must implement MethodInterface")
|
||||
}
|
||||
err := instance.Init([]byte(key), []byte(iv))
|
||||
return instance, err
|
||||
}
|
||||
|
||||
func RecoverMethodPanic(err interface{}) error {
|
||||
if err != nil {
|
||||
s, ok := err.(string)
|
||||
if ok {
|
||||
return errors.New(s)
|
||||
}
|
||||
|
||||
e, ok := err.(error)
|
||||
if ok {
|
||||
return e
|
||||
}
|
||||
|
||||
return errors.New("unknown error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
8
EdgeAdmin/internal/encrypt/method_utils_test.go
Normal file
8
EdgeAdmin/internal/encrypt/method_utils_test.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package encrypt
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFindMethodInstance(t *testing.T) {
|
||||
t.Log(NewMethodInstance("a", "b", ""))
|
||||
t.Log(NewMethodInstance("aes-256-cfb", "123456", ""))
|
||||
}
|
||||
56
EdgeAdmin/internal/errors/error.go
Normal file
56
EdgeAdmin/internal/errors/error.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type errorObj struct {
|
||||
err error
|
||||
file string
|
||||
line int
|
||||
funcName string
|
||||
}
|
||||
|
||||
func (this *errorObj) Error() string {
|
||||
s := this.err.Error() + "\n " + this.file
|
||||
if len(this.funcName) > 0 {
|
||||
s += ":" + this.funcName + "()"
|
||||
}
|
||||
s += ":" + strconv.Itoa(this.line)
|
||||
return s
|
||||
}
|
||||
|
||||
// 新错误
|
||||
func New(errText string) error {
|
||||
ptr, file, line, ok := runtime.Caller(1)
|
||||
funcName := ""
|
||||
if ok {
|
||||
frame, _ := runtime.CallersFrames([]uintptr{ptr}).Next()
|
||||
funcName = filepath.Base(frame.Function)
|
||||
}
|
||||
return &errorObj{
|
||||
err: errors.New(errText),
|
||||
file: file,
|
||||
line: line,
|
||||
funcName: funcName,
|
||||
}
|
||||
}
|
||||
|
||||
// 包装已有错误
|
||||
func Wrap(err error) error {
|
||||
ptr, file, line, ok := runtime.Caller(1)
|
||||
funcName := ""
|
||||
if ok {
|
||||
frame, _ := runtime.CallersFrames([]uintptr{ptr}).Next()
|
||||
funcName = filepath.Base(frame.Function)
|
||||
}
|
||||
return &errorObj{
|
||||
err: err,
|
||||
file: file,
|
||||
line: line,
|
||||
funcName: funcName,
|
||||
}
|
||||
}
|
||||
22
EdgeAdmin/internal/errors/error_test.go
Normal file
22
EdgeAdmin/internal/errors/error_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
t.Log(New("hello"))
|
||||
t.Log(Wrap(errors.New("hello")))
|
||||
t.Log(testError1())
|
||||
t.Log(Wrap(testError1()))
|
||||
t.Log(Wrap(testError2()))
|
||||
}
|
||||
|
||||
func testError1() error {
|
||||
return New("test error1")
|
||||
}
|
||||
|
||||
func testError2() error {
|
||||
return Wrap(testError1())
|
||||
}
|
||||
10
EdgeAdmin/internal/events/events.go
Normal file
10
EdgeAdmin/internal/events/events.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package events
|
||||
|
||||
type Event = string
|
||||
|
||||
const (
|
||||
EventStart Event = "start" // start loading
|
||||
EventQuit Event = "quit" // quit node gracefully
|
||||
|
||||
EventSecurityConfigChanged Event = "securityConfigChanged" // 安全设置变更
|
||||
)
|
||||
27
EdgeAdmin/internal/events/utils.go
Normal file
27
EdgeAdmin/internal/events/utils.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package events
|
||||
|
||||
import "sync"
|
||||
|
||||
var eventsMap = map[string][]func(){} // event => []callbacks
|
||||
var locker = sync.Mutex{}
|
||||
|
||||
// On 增加事件回调
|
||||
func On(event string, callback func()) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var callbacks = eventsMap[event]
|
||||
callbacks = append(callbacks, callback)
|
||||
eventsMap[event] = callbacks
|
||||
}
|
||||
|
||||
// Notify 通知事件
|
||||
func Notify(event string) {
|
||||
locker.Lock()
|
||||
var callbacks = eventsMap[event]
|
||||
locker.Unlock()
|
||||
|
||||
for _, callback := range callbacks {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
16
EdgeAdmin/internal/events/utils_test.go
Normal file
16
EdgeAdmin/internal/events/utils_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package events
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOn(t *testing.T) {
|
||||
On("hello", func() {
|
||||
t.Log("world")
|
||||
})
|
||||
On("hello", func() {
|
||||
t.Log("world2")
|
||||
})
|
||||
On("hello2", func() {
|
||||
t.Log("world2")
|
||||
})
|
||||
Notify("hello")
|
||||
}
|
||||
170
EdgeAdmin/internal/gen/generate.go
Normal file
170
EdgeAdmin/internal/gen/generate.go
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/conds/condutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func Generate() error {
|
||||
err := generateComponentsJSFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate 'components.src.js' failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 生成Javascript文件
|
||||
func generateComponentsJSFile() error {
|
||||
var buffer = bytes.NewBuffer([]byte{})
|
||||
|
||||
var webRoot string
|
||||
if Tea.IsTesting() {
|
||||
webRoot = Tea.Root + "/../web/public/js/components/"
|
||||
} else {
|
||||
webRoot = Tea.Root + "/web/public/js/components/"
|
||||
}
|
||||
var f = files.NewFile(webRoot)
|
||||
|
||||
f.Range(func(file *files.File) {
|
||||
if !file.IsFile() {
|
||||
return
|
||||
}
|
||||
if file.Ext() != ".js" {
|
||||
return
|
||||
}
|
||||
data, err := file.ReadAll()
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
buffer.Write(data)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
})
|
||||
|
||||
// 条件组件
|
||||
typesJSON, err := json.Marshal(condutils.ReadAllAvailableCondTypes())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request cond types failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
|
||||
buffer.Write(typesJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 条件操作符
|
||||
requestOperatorsJSON, err := json.Marshal(shared.AllRequestOperators())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request operators failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
|
||||
buffer.Write(requestOperatorsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 请求变量
|
||||
requestVariablesJSON, err := json.Marshal(shared.DefaultRequestVariables())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal request variables failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_VARIABLES = ")
|
||||
buffer.Write(requestVariablesJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 指标
|
||||
metricHTTPKeysJSON, err := json.Marshal(serverconfigs.FindAllMetricKeyDefinitions(serverconfigs.MetricItemCategoryHTTP))
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal metric http keys failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.METRIC_HTTP_KEYS = ")
|
||||
buffer.Write(metricHTTPKeysJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// IP地址阈值项目
|
||||
ipAddrThresholdItemsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdItems())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal ip addr threshold items failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
|
||||
buffer.Write(ipAddrThresholdItemsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// IP地址阈值动作
|
||||
ipAddrThresholdActionsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdActions())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal ip addr threshold actions failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
|
||||
buffer.Write(ipAddrThresholdActionsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF checkpoints
|
||||
var wafCheckpointsMaps = []maps.Map{}
|
||||
for _, checkpoint := range firewallconfigs.AllCheckpoints {
|
||||
wafCheckpointsMaps = append(wafCheckpointsMaps, maps.Map{
|
||||
"name": checkpoint.Name,
|
||||
"prefix": checkpoint.Prefix,
|
||||
"description": checkpoint.Description,
|
||||
})
|
||||
}
|
||||
wafCheckpointsJSON, err := json.Marshal(wafCheckpointsMaps)
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal waf rule checkpoints failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_RULE_CHECKPOINTS = ")
|
||||
buffer.Write(wafCheckpointsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF操作符
|
||||
wafOperatorsJSON, err := json.Marshal(firewallconfigs.AllRuleOperators)
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal waf rule operators failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
|
||||
buffer.Write(wafOperatorsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF验证码类型
|
||||
captchaTypesJSON, err := json.Marshal(firewallconfigs.FindAllCaptchaTypes())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal captcha types failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_CAPTCHA_TYPES = ")
|
||||
buffer.Write(captchaTypesJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.src.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(fp, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
13
EdgeAdmin/internal/gen/generate_test.go
Normal file
13
EdgeAdmin/internal/gen/generate_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package gen
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
err := Generate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
12
EdgeAdmin/internal/goman/instance.go
Normal file
12
EdgeAdmin/internal/goman/instance.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import "time"
|
||||
|
||||
type Instance struct {
|
||||
Id uint64
|
||||
CreatedTime time.Time
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
81
EdgeAdmin/internal/goman/lib.go
Normal file
81
EdgeAdmin/internal/goman/lib.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var locker = &sync.Mutex{}
|
||||
var instanceMap = map[uint64]*Instance{} // id => *Instance
|
||||
var instanceId = uint64(0)
|
||||
|
||||
// New 新创建goroutine
|
||||
func New(f func()) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
|
||||
go func() {
|
||||
locker.Lock()
|
||||
instanceId++
|
||||
|
||||
var instance = &Instance{
|
||||
Id: instanceId,
|
||||
CreatedTime: time.Now(),
|
||||
}
|
||||
|
||||
instance.File = file
|
||||
instance.Line = line
|
||||
|
||||
instanceMap[instanceId] = instance
|
||||
locker.Unlock()
|
||||
|
||||
// run function
|
||||
f()
|
||||
|
||||
locker.Lock()
|
||||
delete(instanceMap, instanceId)
|
||||
locker.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// NewWithArgs 创建带有参数的goroutine
|
||||
func NewWithArgs(f func(args ...interface{}), args ...interface{}) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
|
||||
go func() {
|
||||
locker.Lock()
|
||||
instanceId++
|
||||
|
||||
var instance = &Instance{
|
||||
Id: instanceId,
|
||||
CreatedTime: time.Now(),
|
||||
}
|
||||
|
||||
instance.File = file
|
||||
instance.Line = line
|
||||
|
||||
instanceMap[instanceId] = instance
|
||||
locker.Unlock()
|
||||
|
||||
// run function
|
||||
f(args...)
|
||||
|
||||
locker.Lock()
|
||||
delete(instanceMap, instanceId)
|
||||
locker.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// List 列出所有正在运行goroutine
|
||||
func List() []*Instance {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var result = []*Instance{}
|
||||
for _, instance := range instanceMap {
|
||||
result = append(result, instance)
|
||||
}
|
||||
return result
|
||||
}
|
||||
28
EdgeAdmin/internal/goman/lib_test.go
Normal file
28
EdgeAdmin/internal/goman/lib_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
New(func() {
|
||||
t.Log("Hello")
|
||||
|
||||
t.Log(List())
|
||||
})
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log(List())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func TestNewWithArgs(t *testing.T) {
|
||||
NewWithArgs(func(args ...interface{}) {
|
||||
t.Log(args[0], args[1])
|
||||
}, 1, 2)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
476
EdgeAdmin/internal/nodes/admin_node.go
Normal file
476
EdgeAdmin/internal/nodes/admin_node.go
Normal file
@@ -0,0 +1,476 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/iwind/TeaGo"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"gopkg.in/yaml.v3"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var SharedAdminNode *AdminNode = nil
|
||||
|
||||
type AdminNode struct {
|
||||
sock *gosock.Sock
|
||||
subPIDs []int
|
||||
}
|
||||
|
||||
func NewAdminNode() *AdminNode {
|
||||
return &AdminNode{}
|
||||
}
|
||||
|
||||
func (this *AdminNode) Run() {
|
||||
SharedAdminNode = this
|
||||
|
||||
// 启动管理界面
|
||||
var secret = this.genSecret()
|
||||
configs.Secret = secret
|
||||
|
||||
// 本地Sock
|
||||
err := this.listenSock()
|
||||
if err != nil {
|
||||
logs.Println("[NODE]", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 检查server配置
|
||||
err = this.checkServer()
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
logs.Println("[NODE]" + err.Error())
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 添加端口到防火墙
|
||||
this.addPortsToFirewall()
|
||||
|
||||
// 监听信号
|
||||
var sigQueue = make(chan os.Signal, 8)
|
||||
signal.Notify(sigQueue, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT)
|
||||
go func() {
|
||||
for range sigQueue {
|
||||
for _, pid := range this.subPIDs {
|
||||
p, err := os.FindProcess(pid)
|
||||
if err == nil && p != nil {
|
||||
_ = p.Kill()
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
|
||||
// 触发事件
|
||||
events.Notify(events.EventStart)
|
||||
|
||||
// 启动API节点
|
||||
this.startAPINode()
|
||||
|
||||
// 设置DNS相关
|
||||
this.setupDNS()
|
||||
|
||||
// 启动IP库
|
||||
this.startIPLibrary()
|
||||
|
||||
// 启动Web服务
|
||||
sessionManager, err := NewSessionManager()
|
||||
if err != nil {
|
||||
log.Fatal("start session failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
TeaGo.NewServer(false).
|
||||
AccessLog(false).
|
||||
EndAll().
|
||||
Session(sessionManager, teaconst.CookieSID).
|
||||
ReadHeaderTimeout(3 * time.Second).
|
||||
ReadTimeout(1200 * time.Second).
|
||||
Start()
|
||||
}
|
||||
|
||||
// Daemon 实现守护进程
|
||||
func (this *AdminNode) Daemon() {
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
var isDebug = lists.ContainsString(os.Args, "debug")
|
||||
for {
|
||||
conn, err := sock.Dial()
|
||||
if err != nil {
|
||||
if isDebug {
|
||||
log.Println("[DAEMON]starting ...")
|
||||
}
|
||||
|
||||
// 尝试启动
|
||||
err = func() error {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(exe)
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
if isDebug {
|
||||
log.Println("[DAEMON]", err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
} else {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
} else {
|
||||
_ = conn.Close()
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InstallSystemService 安装系统服务
|
||||
func (this *AdminNode) InstallSystemService() error {
|
||||
shortName := teaconst.SystemdServiceName
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manager := utils.NewServiceManager(shortName, teaconst.ProductName)
|
||||
err = manager.Install(exe, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSubPID 添加子PID
|
||||
func (this *AdminNode) AddSubPID(pid int) {
|
||||
this.subPIDs = append(this.subPIDs, pid)
|
||||
}
|
||||
|
||||
// 检查Server配置
|
||||
func (this *AdminNode) checkServer() error {
|
||||
configFile := Tea.ConfigFile("server.yaml")
|
||||
_, err := os.Stat(configFile)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
// 创建文件
|
||||
templateFile := Tea.ConfigFile("server.template.yaml")
|
||||
data, err := os.ReadFile(templateFile)
|
||||
if err == nil {
|
||||
err = os.WriteFile(configFile, data, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create config file failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
templateYAML := `# environment code
|
||||
env: prod
|
||||
|
||||
# http
|
||||
http:
|
||||
"on": true
|
||||
listen: [ "0.0.0.0:7788" ]
|
||||
|
||||
# https
|
||||
https:
|
||||
"on": false
|
||||
listen: [ "0.0.0.0:443"]
|
||||
cert: ""
|
||||
key: ""
|
||||
`
|
||||
err = os.WriteFile(configFile, []byte(templateYAML), 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create config file failed: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("can not read config from 'configs/server.yaml': %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 添加端口到防火墙
|
||||
func (this *AdminNode) addPortsToFirewall() {
|
||||
var configFile = Tea.ConfigFile("server.yaml")
|
||||
data, err := os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var config = &TeaGo.ServerConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ports = []int{}
|
||||
if config.Http.On {
|
||||
for _, listen := range config.Http.Listen {
|
||||
_, portString, _ := net.SplitHostPort(listen)
|
||||
var port = types.Int(portString)
|
||||
if port > 0 && !lists.ContainsInt(ports, port) {
|
||||
ports = append(ports, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.Https.On {
|
||||
for _, listen := range config.Https.Listen {
|
||||
_, portString, _ := net.SplitHostPort(listen)
|
||||
var port = types.Int(portString)
|
||||
if port > 0 && !lists.ContainsInt(ports, port) {
|
||||
ports = append(ports, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.AddPortsToFirewall(ports)
|
||||
}
|
||||
|
||||
// 设置DNS相关
|
||||
func (this *AdminNode) setupDNS() {
|
||||
config, loadErr := configloaders.LoadAdminUIConfig()
|
||||
if loadErr != nil {
|
||||
// 默认使用go原生
|
||||
err := os.Setenv("GODEBUG", "netdns=go")
|
||||
if err != nil {
|
||||
logs.Println("[DNS_RESOLVER]set env failed: " + err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
switch config.DNSResolver.Type {
|
||||
case nodeconfigs.DNSResolverTypeGoNative:
|
||||
err = os.Setenv("GODEBUG", "netdns=go")
|
||||
case nodeconfigs.DNSResolverTypeCGO:
|
||||
err = os.Setenv("GODEBUG", "netdns=cgo")
|
||||
default:
|
||||
// 默认使用go原生
|
||||
err = os.Setenv("GODEBUG", "netdns=go")
|
||||
}
|
||||
if err != nil {
|
||||
logs.Println("[DNS_RESOLVER]set env failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 启动API节点
|
||||
func (this *AdminNode) startAPINode() {
|
||||
var configPath = Tea.Root + "/edge-api/configs/api.yaml"
|
||||
_, err := os.Stat(configPath)
|
||||
canStart := false
|
||||
if err == nil {
|
||||
canStart = true
|
||||
} else if err != nil && os.IsNotExist(err) {
|
||||
// 尝试恢复api.yaml
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
paths := []string{}
|
||||
if len(homeDir) > 0 {
|
||||
paths = append(paths, homeDir+"/.edge-api/api.yaml")
|
||||
}
|
||||
paths = append(paths, "/etc/edge-api/api.yaml")
|
||||
for _, path := range paths {
|
||||
_, err = os.Stat(path)
|
||||
if err == nil {
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
err = os.WriteFile(configPath, data, 0666)
|
||||
if err == nil {
|
||||
logs.Println("[NODE]recover 'edge-api/configs/api.yaml' from '" + path + "'")
|
||||
canStart = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbPath := Tea.Root + "/edge-api/configs/db.yaml"
|
||||
_, err = os.Stat(dbPath)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
// 尝试恢复db.yaml
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
paths := []string{}
|
||||
if len(homeDir) > 0 {
|
||||
paths = append(paths, homeDir+"/.edge-api/db.yaml")
|
||||
}
|
||||
paths = append(paths, "/etc/edge-api/db.yaml")
|
||||
for _, path := range paths {
|
||||
_, err = os.Stat(path)
|
||||
if err == nil {
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
err = os.WriteFile(dbPath, data, 0666)
|
||||
if err == nil {
|
||||
logs.Println("[NODE]recover 'edge-api/configs/db.yaml' from '" + path + "'")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if canStart {
|
||||
logs.Println("start edge-api")
|
||||
cmd := exec.Command(Tea.Root + "/edge-api/bin/edge-api")
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
logs.Println("[ERROR]start edge-api failed: " + err.Error())
|
||||
} else {
|
||||
this.subPIDs = append(this.subPIDs, cmd.Process.Pid)
|
||||
// 在后台 goroutine 中等待子进程退出,避免僵尸进程
|
||||
go func() {
|
||||
waitErr := cmd.Wait()
|
||||
if waitErr != nil {
|
||||
logs.Println("[NODE]edge-api process exited with error: " + waitErr.Error())
|
||||
} else {
|
||||
logs.Println("[NODE]edge-api process exited normally")
|
||||
}
|
||||
// 从 subPIDs 中移除已退出的进程
|
||||
for i, pid := range this.subPIDs {
|
||||
if pid == cmd.Process.Pid {
|
||||
this.subPIDs = append(this.subPIDs[:i], this.subPIDs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成Secret
|
||||
func (this *AdminNode) genSecret() string {
|
||||
tmpFile := os.TempDir() + "/edge-admin-secret.tmp"
|
||||
data, err := os.ReadFile(tmpFile)
|
||||
if err == nil && len(data) == 32 {
|
||||
return string(data)
|
||||
}
|
||||
secret := rands.String(32)
|
||||
_ = os.WriteFile(tmpFile, []byte(secret), 0666)
|
||||
return secret
|
||||
}
|
||||
|
||||
// 监听本地sock
|
||||
func (this *AdminNode) listenSock() error {
|
||||
this.sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
|
||||
// 检查是否在运行
|
||||
if this.sock.IsListening() {
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err == nil {
|
||||
return errors.New("error: the process is already running, pid: " + maps.NewMap(reply.Params).GetString("pid"))
|
||||
} else {
|
||||
return errors.New("error: the process is already running")
|
||||
}
|
||||
}
|
||||
|
||||
// 启动监听
|
||||
go func() {
|
||||
this.sock.OnCommand(func(cmd *gosock.Command) {
|
||||
switch cmd.Code {
|
||||
case "pid":
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "pid",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
},
|
||||
})
|
||||
case "stop":
|
||||
_ = cmd.ReplyOk()
|
||||
|
||||
// 关闭子进程
|
||||
for _, pid := range this.subPIDs {
|
||||
p, err := os.FindProcess(pid)
|
||||
if err == nil && p != nil {
|
||||
_ = p.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
// 停止当前目录下的API节点
|
||||
var apiSock = gosock.NewTmpSock("edge-api")
|
||||
apiReply, err := apiSock.Send(&gosock.Command{Code: "info"})
|
||||
if err == nil {
|
||||
adminExe, _ := os.Executable()
|
||||
if len(adminExe) > 0 && apiReply != nil && strings.HasPrefix(maps.NewMap(apiReply.Params).GetString("path"), filepath.Dir(filepath.Dir(adminExe))) {
|
||||
_, _ = apiSock.Send(&gosock.Command{Code: "stop"})
|
||||
}
|
||||
}
|
||||
|
||||
// 退出主进程
|
||||
events.Notify(events.EventQuit)
|
||||
os.Exit(0)
|
||||
case "recover":
|
||||
teaconst.IsRecoverMode = true
|
||||
_ = cmd.ReplyOk()
|
||||
case "demo":
|
||||
teaconst.IsDemoMode = !teaconst.IsDemoMode
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Params: map[string]interface{}{"isDemo": teaconst.IsDemoMode},
|
||||
})
|
||||
case "info":
|
||||
exePath, _ := os.Executable()
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "info",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
"version": teaconst.Version,
|
||||
"path": exePath,
|
||||
},
|
||||
})
|
||||
case "dev": // 切换到dev
|
||||
Tea.Env = Tea.EnvDev
|
||||
_ = cmd.ReplyOk()
|
||||
case "prod": // 切换到prod
|
||||
Tea.Env = Tea.EnvProd
|
||||
_ = cmd.ReplyOk()
|
||||
case "security.reset":
|
||||
var newConfig = configloaders.NewSecurityConfig()
|
||||
_ = configloaders.UpdateSecurityConfig(newConfig)
|
||||
_ = cmd.ReplyOk()
|
||||
}
|
||||
})
|
||||
|
||||
err := this.sock.Listen()
|
||||
if err != nil {
|
||||
logs.Println("NODE", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
logs.Println("NODE", "quit unix sock")
|
||||
_ = this.sock.Close()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
18
EdgeAdmin/internal/nodes/admin_node_ext.go
Normal file
18
EdgeAdmin/internal/nodes/admin_node_ext.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
)
|
||||
|
||||
// 启动IP库
|
||||
func (this *AdminNode) startIPLibrary() {
|
||||
logs.Println("[NODE]initializing ip library ...")
|
||||
err := iplibrary.InitDefault()
|
||||
if err != nil {
|
||||
logs.Println("[NODE]initialize ip library failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
18
EdgeAdmin/internal/nodes/admin_node_ext_plus.go
Normal file
18
EdgeAdmin/internal/nodes/admin_node_ext_plus.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
)
|
||||
|
||||
// 启动IP库
|
||||
func (this *AdminNode) startIPLibrary() {
|
||||
logs.Println("[NODE]initializing ip library ...")
|
||||
err := iplibrary.InitDefault()
|
||||
if err != nil {
|
||||
logs.Println("[NODE]initialize ip library failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
118
EdgeAdmin/internal/nodes/session_manager.go
Normal file
118
EdgeAdmin/internal/nodes/session_manager.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SessionManager SESSION管理
|
||||
type SessionManager struct {
|
||||
life uint
|
||||
}
|
||||
|
||||
func NewSessionManager() (*SessionManager, error) {
|
||||
return &SessionManager{}, nil
|
||||
}
|
||||
|
||||
func (this *SessionManager) Init(config *actions.SessionConfig) {
|
||||
this.life = config.Life
|
||||
}
|
||||
|
||||
func (this *SessionManager) Read(sid string) map[string]string {
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
var result = map[string]string{}
|
||||
|
||||
var cacheKey = "SESSION@" + sid
|
||||
var item = ttlcache.DefaultCache.Read(cacheKey)
|
||||
if item != nil && item.Value != nil {
|
||||
itemMap, ok := item.Value.(map[string]string)
|
||||
if ok {
|
||||
return itemMap
|
||||
}
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
resp, err := rpcClient.LoginSessionRPC().FindLoginSession(rpcClient.Context(0), &pb.FindLoginSessionRequest{Sid: sid})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "read '"+sid+"' failed: "+err.Error())
|
||||
result["@error"] = err.Error()
|
||||
return result
|
||||
}
|
||||
|
||||
var session = resp.LoginSession
|
||||
if session == nil || len(session.ValuesJSON) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
err = json.Unmarshal(session.ValuesJSON, &result)
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "decode '"+sid+"' values failed: "+err.Error())
|
||||
}
|
||||
|
||||
// Write to cache
|
||||
ttlcache.DefaultCache.Write(cacheKey, result, time.Now().Unix()+300 /** must not be too long **/)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *SessionManager) WriteItem(sid string, key string, value string) bool {
|
||||
// 删除缓存
|
||||
defer ttlcache.DefaultCache.Delete("SESSION@" + sid)
|
||||
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return false
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = rpcClient.LoginSessionRPC().WriteLoginSessionValue(rpcClient.Context(0), &pb.WriteLoginSessionValueRequest{
|
||||
Sid: sid,
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "write sid:'"+sid+"' key:'"+key+"' failed: "+err.Error())
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *SessionManager) Delete(sid string) bool {
|
||||
// 删除缓存
|
||||
defer ttlcache.DefaultCache.Delete("SESSION@" + sid)
|
||||
|
||||
// 忽略OTP
|
||||
if strings.HasSuffix(sid, "_otp") {
|
||||
return false
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = rpcClient.LoginSessionRPC().DeleteLoginSession(rpcClient.Context(0), &pb.DeleteLoginSessionRequest{Sid: sid})
|
||||
if err != nil {
|
||||
logs.Println("SESSION", "delete '"+sid+"' failed: "+err.Error())
|
||||
}
|
||||
return true
|
||||
}
|
||||
10
EdgeAdmin/internal/oplogs/levels.go
Normal file
10
EdgeAdmin/internal/oplogs/levels.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package oplogs
|
||||
|
||||
const (
|
||||
LevelNone = "none"
|
||||
LevelInfo = "info"
|
||||
LevelDebug = "debug"
|
||||
LevelWarn = "warn"
|
||||
LevelError = "error"
|
||||
LevelFatal = "fatal"
|
||||
)
|
||||
89
EdgeAdmin/internal/plus/components.go
Normal file
89
EdgeAdmin/internal/plus/components.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package plus
|
||||
|
||||
type ComponentCode = string
|
||||
|
||||
const (
|
||||
ComponentCodeUser ComponentCode = "user"
|
||||
ComponentCodeScheduling ComponentCode = "scheduling"
|
||||
ComponentCodeMonitor ComponentCode = "monitor"
|
||||
ComponentCodeLog ComponentCode = "log"
|
||||
ComponentCodeReporter ComponentCode = "reporter"
|
||||
ComponentCodePlan ComponentCode = "plan"
|
||||
ComponentCodeFinance ComponentCode = "finance"
|
||||
ComponentCodeNS ComponentCode = "ns"
|
||||
ComponentCodeL2Node ComponentCode = "l2node"
|
||||
ComponentCodeComputing ComponentCode = "computing"
|
||||
ComponentCodeTicket ComponentCode = "ticket"
|
||||
ComponentCodeAntiDDoS ComponentCode = "antiDDoS"
|
||||
)
|
||||
|
||||
type ComponentDefinition struct {
|
||||
Name string `json:"name"`
|
||||
Code ComponentCode `json:"code"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func FindAllComponents() []*ComponentDefinition {
|
||||
return []*ComponentDefinition{
|
||||
{
|
||||
Name: "多租户",
|
||||
Code: ComponentCodeUser,
|
||||
},
|
||||
{
|
||||
Name: "智能调度",
|
||||
Code: ComponentCodeScheduling,
|
||||
},
|
||||
{
|
||||
Name: "监控",
|
||||
Code: ComponentCodeMonitor,
|
||||
},
|
||||
{
|
||||
Name: "日志",
|
||||
Code: ComponentCodeLog,
|
||||
},
|
||||
{
|
||||
Name: "区域监控",
|
||||
Code: ComponentCodeReporter,
|
||||
},
|
||||
{
|
||||
Name: "套餐",
|
||||
Code: ComponentCodePlan,
|
||||
},
|
||||
{
|
||||
Name: "财务",
|
||||
Code: ComponentCodeFinance,
|
||||
},
|
||||
{
|
||||
Name: "智能DNS",
|
||||
Code: ComponentCodeNS,
|
||||
},
|
||||
{
|
||||
Name: "L2节点",
|
||||
Code: ComponentCodeL2Node,
|
||||
},
|
||||
{
|
||||
Name: "边缘计算",
|
||||
Code: ComponentCodeComputing,
|
||||
},
|
||||
{
|
||||
Name: "工单系统",
|
||||
Code: ComponentCodeTicket,
|
||||
},
|
||||
{
|
||||
Name: "高防IP",
|
||||
Code: ComponentCodeAntiDDoS,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CheckComponent(code string) bool {
|
||||
for _, c := range FindAllComponents() {
|
||||
if c.Code == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
6
EdgeAdmin/internal/plus/error.go
Normal file
6
EdgeAdmin/internal/plus/error.go
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package plus
|
||||
|
||||
var ErrString = ""
|
||||
51
EdgeAdmin/internal/plus/helper.go
Normal file
51
EdgeAdmin/internal/plus/helper.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package plus
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func NewHelper(component ComponentCode) *Helper {
|
||||
return &Helper{component: component}
|
||||
}
|
||||
|
||||
func NewBasicHelper() *Helper {
|
||||
return NewHelper("")
|
||||
}
|
||||
|
||||
type Helper struct {
|
||||
component string
|
||||
}
|
||||
|
||||
func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) {
|
||||
if !teaconst.IsPlus || !AllowComponent(this.component) {
|
||||
var writer = actionPtr.Object().ResponseWriter
|
||||
writer.WriteHeader(http.StatusForbidden)
|
||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = writer.Write([]byte(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>权限错误</title>
|
||||
<link rel="stylesheet" type="text/css" href="/css/semantic.min.css?v=bRafhK" media="all"/>
|
||||
<style type="text/css">
|
||||
div { max-width: 960px; margin: 4em auto; }
|
||||
h3 { font-weight: normal; }
|
||||
p { font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h3>权限错误</h3>
|
||||
<p>此操作需要商业版用户权限。如果你已经是商业用户,请重新访问 <a href="/settings/authority">商业版认证页</a> 以便激活权限。</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`))
|
||||
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
98
EdgeAdmin/internal/plus/utils.go
Normal file
98
EdgeAdmin/internal/plus/utils.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package plus
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
plusutils "github.com/TeaOSLab/EdgePlus/pkg/utils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 已经开通的组件
|
||||
var plusLocker = sync.Mutex{}
|
||||
var plusComponents = []string{}
|
||||
|
||||
var PlusEdition = ""
|
||||
|
||||
// UpdateComponents 修改开通的组件
|
||||
func UpdateComponents(edition string, components []string) {
|
||||
plusLocker.Lock()
|
||||
PlusEdition = edition
|
||||
plusComponents = components
|
||||
plusLocker.Unlock()
|
||||
}
|
||||
|
||||
// AllowComponent 检查是否允许使用某个组件
|
||||
func AllowComponent(component ComponentCode) bool {
|
||||
if !teaconst.IsPlus {
|
||||
return false
|
||||
}
|
||||
|
||||
// 允许不指定组件,表示只要是plus用户就可以
|
||||
if len(component) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
plusLocker.Lock()
|
||||
defer plusLocker.Unlock()
|
||||
|
||||
if len(plusComponents) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range plusComponents {
|
||||
if c == "*" || c == component {
|
||||
// 如果不是最新版本的授权,则忽略一些权限
|
||||
if c == "*" && (len(PlusEdition) == 0 || plusutils.CompareEdition(PlusEdition, plusutils.EditionPro) < 0) && component == ComponentCodeAntiDDoS {
|
||||
continue
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateMac 校验MAC地址
|
||||
func ValidateMac(mac string) bool {
|
||||
if len(mac) == 0 || mac == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, addr := range FindAllMacAddresses() {
|
||||
if addr == mac {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FindAllMacAddresses 获取MAC地址
|
||||
func FindAllMacAddresses() []string {
|
||||
var macAddresses = []string{}
|
||||
interfaces, _ := net.Interfaces()
|
||||
for _, i := range interfaces {
|
||||
var addr = i.HardwareAddr.String()
|
||||
if len(addr) == 0 {
|
||||
continue
|
||||
}
|
||||
if lists.ContainsString(macAddresses, addr) {
|
||||
continue
|
||||
}
|
||||
if i.Flags&net.FlagLoopback == 1 {
|
||||
continue
|
||||
}
|
||||
if i.Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
ipAddrs, _ := i.Addrs()
|
||||
if len(ipAddrs) == 0 {
|
||||
continue
|
||||
}
|
||||
macAddresses = append(macAddresses, addr)
|
||||
}
|
||||
return macAddresses
|
||||
}
|
||||
621
EdgeAdmin/internal/rpc/rpc_client.go
Normal file
621
EdgeAdmin/internal/rpc/rpc_client.go
Normal file
@@ -0,0 +1,621 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/encrypt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RPCClient RPC客户端
|
||||
type RPCClient struct {
|
||||
apiConfig *configs.APIConfig
|
||||
conns []*grpc.ClientConn
|
||||
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
// NewRPCClient 构造新的RPC客户端
|
||||
func NewRPCClient(apiConfig *configs.APIConfig, isPrimary bool) (*RPCClient, error) {
|
||||
if apiConfig == nil {
|
||||
return nil, errors.New("api config should not be nil")
|
||||
}
|
||||
|
||||
var client = &RPCClient{
|
||||
apiConfig: apiConfig,
|
||||
}
|
||||
|
||||
err := client.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置RPC
|
||||
if isPrimary {
|
||||
dao.SetRPC(client)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (this *RPCClient) APITokenRPC() pb.APITokenServiceClient {
|
||||
return pb.NewAPITokenServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) AdminRPC() pb.AdminServiceClient {
|
||||
return pb.NewAdminServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeRPC() pb.NodeServiceClient {
|
||||
return pb.NewNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeLogRPC() pb.NodeLogServiceClient {
|
||||
return pb.NewNodeLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeGrantRPC() pb.NodeGrantServiceClient {
|
||||
return pb.NewNodeGrantServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeLoginRPC() pb.NodeLoginServiceClient {
|
||||
return pb.NewNodeLoginServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeClusterRPC() pb.NodeClusterServiceClient {
|
||||
return pb.NewNodeClusterServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeClusterFirewallActionRPC() pb.NodeClusterFirewallActionServiceClient {
|
||||
return pb.NewNodeClusterFirewallActionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeGroupRPC() pb.NodeGroupServiceClient {
|
||||
return pb.NewNodeGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeRegionRPC() pb.NodeRegionServiceClient {
|
||||
return pb.NewNodeRegionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient {
|
||||
return pb.NewNodeIPAddressServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressLogRPC() pb.NodeIPAddressLogServiceClient {
|
||||
return pb.NewNodeIPAddressLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeIPAddressThresholdRPC() pb.NodeIPAddressThresholdServiceClient {
|
||||
return pb.NewNodeIPAddressThresholdServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeValueRPC() pb.NodeValueServiceClient {
|
||||
return pb.NewNodeValueServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeThresholdRPC() pb.NodeThresholdServiceClient {
|
||||
return pb.NewNodeThresholdServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerRPC() pb.ServerServiceClient {
|
||||
return pb.NewServerServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerBandwidthStatRPC() pb.ServerBandwidthStatServiceClient {
|
||||
return pb.NewServerBandwidthStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerClientSystemMonthlyStatRPC() pb.ServerClientSystemMonthlyStatServiceClient {
|
||||
return pb.NewServerClientSystemMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerClientBrowserMonthlyStatRPC() pb.ServerClientBrowserMonthlyStatServiceClient {
|
||||
return pb.NewServerClientBrowserMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerRegionCountryMonthlyStatRPC() pb.ServerRegionCountryMonthlyStatServiceClient {
|
||||
return pb.NewServerRegionCountryMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerRegionProvinceMonthlyStatRPC() pb.ServerRegionProvinceMonthlyStatServiceClient {
|
||||
return pb.NewServerRegionProvinceMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerRegionCityMonthlyStatRPC() pb.ServerRegionCityMonthlyStatServiceClient {
|
||||
return pb.NewServerRegionCityMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerRegionProviderMonthlyStatRPC() pb.ServerRegionProviderMonthlyStatServiceClient {
|
||||
return pb.NewServerRegionProviderMonthlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerHTTPFirewallDailyStatRPC() pb.ServerHTTPFirewallDailyStatServiceClient {
|
||||
return pb.NewServerHTTPFirewallDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerDailyStatRPC() pb.ServerDailyStatServiceClient {
|
||||
return pb.NewServerDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerGroupRPC() pb.ServerGroupServiceClient {
|
||||
return pb.NewServerGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) APINodeRPC() pb.APINodeServiceClient {
|
||||
return pb.NewAPINodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) APIMethodStatRPC() pb.APIMethodStatServiceClient {
|
||||
return pb.NewAPIMethodStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DBNodeRPC() pb.DBNodeServiceClient {
|
||||
return pb.NewDBNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DBRPC() pb.DBServiceClient {
|
||||
return pb.NewDBServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) OriginRPC() pb.OriginServiceClient {
|
||||
return pb.NewOriginServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPWebRPC() pb.HTTPWebServiceClient {
|
||||
return pb.NewHTTPWebServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ReverseProxyRPC() pb.ReverseProxyServiceClient {
|
||||
return pb.NewReverseProxyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPGzipRPC() pb.HTTPGzipServiceClient {
|
||||
return pb.NewHTTPGzipServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPHeaderRPC() pb.HTTPHeaderServiceClient {
|
||||
return pb.NewHTTPHeaderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPHeaderPolicyRPC() pb.HTTPHeaderPolicyServiceClient {
|
||||
return pb.NewHTTPHeaderPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPPageRPC() pb.HTTPPageServiceClient {
|
||||
return pb.NewHTTPPageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPAccessLogPolicyRPC() pb.HTTPAccessLogPolicyServiceClient {
|
||||
return pb.NewHTTPAccessLogPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPCachePolicyRPC() pb.HTTPCachePolicyServiceClient {
|
||||
return pb.NewHTTPCachePolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPCacheTaskRPC() pb.HTTPCacheTaskServiceClient {
|
||||
return pb.NewHTTPCacheTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPCacheTaskKeyRPC() pb.HTTPCacheTaskKeyServiceClient {
|
||||
return pb.NewHTTPCacheTaskKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPFirewallPolicyRPC() pb.HTTPFirewallPolicyServiceClient {
|
||||
return pb.NewHTTPFirewallPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPFirewallRuleGroupRPC() pb.HTTPFirewallRuleGroupServiceClient {
|
||||
return pb.NewHTTPFirewallRuleGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPFirewallRuleSetRPC() pb.HTTPFirewallRuleSetServiceClient {
|
||||
return pb.NewHTTPFirewallRuleSetServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FirewallRPC() pb.FirewallServiceClient {
|
||||
return pb.NewFirewallServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPLocationRPC() pb.HTTPLocationServiceClient {
|
||||
return pb.NewHTTPLocationServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPWebsocketRPC() pb.HTTPWebsocketServiceClient {
|
||||
return pb.NewHTTPWebsocketServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPRewriteRuleRPC() pb.HTTPRewriteRuleServiceClient {
|
||||
return pb.NewHTTPRewriteRuleServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
// HTTPAccessLogRPC 访问日志
|
||||
func (this *RPCClient) HTTPAccessLogRPC() pb.HTTPAccessLogServiceClient {
|
||||
return pb.NewHTTPAccessLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPFastcgiRPC() pb.HTTPFastcgiServiceClient {
|
||||
return pb.NewHTTPFastcgiServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPAuthPolicyRPC() pb.HTTPAuthPolicyServiceClient {
|
||||
return pb.NewHTTPAuthPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) SSLCertRPC() pb.SSLCertServiceClient {
|
||||
return pb.NewSSLCertServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) SSLPolicyRPC() pb.SSLPolicyServiceClient {
|
||||
return pb.NewSSLPolicyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) SysSettingRPC() pb.SysSettingServiceClient {
|
||||
return pb.NewSysSettingServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageRPC() pb.MessageServiceClient {
|
||||
return pb.NewMessageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryRPC() pb.IPLibraryServiceClient {
|
||||
return pb.NewIPLibraryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryFileRPC() pb.IPLibraryFileServiceClient {
|
||||
return pb.NewIPLibraryFileServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryArtifactRPC() pb.IPLibraryArtifactServiceClient {
|
||||
return pb.NewIPLibraryArtifactServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPListRPC() pb.IPListServiceClient {
|
||||
return pb.NewIPListServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPItemRPC() pb.IPItemServiceClient {
|
||||
return pb.NewIPItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FileRPC() pb.FileServiceClient {
|
||||
return pb.NewFileServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FileChunkRPC() pb.FileChunkServiceClient {
|
||||
return pb.NewFileChunkServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionCountryRPC() pb.RegionCountryServiceClient {
|
||||
return pb.NewRegionCountryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionProvinceRPC() pb.RegionProvinceServiceClient {
|
||||
return pb.NewRegionProvinceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionCityRPC() pb.RegionCityServiceClient {
|
||||
return pb.NewRegionCityServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionTownRPC() pb.RegionTownServiceClient {
|
||||
return pb.NewRegionTownServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) RegionProviderRPC() pb.RegionProviderServiceClient {
|
||||
return pb.NewRegionProviderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LogRPC() pb.LogServiceClient {
|
||||
return pb.NewLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DNSProviderRPC() pb.DNSProviderServiceClient {
|
||||
return pb.NewDNSProviderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DNSDomainRPC() pb.DNSDomainServiceClient {
|
||||
return pb.NewDNSDomainServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DNSRPC() pb.DNSServiceClient {
|
||||
return pb.NewDNSServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) DNSTaskRPC() pb.DNSTaskServiceClient {
|
||||
return pb.NewDNSTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ACMEUserRPC() pb.ACMEUserServiceClient {
|
||||
return pb.NewACMEUserServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ACMETaskRPC() pb.ACMETaskServiceClient {
|
||||
return pb.NewACMETaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ACMEProviderRPC() pb.ACMEProviderServiceClient {
|
||||
return pb.NewACMEProviderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ACMEProviderAccountRPC() pb.ACMEProviderAccountServiceClient {
|
||||
return pb.NewACMEProviderAccountServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserRPC() pb.UserServiceClient {
|
||||
return pb.NewUserServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccessKeyRPC() pb.UserAccessKeyServiceClient {
|
||||
return pb.NewUserAccessKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserIdentityRPC() pb.UserIdentityServiceClient {
|
||||
return pb.NewUserIdentityServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LoginRPC() pb.LoginServiceClient {
|
||||
return pb.NewLoginServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LoginSessionRPC() pb.LoginSessionServiceClient {
|
||||
return pb.NewLoginSessionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LoginTicketRPC() pb.LoginTicketServiceClient {
|
||||
return pb.NewLoginTicketServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient {
|
||||
return pb.NewNodeTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LatestItemRPC() pb.LatestItemServiceClient {
|
||||
return pb.NewLatestItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricItemRPC() pb.MetricItemServiceClient {
|
||||
return pb.NewMetricItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricStatRPC() pb.MetricStatServiceClient {
|
||||
return pb.NewMetricStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MetricChartRPC() pb.MetricChartServiceClient {
|
||||
return pb.NewMetricChartServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeClusterMetricItemRPC() pb.NodeClusterMetricItemServiceClient {
|
||||
return pb.NewNodeClusterMetricItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerStatBoardRPC() pb.ServerStatBoardServiceClient {
|
||||
return pb.NewServerStatBoardServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerDomainHourlyStatRPC() pb.ServerDomainHourlyStatServiceClient {
|
||||
return pb.NewServerDomainHourlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerStatBoardChartRPC() pb.ServerStatBoardChartServiceClient {
|
||||
return pb.NewServerStatBoardChartServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) TrafficDailyStatRPC() pb.TrafficDailyStatServiceClient {
|
||||
return pb.NewTrafficDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
// Context 构造Admin上下文
|
||||
func (this *RPCClient) Context(adminId int64) context.Context {
|
||||
var ctx = context.Background()
|
||||
var m = maps.Map{
|
||||
"timestamp": time.Now().Unix(),
|
||||
"type": "admin",
|
||||
"userId": adminId,
|
||||
}
|
||||
method, err := encrypt.NewMethodInstance(teaconst.EncryptMethod, this.apiConfig.Secret, this.apiConfig.NodeId)
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
data, err := method.Encrypt(m.AsJSON())
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
var token = base64.StdEncoding.EncodeToString(data)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// APIContext 构造API上下文
|
||||
func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
|
||||
var ctx = context.Background()
|
||||
var m = maps.Map{
|
||||
"timestamp": time.Now().Unix(),
|
||||
"type": "api",
|
||||
"userId": apiNodeId,
|
||||
}
|
||||
method, err := encrypt.NewMethodInstance(teaconst.EncryptMethod, this.apiConfig.Secret, this.apiConfig.NodeId)
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
data, err := method.Encrypt(m.AsJSON())
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
var token = base64.StdEncoding.EncodeToString(data)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// UpdateConfig 修改配置
|
||||
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
|
||||
this.apiConfig = config
|
||||
|
||||
this.locker.Lock()
|
||||
err := this.init()
|
||||
this.locker.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化
|
||||
func (this *RPCClient) init() error {
|
||||
// 当前的IP地址
|
||||
var localIPAddrs = this.localIPAddrs()
|
||||
|
||||
// 重新连接
|
||||
var conns = []*grpc.ClientConn{}
|
||||
for _, endpoint := range this.apiConfig.RPCEndpoints {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse endpoint failed: %w", err)
|
||||
}
|
||||
|
||||
var apiHost = u.Host
|
||||
|
||||
// 如果本机,则将地址修改为回路地址
|
||||
if lists.ContainsString(localIPAddrs, u.Hostname()) {
|
||||
if strings.Contains(apiHost, "[") { // IPv6 [host]:port
|
||||
apiHost = "[::1]"
|
||||
} else {
|
||||
apiHost = "127.0.0.1"
|
||||
}
|
||||
var port = u.Port()
|
||||
if len(port) > 0 {
|
||||
apiHost += ":" + port
|
||||
}
|
||||
}
|
||||
|
||||
var conn *grpc.ClientConn
|
||||
var callOptions = grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(128<<20),
|
||||
grpc.MaxCallSendMsgSize(128<<20),
|
||||
grpc.UseCompressor(gzip.Name),
|
||||
)
|
||||
var keepaliveParams = grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: 30 * time.Second,
|
||||
})
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions, keepaliveParams)
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})), callOptions, keepaliveParams)
|
||||
} else {
|
||||
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conns = append(conns, conn)
|
||||
}
|
||||
if len(conns) == 0 {
|
||||
return errors.New("[RPC]no available endpoints")
|
||||
}
|
||||
|
||||
// 这里不需要加锁,因为会和pickConn冲突
|
||||
this.conns = conns
|
||||
return nil
|
||||
}
|
||||
|
||||
// 随机选择一个连接
|
||||
func (this *RPCClient) pickConn() *grpc.ClientConn {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
// 检查连接状态
|
||||
var countConns = len(this.conns)
|
||||
if countConns > 0 {
|
||||
if countConns == 1 {
|
||||
return this.conns[0]
|
||||
}
|
||||
for _, state := range []connectivity.State{
|
||||
connectivity.Ready,
|
||||
connectivity.Idle,
|
||||
connectivity.Connecting,
|
||||
connectivity.TransientFailure,
|
||||
} {
|
||||
var availableConns = []*grpc.ClientConn{}
|
||||
for _, conn := range this.conns {
|
||||
if conn.GetState() == state {
|
||||
availableConns = append(availableConns, conn)
|
||||
}
|
||||
}
|
||||
if len(availableConns) > 0 {
|
||||
return this.randConn(availableConns)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.randConn(this.conns)
|
||||
}
|
||||
|
||||
// Close 关闭
|
||||
func (this *RPCClient) Close() error {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
var lastErr error
|
||||
for _, conn := range this.conns {
|
||||
var err = conn.Close()
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (this *RPCClient) localIPAddrs() []string {
|
||||
localInterfaceAddrs, err := net.InterfaceAddrs()
|
||||
var localIPAddrs = []string{}
|
||||
if err == nil {
|
||||
for _, addr := range localInterfaceAddrs {
|
||||
var addrString = addr.String()
|
||||
var index = strings.Index(addrString, "/")
|
||||
if index > 0 {
|
||||
localIPAddrs = append(localIPAddrs, addrString[:index])
|
||||
}
|
||||
}
|
||||
}
|
||||
return localIPAddrs
|
||||
}
|
||||
|
||||
func (this *RPCClient) randConn(conns []*grpc.ClientConn) *grpc.ClientConn {
|
||||
var l = len(conns)
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
if l == 1 {
|
||||
return conns[0]
|
||||
}
|
||||
return conns[rands.Int(0, l-1)]
|
||||
}
|
||||
250
EdgeAdmin/internal/rpc/rpc_client_plus.go
Normal file
250
EdgeAdmin/internal/rpc/rpc_client_plus.go
Normal file
@@ -0,0 +1,250 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package rpc
|
||||
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
|
||||
func (this *RPCClient) ScriptRPC() pb.ScriptServiceClient {
|
||||
return pb.NewScriptServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserScriptRPC() pb.UserScriptServiceClient {
|
||||
return pb.NewUserScriptServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserOrderRPC() pb.UserOrderServiceClient {
|
||||
return pb.NewUserOrderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) OrderMethodRPC() pb.OrderMethodServiceClient {
|
||||
return pb.NewOrderMethodServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserTicketRPC() pb.UserTicketServiceClient {
|
||||
return pb.NewUserTicketServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserTicketLogRPC() pb.UserTicketLogServiceClient {
|
||||
return pb.NewUserTicketLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserTicketCategoryRPC() pb.UserTicketCategoryServiceClient {
|
||||
return pb.NewUserTicketCategoryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSDomainRPC() pb.NSDomainServiceClient {
|
||||
return pb.NewNSDomainServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSDomainGroupRPC() pb.NSDomainGroupServiceClient {
|
||||
return pb.NewNSDomainGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRecordRPC() pb.NSRecordServiceClient {
|
||||
return pb.NewNSRecordServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSKeyRPC() pb.NSKeyServiceClient {
|
||||
return pb.NewNSKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRouteRPC() pb.NSRouteServiceClient {
|
||||
return pb.NewNSRouteServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRouteCategoryRPC() pb.NSRouteCategoryServiceClient {
|
||||
return pb.NewNSRouteCategoryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSAccessLogRPC() pb.NSAccessLogServiceClient {
|
||||
return pb.NewNSAccessLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRPC() pb.NSServiceClient {
|
||||
return pb.NewNSServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSQuestionOptionRPC() pb.NSQuestionOptionServiceClient {
|
||||
return pb.NewNSQuestionOptionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSClusterRPC() pb.NSClusterServiceClient {
|
||||
return pb.NewNSClusterServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSNodeRPC() pb.NSNodeServiceClient {
|
||||
return pb.NewNSNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSRecordHourlyStatRPC() pb.NSRecordHourlyStatServiceClient {
|
||||
return pb.NewNSRecordHourlyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSPlanRPC() pb.NSPlanServiceClient {
|
||||
return pb.NewNSPlanServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NSUserPlanRPC() pb.NSUserPlanServiceClient {
|
||||
return pb.NewNSUserPlanServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ReportNodeRPC() pb.ReportNodeServiceClient {
|
||||
return pb.NewReportNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ReportNodeGroupRPC() pb.ReportNodeGroupServiceClient {
|
||||
return pb.NewReportNodeGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ReportResultRPC() pb.ReportResultServiceClient {
|
||||
return pb.NewReportResultServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserNodeRPC() pb.UserNodeServiceClient {
|
||||
return pb.NewUserNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserBillRPC() pb.UserBillServiceClient {
|
||||
return pb.NewUserBillServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ServerBillRPC() pb.ServerBillServiceClient {
|
||||
return pb.NewServerBillServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserTrafficBillRPC() pb.UserTrafficBillServiceClient {
|
||||
return pb.NewUserTrafficBillServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccountRPC() pb.UserAccountServiceClient {
|
||||
return pb.NewUserAccountServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccountLogRPC() pb.UserAccountLogServiceClient {
|
||||
return pb.NewUserAccountLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserAccountDailyStatRPC() pb.UserAccountDailyStatServiceClient {
|
||||
return pb.NewUserAccountDailyStatServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodePriceItemRPC() pb.NodePriceItemServiceClient {
|
||||
return pb.NewNodePriceItemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) PriceRPC() pb.PriceServiceClient {
|
||||
return pb.NewPriceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) PlanRPC() pb.PlanServiceClient {
|
||||
return pb.NewPlanServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserPlanRPC() pb.UserPlanServiceClient {
|
||||
return pb.NewUserPlanServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) TrafficPackageRPC() pb.TrafficPackageServiceClient {
|
||||
return pb.NewTrafficPackageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) TrafficPackagePeriodRPC() pb.TrafficPackagePeriodServiceClient {
|
||||
return pb.NewTrafficPackagePeriodServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) TrafficPackagePriceRPC() pb.TrafficPackagePriceServiceClient {
|
||||
return pb.NewTrafficPackagePriceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserTrafficPackageRPC() pb.UserTrafficPackageServiceClient {
|
||||
return pb.NewUserTrafficPackageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FormalClientSystemRPC() pb.FormalClientSystemServiceClient {
|
||||
return pb.NewFormalClientSystemServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) FormalClientBrowserRPC() pb.FormalClientBrowserServiceClient {
|
||||
return pb.NewFormalClientBrowserServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ClientAgentRPC() pb.ClientAgentServiceClient {
|
||||
return pb.NewClientAgentServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ClientAgentIPRPC() pb.ClientAgentIPServiceClient {
|
||||
return pb.NewClientAgentIPServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ADPackageRPC() pb.ADPackageServiceClient {
|
||||
return pb.NewADPackageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ADPackageInstanceRPC() pb.ADPackageInstanceServiceClient {
|
||||
return pb.NewADPackageInstanceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ADPackagePeriodRPC() pb.ADPackagePeriodServiceClient {
|
||||
return pb.NewADPackagePeriodServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ADPackagePriceRPC() pb.ADPackagePriceServiceClient {
|
||||
return pb.NewADPackagePriceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ADNetworkRPC() pb.ADNetworkServiceClient {
|
||||
return pb.NewADNetworkServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) UserADInstanceRPC() pb.UserADInstanceServiceClient {
|
||||
return pb.NewUserADInstanceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeActionRPC() pb.NodeActionServiceClient {
|
||||
return pb.NewNodeActionServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageRecipientGroupRPC() pb.MessageRecipientGroupServiceClient {
|
||||
return pb.NewMessageRecipientGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageRecipientRPC() pb.MessageRecipientServiceClient {
|
||||
return pb.NewMessageRecipientServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageMediaRPC() pb.MessageMediaServiceClient {
|
||||
return pb.NewMessageMediaServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageMediaInstanceRPC() pb.MessageMediaInstanceServiceClient {
|
||||
return pb.NewMessageMediaInstanceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageTaskRPC() pb.MessageTaskServiceClient {
|
||||
return pb.NewMessageTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageTaskLogRPC() pb.MessageTaskLogServiceClient {
|
||||
return pb.NewMessageTaskLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageReceiverRPC() pb.MessageReceiverServiceClient {
|
||||
return pb.NewMessageReceiverServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) AuthorityKeyRPC() pb.AuthorityKeyServiceClient {
|
||||
return pb.NewAuthorityKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) SMSSenderRPC() pb.SMSSenderServiceClient {
|
||||
return pb.NewSMSSenderServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) PostCategoryRPC() pb.PostCategoryServiceClient {
|
||||
return pb.NewPostCategoryServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) PostRPC() pb.PostServiceClient {
|
||||
return pb.NewPostServiceClient(this.pickConn())
|
||||
}
|
||||
134
EdgeAdmin/internal/rpc/rpc_client_test.go
Normal file
134
EdgeAdmin/internal/rpc/rpc_client_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRPCClient_NodeRPC(t *testing.T) {
|
||||
before := time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp, err := rpc.AdminRPC().LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
|
||||
Username: "admin",
|
||||
Password: stringutil.Md5("123456"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(resp)
|
||||
}
|
||||
|
||||
func TestRPC_Dial_HTTP(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8003"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := client.NodeRPC().FindEnabledNode(client.Context(1), &pb.FindEnabledNodeRequest{NodeId: 4})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(resp.Node)
|
||||
}
|
||||
|
||||
func TestRPC_Dial_HTTP_2(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8003"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := client.NodeRPC().FindEnabledNode(client.Context(1), &pb.FindEnabledNodeRequest{NodeId: 4})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(resp.Node)
|
||||
}
|
||||
|
||||
func TestRPC_Dial_HTTPS(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8004"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := client.NodeRPC().FindEnabledNode(client.Context(1), &pb.FindEnabledNodeRequest{NodeId: 4})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(resp.Node)
|
||||
}
|
||||
|
||||
func BenchmarkNewRPCClient(b *testing.B) {
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := rpc.AdminRPC().LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
|
||||
Username: "admin",
|
||||
Password: stringutil.Md5("123456"),
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewRPCClient_2(b *testing.B) {
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
rpc, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
var conn = rpc.AdminRPC()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := conn.LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
|
||||
Username: "admin",
|
||||
Password: stringutil.Md5("123456"),
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
69
EdgeAdmin/internal/rpc/rpc_utils.go
Normal file
69
EdgeAdmin/internal/rpc/rpc_utils.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var sharedRPC *RPCClient = nil
|
||||
var locker = &sync.Mutex{}
|
||||
|
||||
func SharedRPC() (*RPCClient, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
if sharedRPC != nil {
|
||||
return sharedRPC, nil
|
||||
}
|
||||
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := NewRPCClient(config, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sharedRPC = client
|
||||
return sharedRPC, nil
|
||||
}
|
||||
|
||||
// IsConnError 是否为连接错误
|
||||
func IsConnError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查是否为连接错误
|
||||
statusErr, ok := status.FromError(err)
|
||||
if ok {
|
||||
var errorCode = statusErr.Code()
|
||||
return errorCode == codes.Unavailable || errorCode == codes.Canceled
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "code = Canceled") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnimplementedError 检查是否为未实现错误
|
||||
func IsUnimplementedError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
statusErr, ok := status.FromError(err)
|
||||
if ok {
|
||||
if statusErr.Code() == codes.Unimplemented {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
23
EdgeAdmin/internal/setup/utils.go
Normal file
23
EdgeAdmin/internal/setup/utils.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
)
|
||||
|
||||
var isConfigured bool
|
||||
|
||||
// IsConfigured 判断系统是否已经配置过
|
||||
func IsConfigured() bool {
|
||||
if isConfigured {
|
||||
return true
|
||||
}
|
||||
|
||||
_, err := configs.LoadAPIConfig()
|
||||
isConfigured = err == nil
|
||||
return isConfigured
|
||||
}
|
||||
|
||||
// IsNewInstalled IsNew 检查是否新安装
|
||||
func IsNewInstalled() bool {
|
||||
return configs.IsNewInstalled()
|
||||
}
|
||||
177
EdgeAdmin/internal/tasks/task_authority_plus.go
Normal file
177
EdgeAdmin/internal/tasks/task_authority_plus.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/plus"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
plusutils "github.com/TeaOSLab/EdgePlus/pkg/utils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
var task = NewAuthorityTask()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var authorityTaskNotifyChange = make(chan bool, 1)
|
||||
|
||||
func NotifyAuthorityTask() {
|
||||
select {
|
||||
case authorityTaskNotifyChange <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
type AuthorityTask struct {
|
||||
oldIsPlus bool
|
||||
}
|
||||
|
||||
func NewAuthorityTask() *AuthorityTask {
|
||||
return &AuthorityTask{}
|
||||
}
|
||||
|
||||
func (this *AuthorityTask) Start() {
|
||||
// 从缓存中读取
|
||||
var config = configs.ReadPlusConfig()
|
||||
if config != nil {
|
||||
teaconst.IsPlus = config.IsPlus
|
||||
this.notifyChange()
|
||||
plus.UpdateComponents(config.Edition, config.Components)
|
||||
}
|
||||
|
||||
// 开始计时器
|
||||
var ticker = time.NewTicker(10 * time.Minute)
|
||||
if Tea.IsTesting() {
|
||||
// 快速测试
|
||||
ticker = time.NewTicker(1 * time.Minute)
|
||||
}
|
||||
|
||||
// 初始化的时候先获取一次
|
||||
var timeout = time.NewTimer(3 * time.Second)
|
||||
<-timeout.C
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][AuthorityTask]" + err.Error())
|
||||
}
|
||||
|
||||
// 定时获取
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-authorityTaskNotifyChange:
|
||||
}
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][AuthorityTask]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *AuthorityTask) Loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() {
|
||||
return nil
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := rpcClient.AuthorityKeyRPC().ReadAuthorityKey(rpcClient.Context(0), &pb.ReadAuthorityKeyRequest{})
|
||||
if err != nil {
|
||||
teaconst.IsPlus = false
|
||||
|
||||
if !rpc.IsConnError(err) {
|
||||
this.notifyChange()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
defer this.notifyChange()
|
||||
|
||||
if resp.AuthorityKey == nil {
|
||||
teaconst.IsPlus = false
|
||||
return nil
|
||||
}
|
||||
|
||||
plus.ErrString = ""
|
||||
var plusEdition = ""
|
||||
if resp.AuthorityKey != nil && len(resp.AuthorityKey.Value) > 0 && resp.AuthorityKey.DayTo >= timeutil.Format("Y-m-d") {
|
||||
nativeKey, err := plusutils.DecodeKey([]byte(resp.AuthorityKey.Value))
|
||||
if err != nil {
|
||||
teaconst.IsPlus = false
|
||||
return nil
|
||||
}
|
||||
var isOk = true
|
||||
|
||||
if len(nativeKey.RequestCode) > 0 {
|
||||
isOk, _ = plusutils.ValidateRequestCode(nativeKey.RequestCode)
|
||||
} else if len(nativeKey.MacAddresses) > 0 {
|
||||
var macAddresses = resp.AuthorityKey.MacAddresses
|
||||
for _, addr := range macAddresses {
|
||||
if !plus.ValidateMac(addr) {
|
||||
isOk = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isOk {
|
||||
plusEdition = resp.AuthorityKey.Edition
|
||||
teaconst.IsPlus = true
|
||||
plus.UpdateComponents(resp.AuthorityKey.Edition, resp.AuthorityKey.Components)
|
||||
} else {
|
||||
plus.ErrString = "当前管理平台所在服务器环境与商业版认证分发时发生了变化,因此无法使用商业版功能。如果你正在迁移管理平台,请联系开发者重新申请新的注册码。"
|
||||
teaconst.IsPlus = false
|
||||
}
|
||||
} else {
|
||||
teaconst.IsPlus = false
|
||||
}
|
||||
|
||||
_ = configs.WritePlusConfig(&configs.PlusConfig{
|
||||
Edition: plusEdition,
|
||||
IsPlus: teaconst.IsPlus,
|
||||
Components: resp.AuthorityKey.Components,
|
||||
DayTo: resp.AuthorityKey.DayTo,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AuthorityTask) notifyChange() {
|
||||
if this.oldIsPlus == teaconst.IsPlus {
|
||||
return
|
||||
}
|
||||
this.oldIsPlus = teaconst.IsPlus
|
||||
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second) // 等待默认的加载程序完成
|
||||
|
||||
var err error
|
||||
if teaconst.IsPlus {
|
||||
err = iplibrary.InitPlus()
|
||||
} else {
|
||||
err = iplibrary.InitDefault()
|
||||
}
|
||||
if err != nil {
|
||||
logs.Println("[NODE]initialize ip library failed: " + err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
133
EdgeAdmin/internal/tasks/task_check_updates.go
Normal file
133
EdgeAdmin/internal/tasks/task_check_updates.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
var task = NewCheckUpdatesTask()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type CheckUpdatesTask struct {
|
||||
ticker *time.Ticker
|
||||
}
|
||||
|
||||
func NewCheckUpdatesTask() *CheckUpdatesTask {
|
||||
return &CheckUpdatesTask{}
|
||||
}
|
||||
|
||||
func (this *CheckUpdatesTask) Start() {
|
||||
this.ticker = time.NewTicker(12 * time.Hour)
|
||||
for range this.ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][CHECK_UPDATES_TASK]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CheckUpdatesTask) Loop() error {
|
||||
// 检查是否开启
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueResp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeCheckUpdates})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var valueJSON = valueResp.ValueJSON
|
||||
var config = systemconfigs.NewCheckUpdatesConfig()
|
||||
if len(valueJSON) > 0 {
|
||||
err = json.Unmarshal(valueJSON, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode config failed: %w", err)
|
||||
}
|
||||
if !config.AutoCheck {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 开始检查
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// 目前支持Linux
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var apiURL = teaconst.UpdatesURL
|
||||
apiURL = strings.ReplaceAll(apiURL, "${os}", runtime.GOOS)
|
||||
apiURL = strings.ReplaceAll(apiURL, "${arch}", runtime.GOARCH)
|
||||
apiURL = strings.ReplaceAll(apiURL, "${version}", teaconst.Version)
|
||||
resp, err := http.Get(apiURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read api failed: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read api failed: %w", err)
|
||||
}
|
||||
|
||||
var apiResponse = &Response{}
|
||||
err = json.Unmarshal(data, apiResponse)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode version data failed: %w", err)
|
||||
}
|
||||
|
||||
if apiResponse.Code != 200 {
|
||||
return errors.New("invalid response: " + apiResponse.Message)
|
||||
}
|
||||
|
||||
var m = maps.NewMap(apiResponse.Data)
|
||||
var dlHost = m.GetString("host")
|
||||
var versions = m.GetSlice("versions")
|
||||
if len(versions) > 0 {
|
||||
for _, version := range versions {
|
||||
var vMap = maps.NewMap(version)
|
||||
if vMap.GetString("code") == "admin" {
|
||||
var latestVersion = vMap.GetString("version")
|
||||
if stringutil.VersionCompare(teaconst.Version, latestVersion) < 0 && (len(config.IgnoredVersion) == 0 || stringutil.VersionCompare(latestVersion, config.IgnoredVersion) > 0) {
|
||||
teaconst.NewVersionCode = latestVersion
|
||||
teaconst.NewVersionDownloadURL = dlHost + vMap.GetString("url")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
166
EdgeAdmin/internal/tasks/task_sync_api_nodes.go
Normal file
166
EdgeAdmin/internal/tasks/task_sync_api_nodes.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
task := NewSyncAPINodesTask()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// SyncAPINodesTask API节点同步任务
|
||||
type SyncAPINodesTask struct {
|
||||
}
|
||||
|
||||
func NewSyncAPINodesTask() *SyncAPINodesTask {
|
||||
return &SyncAPINodesTask{}
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) Start() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
if Tea.IsTesting() {
|
||||
// 快速测试
|
||||
ticker = time.NewTicker(1 * time.Minute)
|
||||
}
|
||||
for range ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][SYNC_API_NODES]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) Loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() || teaconst.IsRecoverMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 是否禁止自动升级
|
||||
if config.RPCDisableUpdate {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取所有可用的节点
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := rpcClient.APINodeRPC().FindAllEnabledAPINodes(rpcClient.Context(0), &pb.FindAllEnabledAPINodesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var newEndpoints = []string{}
|
||||
for _, node := range resp.ApiNodes {
|
||||
if !node.IsOn {
|
||||
continue
|
||||
}
|
||||
newEndpoints = append(newEndpoints, node.AccessAddrs...)
|
||||
}
|
||||
|
||||
// 和现有的对比
|
||||
if this.isSame(newEndpoints, config.RPCEndpoints) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 测试是否有API节点可用
|
||||
hasOk := this.testEndpoints(newEndpoints)
|
||||
if !hasOk {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 修改RPC对象配置
|
||||
config.RPCEndpoints = newEndpoints
|
||||
err = rpcClient.UpdateConfig(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = config.WriteFile(Tea.ConfigFile(configs.ConfigFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) isSame(endpoints1 []string, endpoints2 []string) bool {
|
||||
sort.Strings(endpoints1)
|
||||
sort.Strings(endpoints2)
|
||||
return strings.Join(endpoints1, "&") == strings.Join(endpoints2, "&")
|
||||
}
|
||||
|
||||
func (this *SyncAPINodesTask) testEndpoints(endpoints []string) bool {
|
||||
if len(endpoints) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var wg = sync.WaitGroup{}
|
||||
wg.Add(len(endpoints))
|
||||
|
||||
var ok = false
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
go func(endpoint string) {
|
||||
defer wg.Done()
|
||||
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer func() {
|
||||
cancel()
|
||||
}()
|
||||
var conn *grpc.ClientConn
|
||||
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})), grpc.WithBlock())
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = conn.Close()
|
||||
|
||||
ok = true
|
||||
}(endpoint)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return ok
|
||||
}
|
||||
15
EdgeAdmin/internal/tasks/task_sync_api_nodes_test.go
Normal file
15
EdgeAdmin/internal/tasks/task_sync_api_nodes_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSyncAPINodesTask_Loop(t *testing.T) {
|
||||
task := NewSyncAPINodesTask()
|
||||
err := task.Loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
84
EdgeAdmin/internal/tasks/task_sync_cluster.go
Normal file
84
EdgeAdmin/internal/tasks/task_sync_cluster.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/nodeutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/messageconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
events.On(events.EventStart, func() {
|
||||
task := NewSyncClusterTask()
|
||||
goman.New(func() {
|
||||
task.Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// SyncClusterTask 自动同步集群任务
|
||||
type SyncClusterTask struct {
|
||||
}
|
||||
|
||||
func NewSyncClusterTask() *SyncClusterTask {
|
||||
return &SyncClusterTask{}
|
||||
}
|
||||
|
||||
func (this *SyncClusterTask) Start() {
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
for range ticker.C {
|
||||
err := this.loop()
|
||||
if err != nil {
|
||||
logs.Println("[TASK][SYNC_CLUSTER]" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *SyncClusterTask) loop() error {
|
||||
// 如果还没有安装直接返回
|
||||
if !setup.IsConfigured() || teaconst.IsRecoverMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := rpcClient.Context(0)
|
||||
|
||||
tasksResp, err := rpcClient.NodeTaskRPC().FindNotifyingNodeTasks(ctx, &pb.FindNotifyingNodeTasksRequest{Size: 500})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodeIds := []int64{}
|
||||
taskIds := []int64{}
|
||||
for _, task := range tasksResp.NodeTasks {
|
||||
if !lists.ContainsInt64(nodeIds, task.Node.Id) {
|
||||
nodeIds = append(nodeIds, task.Node.Id)
|
||||
}
|
||||
taskIds = append(taskIds, task.Id)
|
||||
}
|
||||
if len(nodeIds) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err = nodeutils.SendMessageToNodeIds(ctx, nodeIds, messageconfigs.MessageCodeNewNodeTask, &messageconfigs.NewNodeTaskMessage{}, 3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 设置已通知
|
||||
_, err = rpcClient.NodeTaskRPC().UpdateNodeTasksNotified(ctx, &pb.UpdateNodeTasksNotifiedRequest{NodeTaskIds: taskIds})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
12
EdgeAdmin/internal/tasks/task_sync_cluster_test.go
Normal file
12
EdgeAdmin/internal/tasks/task_sync_cluster_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package tasks
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSyncClusterTask_loop(t *testing.T) {
|
||||
task := NewSyncClusterTask()
|
||||
err := task.loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user