Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23751440c6 | ||
|
|
989c9f9740 | ||
|
|
71508f8724 | ||
|
|
eb6dd2855e | ||
|
|
64da3c7438 |
+118
-73
@@ -4,54 +4,138 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
build:
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
goos: [linux, windows]
|
||||||
|
goarch: [amd64]
|
||||||
|
include:
|
||||||
|
- goos: linux
|
||||||
|
goarch: s390x
|
||||||
|
- goos: linux
|
||||||
|
goarch: arm64
|
||||||
|
|
||||||
|
name: Build artifacts
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: goreleaser/goreleaser-cross:v1.21
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
outputs:
|
||||||
|
tag_name: ${{ steps.extract_branch.outputs.tag }}
|
||||||
steps:
|
steps:
|
||||||
|
- run: git config --global --add safe.directory /__w/nezha/nezha
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Fetch IPInfo GeoIP Database
|
- name: Fetch IPInfo GeoIP Database
|
||||||
env:
|
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
|
||||||
|
env:
|
||||||
IPINFO_TOKEN: ${{ secrets.IPINFO_TOKEN }}
|
IPINFO_TOKEN: ${{ secrets.IPINFO_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
rm pkg/geoip/geoip.db
|
rm pkg/geoip/geoip.db
|
||||||
wget -O pkg/geoip/geoip.db https://ipinfo.io/data/free/country.mmdb?token=${IPINFO_TOKEN}
|
wget -qO pkg/geoip/geoip.db https://ipinfo.io/data/free/country.mmdb?token=${IPINFO_TOKEN}
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.21.x"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
distribution: goreleaser
|
||||||
|
version: '~> v2'
|
||||||
|
args: build --single-target --clean --skip=validate
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dashboard-${{ env.GOOS }}-${{ env.GOARCH }}
|
||||||
|
path: |
|
||||||
|
./dist/*/*
|
||||||
|
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
|
||||||
|
needs: build
|
||||||
|
name: Release
|
||||||
|
steps:
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ./assets
|
||||||
|
|
||||||
|
- name: Archive and compress binaries
|
||||||
|
run: |
|
||||||
|
for file in assets/*/*/*; do
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
chmod +x "$file"
|
||||||
|
zip -jr "$file.zip" "$file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
artifacts: "assets/*/*/*.zip"
|
||||||
|
generateReleaseNotes: true
|
||||||
|
|
||||||
|
- name: Purge jsdelivr cache
|
||||||
|
run: |
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/install.sh
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/nezha-agent.service
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/docker-compose.yaml
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/config.yaml
|
||||||
|
LOWER_USERNAME=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/install.sh
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/nezha-agent.service
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/docker-compose.yaml
|
||||||
|
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/config.yaml
|
||||||
|
|
||||||
|
- name: Trigger sync
|
||||||
|
env:
|
||||||
|
GH_REPO: ${{ github.repository }}
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
GH_DEBUG: api
|
||||||
|
run: |
|
||||||
|
gh workflow run sync-release.yml
|
||||||
|
|
||||||
|
release-docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
|
||||||
|
needs: build
|
||||||
|
name: Release Docker images
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ./assets
|
||||||
|
|
||||||
|
- name: Fix permissions
|
||||||
|
run: |
|
||||||
|
chmod -R +x ./assets/*
|
||||||
|
mkdir dist
|
||||||
|
mv ./assets/*/*/* ./dist
|
||||||
|
|
||||||
- name: Extract branch name
|
- name: Extract branch name
|
||||||
run: |
|
run: |
|
||||||
export TAG_NAME=$(echo ${GITHUB_REF#refs/tags/})
|
export TAG_NAME=$(echo ${GITHUB_REF#refs/tags/})
|
||||||
echo "tag=$TAG_NAME" >> $GITHUB_OUTPUT
|
echo "tag=$TAG_NAME" >> $GITHUB_OUTPUT
|
||||||
id: extract_branch
|
id: extract_branch
|
||||||
|
|
||||||
- name: xgo build
|
- name: Log into GHCR
|
||||||
uses: crazy-max/ghaction-xgo@v2
|
|
||||||
with:
|
|
||||||
xgo_version: latest
|
|
||||||
go_version: 1.21.x
|
|
||||||
dest: dist
|
|
||||||
pkg: cmd/dashboard
|
|
||||||
prefix: dashboard
|
|
||||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64,windows/amd64,windows/386,windows/arm64 # linux/386,
|
|
||||||
v: true
|
|
||||||
x: false
|
|
||||||
race: false
|
|
||||||
tags: timetzdata
|
|
||||||
ldflags: -s -w --extldflags '-static -fpic' -X github.com/naiba/nezha/service/singleton.Version=${{ steps.extract_branch.outputs.tag }}
|
|
||||||
buildmode: default
|
|
||||||
|
|
||||||
- name: fix dist
|
|
||||||
run: |
|
|
||||||
ls -al dist/
|
|
||||||
mv dist/dashboard-linux-arm-7 dist/dashboard-linux-arm
|
|
||||||
|
|
||||||
- name: Log in to the GHCR
|
|
||||||
uses: docker/login-action@master
|
uses: docker/login-action@master
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ github.token }}
|
||||||
|
|
||||||
- name: Log in to the AliyunCS
|
- name: Log in to the AliyunCS
|
||||||
uses: docker/login-action@master
|
uses: docker/login-action@master
|
||||||
@@ -68,13 +152,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up image name
|
- name: Set up image name
|
||||||
run: |
|
run: |
|
||||||
GHRC_IMAGE_NAME=$(echo "ghcr.io/${{ github.repository_owner }}/nezha-dashboard" | tr '[:upper:]' '[:lower:]')
|
GHCR_IMAGE_NAME=$(echo "ghcr.io/${{ github.repository_owner }}/nezha-dashboard" | tr '[:upper:]' '[:lower:]')
|
||||||
if [ ${{ github.repository_owner }} = "naiba" ]
|
if [ ${{ github.repository_owner }} = "naiba" ]
|
||||||
then ALI_IMAGE_NAME=$(echo "registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard")
|
then ALI_IMAGE_NAME=$(echo "registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard")
|
||||||
else ALI_IMAGE_NAME=$(echo "registry.cn-shanghai.aliyuncs.com/${{ github.repository_owner }}/nezha-dashboard" | tr '[:upper:]' '[:lower:]')
|
else ALI_IMAGE_NAME=$(echo "registry.cn-shanghai.aliyuncs.com/${{ github.repository_owner }}/nezha-dashboard" | tr '[:upper:]' '[:lower:]')
|
||||||
fi
|
fi
|
||||||
echo "::set-output name=GHRC_IMAGE_NAME::$GHRC_IMAGE_NAME"
|
echo "GHCR_IMAGE_NAME=$GHCR_IMAGE_NAME" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=ALI_IMAGE_NAME::$ALI_IMAGE_NAME"
|
echo "ALI_IMAGE_NAME=$ALI_IMAGE_NAME" >> $GITHUB_OUTPUT
|
||||||
id: image-name
|
id: image-name
|
||||||
|
|
||||||
- name: Build dasbboard image And Push
|
- name: Build dasbboard image And Push
|
||||||
@@ -82,49 +166,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm,linux/s390x,linux/riscv64 # linux/386,
|
platforms: linux/amd64,linux/arm64,linux/s390x
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ steps.image-name.outputs.GHRC_IMAGE_NAME }}:latest
|
${{ steps.image-name.outputs.GHCR_IMAGE_NAME }}:latest
|
||||||
${{ steps.image-name.outputs.GHRC_IMAGE_NAME }}:${{ steps.extract_branch.outputs.tag }}
|
${{ steps.image-name.outputs.GHCR_IMAGE_NAME }}:${{ steps.extract_branch.outputs.tag }}
|
||||||
${{ steps.image-name.outputs.ALI_IMAGE_NAME }}:latest
|
${{ steps.image-name.outputs.ALI_IMAGE_NAME }}:latest
|
||||||
${{ steps.image-name.outputs.ALI_IMAGE_NAME }}:${{ steps.extract_branch.outputs.tag }}
|
${{ steps.image-name.outputs.ALI_IMAGE_NAME }}:${{ steps.extract_branch.outputs.tag }}
|
||||||
|
|
||||||
- name: Compress dist files
|
|
||||||
run: |
|
|
||||||
for file in dist/*; do
|
|
||||||
if [ -f "$file" ]; then
|
|
||||||
zip -r "$file.zip" "$file"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
with:
|
|
||||||
artifacts: "dist/*.zip"
|
|
||||||
generateReleaseNotes: true
|
|
||||||
|
|
||||||
- name: Purge jsdelivr cache
|
|
||||||
run: |
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/install.sh
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/nezha-agent.service
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/docker-compose.yaml
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/${{ github.repository_owner }}/nezha@master/script/config.yaml
|
|
||||||
LOWER_USERNAME=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/install.sh
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/nezha-agent.service
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/docker-compose.yaml
|
|
||||||
curl -s https://purge.jsdelivr.net/gh/$LOWER_USERNAME/nezha@master/script/config.yaml
|
|
||||||
|
|
||||||
- name: Trigger sync
|
|
||||||
if: ${{ env.SYNCED == 0 }}
|
|
||||||
uses: actions/github-script@v6
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
script: |
|
|
||||||
await github.rest.actions.createWorkflowDispatch({
|
|
||||||
owner: 'naiba',
|
|
||||||
repo: 'nezha',
|
|
||||||
workflow_id: 'sync-release.yml',
|
|
||||||
ref: 'master'
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
name: Run Tests on PR
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
jobs:
|
|
||||||
tests:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
GO111MODULE: on
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: "^1.21.3"
|
|
||||||
- name: Unit test
|
|
||||||
run: |
|
|
||||||
go test -v ./...
|
|
||||||
- name: Run Gosec Security Scanner
|
|
||||||
uses: securego/gosec@master
|
|
||||||
with:
|
|
||||||
args: --exclude=G104,G402 ./...
|
|
||||||
- name: xgo build
|
|
||||||
uses: crazy-max/ghaction-xgo@v2
|
|
||||||
with:
|
|
||||||
xgo_version: latest
|
|
||||||
go_version: 1.21.x
|
|
||||||
dest: dist
|
|
||||||
pkg: cmd/dashboard
|
|
||||||
prefix: dashboard
|
|
||||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64,windows/amd64,windows/386,windows/arm64 # linux/386,
|
|
||||||
v: true
|
|
||||||
x: false
|
|
||||||
race: false
|
|
||||||
tags: timetzdata
|
|
||||||
ldflags: -s -w --extldflags '-static -fpic' -X github.com/naiba/nezha/service/singleton.Version=test
|
|
||||||
buildmode: default
|
|
||||||
- name: fix dist
|
|
||||||
run: |
|
|
||||||
ls -al dist/
|
|
||||||
mv dist/dashboard-linux-arm-7 dist/dashboard-linux-arm
|
|
||||||
|
|
||||||
+18
-22
@@ -10,40 +10,36 @@ on:
|
|||||||
- "go.sum"
|
- "go.sum"
|
||||||
- "resource/**"
|
- "resource/**"
|
||||||
- ".github/workflows/test.yml"
|
- ".github/workflows/test.yml"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu, windows, macos]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}-latest
|
||||||
env:
|
env:
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v4
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "^1.21.3"
|
go-version: "1.21.x"
|
||||||
|
|
||||||
- name: Unit test
|
- name: Unit test
|
||||||
run: |
|
run: |
|
||||||
go test -v ./...
|
go test -v ./...
|
||||||
|
|
||||||
|
- name: Build test
|
||||||
|
run: go build -v ./cmd/dashboard
|
||||||
|
|
||||||
- name: Run Gosec Security Scanner
|
- name: Run Gosec Security Scanner
|
||||||
|
if: runner.os == 'Linux'
|
||||||
uses: securego/gosec@master
|
uses: securego/gosec@master
|
||||||
with:
|
with:
|
||||||
args: --exclude=G104,G402 ./...
|
args: --exclude=G104,G402 ./...
|
||||||
- name: xgo build
|
|
||||||
uses: crazy-max/ghaction-xgo@v2
|
|
||||||
with:
|
|
||||||
xgo_version: latest
|
|
||||||
go_version: 1.21.x
|
|
||||||
dest: dist
|
|
||||||
pkg: cmd/dashboard
|
|
||||||
prefix: dashboard
|
|
||||||
targets: linux/amd64,linux/arm64,linux/arm-7,linux/s390x,linux/riscv64,windows/amd64,windows/386,windows/arm64 # linux/386,
|
|
||||||
v: true
|
|
||||||
x: false
|
|
||||||
race: false
|
|
||||||
tags: timetzdata
|
|
||||||
ldflags: -s -w --extldflags '-static -fpic' -X github.com/naiba/nezha/service/singleton.Version=test
|
|
||||||
buildmode: default
|
|
||||||
- name: fix dist
|
|
||||||
run: |
|
|
||||||
ls -al dist/
|
|
||||||
mv dist/dashboard-linux-arm-7 dist/dashboard-linux-arm
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
version: 2
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- go mod tidy -v
|
||||||
|
builds:
|
||||||
|
- id: linux_arm64
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=aarch64-linux-gnu-gcc
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/naiba/nezha/service/singleton.Version={{.Version}}
|
||||||
|
- -extldflags "-static -fpic"
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- arm64
|
||||||
|
main: ./cmd/dashboard
|
||||||
|
binary: dashboard-{{ .Os }}-{{ .Arch }}
|
||||||
|
- id: linux_amd64
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=x86_64-linux-gnu-gcc
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/naiba/nezha/service/singleton.Version={{.Version}}
|
||||||
|
- -extldflags "-static -fpic"
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
main: ./cmd/dashboard
|
||||||
|
binary: dashboard-{{ .Os }}-{{ .Arch }}
|
||||||
|
- id: linux_s390x
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=s390x-linux-gnu-gcc
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/naiba/nezha/service/singleton.Version={{.Version}}
|
||||||
|
- -extldflags "-static -fpic"
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- s390x
|
||||||
|
main: ./cmd/dashboard
|
||||||
|
binary: dashboard-{{ .Os }}-{{ .Arch }}
|
||||||
|
- id: windows_amd64
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
- CC=x86_64-w64-mingw32-gcc
|
||||||
|
ldflags:
|
||||||
|
- -s -w
|
||||||
|
- -X github.com/naiba/nezha/service/singleton.Version={{.Version}}
|
||||||
|
- -extldflags "-static -fpic"
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
|
goos:
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
main: ./cmd/dashboard
|
||||||
|
binary: dashboard-{{ .Os }}-{{ .Arch }}
|
||||||
|
snapshot:
|
||||||
|
version_template: "dashboard"
|
||||||
+3
-9
@@ -1,16 +1,8 @@
|
|||||||
# latest 镜像还没有 riscv64 https://hub.docker.com/_/alpine/tags
|
FROM busybox:stable-musl
|
||||||
FROM alpine:edge
|
|
||||||
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
RUN apk update && \
|
|
||||||
apk upgrade --no-cache && \
|
|
||||||
apk add --no-cache tzdata && \
|
|
||||||
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
|
||||||
echo 'Asia/Shanghai' >/etc/timezone && \
|
|
||||||
rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
COPY ./script/entrypoint.sh /entrypoint.sh
|
COPY ./script/entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
@@ -19,4 +11,6 @@ COPY dist/dashboard-${TARGETOS}-${TARGETARCH} ./app
|
|||||||
|
|
||||||
VOLUME ["/dashboard/data"]
|
VOLUME ["/dashboard/data"]
|
||||||
EXPOSE 80 5555
|
EXPOSE 80 5555
|
||||||
|
ARG TZ=Asia/Shanghai
|
||||||
|
ENV TZ=$TZ
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
@@ -277,7 +276,7 @@ func natGateway(c *gin.Context) {
|
|||||||
rpc.NezhaHandlerSingleton.CreateStream(streamId)
|
rpc.NezhaHandlerSingleton.CreateStream(streamId)
|
||||||
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
|
defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
|
||||||
|
|
||||||
taskData, err := json.Marshal(model.TaskNAT{
|
taskData, err := utils.Json.Marshal(model.TaskNAT{
|
||||||
StreamID: streamId,
|
StreamID: streamId,
|
||||||
Host: natConfig.Host,
|
Host: natConfig.Host,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -199,7 +198,7 @@ func (oa *oauth2controller) callback(c *gin.Context) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
var cloudflareUserInfo *cloudflare.UserInfo
|
var cloudflareUserInfo *cloudflare.UserInfo
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&cloudflareUserInfo); err == nil {
|
if err := utils.Json.NewDecoder(resp.Body).Decode(&cloudflareUserInfo); err == nil {
|
||||||
user = cloudflareUserInfo.MapToNezhaUser()
|
user = cloudflareUserInfo.MapToNezhaUser()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
_ "time/tzdata"
|
||||||
|
|
||||||
"github.com/naiba/nezha/cmd/dashboard/controller"
|
"github.com/naiba/nezha/cmd/dashboard/controller"
|
||||||
"github.com/naiba/nezha/cmd/dashboard/rpc"
|
"github.com/naiba/nezha/cmd/dashboard/rpc"
|
||||||
|
|||||||
+2
-14
@@ -1,6 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -44,19 +45,6 @@ func percentage(used, total uint64) float64 {
|
|||||||
return float64(used) * 100 / float64(total)
|
return float64(used) * 100 / float64(total)
|
||||||
}
|
}
|
||||||
|
|
||||||
func maxSliceValue(slice []float64) float64 {
|
|
||||||
if len(slice) != 0 {
|
|
||||||
max := slice[0]
|
|
||||||
for _, val := range slice {
|
|
||||||
if max < val {
|
|
||||||
max = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshot 未通过规则返回 struct{}{}, 通过返回 nil
|
// Snapshot 未通过规则返回 struct{}{}, 通过返回 nil
|
||||||
func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) interface{} {
|
func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server, db *gorm.DB) interface{} {
|
||||||
// 监控全部但是排除了此服务器
|
// 监控全部但是排除了此服务器
|
||||||
@@ -145,7 +133,7 @@ func (u *Rule) Snapshot(cycleTransferStats *CycleTransferStats, server *Server,
|
|||||||
temp = append(temp, tempStat.Temperature)
|
temp = append(temp, tempStat.Temperature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
src = maxSliceValue(temp)
|
src = slices.Max(temp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+131
-83
@@ -2,169 +2,217 @@ package ddns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const baseEndpoint = "https://api.cloudflare.com/client/v4/zones"
|
||||||
|
|
||||||
type ProviderCloudflare struct {
|
type ProviderCloudflare struct {
|
||||||
Secret string
|
secret string
|
||||||
|
zoneId string
|
||||||
|
recordId string
|
||||||
|
domainConfig *DomainConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) bool {
|
type cfReq struct {
|
||||||
if domainConfig == nil {
|
Name string `json:"name"`
|
||||||
return false
|
Type string `json:"type"`
|
||||||
}
|
Content string `json:"content"`
|
||||||
|
TTL uint32 `json:"ttl"`
|
||||||
|
Proxied bool `json:"proxied"`
|
||||||
|
}
|
||||||
|
|
||||||
zoneID, err := provider.getZoneID(domainConfig.FullDomain)
|
type cfResp struct {
|
||||||
|
Result []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProviderCloudflare(s string) *ProviderCloudflare {
|
||||||
|
return &ProviderCloudflare{
|
||||||
|
secret: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ProviderCloudflare) UpdateDomain(domainConfig *DomainConfig) error {
|
||||||
|
if domainConfig == nil {
|
||||||
|
return fmt.Errorf("获取 DDNS 配置失败")
|
||||||
|
}
|
||||||
|
provider.domainConfig = domainConfig
|
||||||
|
|
||||||
|
err := provider.getZoneID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("无法获取 zone ID: %s\n", err)
|
return fmt.Errorf("无法获取 zone ID: %s", err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当IPv4和IPv6同时成功才算作成功
|
// 当IPv4和IPv6同时成功才算作成功
|
||||||
var resultV4 = true
|
if provider.domainConfig.EnableIPv4 {
|
||||||
var resultV6 = true
|
if err = provider.addDomainRecord(true); err != nil {
|
||||||
if domainConfig.EnableIPv4 {
|
return err
|
||||||
if !provider.addDomainRecord(zoneID, domainConfig, true) {
|
|
||||||
resultV4 = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if domainConfig.EnableIpv6 {
|
if provider.domainConfig.EnableIpv6 {
|
||||||
if !provider.addDomainRecord(zoneID, domainConfig, false) {
|
if err = provider.addDomainRecord(false); err != nil {
|
||||||
resultV6 = false
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultV4 && resultV6
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) addDomainRecord(zoneID string, domainConfig *DomainConfig, isIpv4 bool) bool {
|
func (provider *ProviderCloudflare) addDomainRecord(isIpv4 bool) error {
|
||||||
record, err := provider.findDNSRecord(zoneID, domainConfig.FullDomain, isIpv4)
|
err := provider.findDNSRecord(isIpv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("查找 DNS 记录时出错: %s\n", err)
|
return fmt.Errorf("查找 DNS 记录时出错: %s", err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if record == nil {
|
if provider.recordId == "" {
|
||||||
// 添加 DNS 记录
|
// 添加 DNS 记录
|
||||||
return provider.createDNSRecord(zoneID, domainConfig, isIpv4)
|
return provider.createDNSRecord(isIpv4)
|
||||||
} else {
|
} else {
|
||||||
// 更新 DNS 记录
|
// 更新 DNS 记录
|
||||||
return provider.updateDNSRecord(zoneID, record["id"].(string), domainConfig, isIpv4)
|
return provider.updateDNSRecord(isIpv4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) getZoneID(domain string) (string, error) {
|
func (provider *ProviderCloudflare) getZoneID() error {
|
||||||
_, realDomain := SplitDomain(domain)
|
_, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
||||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones?name=%s", realDomain)
|
zu, _ := url.Parse(baseEndpoint)
|
||||||
body, err := provider.sendRequest("GET", url, nil)
|
|
||||||
|
q := zu.Query()
|
||||||
|
q.Set("name", realDomain)
|
||||||
|
zu.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
body, err := provider.sendRequest("GET", zu.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var res map[string]interface{}
|
res := &cfResp{}
|
||||||
err = json.Unmarshal(body, &res)
|
err = utils.Json.Unmarshal(body, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := res["result"].([]interface{})
|
result := res.Result
|
||||||
if len(result) > 0 {
|
if len(result) > 0 {
|
||||||
zoneID := result[0].(map[string]interface{})["id"].(string)
|
provider.zoneId = result[0].ID
|
||||||
return zoneID, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf("找不到 Zone ID")
|
return fmt.Errorf("找不到 Zone ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) findDNSRecord(zoneID string, domain string, isIPv4 bool) (map[string]interface{}, error) {
|
func (provider *ProviderCloudflare) findDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType string
|
||||||
if !isIPv4 {
|
if isIPv4 {
|
||||||
|
ipType = "A"
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records?type=%s&name=%s", zoneID, ipType, domain)
|
|
||||||
body, err := provider.sendRequest("GET", url, nil)
|
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
|
||||||
|
du, _ := url.Parse(de)
|
||||||
|
|
||||||
|
q := du.Query()
|
||||||
|
q.Set("name", provider.domainConfig.FullDomain)
|
||||||
|
q.Set("type", ipType)
|
||||||
|
du.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
body, err := provider.sendRequest("GET", du.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var res map[string]interface{}
|
res := &cfResp{}
|
||||||
err = json.Unmarshal(body, &res)
|
err = utils.Json.Unmarshal(body, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := res["result"].([]interface{})
|
result := res.Result
|
||||||
if len(result) > 0 {
|
if len(result) > 0 {
|
||||||
return result[0].(map[string]interface{}), nil
|
provider.recordId = result[0].ID
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil // 没有找到 DNS 记录
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) createDNSRecord(zoneID string, domainConfig *DomainConfig, isIPv4 bool) bool {
|
func (provider *ProviderCloudflare) createDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType, ipAddr string
|
||||||
var ipAddr = domainConfig.Ipv4Addr
|
if isIPv4 {
|
||||||
if !isIPv4 {
|
ipType = "A"
|
||||||
|
ipAddr = provider.domainConfig.Ipv4Addr
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
ipAddr = domainConfig.Ipv6Addr
|
ipAddr = provider.domainConfig.Ipv6Addr
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records", zoneID)
|
|
||||||
data := map[string]interface{}{
|
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records")
|
||||||
"type": ipType,
|
data := &cfReq{
|
||||||
"name": domainConfig.FullDomain,
|
Name: provider.domainConfig.FullDomain,
|
||||||
"content": ipAddr,
|
Type: ipType,
|
||||||
"ttl": 60,
|
Content: ipAddr,
|
||||||
"proxied": false,
|
TTL: 60,
|
||||||
|
Proxied: false,
|
||||||
}
|
}
|
||||||
jsonData, _ := json.Marshal(data)
|
|
||||||
_, err := provider.sendRequest("POST", url, jsonData)
|
jsonData, _ := utils.Json.Marshal(data)
|
||||||
return err == nil
|
_, err := provider.sendRequest("POST", de, jsonData)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderCloudflare) updateDNSRecord(zoneID string, recordID string, domainConfig *DomainConfig, isIPv4 bool) bool {
|
func (provider *ProviderCloudflare) updateDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType, ipAddr string
|
||||||
var ipAddr = domainConfig.Ipv4Addr
|
if isIPv4 {
|
||||||
if !isIPv4 {
|
ipType = "A"
|
||||||
|
ipAddr = provider.domainConfig.Ipv4Addr
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
ipAddr = domainConfig.Ipv6Addr
|
ipAddr = provider.domainConfig.Ipv6Addr
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneID, recordID)
|
|
||||||
data := map[string]interface{}{
|
de, _ := url.JoinPath(baseEndpoint, provider.zoneId, "dns_records", provider.recordId)
|
||||||
"type": ipType,
|
data := &cfReq{
|
||||||
"name": domainConfig.FullDomain,
|
Name: provider.domainConfig.FullDomain,
|
||||||
"content": ipAddr,
|
Type: ipType,
|
||||||
"ttl": 60,
|
Content: ipAddr,
|
||||||
"proxied": false,
|
TTL: 60,
|
||||||
|
Proxied: false,
|
||||||
}
|
}
|
||||||
jsonData, _ := json.Marshal(data)
|
|
||||||
_, err := provider.sendRequest("PATCH", url, jsonData)
|
jsonData, _ := utils.Json.Marshal(data)
|
||||||
return err == nil
|
_, err := provider.sendRequest("PATCH", de, jsonData)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以下为辅助方法,如发送 HTTP 请求等
|
// 以下为辅助方法,如发送 HTTP 请求等
|
||||||
func (provider *ProviderCloudflare) sendRequest(method string, url string, data []byte) ([]byte, error) {
|
func (provider *ProviderCloudflare) sendRequest(method string, url string, data []byte) ([]byte, error) {
|
||||||
client := &http.Client{}
|
|
||||||
req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
|
req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", provider.Secret))
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", provider.secret))
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := utils.HttpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func(Body io.ReadCloser) {
|
defer func(Body io.ReadCloser) {
|
||||||
err := Body.Close()
|
err := Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s\n", err.Error())
|
log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s", err.Error())
|
||||||
}
|
}
|
||||||
}(resp.Body)
|
}(resp.Body)
|
||||||
|
|
||||||
|
|||||||
+9
-1
@@ -1,5 +1,7 @@
|
|||||||
package ddns
|
package ddns
|
||||||
|
|
||||||
|
import "golang.org/x/net/publicsuffix"
|
||||||
|
|
||||||
type DomainConfig struct {
|
type DomainConfig struct {
|
||||||
EnableIPv4 bool
|
EnableIPv4 bool
|
||||||
EnableIpv6 bool
|
EnableIpv6 bool
|
||||||
@@ -10,5 +12,11 @@ type DomainConfig struct {
|
|||||||
|
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
// UpdateDomain Return is updated
|
// UpdateDomain Return is updated
|
||||||
UpdateDomain(domainConfig *DomainConfig) bool
|
UpdateDomain(*DomainConfig) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitDomain(domain string) (prefix string, realDomain string) {
|
||||||
|
realDomain, _ = publicsuffix.EffectiveTLDPlusOne(domain)
|
||||||
|
prefix = domain[:len(domain)-len(realDomain)-1]
|
||||||
|
return prefix, realDomain
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -2,6 +2,6 @@ package ddns
|
|||||||
|
|
||||||
type ProviderDummy struct{}
|
type ProviderDummy struct{}
|
||||||
|
|
||||||
func (provider *ProviderDummy) UpdateDomain(domainConfig *DomainConfig) bool {
|
func (provider *ProviderDummy) UpdateDomain(domainConfig *DomainConfig) error {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package ddns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/publicsuffix"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (provider ProviderWebHook) FormatWebhookString(s string, config *DomainConfig, ipType string) string {
|
|
||||||
if config == nil {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
result := strings.TrimSpace(s)
|
|
||||||
result = strings.Replace(s, "{ip}", config.Ipv4Addr, -1)
|
|
||||||
result = strings.Replace(result, "{domain}", config.FullDomain, -1)
|
|
||||||
result = strings.Replace(result, "{type}", ipType, -1)
|
|
||||||
// remove \r
|
|
||||||
result = strings.Replace(result, "\r", "", -1)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetStringHeadersToRequest(req *http.Request, headers []string) {
|
|
||||||
if req == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, element := range headers {
|
|
||||||
kv := strings.SplitN(element, ":", 2)
|
|
||||||
if len(kv) == 2 {
|
|
||||||
req.Header.Add(kv[0], kv[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitDomain 分割域名为前缀和一级域名
|
|
||||||
func SplitDomain(domain string) (prefix string, realDomain string) {
|
|
||||||
realDomain, _ = publicsuffix.EffectiveTLDPlusOne(domain)
|
|
||||||
prefix = domain[:len(domain)-len(realDomain)-1]
|
|
||||||
return prefix, realDomain
|
|
||||||
}
|
|
||||||
+119
-84
@@ -5,145 +5,180 @@ import (
|
|||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const te = "https://dnspod.tencentcloudapi.com"
|
||||||
url = "https://dnspod.tencentcloudapi.com"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProviderTencentCloud struct {
|
type ProviderTencentCloud struct {
|
||||||
SecretID string
|
secretID string
|
||||||
SecretKey string
|
secretKey string
|
||||||
|
domainConfig *DomainConfig
|
||||||
|
resp *tcResp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) bool {
|
type tcReq struct {
|
||||||
if domainConfig == nil {
|
RecordType string `json:"RecordType"`
|
||||||
return false
|
Domain string `json:"Domain"`
|
||||||
|
RecordLine string `json:"RecordLine"`
|
||||||
|
Subdomain string `json:"Subdomain,omitempty"`
|
||||||
|
SubDomain string `json:"SubDomain,omitempty"` // As is
|
||||||
|
Value string `json:"Value,omitempty"`
|
||||||
|
TTL uint32 `json:"TTL,omitempty"`
|
||||||
|
RecordId uint64 `json:"RecordId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type tcResp struct {
|
||||||
|
Response struct {
|
||||||
|
RecordList []struct {
|
||||||
|
RecordId uint64
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
Error struct {
|
||||||
|
Code string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProviderTencentCloud(id, key string) *ProviderTencentCloud {
|
||||||
|
return &ProviderTencentCloud{
|
||||||
|
secretID: id,
|
||||||
|
secretKey: key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) error {
|
||||||
|
if domainConfig == nil {
|
||||||
|
return fmt.Errorf("获取 DDNS 配置失败")
|
||||||
|
}
|
||||||
|
provider.domainConfig = domainConfig
|
||||||
|
|
||||||
// 当IPv4和IPv6同时成功才算作成功
|
// 当IPv4和IPv6同时成功才算作成功
|
||||||
var resultV4 = true
|
var err error
|
||||||
var resultV6 = true
|
if provider.domainConfig.EnableIPv4 {
|
||||||
if domainConfig.EnableIPv4 {
|
if err = provider.addDomainRecord(true); err != nil {
|
||||||
if !provider.addDomainRecord(domainConfig, true) {
|
return err
|
||||||
resultV4 = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if domainConfig.EnableIpv6 {
|
if provider.domainConfig.EnableIpv6 {
|
||||||
if !provider.addDomainRecord(domainConfig, false) {
|
if err = provider.addDomainRecord(false); err != nil {
|
||||||
resultV6 = false
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultV4 && resultV6
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderTencentCloud) addDomainRecord(domainConfig *DomainConfig, isIpv4 bool) bool {
|
func (provider *ProviderTencentCloud) addDomainRecord(isIpv4 bool) error {
|
||||||
record, err := provider.findDNSRecord(domainConfig.FullDomain, isIpv4)
|
err := provider.findDNSRecord(isIpv4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("查找 DNS 记录时出错: %s\n", err)
|
return fmt.Errorf("查找 DNS 记录时出错: %s", err)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if errResponse, ok := record["Error"].(map[string]interface{}); ok {
|
if provider.resp.Response.Error.Code == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录
|
||||||
if errCode, ok := errResponse["Code"].(string); ok && errCode == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录
|
return provider.createDNSRecord(isIpv4)
|
||||||
// 添加 DNS 记录
|
} else if provider.resp.Response.Error.Code != "" {
|
||||||
return provider.createDNSRecord(domainConfig.FullDomain, domainConfig, isIpv4)
|
return fmt.Errorf("查询 DNS 记录时出错,错误代码为: %s", provider.resp.Response.Error.Code)
|
||||||
} else {
|
|
||||||
log.Printf("查询 DNS 记录时出错,错误代码为: %s\n", errCode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认情况下更新 DNS 记录
|
// 默认情况下更新 DNS 记录
|
||||||
return provider.updateDNSRecord(domainConfig.FullDomain, record["RecordList"].([]interface{})[0].(map[string]interface{})["RecordId"].(float64), domainConfig, isIpv4)
|
return provider.updateDNSRecord(isIpv4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderTencentCloud) findDNSRecord(domain string, isIPv4 bool) (map[string]interface{}, error) {
|
func (provider *ProviderTencentCloud) findDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType string
|
||||||
if !isIPv4 {
|
if isIPv4 {
|
||||||
|
ipType = "A"
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
}
|
}
|
||||||
_, realDomain := SplitDomain(domain)
|
|
||||||
prefix, _ := SplitDomain(domain)
|
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
||||||
data := map[string]interface{}{
|
data := &tcReq{
|
||||||
"RecordType": ipType,
|
RecordType: ipType,
|
||||||
"Domain": realDomain,
|
Domain: realDomain,
|
||||||
"RecordLine": "默认",
|
RecordLine: "默认",
|
||||||
"Subdomain": prefix,
|
Subdomain: prefix,
|
||||||
}
|
}
|
||||||
jsonData, _ := json.Marshal(data)
|
|
||||||
|
jsonData, _ := utils.Json.Marshal(data)
|
||||||
body, err := provider.sendRequest("DescribeRecordList", jsonData)
|
body, err := provider.sendRequest("DescribeRecordList", jsonData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var res map[string]interface{}
|
provider.resp = &tcResp{}
|
||||||
err = json.Unmarshal(body, &res)
|
err = utils.Json.Unmarshal(body, provider.resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := res["Response"].(map[string]interface{})
|
return nil
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderTencentCloud) createDNSRecord(domain string, domainConfig *DomainConfig, isIPv4 bool) bool {
|
func (provider *ProviderTencentCloud) createDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType, ipAddr string
|
||||||
var ipAddr = domainConfig.Ipv4Addr
|
if isIPv4 {
|
||||||
if !isIPv4 {
|
ipType = "A"
|
||||||
|
ipAddr = provider.domainConfig.Ipv4Addr
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
ipAddr = domainConfig.Ipv6Addr
|
ipAddr = provider.domainConfig.Ipv6Addr
|
||||||
}
|
}
|
||||||
_, realDomain := SplitDomain(domain)
|
|
||||||
prefix, _ := SplitDomain(domain)
|
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
||||||
data := map[string]interface{}{
|
data := &tcReq{
|
||||||
"RecordType": ipType,
|
RecordType: ipType,
|
||||||
"RecordLine": "默认",
|
RecordLine: "默认",
|
||||||
"Domain": realDomain,
|
Domain: realDomain,
|
||||||
"SubDomain": prefix,
|
SubDomain: prefix,
|
||||||
"Value": ipAddr,
|
Value: ipAddr,
|
||||||
"TTL": 600,
|
TTL: 600,
|
||||||
}
|
}
|
||||||
jsonData, _ := json.Marshal(data)
|
|
||||||
|
jsonData, _ := utils.Json.Marshal(data)
|
||||||
_, err := provider.sendRequest("CreateRecord", jsonData)
|
_, err := provider.sendRequest("CreateRecord", jsonData)
|
||||||
return err == nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderTencentCloud) updateDNSRecord(domain string, recordID float64, domainConfig *DomainConfig, isIPv4 bool) bool {
|
func (provider *ProviderTencentCloud) updateDNSRecord(isIPv4 bool) error {
|
||||||
var ipType = "A"
|
var ipType, ipAddr string
|
||||||
var ipAddr = domainConfig.Ipv4Addr
|
if isIPv4 {
|
||||||
if !isIPv4 {
|
ipType = "A"
|
||||||
|
ipAddr = provider.domainConfig.Ipv4Addr
|
||||||
|
} else {
|
||||||
ipType = "AAAA"
|
ipType = "AAAA"
|
||||||
ipAddr = domainConfig.Ipv6Addr
|
ipAddr = provider.domainConfig.Ipv6Addr
|
||||||
}
|
}
|
||||||
_, realDomain := SplitDomain(domain)
|
|
||||||
prefix, _ := SplitDomain(domain)
|
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
||||||
data := map[string]interface{}{
|
data := &tcReq{
|
||||||
"RecordType": ipType,
|
RecordType: ipType,
|
||||||
"RecordLine": "默认",
|
RecordLine: "默认",
|
||||||
"Domain": realDomain,
|
Domain: realDomain,
|
||||||
"SubDomain": prefix,
|
SubDomain: prefix,
|
||||||
"Value": ipAddr,
|
Value: ipAddr,
|
||||||
"TTL": 600,
|
TTL: 600,
|
||||||
"RecordId": recordID,
|
RecordId: provider.resp.Response.RecordList[0].RecordId,
|
||||||
}
|
}
|
||||||
jsonData, _ := json.Marshal(data)
|
|
||||||
|
jsonData, _ := utils.Json.Marshal(data)
|
||||||
_, err := provider.sendRequest("ModifyRecord", jsonData)
|
_, err := provider.sendRequest("ModifyRecord", jsonData)
|
||||||
return err == nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以下为辅助方法,如发送 HTTP 请求等
|
// 以下为辅助方法,如发送 HTTP 请求等
|
||||||
func (provider *ProviderTencentCloud) sendRequest(action string, data []byte) ([]byte, error) {
|
func (provider *ProviderTencentCloud) sendRequest(action string, data []byte) ([]byte, error) {
|
||||||
client := &http.Client{}
|
req, err := http.NewRequest("POST", te, bytes.NewBuffer(data))
|
||||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -151,8 +186,8 @@ func (provider *ProviderTencentCloud) sendRequest(action string, data []byte) ([
|
|||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("X-TC-Version", "2021-03-23")
|
req.Header.Set("X-TC-Version", "2021-03-23")
|
||||||
|
|
||||||
provider.signRequest(provider.SecretID, provider.SecretKey, req, action, string(data))
|
provider.signRequest(provider.secretID, provider.secretKey, req, action, string(data))
|
||||||
resp, err := client.Do(req)
|
resp, err := utils.HttpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+91
-40
@@ -2,58 +2,109 @@ package ddns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"log"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProviderWebHook struct {
|
type ProviderWebHook struct {
|
||||||
URL string
|
url string
|
||||||
RequestMethod string
|
requestMethod string
|
||||||
RequestBody string
|
requestBody string
|
||||||
RequestHeader string
|
requestHeader string
|
||||||
|
domainConfig *DomainConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *ProviderWebHook) UpdateDomain(domainConfig *DomainConfig) bool {
|
func NewProviderWebHook(s, rm, rb, rh string) *ProviderWebHook {
|
||||||
|
return &ProviderWebHook{
|
||||||
|
url: s,
|
||||||
|
requestMethod: rm,
|
||||||
|
requestBody: rb,
|
||||||
|
requestHeader: rh,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ProviderWebHook) UpdateDomain(domainConfig *DomainConfig) error {
|
||||||
if domainConfig == nil {
|
if domainConfig == nil {
|
||||||
return false
|
return fmt.Errorf("获取 DDNS 配置失败")
|
||||||
|
}
|
||||||
|
provider.domainConfig = domainConfig
|
||||||
|
|
||||||
|
if provider.domainConfig.FullDomain == "" {
|
||||||
|
return fmt.Errorf("failed to update an empty domain")
|
||||||
}
|
}
|
||||||
|
|
||||||
if domainConfig.FullDomain == "" {
|
if provider.domainConfig.EnableIPv4 && provider.domainConfig.Ipv4Addr != "" {
|
||||||
log.Println("NEZHA>> Failed to update an empty domain")
|
req, err := provider.prepareRequest(true)
|
||||||
return false
|
if err != nil {
|
||||||
}
|
return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
|
||||||
updated := false
|
}
|
||||||
client := &http.Client{}
|
if _, err := utils.HttpClient.Do(req); err != nil {
|
||||||
if domainConfig.EnableIPv4 && domainConfig.Ipv4Addr != "" {
|
return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
|
||||||
url := provider.FormatWebhookString(provider.URL, domainConfig, "ipv4")
|
|
||||||
body := provider.FormatWebhookString(provider.RequestBody, domainConfig, "ipv4")
|
|
||||||
header := provider.FormatWebhookString(provider.RequestHeader, domainConfig, "ipv4")
|
|
||||||
headers := strings.Split(header, "\n")
|
|
||||||
req, err := http.NewRequest(provider.RequestMethod, url, bytes.NewBufferString(body))
|
|
||||||
if err == nil && req != nil {
|
|
||||||
SetStringHeadersToRequest(req, headers)
|
|
||||||
if _, err := client.Do(req); err != nil {
|
|
||||||
log.Printf("NEZHA>> Failed to update a domain: %s. Cause by: %s\n", domainConfig.FullDomain, err.Error())
|
|
||||||
} else {
|
|
||||||
updated = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if domainConfig.EnableIpv6 && domainConfig.Ipv6Addr != "" {
|
|
||||||
url := provider.FormatWebhookString(provider.URL, domainConfig, "ipv6")
|
if provider.domainConfig.EnableIpv6 && provider.domainConfig.Ipv6Addr != "" {
|
||||||
body := provider.FormatWebhookString(provider.RequestBody, domainConfig, "ipv6")
|
req, err := provider.prepareRequest(false)
|
||||||
header := provider.FormatWebhookString(provider.RequestHeader, domainConfig, "ipv6")
|
if err != nil {
|
||||||
headers := strings.Split(header, "\n")
|
return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
|
||||||
req, err := http.NewRequest(provider.RequestMethod, url, bytes.NewBufferString(body))
|
}
|
||||||
if err == nil && req != nil {
|
if _, err := utils.HttpClient.Do(req); err != nil {
|
||||||
SetStringHeadersToRequest(req, headers)
|
return fmt.Errorf("failed to update a domain: %s. Cause by: %v", provider.domainConfig.FullDomain, err)
|
||||||
if _, err := client.Do(req); err != nil {
|
|
||||||
log.Printf("NEZHA>> Failed to update a domain: %s. Cause by: %s\n", domainConfig.FullDomain, err.Error())
|
|
||||||
} else {
|
|
||||||
updated = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return updated
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ProviderWebHook) prepareRequest(isIPv4 bool) (*http.Request, error) {
|
||||||
|
u, err := url.Parse(provider.url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed parsing url: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only handle queries here
|
||||||
|
q := u.Query()
|
||||||
|
for p, vals := range q {
|
||||||
|
for n, v := range vals {
|
||||||
|
vals[n] = provider.formatWebhookString(v, isIPv4)
|
||||||
|
}
|
||||||
|
q[p] = vals
|
||||||
|
}
|
||||||
|
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
body := provider.formatWebhookString(provider.requestBody, isIPv4)
|
||||||
|
header := provider.formatWebhookString(provider.requestHeader, isIPv4)
|
||||||
|
headers := strings.Split(header, "\n")
|
||||||
|
|
||||||
|
req, err := http.NewRequest(provider.requestMethod, u.String(), bytes.NewBufferString(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed creating new request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SetStringHeadersToRequest(req, headers)
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ProviderWebHook) formatWebhookString(s string, isIPv4 bool) string {
|
||||||
|
var ipAddr, ipType string
|
||||||
|
if isIPv4 {
|
||||||
|
ipAddr = provider.domainConfig.Ipv4Addr
|
||||||
|
ipType = "ipv4"
|
||||||
|
} else {
|
||||||
|
ipAddr = provider.domainConfig.Ipv6Addr
|
||||||
|
ipType = "ipv6"
|
||||||
|
}
|
||||||
|
|
||||||
|
r := strings.NewReplacer(
|
||||||
|
"{ip}", ipAddr,
|
||||||
|
"{domain}", provider.domainConfig.FullDomain,
|
||||||
|
"{type}", ipType,
|
||||||
|
"\r", "",
|
||||||
|
)
|
||||||
|
|
||||||
|
result := r.Replace(strings.TrimSpace(s))
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -86,3 +87,15 @@ func Uint64SubInt64(a uint64, b int64) uint64 {
|
|||||||
}
|
}
|
||||||
return a - uint64(b)
|
return a - uint64(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetStringHeadersToRequest(req *http.Request, headers []string) {
|
||||||
|
if req == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, element := range headers {
|
||||||
|
kv := strings.SplitN(element, ":", 2)
|
||||||
|
if len(kv) == 2 {
|
||||||
|
req.Header.Add(kv[0], kv[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+32
-2
@@ -653,7 +653,37 @@ other = "Disable Switch Template in Frontend"
|
|||||||
other = "Servers On World Map"
|
other = "Servers On World Map"
|
||||||
|
|
||||||
[NAT]
|
[NAT]
|
||||||
other = "NAT"
|
other = "NAT Traversal"
|
||||||
|
|
||||||
|
[LocalService]
|
||||||
|
other = "Local service"
|
||||||
|
|
||||||
|
[LocalServicePlaceholder]
|
||||||
|
other = "192.168.1.1:80 (with port)"
|
||||||
|
|
||||||
|
[BindHostname]
|
||||||
|
other = "Bind hostname"
|
||||||
|
|
||||||
[NetworkSpiterList]
|
[NetworkSpiterList]
|
||||||
other = "Network Monitor"
|
other = "Network Monitor"
|
||||||
|
|
||||||
|
[Refresh]
|
||||||
|
other = "Refresh"
|
||||||
|
|
||||||
|
[CopyPath]
|
||||||
|
other = "Copy Path"
|
||||||
|
|
||||||
|
[Goto]
|
||||||
|
other = "Go to"
|
||||||
|
|
||||||
|
[GotoHeadline]
|
||||||
|
other = "Go to a Folder"
|
||||||
|
|
||||||
|
[GotoGo]
|
||||||
|
other = "Go"
|
||||||
|
|
||||||
|
[GotoClose]
|
||||||
|
other = "Cancel"
|
||||||
|
|
||||||
|
[FMError]
|
||||||
|
other = "Agent returned an error, please view the console for details. To open a new connection, reopen the FM again."
|
||||||
|
|||||||
Vendored
+32
-2
@@ -653,7 +653,37 @@ other = "Deshabilitar Cambio de Plantilla en Frontend"
|
|||||||
other = "Servidores en el mapa mundial"
|
other = "Servidores en el mapa mundial"
|
||||||
|
|
||||||
[NAT]
|
[NAT]
|
||||||
other = "NAT"
|
other = "NAT traversal"
|
||||||
|
|
||||||
|
[LocalService]
|
||||||
|
other = "Servicio de red local"
|
||||||
|
|
||||||
|
[LocalServicePlaceholder]
|
||||||
|
other = "192.168.1.1:80 (con puerto)"
|
||||||
|
|
||||||
|
[BindHostname]
|
||||||
|
other = "Vincular nombre de host"
|
||||||
|
|
||||||
[NetworkSpiterList]
|
[NetworkSpiterList]
|
||||||
other = "Red Monitor"
|
other = "Monitor de red"
|
||||||
|
|
||||||
|
[Refresh]
|
||||||
|
other = "Actualizar"
|
||||||
|
|
||||||
|
[CopyPath]
|
||||||
|
other = "Copiar ruta"
|
||||||
|
|
||||||
|
[Goto]
|
||||||
|
other = "Ir a"
|
||||||
|
|
||||||
|
[GotoHeadline]
|
||||||
|
other = "Ir a una carpeta"
|
||||||
|
|
||||||
|
[GotoGo]
|
||||||
|
other = "Ir"
|
||||||
|
|
||||||
|
[GotoClose]
|
||||||
|
other = "Cancelar"
|
||||||
|
|
||||||
|
[FMError]
|
||||||
|
other = "Agent devolvió un error, consulte la consola para obtener más detalles. Para abrir una nueva conexión, vuelva a abrir el FM."
|
||||||
|
|||||||
Vendored
+30
@@ -655,5 +655,35 @@ other = "服务器世界分布图"
|
|||||||
[NAT]
|
[NAT]
|
||||||
other = "内网穿透"
|
other = "内网穿透"
|
||||||
|
|
||||||
|
[LocalService]
|
||||||
|
other = "内网服务"
|
||||||
|
|
||||||
|
[LocalServicePlaceholder]
|
||||||
|
other = "192.168.1.1:80(带端口)"
|
||||||
|
|
||||||
|
[BindHostname]
|
||||||
|
other = "绑定域名"
|
||||||
|
|
||||||
[NetworkSpiterList]
|
[NetworkSpiterList]
|
||||||
other = "网络监控"
|
other = "网络监控"
|
||||||
|
|
||||||
|
[Refresh]
|
||||||
|
other = "刷新"
|
||||||
|
|
||||||
|
[CopyPath]
|
||||||
|
other = "复制路径"
|
||||||
|
|
||||||
|
[Goto]
|
||||||
|
other = "跳往"
|
||||||
|
|
||||||
|
[GotoHeadline]
|
||||||
|
other = "跳往文件夹"
|
||||||
|
|
||||||
|
[GotoGo]
|
||||||
|
other = "确认"
|
||||||
|
|
||||||
|
[GotoClose]
|
||||||
|
other = "取消"
|
||||||
|
|
||||||
|
[FMError]
|
||||||
|
other = "Agent 返回了错误,请查看控制台获取详细信息。要建立新连接,请重新打开 FM。"
|
||||||
|
|||||||
Vendored
+50
-20
@@ -50,7 +50,7 @@ other = "新增計劃任務"
|
|||||||
other = "名稱"
|
other = "名稱"
|
||||||
|
|
||||||
[Scheduler]
|
[Scheduler]
|
||||||
other = "計劃"
|
other = "排程"
|
||||||
|
|
||||||
[BackUp]
|
[BackUp]
|
||||||
other = "備份"
|
other = "備份"
|
||||||
@@ -80,37 +80,37 @@ other = "特定伺服器"
|
|||||||
other = "輸入ID/名稱以搜尋"
|
other = "輸入ID/名稱以搜尋"
|
||||||
|
|
||||||
[NotificationMethodGroup]
|
[NotificationMethodGroup]
|
||||||
other = "通知方式組"
|
other = "通知群組"
|
||||||
|
|
||||||
[PushSuccessMessages]
|
[PushSuccessMessages]
|
||||||
other = "推送成功的消息"
|
other = "推送成功的訊息"
|
||||||
|
|
||||||
[TaskType]
|
[TaskType]
|
||||||
other = "任務類型"
|
other = "任務類型"
|
||||||
|
|
||||||
[CronTask]
|
[CronTask]
|
||||||
other = "計劃任務"
|
other = "排程任務"
|
||||||
|
|
||||||
[TriggerTask]
|
[TriggerTask]
|
||||||
other = "觸發任務"
|
other = "觸發任務"
|
||||||
|
|
||||||
[TheFormaOfTheScheduleIs]
|
[TheFormaOfTheScheduleIs]
|
||||||
other = "計劃的格式為:"
|
other = "排程的格式為:"
|
||||||
|
|
||||||
[SecondsMinutesHoursDaysMonthsWeeksSeeDetails]
|
[SecondsMinutesHoursDaysMonthsWeeksSeeDetails]
|
||||||
other = "秒 分 時 天 月 星期,詳情見"
|
other = "秒 分 時 天 月 星期,詳情見"
|
||||||
|
|
||||||
[ScheduleExpressionFormat]
|
[ScheduleExpressionFormat]
|
||||||
other = "計劃表達式格式"
|
other = "排程表達式格式"
|
||||||
|
|
||||||
[IntroductionOfCommands]
|
[IntroductionOfCommands]
|
||||||
other = "命令說明:編寫命令時類似於 shell/bat 腳本。建議不要換行,多個命令可用 <code>&&</code> 或 <code>&</code> 連接,若出現命令無法找到的情況,可能是由於 <code>PATH</code> 環境變量配置問題。在 <code>Linux</code> 伺服器上,可在命令開頭加入 <code>source ~/.bashrc</code>,或使用命令的絕對路徑執行。"
|
other = "命令說明:編寫命令時類似於 shell/bat 腳本。建議不要換行,多個命令可用 <code>&&</code> 或 <code>&</code> 連接,若出現命令無法找到的情況,可能是由於 <code>PATH</code> 環境變數配置問題。在 <code>Linux</code> 伺服器上,可在命令開頭加入 <code>source ~/.bashrc</code>,或使用命令的絕對路徑執行。"
|
||||||
|
|
||||||
[AddMonitor]
|
[AddMonitor]
|
||||||
other = "新增監控"
|
other = "新增監控"
|
||||||
|
|
||||||
[Blog]
|
[Blog]
|
||||||
other = "博客"
|
other = "部落格"
|
||||||
|
|
||||||
[Target]
|
[Target]
|
||||||
other = "目標"
|
other = "目標"
|
||||||
@@ -158,7 +158,7 @@ other = "新增通知方式"
|
|||||||
other = "分組"
|
other = "分組"
|
||||||
|
|
||||||
[DoNotSendTestMessages]
|
[DoNotSendTestMessages]
|
||||||
other = "不發送測試信息"
|
other = "不發送測試訊息"
|
||||||
|
|
||||||
[RequestMethod]
|
[RequestMethod]
|
||||||
other = "請求方式"
|
other = "請求方式"
|
||||||
@@ -221,7 +221,7 @@ other = "排序"
|
|||||||
other = "越大越靠前"
|
other = "越大越靠前"
|
||||||
|
|
||||||
[Secret]
|
[Secret]
|
||||||
other = "密鑰"
|
other = "金鑰"
|
||||||
|
|
||||||
[Note]
|
[Note]
|
||||||
other = "備註"
|
other = "備註"
|
||||||
@@ -254,10 +254,10 @@ other = "忽略所有"
|
|||||||
other = "觸發執行"
|
other = "觸發執行"
|
||||||
|
|
||||||
[DeleteScheduledTask]
|
[DeleteScheduledTask]
|
||||||
other = "刪除計劃任務"
|
other = "刪除排程任務"
|
||||||
|
|
||||||
[ConfirmToDeleteThisScheduledTask]
|
[ConfirmToDeleteThisScheduledTask]
|
||||||
other = "確認刪除此計劃任務?"
|
other = "確認刪除此排程任務?"
|
||||||
|
|
||||||
[AccessDenied]
|
[AccessDenied]
|
||||||
other = "訪問被拒絕"
|
other = "訪問被拒絕"
|
||||||
@@ -407,7 +407,7 @@ other = "流量"
|
|||||||
other = "負載"
|
other = "負載"
|
||||||
|
|
||||||
[ProcessCount]
|
[ProcessCount]
|
||||||
other = "進程數"
|
other = "行程數"
|
||||||
|
|
||||||
[ConnCount]
|
[ConnCount]
|
||||||
other = "連接數"
|
other = "連接數"
|
||||||
@@ -422,7 +422,7 @@ other = "活動"
|
|||||||
other = "版本"
|
other = "版本"
|
||||||
|
|
||||||
[NetSpeed]
|
[NetSpeed]
|
||||||
other = "網絡"
|
other = "網路"
|
||||||
|
|
||||||
[Uptime]
|
[Uptime]
|
||||||
other = "在線"
|
other = "在線"
|
||||||
@@ -458,7 +458,7 @@ other = "狀態"
|
|||||||
other = "可用性"
|
other = "可用性"
|
||||||
|
|
||||||
[AverageLatency]
|
[AverageLatency]
|
||||||
other = "平均響應時間"
|
other = "平均回應時間"
|
||||||
|
|
||||||
[CycleTransferStats]
|
[CycleTransferStats]
|
||||||
other = "周期性流量統計"
|
other = "周期性流量統計"
|
||||||
@@ -524,7 +524,7 @@ other = "發生錯誤"
|
|||||||
other = "系統錯誤"
|
other = "系統錯誤"
|
||||||
|
|
||||||
[NetworkError]
|
[NetworkError]
|
||||||
other = "網絡錯誤"
|
other = "網路錯誤"
|
||||||
|
|
||||||
[ServicesStatus]
|
[ServicesStatus]
|
||||||
other = "服務狀態"
|
other = "服務狀態"
|
||||||
@@ -536,7 +536,7 @@ other = "伺服器管理"
|
|||||||
other = "服務監控"
|
other = "服務監控"
|
||||||
|
|
||||||
[ScheduledTasks]
|
[ScheduledTasks]
|
||||||
other = "計劃任務"
|
other = "排程任務"
|
||||||
|
|
||||||
[ApiManagement]
|
[ApiManagement]
|
||||||
other = "API 管理"
|
other = "API 管理"
|
||||||
@@ -614,7 +614,7 @@ other = "對遊客隱藏"
|
|||||||
other = "菜單"
|
other = "菜單"
|
||||||
|
|
||||||
[NetworkSpiter]
|
[NetworkSpiter]
|
||||||
other = "網絡"
|
other = "網路"
|
||||||
|
|
||||||
[EnableShowInService]
|
[EnableShowInService]
|
||||||
other = "在服務中顯示"
|
other = "在服務中顯示"
|
||||||
@@ -653,7 +653,37 @@ other = "禁止前台切換主題"
|
|||||||
other = "伺服器世界分布圖"
|
other = "伺服器世界分布圖"
|
||||||
|
|
||||||
[NAT]
|
[NAT]
|
||||||
other = "NAT"
|
other = "NAT穿透"
|
||||||
|
|
||||||
|
[LocalService]
|
||||||
|
other = "內網服務"
|
||||||
|
|
||||||
|
[LocalServicePlaceholder]
|
||||||
|
other = "192.168.1.1:80(帶埠號)"
|
||||||
|
|
||||||
|
[BindHostname]
|
||||||
|
other = "綁定網域"
|
||||||
|
|
||||||
[NetworkSpiterList]
|
[NetworkSpiterList]
|
||||||
other = "網絡監控"
|
other = "網路監控"
|
||||||
|
|
||||||
|
[Refresh]
|
||||||
|
other = "重新整理"
|
||||||
|
|
||||||
|
[CopyPath]
|
||||||
|
other = "複製路徑"
|
||||||
|
|
||||||
|
[Goto]
|
||||||
|
other = "跳至"
|
||||||
|
|
||||||
|
[GotoHeadline]
|
||||||
|
other = "跳至資料夾"
|
||||||
|
|
||||||
|
[GotoGo]
|
||||||
|
other = "確定"
|
||||||
|
|
||||||
|
[GotoClose]
|
||||||
|
other = "取消"
|
||||||
|
|
||||||
|
[FMError]
|
||||||
|
other = "Agent 回傳了錯誤,請查看主控台獲取詳細資訊。要建立新連線,請重新開啟 FM。"
|
||||||
|
|||||||
Vendored
+3
-3
@@ -13,11 +13,11 @@
|
|||||||
<input type="number" name="ServerID" placeholder="1">
|
<input type="number" name="ServerID" placeholder="1">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>内网服务</label>
|
<label>{{tr "LocalService"}}</label>
|
||||||
<input type="text" name="Host" placeholder="192.168.1.1:80(带端口)">
|
<input type="text" name="Host" placeholder="{{tr "LocalServicePlaceholder"}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>绑定域名</label>
|
<label>{{tr "BindHostname"}}</label>
|
||||||
<input type="text" name="Domain" placeholder="router.app.yourdomain.com">
|
<input type="text" name="Domain" placeholder="router.app.yourdomain.com">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+9
-8
@@ -58,9 +58,9 @@
|
|||||||
<mdui-dropdown>
|
<mdui-dropdown>
|
||||||
<mdui-button-icon slot="trigger" icon="menu"></mdui-button-icon>
|
<mdui-button-icon slot="trigger" icon="menu"></mdui-button-icon>
|
||||||
<mdui-menu>
|
<mdui-menu>
|
||||||
<mdui-menu-item id="refresh">Refresh</mdui-menu-item>
|
<mdui-menu-item id="refresh">{{tr "Refresh"}}</mdui-menu-item>
|
||||||
<mdui-menu-item id="copy">Copy path</mdui-menu-item>
|
<mdui-menu-item id="copy">{{tr "CopyPath"}}</mdui-menu-item>
|
||||||
<mdui-menu-item id="goto">Go to</mdui-menu-item>
|
<mdui-menu-item id="goto">{{tr "Goto"}}</mdui-menu-item>
|
||||||
</mdui-menu>
|
</mdui-menu>
|
||||||
</mdui-dropdown>
|
</mdui-dropdown>
|
||||||
<span id="current-directory"></span>
|
<span id="current-directory"></span>
|
||||||
@@ -70,16 +70,16 @@
|
|||||||
<mdui-list id="file-list" class="file-list"></mdui-list>
|
<mdui-list id="file-list" class="file-list"></mdui-list>
|
||||||
|
|
||||||
<mdui-dialog id="error-dialog" headline="Error"
|
<mdui-dialog id="error-dialog" headline="Error"
|
||||||
description="Agent returned an error, please view the console for details. To open a new connection, reopen the FM again."></mdui-dialog>
|
description="{{tr "FMError"}}"></mdui-dialog>
|
||||||
|
|
||||||
<mdui-dialog id="upd-modal" class="modal">
|
<mdui-dialog id="upd-modal" class="modal">
|
||||||
<mdui-linear-progress id="upd-progress"></mdui-linear-progress>
|
<mdui-linear-progress id="upd-progress"></mdui-linear-progress>
|
||||||
</mdui-dialog>
|
</mdui-dialog>
|
||||||
|
|
||||||
<mdui-dialog id="goto-dialog" headline="Go to a folder" close-on-overlay-click>
|
<mdui-dialog id="goto-dialog" headline="{{tr "GotoHeadline"}}" close-on-overlay-click>
|
||||||
<mdui-text-field id="goto-text" variant="outlined" value=""></mdui-text-field>
|
<mdui-text-field id="goto-text" variant="outlined" value=""></mdui-text-field>
|
||||||
<mdui-button id="goto-go" slot="action" variant="text">Go</mdui-button>
|
<mdui-button id="goto-go" slot="action" variant="text">{{tr "GotoGo"}}</mdui-button>
|
||||||
<mdui-button id="goto-close" slot="action" variant="tonal">Close</mdui-button>
|
<mdui-button id="goto-close" slot="action" variant="tonal">{{tr "GotoClose"}}</mdui-button>
|
||||||
</mdui-dialog>
|
</mdui-dialog>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
|
|
||||||
const checkCompletion = setInterval(() => {
|
const checkCompletion = setInterval(() => {
|
||||||
if (isUpCompleted) {
|
if (isUpCompleted) {
|
||||||
clearInterval(checkCompletion); // 任务完成后停止检查
|
clearInterval(checkCompletion);
|
||||||
hideUpdModal();
|
hideUpdModal();
|
||||||
resetUpdState();
|
resetUpdState();
|
||||||
listFile();
|
listFile();
|
||||||
@@ -480,6 +480,7 @@
|
|||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
await uploadFile(file);
|
await uploadFile(file);
|
||||||
|
isUpCompleted = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -16,8 +16,8 @@
|
|||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>{{tr "Name"}}</th>
|
<th>{{tr "Name"}}</th>
|
||||||
<th>Agent ID</th>
|
<th>Agent ID</th>
|
||||||
<th>内网服务</th>
|
<th>{{tr "LocalService"}}</th>
|
||||||
<th>绑定域名</th>
|
<th>{{tr "BindHostname"}}</th>
|
||||||
<th>{{tr "Administration"}}</th>
|
<th>{{tr "Administration"}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
+4
-1
@@ -63,6 +63,7 @@
|
|||||||
<script src="https://unpkg.com/[email protected]/lib/xterm.js"></script>
|
<script src="https://unpkg.com/[email protected]/lib/xterm.js"></script>
|
||||||
<script src="https://unpkg.com/@xterm/[email protected]/lib/addon-fit.js"></script>
|
<script src="https://unpkg.com/@xterm/[email protected]/lib/addon-fit.js"></script>
|
||||||
<script src="https://unpkg.com/@xterm/[email protected]/lib/addon-web-links.js"></script>
|
<script src="https://unpkg.com/@xterm/[email protected]/lib/addon-web-links.js"></script>
|
||||||
|
<script src="https://unpkg.com/@xterm/[email protected]/lib/addon-attach.js"></script>
|
||||||
<script>
|
<script>
|
||||||
let sendResizing = false;
|
let sendResizing = false;
|
||||||
|
|
||||||
@@ -106,7 +107,9 @@
|
|||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
});
|
});
|
||||||
const socket = new WebSocket((window.location.protocol == 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/terminal/' + '{{.SessionID}}');
|
const socket = new WebSocket((window.location.protocol == 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/terminal/' + '{{.SessionID}}');
|
||||||
socket.binaryType = 'arraybuffer';
|
|
||||||
|
const attachAddon = new AttachAddon.AttachAddon(socket);
|
||||||
|
term.loadAddon(attachAddon);
|
||||||
|
|
||||||
const fitAddon = new FitAddon.FitAddon();
|
const fitAddon = new FitAddon.FitAddon();
|
||||||
term.loadAddon(fitAddon);
|
term.loadAddon(fitAddon);
|
||||||
|
|||||||
+16
-15
@@ -261,22 +261,23 @@
|
|||||||
const x = readableBytes(bs)
|
const x = readableBytes(bs)
|
||||||
return x != "NaN undefined" ? x : '0B'
|
return x != "NaN undefined" ? x : '0B'
|
||||||
},
|
},
|
||||||
getCoreAndGHz(str){
|
getCoreAndGHz(arr) {
|
||||||
if((str || []).hasOwnProperty(0) === false){
|
if ((arr || []).length === 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
str = str[0];
|
let totalCores = 0;
|
||||||
let GHz = str.match(/(\d|\.)+GHz/g);
|
arr.forEach(str => {
|
||||||
let Core = str.match(/(\d|\.)+ Physical/g);
|
let coreMatch = str.match(/(\d+(\.\d+)?) Physical/g);
|
||||||
GHz = GHz!==null?GHz.hasOwnProperty(0)===false?'':GHz[0]:''
|
let coreCount = 0;
|
||||||
Core = Core!==null?Core.hasOwnProperty(0)===false?'?':Core[0]:'?'
|
if (coreMatch) {
|
||||||
if(Core === '?'){
|
coreCount = parseFloat(coreMatch[0]);
|
||||||
let Core = str.match(/(\d|\.)+ Virtual/g);
|
} else {
|
||||||
Core = Core!==null?Core.hasOwnProperty(0)===false?'?':Core[0]:'?'
|
let coreMatch = str.match(/(\d+(\.\d+)?) Virtual/g);
|
||||||
return Core.replace('Virtual','Core')
|
coreCount = coreMatch ? parseFloat(coreMatch[0]) : 0;
|
||||||
}
|
}
|
||||||
return Core.replace('Physical','Core');
|
totalCores += coreCount;
|
||||||
|
});
|
||||||
|
return `${totalCores} Cores`;
|
||||||
},
|
},
|
||||||
getByteToGB(bs){
|
getByteToGB(bs){
|
||||||
return (bs/1024/1024/1024).toFixed(2) + 'GB'
|
return (bs/1024/1024/1024).toFixed(2) + 'GB'
|
||||||
|
|||||||
+15
-13
@@ -474,21 +474,23 @@
|
|||||||
const sizes = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"];
|
const sizes = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"];
|
||||||
return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + sizes[i];
|
return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + sizes[i];
|
||||||
},
|
},
|
||||||
getCoreAndGHz(str){
|
getCoreAndGHz(arr) {
|
||||||
if ((str || []).hasOwnProperty(0) === false) {
|
if ((arr || []).length === 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
str = str[0];
|
let totalCores = 0;
|
||||||
let GHz = str.match(/(\d|\.)+GHz/g);
|
arr.forEach(str => {
|
||||||
let Core = str.match(/(\d|\.)+ Physical/g);
|
let coreMatch = str.match(/(\d+(\.\d+)?) Physical/g);
|
||||||
GHz = GHz !== null ? GHz.hasOwnProperty(0) === false ? '' : GHz[0] : ''
|
let coreCount = 0;
|
||||||
Core = Core !== null ? Core.hasOwnProperty(0) === false ? '?' : Core[0] : '?'
|
if (coreMatch) {
|
||||||
if (Core === '?') {
|
coreCount = parseFloat(coreMatch[0]);
|
||||||
let Core = str.match(/(\d|\.)+ Virtual/g);
|
} else {
|
||||||
Core = Core !== null ? Core.hasOwnProperty(0) === false ? '?' : Core[0] : '?'
|
let coreMatch = str.match(/(\d+(\.\d+)?) Virtual/g);
|
||||||
return Core.replace('Virtual', 'Core')
|
coreCount = coreMatch ? parseFloat(coreMatch[0]) : 0;
|
||||||
}
|
}
|
||||||
return Core.replace('Physical', 'Core');
|
totalCores += coreCount;
|
||||||
|
});
|
||||||
|
return `${totalCores} Cores`;
|
||||||
},
|
},
|
||||||
getK2Gb(bs){
|
getK2Gb(bs){
|
||||||
bs = bs / 1024 / 1024 / 1024;
|
bs = bs / 1024 / 1024 / 1024;
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ func (s *NezhaHandler) ReportSystemInfo(c context.Context, r *pb.Host) (*pb.Rece
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 虽然会在启动时panic, 可以断言不会走这个分支, 但是考虑到动态加载配置或者其它情况, 这里输出一下方便检查奇奇怪怪的BUG
|
// 虽然会在启动时panic, 可以断言不会走这个分支, 但是考虑到动态加载配置或者其它情况, 这里输出一下方便检查奇奇怪怪的BUG
|
||||||
log.Printf("NEZHA>> 未找到对应的DDNS配置(%s), 或者是provider填写不正确, 请前往config.yml检查你的设置\n", singleton.ServerList[clientID].DDNSProfile)
|
log.Printf("NEZHA>> 未找到对应的DDNS配置(%s), 或者是provider填写不正确, 请前往config.yml检查你的设置", singleton.ServerList[clientID].DDNSProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-53
@@ -3,86 +3,72 @@ package singleton
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"slices"
|
||||||
|
|
||||||
ddns2 "github.com/naiba/nezha/pkg/ddns"
|
ddns2 "github.com/naiba/nezha/pkg/ddns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RetryableUpdateDomain(provider ddns2.Provider, config *ddns2.DomainConfig, maxRetries int) bool {
|
const (
|
||||||
if nil == config {
|
ProviderWebHook = "webhook"
|
||||||
return false
|
ProviderCloudflare = "cloudflare"
|
||||||
|
ProviderTencentCloud = "tencentcloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProviderFunc func(*ddns2.DomainConfig) ddns2.Provider
|
||||||
|
|
||||||
|
func RetryableUpdateDomain(provider ddns2.Provider, domainConfig *ddns2.DomainConfig, maxRetries int) {
|
||||||
|
if domainConfig == nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
for retries := 0; retries < maxRetries; retries++ {
|
for retries := 0; retries < maxRetries; retries++ {
|
||||||
log.Printf("NEZHA>> 正在尝试更新域名(%s)DDNS(%d/%d)\n", config.FullDomain, retries+1, maxRetries)
|
log.Printf("NEZHA>> 正在尝试更新域名(%s)DDNS(%d/%d)", domainConfig.FullDomain, retries+1, maxRetries)
|
||||||
if provider.UpdateDomain(config) {
|
if err := provider.UpdateDomain(domainConfig); err != nil {
|
||||||
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS成功\n", config.FullDomain)
|
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS失败: %v", domainConfig.FullDomain, err)
|
||||||
return true
|
} else {
|
||||||
|
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS成功", domainConfig.FullDomain)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("NEZHA>> 尝试更新域名(%s)DDNS失败\n", config.FullDomain)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
func GetDDNSProviderFromString(provider string) (ddns2.Provider, error) {
|
func GetDDNSProviderFromString(provider string) (ddns2.Provider, error) {
|
||||||
switch provider {
|
switch provider {
|
||||||
case "webhook":
|
case ProviderWebHook:
|
||||||
return &ddns2.ProviderWebHook{
|
return ddns2.NewProviderWebHook(Conf.DDNS.WebhookURL, Conf.DDNS.WebhookMethod, Conf.DDNS.WebhookRequestBody, Conf.DDNS.WebhookHeaders), nil
|
||||||
URL: Conf.DDNS.WebhookURL,
|
case ProviderCloudflare:
|
||||||
RequestMethod: Conf.DDNS.WebhookMethod,
|
return ddns2.NewProviderCloudflare(Conf.DDNS.AccessSecret), nil
|
||||||
RequestBody: Conf.DDNS.WebhookRequestBody,
|
case ProviderTencentCloud:
|
||||||
RequestHeader: Conf.DDNS.WebhookHeaders,
|
return ddns2.NewProviderTencentCloud(Conf.DDNS.AccessID, Conf.DDNS.AccessSecret), nil
|
||||||
}, nil
|
default:
|
||||||
case "dummy":
|
return new(ddns2.ProviderDummy), fmt.Errorf("无法找到配置的DDNS提供者 %s", provider)
|
||||||
return &ddns2.ProviderDummy{}, nil
|
|
||||||
case "cloudflare":
|
|
||||||
return &ddns2.ProviderCloudflare{
|
|
||||||
Secret: Conf.DDNS.AccessSecret,
|
|
||||||
}, nil
|
|
||||||
case "tencentcloud":
|
|
||||||
return &ddns2.ProviderTencentCloud{
|
|
||||||
SecretID: Conf.DDNS.AccessID,
|
|
||||||
SecretKey: Conf.DDNS.AccessSecret,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
return &ddns2.ProviderDummy{}, fmt.Errorf("无法找到配置的DDNS提供者%s", Conf.DDNS.Provider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDDNSProviderFromProfile(profileName string) (ddns2.Provider, error) {
|
func GetDDNSProviderFromProfile(profileName string) (ddns2.Provider, error) {
|
||||||
profile, ok := Conf.DDNS.Profiles[profileName]
|
profile, ok := Conf.DDNS.Profiles[profileName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return &ddns2.ProviderDummy{}, fmt.Errorf("未找到配置项 %s", profileName)
|
return new(ddns2.ProviderDummy), fmt.Errorf("未找到配置项 %s", profileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch profile.Provider {
|
switch profile.Provider {
|
||||||
case "webhook":
|
case ProviderWebHook:
|
||||||
return &ddns2.ProviderWebHook{
|
return ddns2.NewProviderWebHook(profile.WebhookURL, profile.WebhookMethod, profile.WebhookRequestBody, profile.WebhookHeaders), nil
|
||||||
URL: profile.WebhookURL,
|
case ProviderCloudflare:
|
||||||
RequestMethod: profile.WebhookMethod,
|
return ddns2.NewProviderCloudflare(profile.AccessSecret), nil
|
||||||
RequestBody: profile.WebhookRequestBody,
|
case ProviderTencentCloud:
|
||||||
RequestHeader: profile.WebhookHeaders,
|
return ddns2.NewProviderTencentCloud(profile.AccessID, profile.AccessSecret), nil
|
||||||
}, nil
|
default:
|
||||||
case "dummy":
|
return new(ddns2.ProviderDummy), fmt.Errorf("无法找到配置的DDNS提供者 %s", profile.Provider)
|
||||||
return &ddns2.ProviderDummy{}, nil
|
|
||||||
case "cloudflare":
|
|
||||||
return &ddns2.ProviderCloudflare{
|
|
||||||
Secret: profile.AccessSecret,
|
|
||||||
}, nil
|
|
||||||
case "tencentcloud":
|
|
||||||
return &ddns2.ProviderTencentCloud{
|
|
||||||
SecretID: profile.AccessID,
|
|
||||||
SecretKey: profile.AccessSecret,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
return &ddns2.ProviderDummy{}, fmt.Errorf("无法找到配置的DDNS提供者%s", profile.Provider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateDDNSProvidersFromProfiles() error {
|
func ValidateDDNSProvidersFromProfiles() error {
|
||||||
validProviders := map[string]bool{"webhook": true, "dummy": true, "cloudflare": true, "tencentcloud": true}
|
validProviders := []string{ProviderWebHook, ProviderCloudflare, ProviderTencentCloud}
|
||||||
providers := make(map[string]string)
|
for _, profile := range Conf.DDNS.Profiles {
|
||||||
for profileName, profile := range Conf.DDNS.Profiles {
|
if ok := slices.Contains(validProviders, profile.Provider); !ok {
|
||||||
if _, ok := validProviders[profile.Provider]; !ok {
|
|
||||||
return fmt.Errorf("无法找到配置的DDNS提供者%s", profile.Provider)
|
return fmt.Errorf("无法找到配置的DDNS提供者%s", profile.Provider)
|
||||||
}
|
}
|
||||||
providers[profileName] = profile.Provider
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user