mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-12 04:00:04 +02:00
Compare commits
15 Commits
dev
...
feat/rules
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95ffd35585 | ||
|
|
7b0d846576 | ||
|
|
458c7779d3 | ||
|
|
dc6c649f2c | ||
|
|
3c5c3ecac2 | ||
|
|
a94442b001 | ||
|
|
2a51c2ef52 | ||
|
|
6477c35b15 | ||
|
|
5b20bbeb6f | ||
|
|
5ba475c489 | ||
|
|
54be056530 | ||
|
|
08de9086c3 | ||
|
|
1a17f3943a | ||
|
|
9bb5c54e7c | ||
|
|
faecbab2cb |
10
.github/workflows/cli-binary.yml
vendored
10
.github/workflows/cli-binary.yml
vendored
@@ -47,20 +47,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Build CLI
|
- name: Build CLI
|
||||||
run: |
|
run: |
|
||||||
make cli=1 NAME=${{ matrix.binary_name }} build
|
make CLI_BIN_PATH=bin/${{ matrix.binary_name }} build-cli
|
||||||
|
|
||||||
- name: Check binary
|
- name: Check binary
|
||||||
run: |
|
run: |
|
||||||
file bin/${{ matrix.binary_name }}
|
file bin/${{ matrix.binary_name }}
|
||||||
|
|
||||||
- name: Upload
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.binary_name }}
|
name: ${{ matrix.binary_name }}
|
||||||
path: bin/${{ matrix.binary_name }}
|
path: bin/${{ matrix.binary_name }}
|
||||||
|
|
||||||
- name: Upload to release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
with:
|
|
||||||
files: bin/${{ matrix.binary_name }}
|
|
||||||
|
|||||||
25
.github/workflows/docker-image-compat.yml
vendored
25
.github/workflows/docker-image-compat.yml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Docker Image CI (nightly)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "*" # matches every branch that doesn't contain a '/'
|
|
||||||
- "*/*" # matches every branch containing a single '/'
|
|
||||||
- "**" # matches every branch
|
|
||||||
- "!dependabot/*"
|
|
||||||
- "!main" # excludes main
|
|
||||||
- "!compat" # excludes compat branch
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-nightly:
|
|
||||||
uses: ./.github/workflows/docker-image.yml
|
|
||||||
with:
|
|
||||||
image_name: ${{ github.repository_owner }}/godoxy
|
|
||||||
tag: nightly
|
|
||||||
target: main
|
|
||||||
build-nightly-agent:
|
|
||||||
uses: ./.github/workflows/docker-image.yml
|
|
||||||
with:
|
|
||||||
image_name: ${{ github.repository_owner }}/godoxy-agent
|
|
||||||
tag: nightly
|
|
||||||
target: agent
|
|
||||||
16
.github/workflows/docker-image-nightly.yml
vendored
16
.github/workflows/docker-image-nightly.yml
vendored
@@ -1,20 +1,24 @@
|
|||||||
name: Docker Image CI (compat)
|
name: Docker Image CI (nightly)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "compat" # compat branch
|
- "*" # matches every branch that doesn't contain a '/'
|
||||||
|
- "*/*" # matches every branch containing a single '/'
|
||||||
|
- "**" # matches every branch
|
||||||
|
- "!dependabot/*"
|
||||||
|
- "!main" # excludes main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-compat:
|
build-nightly:
|
||||||
uses: ./.github/workflows/docker-image.yml
|
uses: ./.github/workflows/docker-image.yml
|
||||||
with:
|
with:
|
||||||
image_name: ${{ github.repository_owner }}/godoxy
|
image_name: ${{ github.repository_owner }}/godoxy
|
||||||
tag: compat
|
tag: nightly
|
||||||
target: main
|
target: main
|
||||||
build-compat-agent:
|
build-nightly-agent:
|
||||||
uses: ./.github/workflows/docker-image.yml
|
uses: ./.github/workflows/docker-image.yml
|
||||||
with:
|
with:
|
||||||
image_name: ${{ github.repository_owner }}/godoxy-agent
|
image_name: ${{ github.repository_owner }}/godoxy-agent
|
||||||
tag: compat
|
tag: nightly
|
||||||
target: agent
|
target: agent
|
||||||
|
|||||||
21
.github/workflows/merge-main-into-compat.yml
vendored
21
.github/workflows/merge-main-into-compat.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Refresh Compat from Main Patch
|
name: Cherry-pick into Compat
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -8,7 +8,7 @@ on:
|
|||||||
- ".github/workflows/merge-main-into-compat.yml"
|
- ".github/workflows/merge-main-into-compat.yml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
refresh-compat:
|
cherry-pick:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -20,9 +20,20 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "github-actions[bot]"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
- name: Refresh compat with single patch commit
|
- name: Cherry-pick commits from last tag
|
||||||
run: |
|
run: |
|
||||||
./scripts/refresh-compat.sh
|
git fetch origin compat
|
||||||
|
git checkout compat
|
||||||
|
CURRENT_TAG=${{ github.ref_name }}
|
||||||
|
PREV_TAG=$(git describe --tags --abbrev=0 $CURRENT_TAG^ 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -z "$PREV_TAG" ]; then
|
||||||
|
echo "No previous tag found. Cherry-picking all commits up to $CURRENT_TAG"
|
||||||
|
git rev-list --reverse --no-merges $CURRENT_TAG | xargs -r git cherry-pick
|
||||||
|
else
|
||||||
|
echo "Cherry-picking commits from $PREV_TAG to $CURRENT_TAG"
|
||||||
|
git rev-list --reverse --no-merges $PREV_TAG..$CURRENT_TAG | xargs -r git cherry-pick
|
||||||
|
fi
|
||||||
- name: Push compat
|
- name: Push compat
|
||||||
run: |
|
run: |
|
||||||
git push origin compat --force
|
git push origin compat
|
||||||
|
|||||||
32
AGENTS.md
32
AGENTS.md
@@ -1,32 +0,0 @@
|
|||||||
# AGENTS.md
|
|
||||||
|
|
||||||
## Development Commands
|
|
||||||
|
|
||||||
- Build: You should not run build command.
|
|
||||||
- Test: `go test -ldflags="-checklinkname=0" ...`
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Update package level `README.md` if exists after making significant changes.
|
|
||||||
|
|
||||||
## Go Guidelines
|
|
||||||
|
|
||||||
1. Use builtin `min` and `max` functions instead of creating custom ones
|
|
||||||
2. Prefer `for i := range 10` over `for i := 0; i < 10; i++`
|
|
||||||
3. Beware of variable shadowing when making edits
|
|
||||||
4. Use `internal/task/task.go` for lifetime management:
|
|
||||||
- `task.RootTask()` for background operations
|
|
||||||
- `parent.Subtask()` for nested tasks
|
|
||||||
- `OnFinished()` and `OnCancel()` callbacks for cleanup
|
|
||||||
5. Use `gperr "goutils/errs"` to build pretty nested errors:
|
|
||||||
- `gperr.Multiline()` for multiple operation attempts
|
|
||||||
- `gperr.NewBuilder()` to collect errors
|
|
||||||
- `gperr.NewGroup() + group.Go()` to collect errors of multiple concurrent operations
|
|
||||||
- `gperr.PrependSubject()` to prepend subject to errors
|
|
||||||
6. Use `github.com/puzpuzpuz/xsync/v4` for lock-free thread safe maps
|
|
||||||
7. Use `goutils/synk` to retrieve and put byte buffer
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
- Run scoped tests instead of `./...`
|
|
||||||
- Use `testify`, no manual assertions.
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Stage 1: deps
|
# Stage 1: deps
|
||||||
FROM golang:1.26.1-alpine AS deps
|
FROM golang:1.26.0-alpine AS deps
|
||||||
HEALTHCHECK NONE
|
HEALTHCHECK NONE
|
||||||
|
|
||||||
# package version does not matter
|
# package version does not matter
|
||||||
|
|||||||
36
Makefile
36
Makefile
@@ -6,8 +6,8 @@ export GOOS = linux
|
|||||||
|
|
||||||
REPO_URL ?= https://github.com/yusing/godoxy
|
REPO_URL ?= https://github.com/yusing/godoxy
|
||||||
|
|
||||||
WEBUI_DIR ?= $(shell pwd)/../godoxy-webui
|
WEBUI_DIR ?= ../godoxy-webui
|
||||||
DOCS_DIR ?= ${WEBUI_DIR}/wiki
|
DOCS_DIR ?= wiki
|
||||||
|
|
||||||
ifneq ($(BRANCH), compat)
|
ifneq ($(BRANCH), compat)
|
||||||
GO_TAGS = sonic
|
GO_TAGS = sonic
|
||||||
@@ -17,22 +17,15 @@ endif
|
|||||||
|
|
||||||
LDFLAGS = -X github.com/yusing/goutils/version.version=${VERSION} -checklinkname=0
|
LDFLAGS = -X github.com/yusing/goutils/version.version=${VERSION} -checklinkname=0
|
||||||
|
|
||||||
PACKAGE ?= ./cmd
|
|
||||||
|
|
||||||
ifeq ($(agent), 1)
|
ifeq ($(agent), 1)
|
||||||
NAME = godoxy-agent
|
NAME = godoxy-agent
|
||||||
PWD = ${shell pwd}/agent
|
PWD = ${shell pwd}/agent
|
||||||
else ifeq ($(socket-proxy), 1)
|
else ifeq ($(socket-proxy), 1)
|
||||||
NAME = godoxy-socket-proxy
|
NAME = godoxy-socket-proxy
|
||||||
PWD = ${shell pwd}/socket-proxy
|
PWD = ${shell pwd}/socket-proxy
|
||||||
else ifeq ($(cli), 1)
|
|
||||||
NAME = godoxy-cli
|
|
||||||
PWD = ${shell pwd}/cmd/cli
|
|
||||||
PACKAGE = .
|
|
||||||
else
|
else
|
||||||
NAME = godoxy
|
NAME = godoxy
|
||||||
PWD = ${shell pwd}
|
PWD = ${shell pwd}
|
||||||
godoxy = 1
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(trace), 1)
|
ifeq ($(trace), 1)
|
||||||
@@ -65,6 +58,7 @@ endif
|
|||||||
|
|
||||||
BUILD_FLAGS += -tags '$(GO_TAGS)' -ldflags='$(LDFLAGS)'
|
BUILD_FLAGS += -tags '$(GO_TAGS)' -ldflags='$(LDFLAGS)'
|
||||||
BIN_PATH := $(shell pwd)/bin/${NAME}
|
BIN_PATH := $(shell pwd)/bin/${NAME}
|
||||||
|
CLI_BIN_PATH ?= $(shell pwd)/bin/godoxy-cli
|
||||||
|
|
||||||
export NAME
|
export NAME
|
||||||
export CGO_ENABLED
|
export CGO_ENABLED
|
||||||
@@ -82,11 +76,7 @@ endif
|
|||||||
|
|
||||||
|
|
||||||
# CAP_NET_BIND_SERVICE: permission for binding to :80 and :443
|
# CAP_NET_BIND_SERVICE: permission for binding to :80 and :443
|
||||||
POST_BUILD = echo;
|
POST_BUILD = $(SETCAP_CMD) CAP_NET_BIND_SERVICE=+ep ${BIN_PATH};
|
||||||
|
|
||||||
ifeq ($(godoxy), 1)
|
|
||||||
POST_BUILD += $(SETCAP_CMD) CAP_NET_BIND_SERVICE=+ep ${BIN_PATH};
|
|
||||||
endif
|
|
||||||
ifeq ($(docker), 1)
|
ifeq ($(docker), 1)
|
||||||
POST_BUILD += mkdir -p /app && mv ${BIN_PATH} /app/run;
|
POST_BUILD += mkdir -p /app && mv ${BIN_PATH} /app/run;
|
||||||
endif
|
endif
|
||||||
@@ -143,18 +133,13 @@ minify-js:
|
|||||||
done \
|
done \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
build:
|
build: minify-js
|
||||||
@if [ "${godoxy}" = "1" ]; then \
|
|
||||||
make minify-js; \
|
|
||||||
elif [ "${cli}" = "1" ]; then \
|
|
||||||
make gen-cli; \
|
|
||||||
fi
|
|
||||||
mkdir -p $(shell dirname ${BIN_PATH})
|
mkdir -p $(shell dirname ${BIN_PATH})
|
||||||
go build -C ${PWD} ${BUILD_FLAGS} -o ${BIN_PATH} ${PACKAGE}
|
go build -C ${PWD} ${BUILD_FLAGS} -o ${BIN_PATH} ./cmd
|
||||||
${POST_BUILD}
|
${POST_BUILD}
|
||||||
|
|
||||||
run: minify-js
|
run: minify-js
|
||||||
cd ${PWD} && [ -f .env ] && godotenv -f .env go run ${BUILD_FLAGS} ${PACKAGE}
|
cd ${PWD} && [ -f .env ] && godotenv -f .env go run ${BUILD_FLAGS} ./cmd
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
docker compose -f dev.compose.yml $(args)
|
docker compose -f dev.compose.yml $(args)
|
||||||
@@ -201,10 +186,13 @@ gen-api-types: gen-swagger
|
|||||||
bunx --bun swagger-typescript-api generate --sort-types --generate-union-enums --axios --add-readonly --route-types \
|
bunx --bun swagger-typescript-api generate --sort-types --generate-union-enums --axios --add-readonly --route-types \
|
||||||
--responses -o ${WEBUI_DIR}/src/lib -n api.ts -p internal/api/v1/docs/swagger.json
|
--responses -o ${WEBUI_DIR}/src/lib -n api.ts -p internal/api/v1/docs/swagger.json
|
||||||
|
|
||||||
.PHONY: gen-cli build-cli update-wiki
|
|
||||||
|
|
||||||
gen-cli:
|
gen-cli:
|
||||||
cd cmd/cli && go run ./gen
|
cd cmd/cli && go run ./gen
|
||||||
|
|
||||||
|
build-cli: gen-cli
|
||||||
|
mkdir -p $(shell dirname ${CLI_BIN_PATH})
|
||||||
|
go build -C cmd/cli -o ${CLI_BIN_PATH} .
|
||||||
|
|
||||||
|
.PHONY: gen-cli build-cli update-wiki
|
||||||
update-wiki:
|
update-wiki:
|
||||||
DOCS_DIR=${DOCS_DIR} REPO_URL=${REPO_URL} bun --bun scripts/update-wiki/main.ts
|
DOCS_DIR=${DOCS_DIR} REPO_URL=${REPO_URL} bun --bun scripts/update-wiki/main.ts
|
||||||
|
|||||||
41
agent/go.mod
41
agent/go.mod
@@ -1,12 +1,10 @@
|
|||||||
module github.com/yusing/godoxy/agent
|
module github.com/yusing/godoxy/agent
|
||||||
|
|
||||||
go 1.26.1
|
go 1.26.0
|
||||||
|
|
||||||
exclude (
|
exclude (
|
||||||
github.com/moby/moby/api v1.53.0 // allow older daemon versions
|
github.com/moby/moby/api v1.53.0 // allow older daemon versions
|
||||||
github.com/moby/moby/api v1.54.0 // allow older daemon versions
|
|
||||||
github.com/moby/moby/client v0.2.2 // allow older daemon versions
|
github.com/moby/moby/client v0.2.2 // allow older daemon versions
|
||||||
github.com/moby/moby/client v0.3.0 // allow older daemon versions
|
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
@@ -23,13 +21,13 @@ exclude github.com/containerd/nerdctl/mod/tigron v0.0.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.15.0
|
github.com/bytedance/sonic v1.15.0
|
||||||
github.com/gin-gonic/gin v1.12.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/pion/dtls/v3 v3.1.2
|
github.com/pion/dtls/v3 v3.1.2
|
||||||
github.com/pion/transport/v3 v3.1.1
|
github.com/pion/transport/v3 v3.1.1
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/yusing/godoxy v0.27.4
|
github.com/yusing/godoxy v0.26.0
|
||||||
github.com/yusing/godoxy/socketproxy v0.0.0-00010101000000-000000000000
|
github.com/yusing/godoxy/socketproxy v0.0.0-00010101000000-000000000000
|
||||||
github.com/yusing/goutils v0.7.0
|
github.com/yusing/goutils v0.7.0
|
||||||
)
|
)
|
||||||
@@ -37,7 +35,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
@@ -45,10 +43,10 @@ require (
|
|||||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
github.com/docker/cli v29.3.0+incompatible // indirect
|
github.com/docker/cli v29.2.1+incompatible // indirect
|
||||||
github.com/docker/go-connections v0.6.0 // indirect
|
github.com/docker/go-connections v0.6.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/ebitengine/purego v0.10.0 // indirect
|
github.com/ebitengine/purego v0.9.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
@@ -58,7 +56,7 @@ require (
|
|||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.6 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||||
@@ -83,7 +81,7 @@ require (
|
|||||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
|
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||||
github.com/shirou/gopsutil/v4 v4.26.2 // indirect
|
github.com/shirou/gopsutil/v4 v4.26.1 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||||
@@ -93,20 +91,19 @@ require (
|
|||||||
github.com/valyala/fasthttp v1.69.0 // indirect
|
github.com/valyala/fasthttp v1.69.0 // indirect
|
||||||
github.com/yusing/ds v0.4.1 // indirect
|
github.com/yusing/ds v0.4.1 // indirect
|
||||||
github.com/yusing/gointernals v0.2.0 // indirect
|
github.com/yusing/gointernals v0.2.0 // indirect
|
||||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260310041503-e48e337bd10c // indirect
|
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260218062549-0b0fa3a059ec // indirect
|
||||||
github.com/yusing/goutils/http/websocket v0.0.0-20260310041503-e48e337bd10c // indirect
|
github.com/yusing/goutils/http/websocket v0.0.0-20260218062549-0b0fa3a059ec // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.42.0 // indirect
|
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.42.0 // indirect
|
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||||
golang.org/x/arch v0.25.0 // indirect
|
golang.org/x/arch v0.24.0 // indirect
|
||||||
golang.org/x/crypto v0.49.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.52.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
106
agent/go.sum
106
agent/go.sum
@@ -1,15 +1,15 @@
|
|||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
|
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||||
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
|
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||||
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
|
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
|
||||||
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
|
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
|
||||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||||
@@ -37,14 +37,14 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
|||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||||
github.com/docker/cli v29.3.0+incompatible h1:z3iWveU7h19Pqx7alZES8j+IeFQZ1lhTwb2F+V9SVvk=
|
github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg=
|
||||||
github.com/docker/cli v29.3.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
@@ -53,8 +53,8 @@ github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9
|
|||||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
github.com/go-acme/lego/v4 v4.32.0 h1:z7Ss7aa1noabhKj+DBzhNCO2SM96xhE3b0ucVW3x8Tc=
|
github.com/go-acme/lego/v4 v4.32.0 h1:z7Ss7aa1noabhKj+DBzhNCO2SM96xhE3b0ucVW3x8Tc=
|
||||||
github.com/go-acme/lego/v4 v4.32.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
github.com/go-acme/lego/v4 v4.32.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||||
@@ -77,8 +77,8 @@ github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy0
|
|||||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
@@ -93,8 +93,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
|||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gotify/server/v2 v2.9.1 h1:wsQUCdYJ4ZvP7RIRKDLtAtmFQc3kxbrv3QqccO5RWzs=
|
github.com/gotify/server/v2 v2.9.0 h1:2zRCl28wkq0oc6YNbyJS2n0dDOOVvOS3Oez5AG2ij54=
|
||||||
github.com/gotify/server/v2 v2.9.1/go.mod h1:8scw0hiExomp4rJDrXBwRIcgQm7kv74P4Z4B+iM4l8w=
|
github.com/gotify/server/v2 v2.9.0/go.mod h1:249wwlUqHTr0QsiKARGtFVqds0pNLIMjYLinHyMACdQ=
|
||||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
|
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
|
||||||
@@ -115,8 +115,8 @@ github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6
|
|||||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
github.com/luthermonson/go-proxmox v0.4.0 h1:LKXpG9d64zTaQF79wV0kfOnnSwIcdG39m7sc4ga+XZs=
|
github.com/luthermonson/go-proxmox v0.4.0 h1:LKXpG9d64zTaQF79wV0kfOnnSwIcdG39m7sc4ga+XZs=
|
||||||
github.com/luthermonson/go-proxmox v0.4.0/go.mod h1:U6dAkJ+iiwaeb1g/LMWpWuWN4nmvWeXhmoMuYJMumS4=
|
github.com/luthermonson/go-proxmox v0.4.0/go.mod h1:U6dAkJ+iiwaeb1g/LMWpWuWN4nmvWeXhmoMuYJMumS4=
|
||||||
github.com/magefile/mage v1.16.1 h1:j5UwkdA48xTlGs0Hcm1Q3sSAcxBorntQjiewDNMsqlo=
|
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||||
github.com/magefile/mage v1.16.1/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
@@ -172,8 +172,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
|
|||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||||
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||||
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
|
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
|
||||||
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
||||||
github.com/samber/slog-zerolog/v2 v2.9.1 h1:RMOq8XqzfuGx1X0TEIlS9OXbbFmqLY2/wJppghz66YY=
|
github.com/samber/slog-zerolog/v2 v2.9.1 h1:RMOq8XqzfuGx1X0TEIlS9OXbbFmqLY2/wJppghz66YY=
|
||||||
@@ -214,52 +214,50 @@ github.com/yusing/gointernals v0.2.0 h1:jyWB3kdUPkuU6s0r8QY/sS5h2WNBF4Kfisly8dtS
|
|||||||
github.com/yusing/gointernals v0.2.0/go.mod h1:xGzNbPGMm5Z8kG0t4JYISMscw+gMQlgghkLxlgRZv5Y=
|
github.com/yusing/gointernals v0.2.0/go.mod h1:xGzNbPGMm5Z8kG0t4JYISMscw+gMQlgghkLxlgRZv5Y=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
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/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||||
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
|
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||||
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
|
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
|
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
|
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||||
golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
|
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
|
||||||
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.19.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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.26.1-alpine AS builder
|
FROM golang:1.26.0-alpine AS builder
|
||||||
|
|
||||||
HEALTHCHECK NONE
|
HEALTHCHECK NONE
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module github.com/yusing/godoxy/cmd/bench_server
|
module github.com/yusing/godoxy/cmd/bench_server
|
||||||
|
|
||||||
go 1.26.1
|
go 1.26.0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/yusing/godoxy/cli
|
module github.com/yusing/godoxy/cli
|
||||||
|
|
||||||
go 1.26.1
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.26.1-alpine AS builder
|
FROM golang:1.26.0-alpine AS builder
|
||||||
|
|
||||||
HEALTHCHECK NONE
|
HEALTHCHECK NONE
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module github.com/yusing/godoxy/cmd/h2c_test_server
|
module github.com/yusing/godoxy/cmd/h2c_test_server
|
||||||
|
|
||||||
go 1.26.1
|
go 1.26.0
|
||||||
|
|
||||||
require golang.org/x/net v0.52.0
|
require golang.org/x/net v0.50.0
|
||||||
|
|
||||||
require golang.org/x/text v0.35.0 // indirect
|
require golang.org/x/text v0.34.0 // indirect
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
|
|||||||
@@ -52,9 +52,6 @@ entrypoint:
|
|||||||
# Note that HTTP/3 with proxy protocol is not supported yet.
|
# Note that HTTP/3 with proxy protocol is not supported yet.
|
||||||
support_proxy_protocol: false
|
support_proxy_protocol: false
|
||||||
|
|
||||||
# To relay the client address to a TCP upstream, enable `relay_proxy_protocol_header: true`
|
|
||||||
# on that specific TCP route. UDP relay is not supported yet.
|
|
||||||
|
|
||||||
# Below define an example of middleware config
|
# Below define an example of middleware config
|
||||||
# 1. set security headers
|
# 1. set security headers
|
||||||
# 2. block non local IP connections
|
# 2. block non local IP connections
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
services:
|
|
||||||
netbird-dashboard:
|
|
||||||
image: netbirdio/dashboard:latest
|
|
||||||
container_name: netbird-dashboard
|
|
||||||
restart: unless-stopped
|
|
||||||
networks: [netbird-net]
|
|
||||||
env_file: dashboard.env
|
|
||||||
labels:
|
|
||||||
proxy.aliases: netbird
|
|
||||||
proxy.#1.port: 80
|
|
||||||
proxy.#1.scheme: http
|
|
||||||
proxy.#1.homepage.name: NetBird
|
|
||||||
proxy.#1.homepage.icon: "@selfhst/netbird.svg"
|
|
||||||
proxy.#1.homepage.category: networking
|
|
||||||
proxy.#1.rules: | # https://docs.netbird.io/selfhosted/configuration-files
|
|
||||||
path glob(/signalexchange.SignalExchange/**) | path glob(/management.ManagementService/**) | path glob(/management.ProxyService/**) {
|
|
||||||
route netbird-grpc
|
|
||||||
}
|
|
||||||
path glob(/relay*) | path glob(/ws-proxy/**) | path glob(/api*) | path glob(/oauth2*) {
|
|
||||||
route netbird-api
|
|
||||||
}
|
|
||||||
default {
|
|
||||||
pass
|
|
||||||
}
|
|
||||||
netbird-server:
|
|
||||||
image: netbirdio/netbird-server:latest
|
|
||||||
container_name: netbird-server
|
|
||||||
restart: unless-stopped
|
|
||||||
networks:
|
|
||||||
- netbird-net
|
|
||||||
volumes:
|
|
||||||
- netbird_data:/var/lib/netbird
|
|
||||||
- ./config.yaml:/etc/netbird/config.yaml
|
|
||||||
command: ["--config", "/etc/netbird/config.yaml"]
|
|
||||||
labels:
|
|
||||||
proxy.aliases: netbird-api, netbird-grpc
|
|
||||||
proxy.*.port: 80
|
|
||||||
proxy.*.homepage.show: false
|
|
||||||
proxy.#1.scheme: http
|
|
||||||
proxy.#2.scheme: h2c
|
|
||||||
|
|
||||||
networks:
|
|
||||||
netbird-net:
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
netbird_data:
|
|
||||||
83
go.mod
83
go.mod
@@ -1,12 +1,10 @@
|
|||||||
module github.com/yusing/godoxy
|
module github.com/yusing/godoxy
|
||||||
|
|
||||||
go 1.26.1
|
go 1.26.0
|
||||||
|
|
||||||
exclude (
|
exclude (
|
||||||
github.com/moby/moby/api v1.53.0 // allow older daemon versions
|
github.com/moby/moby/api v1.53.0 // allow older daemon versions
|
||||||
github.com/moby/moby/api v1.54.0 // allow older daemon versions
|
|
||||||
github.com/moby/moby/client v0.2.2 // allow older daemon versions
|
github.com/moby/moby/client v0.2.2 // allow older daemon versions
|
||||||
github.com/moby/moby/client v0.3.0 // allow older daemon versions
|
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
@@ -22,32 +20,32 @@ replace (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.12.0 // parsing HTML for extract fav icon; modify_html middleware
|
github.com/PuerkitoBio/goquery v1.11.0 // parsing HTML for extract fav icon; modify_html middleware
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 // backoff for retrying operations
|
github.com/cenkalti/backoff/v5 v5.0.3 // backoff for retrying operations
|
||||||
github.com/coreos/go-oidc/v3 v3.17.0 // oidc authentication
|
github.com/coreos/go-oidc/v3 v3.17.0 // oidc authentication
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // file watcher
|
github.com/fsnotify/fsnotify v1.9.0 // file watcher
|
||||||
github.com/gin-gonic/gin v1.12.0 // api server
|
github.com/gin-gonic/gin v1.11.0 // api server
|
||||||
github.com/go-acme/lego/v4 v4.32.0 // acme client
|
github.com/go-acme/lego/v4 v4.32.0 // acme client
|
||||||
github.com/go-playground/validator/v10 v10.30.1 // validator
|
github.com/go-playground/validator/v10 v10.30.1 // validator
|
||||||
github.com/gobwas/glob v0.2.3 // glob matcher for route rules
|
github.com/gobwas/glob v0.2.3 // glob matcher for route rules
|
||||||
github.com/gorilla/websocket v1.5.3 // websocket for API and agent
|
github.com/gorilla/websocket v1.5.3 // websocket for API and agent
|
||||||
github.com/gotify/server/v2 v2.9.1 // reference the Message struct for json response
|
github.com/gotify/server/v2 v2.9.0 // reference the Message struct for json response
|
||||||
github.com/lithammer/fuzzysearch v1.1.8 // fuzzy search for searching icons and filtering metrics
|
github.com/lithammer/fuzzysearch v1.1.8 // fuzzy search for searching icons and filtering metrics
|
||||||
github.com/pires/go-proxyproto v0.11.0 // proxy protocol support
|
github.com/pires/go-proxyproto v0.11.0 // proxy protocol support
|
||||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 // lock free map for concurrent operations
|
github.com/puzpuzpuz/xsync/v4 v4.4.0 // lock free map for concurrent operations
|
||||||
github.com/rs/zerolog v1.34.0 // logging
|
github.com/rs/zerolog v1.34.0 // logging
|
||||||
github.com/vincent-petithory/dataurl v1.0.0 // data url for fav icon
|
github.com/vincent-petithory/dataurl v1.0.0 // data url for fav icon
|
||||||
golang.org/x/crypto v0.49.0 // encrypting password with bcrypt
|
golang.org/x/crypto v0.48.0 // encrypting password with bcrypt
|
||||||
golang.org/x/net v0.52.0 // HTTP header utilities
|
golang.org/x/net v0.50.0 // HTTP header utilities
|
||||||
golang.org/x/oauth2 v0.36.0 // oauth2 authentication
|
golang.org/x/oauth2 v0.35.0 // oauth2 authentication
|
||||||
golang.org/x/sync v0.20.0 // errgroup and singleflight for concurrent operations
|
golang.org/x/sync v0.19.0 // errgroup and singleflight for concurrent operations
|
||||||
golang.org/x/time v0.15.0 // time utilities
|
golang.org/x/time v0.14.0 // time utilities
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/gopkg v0.1.4 // xxhash64 for fast hash
|
github.com/bytedance/gopkg v0.1.3 // xxhash64 for fast hash
|
||||||
github.com/bytedance/sonic v1.15.0 // fast json parsing
|
github.com/bytedance/sonic v1.15.0 // fast json parsing
|
||||||
github.com/docker/cli v29.3.0+incompatible // needs docker/cli/cli/connhelper connection helper for docker client
|
github.com/docker/cli v29.2.1+incompatible // needs docker/cli/cli/connhelper connection helper for docker client
|
||||||
github.com/goccy/go-yaml v1.19.2 // yaml parsing for different config files
|
github.com/goccy/go-yaml v1.19.2 // yaml parsing for different config files
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 // jwt authentication
|
github.com/golang-jwt/jwt/v5 v5.3.1 // jwt authentication
|
||||||
github.com/luthermonson/go-proxmox v0.4.0 // proxmox API client
|
github.com/luthermonson/go-proxmox v0.4.0 // proxmox API client
|
||||||
@@ -55,18 +53,18 @@ require (
|
|||||||
github.com/moby/moby/client v0.2.1 // docker client
|
github.com/moby/moby/client v0.2.1 // docker client
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1 // maxminddb for geoip database
|
github.com/oschwald/maxminddb-golang v1.13.1 // maxminddb for geoip database
|
||||||
github.com/quic-go/quic-go v0.59.0 // http3 support
|
github.com/quic-go/quic-go v0.59.0 // http3 support
|
||||||
github.com/shirou/gopsutil/v4 v4.26.2 // system information
|
github.com/shirou/gopsutil/v4 v4.26.1 // system information
|
||||||
github.com/spf13/afero v1.15.0 // afero for file system operations
|
github.com/spf13/afero v1.15.0 // afero for file system operations
|
||||||
github.com/stretchr/testify v1.11.1 // testing framework
|
github.com/stretchr/testify v1.11.1 // testing framework
|
||||||
github.com/valyala/fasthttp v1.69.0 // fast http for health check
|
github.com/valyala/fasthttp v1.69.0 // fast http for health check
|
||||||
github.com/yusing/ds v0.4.1 // data structures and algorithms
|
github.com/yusing/ds v0.4.1 // data structures and algorithms
|
||||||
github.com/yusing/godoxy/agent v0.0.0-20260311035107-3c84692b40d7
|
github.com/yusing/godoxy/agent v0.0.0-20260218101334-add7884a365e
|
||||||
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260311035107-3c84692b40d7
|
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260218101334-add7884a365e
|
||||||
github.com/yusing/gointernals v0.2.0
|
github.com/yusing/gointernals v0.2.0
|
||||||
github.com/yusing/goutils v0.7.0
|
github.com/yusing/goutils v0.7.0
|
||||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260310041503-e48e337bd10c
|
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260218062549-0b0fa3a059ec
|
||||||
github.com/yusing/goutils/http/websocket v0.0.0-20260310041503-e48e337bd10c
|
github.com/yusing/goutils/http/websocket v0.0.0-20260218062549-0b0fa3a059ec
|
||||||
github.com/yusing/goutils/server v0.0.0-20260310041503-e48e337bd10c
|
github.com/yusing/goutils/server v0.0.0-20260218062549-0b0fa3a059ec
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -79,7 +77,7 @@ require (
|
|||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 // indirect
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||||
@@ -90,7 +88,7 @@ require (
|
|||||||
github.com/djherbis/times v1.6.0 // indirect
|
github.com/djherbis/times v1.6.0 // indirect
|
||||||
github.com/docker/go-connections v0.6.0
|
github.com/docker/go-connections v0.6.0
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/ebitengine/purego v0.10.0 // indirect
|
github.com/ebitengine/purego v0.9.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||||
@@ -101,15 +99,15 @@ require (
|
|||||||
github.com/gofrs/flock v0.13.0 // indirect
|
github.com/gofrs/flock v0.13.0 // indirect
|
||||||
github.com/google/s2a-go v0.1.9 // indirect
|
github.com/google/s2a-go v0.1.9 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.19.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||||
github.com/jinzhu/copier v0.4.0 // indirect
|
github.com/jinzhu/copier v0.4.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/magefile/mage v1.16.1 // indirect
|
github.com/magefile/mage v1.15.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/miekg/dns v1.1.72 // indirect
|
github.com/miekg/dns v1.1.72 // indirect
|
||||||
@@ -126,7 +124,7 @@ require (
|
|||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/samber/lo v1.53.0 // indirect
|
github.com/samber/lo v1.52.0 // indirect
|
||||||
github.com/samber/slog-common v0.20.0 // indirect
|
github.com/samber/slog-common v0.20.0 // indirect
|
||||||
github.com/samber/slog-zerolog/v2 v2.9.1 // indirect
|
github.com/samber/slog-zerolog/v2 v2.9.1 // indirect
|
||||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
|
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
|
||||||
@@ -134,19 +132,19 @@ require (
|
|||||||
github.com/sony/gobreaker v1.0.0 // indirect
|
github.com/sony/gobreaker v1.0.0 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0
|
||||||
go.opentelemetry.io/otel v1.42.0 // indirect
|
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.42.0 // indirect
|
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
go.uber.org/ratelimit v0.3.1 // indirect
|
go.uber.org/ratelimit v0.3.1 // indirect
|
||||||
golang.org/x/mod v0.34.0 // indirect
|
golang.org/x/mod v0.33.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
golang.org/x/tools v0.43.0 // indirect
|
golang.org/x/tools v0.42.0 // indirect
|
||||||
google.golang.org/api v0.272.0 // indirect
|
google.golang.org/api v0.267.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
||||||
google.golang.org/grpc v1.79.3 // indirect
|
google.golang.org/grpc v1.79.1 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.1 // indirect
|
gopkg.in/ini.v1 v1.67.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
@@ -169,16 +167,16 @@ require (
|
|||||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
||||||
github.com/go-resty/resty/v2 v2.17.2 // indirect
|
github.com/go-resty/resty/v2 v2.17.2 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.6 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/google/go-querystring v1.2.0 // indirect
|
github.com/google/go-querystring v1.2.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.4 // indirect
|
github.com/klauspost/compress v1.18.4 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||||
github.com/linode/linodego v1.66.0 // indirect
|
github.com/linode/linodego v1.65.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
||||||
github.com/nrdcg/goinwx v0.12.0 // indirect
|
github.com/nrdcg/goinwx v0.12.0 // indirect
|
||||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2 // indirect
|
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 // indirect
|
||||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2 // indirect
|
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
github.com/pion/dtls/v3 v3.1.2 // indirect
|
github.com/pion/dtls/v3 v3.1.2 // indirect
|
||||||
github.com/pion/logging v0.2.4 // indirect
|
github.com/pion/logging v0.2.4 // indirect
|
||||||
@@ -192,8 +190,7 @@ require (
|
|||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/vultr/govultr/v3 v3.28.1 // indirect
|
github.com/vultr/govultr/v3 v3.27.0 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
golang.org/x/arch v0.24.0 // indirect
|
||||||
golang.org/x/arch v0.25.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
154
go.sum
154
go.sum
@@ -25,12 +25,12 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.
|
|||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
|
||||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 h1:4iB+IesclUXdP0ICgAabvq2FYLXrJWKx1fJQ+GxSo3Y=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
|
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||||
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
|
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 h1:h/33OxYLqBk0BYmEbSUy7MlvgQR/m1w1/7OJFKoPL1I=
|
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 h1:h/33OxYLqBk0BYmEbSUy7MlvgQR/m1w1/7OJFKoPL1I=
|
||||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q=
|
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q=
|
||||||
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
|
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
|
||||||
@@ -49,8 +49,8 @@ github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVk
|
|||||||
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
|
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
|
||||||
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
|
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
|
||||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||||
@@ -76,14 +76,14 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
|||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||||
github.com/docker/cli v29.3.0+incompatible h1:z3iWveU7h19Pqx7alZES8j+IeFQZ1lhTwb2F+V9SVvk=
|
github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg=
|
||||||
github.com/docker/cli v29.3.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY=
|
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY=
|
||||||
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
|
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
@@ -98,8 +98,8 @@ github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9
|
|||||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
github.com/go-acme/lego/v4 v4.32.0 h1:z7Ss7aa1noabhKj+DBzhNCO2SM96xhE3b0ucVW3x8Tc=
|
github.com/go-acme/lego/v4 v4.32.0 h1:z7Ss7aa1noabhKj+DBzhNCO2SM96xhE3b0ucVW3x8Tc=
|
||||||
github.com/go-acme/lego/v4 v4.32.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
github.com/go-acme/lego/v4 v4.32.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||||
@@ -130,8 +130,8 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE
|
|||||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
@@ -151,14 +151,14 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
|||||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||||
github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
|
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||||
github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
|
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gotify/server/v2 v2.9.1 h1:wsQUCdYJ4ZvP7RIRKDLtAtmFQc3kxbrv3QqccO5RWzs=
|
github.com/gotify/server/v2 v2.9.0 h1:2zRCl28wkq0oc6YNbyJS2n0dDOOVvOS3Oez5AG2ij54=
|
||||||
github.com/gotify/server/v2 v2.9.1/go.mod h1:8scw0hiExomp4rJDrXBwRIcgQm7kv74P4Z4B+iM4l8w=
|
github.com/gotify/server/v2 v2.9.0/go.mod h1:249wwlUqHTr0QsiKARGtFVqds0pNLIMjYLinHyMACdQ=
|
||||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||||
@@ -191,14 +191,14 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
|||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/linode/linodego v1.66.0 h1:rK8QJFaV53LWOEJvb/evhTg/dP5ElvtuZmx4iv4RJds=
|
github.com/linode/linodego v1.65.0 h1:SdsuGD8VSsPWeShXpE7ihl5vec+fD3MgwhnfYC/rj7k=
|
||||||
github.com/linode/linodego v1.66.0/go.mod h1:12ykGs9qsvxE+OU3SXuW2w+DTruWF35FPlXC7gGk2tU=
|
github.com/linode/linodego v1.65.0/go.mod h1:tOFiTErdjkbVnV+4S0+NmIE9dqqZUEM2HsJaGu8wMh8=
|
||||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
|
||||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
github.com/magefile/mage v1.16.1 h1:j5UwkdA48xTlGs0Hcm1Q3sSAcxBorntQjiewDNMsqlo=
|
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||||
github.com/magefile/mage v1.16.1/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
@@ -227,10 +227,10 @@ github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0
|
|||||||
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
||||||
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
|
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
|
||||||
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
|
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
|
||||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2 h1:SlJJlU2lgrB8dB9UFopLUNaO+JxtzLK4UWHYY3e3gvo=
|
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 h1:OWijzl3nHUApvTivl+3+78dbBwmyEHOnb+W9m6ixGbk=
|
||||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2 h1:c8B4nduK77OvP6WnsfzfZgnK6uHhZETZGYnxNfu23Go=
|
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 h1:9LsjN/zaIN7H8JE61NHpbWhxF0UGY96+kMlk3g8OvGU=
|
||||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2/go.mod h1:twxIRVoHRrrMZp1A8Mh46CqCG/gyzLnImEb4dpzleIE=
|
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2/go.mod h1:32vZH06TuwZSn+IDMO1qcDvC2vHVlzUALCwXGWPA+dc=
|
||||||
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
|
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
|
||||||
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
|
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
@@ -276,8 +276,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
|
|||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||||
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||||
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
|
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
|
||||||
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
||||||
github.com/samber/slog-zerolog/v2 v2.9.1 h1:RMOq8XqzfuGx1X0TEIlS9OXbbFmqLY2/wJppghz66YY=
|
github.com/samber/slog-zerolog/v2 v2.9.1 h1:RMOq8XqzfuGx1X0TEIlS9OXbbFmqLY2/wJppghz66YY=
|
||||||
@@ -320,8 +320,8 @@ github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZy
|
|||||||
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
||||||
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
|
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
|
||||||
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
|
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
|
||||||
github.com/vultr/govultr/v3 v3.28.1 h1:KR3LhppYARlBujY7+dcrE7YKL0Yo9qXL+msxykKQrLI=
|
github.com/vultr/govultr/v3 v3.27.0 h1:J8etMyu/Jh5+idMsu2YZpOWmDXXHeW4VZnkYXmJYHx8=
|
||||||
github.com/vultr/govultr/v3 v3.28.1/go.mod h1:2zyUw9yADQaGwKnwDesmIOlBNLrm7edsCfWHFJpWKf8=
|
github.com/vultr/govultr/v3 v3.27.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||||
@@ -333,47 +333,45 @@ github.com/yusing/gointernals v0.2.0 h1:jyWB3kdUPkuU6s0r8QY/sS5h2WNBF4Kfisly8dtS
|
|||||||
github.com/yusing/gointernals v0.2.0/go.mod h1:xGzNbPGMm5Z8kG0t4JYISMscw+gMQlgghkLxlgRZv5Y=
|
github.com/yusing/gointernals v0.2.0/go.mod h1:xGzNbPGMm5Z8kG0t4JYISMscw+gMQlgghkLxlgRZv5Y=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
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/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||||
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
|
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||||
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
|
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
|
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
|
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||||
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
|
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
|
||||||
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
|
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
|
||||||
golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
|
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
|
||||||
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
@@ -383,10 +381,10 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -394,8 +392,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -414,8 +412,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -434,31 +432,31 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
|
google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE=
|
||||||
google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
|
google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
|
||||||
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
|
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||||
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
|
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
2
goutils
2
goutils
Submodule goutils updated: 635feb302e...3be815cb6e
@@ -1,109 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/yusing/godoxy/internal/auth"
|
|
||||||
apitypes "github.com/yusing/goutils/apitypes"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CSRFMiddleware implements the Signed Double Submit Cookie pattern.
|
|
||||||
//
|
|
||||||
// Safe methods (GET/HEAD/OPTIONS): ensure a signed CSRF cookie exists.
|
|
||||||
// Unsafe methods (POST/PUT/DELETE/PATCH): require X-CSRF-Token header
|
|
||||||
// matching the cookie value, with a valid HMAC signature.
|
|
||||||
func CSRFMiddleware() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
switch c.Request.Method {
|
|
||||||
case http.MethodGet, http.MethodHead, http.MethodOptions:
|
|
||||||
ensureCSRFCookie(c)
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if allowSameOriginAuthBootstrap(c.Request) {
|
|
||||||
ensureCSRFCookie(c)
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie, err := c.Request.Cookie(auth.CSRFCookieName)
|
|
||||||
if err != nil {
|
|
||||||
// No cookie at all — issue one so the frontend can retry.
|
|
||||||
reissueCSRFCookie(c)
|
|
||||||
c.JSON(http.StatusForbidden, apitypes.Error("missing CSRF token"))
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cookieToken := canonicalCSRFToken(cookie.Value)
|
|
||||||
headerToken := canonicalCSRFToken(c.GetHeader(auth.CSRFHeaderName))
|
|
||||||
if headerToken == "" || cookieToken != headerToken || !auth.ValidateCSRFToken(cookieToken) {
|
|
||||||
// Stale or forged token — issue a fresh one so the
|
|
||||||
// frontend can read the new cookie and retry.
|
|
||||||
reissueCSRFCookie(c)
|
|
||||||
c.JSON(http.StatusForbidden, apitypes.Error("invalid CSRF token"))
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureCSRFCookie(c *gin.Context) {
|
|
||||||
if _, err := c.Request.Cookie(auth.CSRFCookieName); err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reissueCSRFCookie(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reissueCSRFCookie(c *gin.Context) {
|
|
||||||
token, err := auth.GenerateCSRFToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
auth.SetCSRFCookie(c.Writer, c.Request, token)
|
|
||||||
}
|
|
||||||
|
|
||||||
func allowSameOriginAuthBootstrap(r *http.Request) bool {
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
switch r.URL.Path {
|
|
||||||
case "/api/v1/auth/login", "/api/v1/auth/callback":
|
|
||||||
return requestSourceMatchesHost(r)
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestSourceMatchesHost(r *http.Request) bool {
|
|
||||||
for _, header := range []string{"Origin", "Referer"} {
|
|
||||||
value := r.Header.Get(header)
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
u, err := url.Parse(value)
|
|
||||||
if err != nil || u.Host == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return normalizeHost(u.Hostname()) == normalizeHost(r.Host)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeHost(host string) string {
|
|
||||||
host = strings.ToLower(host)
|
|
||||||
if h, _, err := net.SplitHostPort(host); err == nil {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
return host
|
|
||||||
}
|
|
||||||
|
|
||||||
func canonicalCSRFToken(token string) string {
|
|
||||||
return strings.Trim(strings.TrimSpace(token), "\"")
|
|
||||||
}
|
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/yusing/godoxy/internal/auth"
|
|
||||||
autocert "github.com/yusing/godoxy/internal/autocert/types"
|
|
||||||
"github.com/yusing/godoxy/internal/common"
|
|
||||||
"github.com/yusing/goutils/task"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAuthCheckIssuesCSRFCookie(t *testing.T) {
|
|
||||||
handler := newAuthenticatedHandler(t)
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodHead, "/api/v1/auth/check", nil)
|
|
||||||
req.Host = "app.example.com"
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusFound, rec.Code)
|
|
||||||
|
|
||||||
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
|
|
||||||
require.NotNil(t, csrfCookie)
|
|
||||||
assert.NotEmpty(t, csrfCookie.Value)
|
|
||||||
assert.Empty(t, csrfCookie.Domain)
|
|
||||||
assert.Equal(t, "/", csrfCookie.Path)
|
|
||||||
assert.Equal(t, http.SameSiteStrictMode, csrfCookie.SameSite)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserPassCallbackAllowsSameOriginFormPostWithoutCSRFCookie(t *testing.T) {
|
|
||||||
handler := newAuthenticatedHandler(t)
|
|
||||||
|
|
||||||
req := newJSONRequest(t, http.MethodPost, "/api/v1/auth/callback", map[string]string{
|
|
||||||
"username": common.APIUser,
|
|
||||||
"password": common.APIPassword,
|
|
||||||
})
|
|
||||||
req.Host = "app.example.com"
|
|
||||||
req.Header.Set("Origin", "https://app.example.com")
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
|
||||||
tokenCookie := findCookie(rec.Result().Cookies(), "godoxy_token")
|
|
||||||
require.NotNil(t, tokenCookie)
|
|
||||||
assert.NotEmpty(t, tokenCookie.Value)
|
|
||||||
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
|
|
||||||
require.NotNil(t, csrfCookie)
|
|
||||||
assert.NotEmpty(t, csrfCookie.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserPassCallbackRejectsCrossOriginPostWithoutCSRFCookie(t *testing.T) {
|
|
||||||
handler := newAuthenticatedHandler(t)
|
|
||||||
|
|
||||||
req := newJSONRequest(t, http.MethodPost, "/api/v1/auth/callback", map[string]string{
|
|
||||||
"username": common.APIUser,
|
|
||||||
"password": common.APIPassword,
|
|
||||||
})
|
|
||||||
req.Host = "app.example.com"
|
|
||||||
req.Header.Set("Origin", "https://evil.example.com")
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
|
||||||
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
|
|
||||||
require.NotNil(t, csrfCookie)
|
|
||||||
assert.NotEmpty(t, csrfCookie.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUserPassCallbackAcceptsValidCSRFCookie(t *testing.T) {
|
|
||||||
handler := newAuthenticatedHandler(t)
|
|
||||||
csrfCookie := issueCSRFCookie(t, handler)
|
|
||||||
|
|
||||||
req := newJSONRequest(t, http.MethodPost, "/api/v1/auth/callback", map[string]string{
|
|
||||||
"username": common.APIUser,
|
|
||||||
"password": common.APIPassword,
|
|
||||||
})
|
|
||||||
req.Host = "app.example.com"
|
|
||||||
req.AddCookie(csrfCookie)
|
|
||||||
req.Header.Set(auth.CSRFHeaderName, csrfCookie.Value)
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
|
||||||
tokenCookie := findCookie(rec.Result().Cookies(), "godoxy_token")
|
|
||||||
require.NotNil(t, tokenCookie)
|
|
||||||
assert.NotEmpty(t, tokenCookie.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnsafeRequestAcceptsQuotedCSRFCookieValue(t *testing.T) {
|
|
||||||
handler := newAuthenticatedHandler(t)
|
|
||||||
csrfCookie := issueCSRFCookie(t, handler)
|
|
||||||
sessionToken := issueSessionToken(t)
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/logout", nil)
|
|
||||||
req.Host = "app.example.com"
|
|
||||||
req.Header.Set("Cookie", `godoxy_token=`+sessionToken+`; godoxy_csrf="`+csrfCookie.Value+`"`)
|
|
||||||
req.Header.Set(auth.CSRFHeaderName, csrfCookie.Value)
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusFound, rec.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLogoutRequiresCSRFCookie(t *testing.T) {
|
|
||||||
handler := newAuthenticatedHandler(t)
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/logout", nil)
|
|
||||||
req.Host = "app.example.com"
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoginAllowsSameOriginPostWithoutCSRFCookie(t *testing.T) {
|
|
||||||
handler := newAuthenticatedHandler(t)
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", nil)
|
|
||||||
req.Host = "app.example.com"
|
|
||||||
req.Header.Set("Origin", "https://app.example.com")
|
|
||||||
req.Header.Set("Accept", "text/html")
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusFound, rec.Code)
|
|
||||||
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
|
|
||||||
require.NotNil(t, csrfCookie)
|
|
||||||
assert.NotEmpty(t, csrfCookie.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetLogoutRouteStillAvailableForFrontend(t *testing.T) {
|
|
||||||
handler := newAuthenticatedHandler(t)
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/logout", nil)
|
|
||||||
req.Host = "app.example.com"
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusFound, rec.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCertRenewRejectsCrossOriginWebSocketRequest(t *testing.T) {
|
|
||||||
handler := newAuthenticatedHandler(t)
|
|
||||||
provider := &stubAutocertProvider{}
|
|
||||||
sessionToken := issueSessionToken(t)
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/cert/renew", nil)
|
|
||||||
req.Host = "app.example.com"
|
|
||||||
req.Header.Set("Connection", "Upgrade")
|
|
||||||
req.Header.Set("Upgrade", "websocket")
|
|
||||||
req.Header.Set("Sec-WebSocket-Version", "13")
|
|
||||||
req.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
|
|
||||||
req.Header.Set("Origin", "https://evil.example.com")
|
|
||||||
req.AddCookie(&http.Cookie{Name: "godoxy_token", Value: sessionToken})
|
|
||||||
req = req.WithContext(context.WithValue(req.Context(), autocert.ContextKey{}, provider))
|
|
||||||
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
|
||||||
assert.Zero(t, provider.forceExpiryCalls)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAuthenticatedHandler(t *testing.T) *gin.Engine {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
gin.SetMode(gin.TestMode)
|
|
||||||
|
|
||||||
prevSecret := common.APIJWTSecret
|
|
||||||
prevUser := common.APIUser
|
|
||||||
prevPassword := common.APIPassword
|
|
||||||
prevDisableAuth := common.DebugDisableAuth
|
|
||||||
prevIssuerURL := common.OIDCIssuerURL
|
|
||||||
|
|
||||||
common.APIJWTSecret = []byte("0123456789abcdef0123456789abcdef")
|
|
||||||
common.APIUser = "username"
|
|
||||||
common.APIPassword = "password"
|
|
||||||
common.DebugDisableAuth = false
|
|
||||||
common.OIDCIssuerURL = ""
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
common.APIJWTSecret = prevSecret
|
|
||||||
common.APIUser = prevUser
|
|
||||||
common.APIPassword = prevPassword
|
|
||||||
common.DebugDisableAuth = prevDisableAuth
|
|
||||||
common.OIDCIssuerURL = prevIssuerURL
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, auth.Initialize())
|
|
||||||
return NewHandler(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func issueCSRFCookie(t *testing.T, handler http.Handler) *http.Cookie {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodHead, "/api/v1/auth/check", nil)
|
|
||||||
req.Host = "app.example.com"
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
csrfCookie := findCookie(rec.Result().Cookies(), auth.CSRFCookieName)
|
|
||||||
require.NotNil(t, csrfCookie)
|
|
||||||
return csrfCookie
|
|
||||||
}
|
|
||||||
|
|
||||||
func issueSessionToken(t *testing.T) string {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
userpass, ok := auth.GetDefaultAuth().(*auth.UserPassAuth)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
token, err := userpass.NewToken()
|
|
||||||
require.NoError(t, err)
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
func newJSONRequest(t *testing.T, method, target string, body any) *http.Request {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
encoded, err := json.Marshal(body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req := httptest.NewRequest(method, target, bytes.NewReader(encoded))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func findCookie(cookies []*http.Cookie, name string) *http.Cookie {
|
|
||||||
for _, cookie := range cookies {
|
|
||||||
if cookie.Name == name {
|
|
||||||
return cookie
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type stubAutocertProvider struct {
|
|
||||||
forceExpiryCalls int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *stubAutocertProvider) GetCert(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *stubAutocertProvider) GetCertInfos() ([]autocert.CertInfo, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *stubAutocertProvider) ScheduleRenewalAll(task.Parent) {}
|
|
||||||
|
|
||||||
func (p *stubAutocertProvider) ObtainCertAll() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *stubAutocertProvider) ForceExpiryAll() bool {
|
|
||||||
p.forceExpiryCalls++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *stubAutocertProvider) WaitRenewalDone(context.Context) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -56,11 +56,11 @@ func NewHandler(requireAuth bool) *gin.Engine {
|
|||||||
if auth.IsEnabled() && requireAuth {
|
if auth.IsEnabled() && requireAuth {
|
||||||
v1Auth := r.Group("/api/v1/auth")
|
v1Auth := r.Group("/api/v1/auth")
|
||||||
{
|
{
|
||||||
v1Auth.HEAD("/check", CSRFMiddleware(), authApi.Check)
|
v1Auth.HEAD("/check", authApi.Check)
|
||||||
v1Auth.POST("/login", CSRFMiddleware(), authApi.Login)
|
v1Auth.POST("/login", authApi.Login)
|
||||||
v1Auth.GET("/callback", authApi.Callback)
|
v1Auth.GET("/callback", authApi.Callback)
|
||||||
v1Auth.POST("/callback", CSRFMiddleware(), authApi.Callback)
|
v1Auth.POST("/callback", authApi.Callback)
|
||||||
v1Auth.POST("/logout", CSRFMiddleware(), authApi.Logout)
|
v1Auth.POST("/logout", authApi.Logout)
|
||||||
v1Auth.GET("/logout", authApi.Logout)
|
v1Auth.GET("/logout", authApi.Logout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,6 @@ func NewHandler(requireAuth bool) *gin.Engine {
|
|||||||
v1 := r.Group("/api/v1")
|
v1 := r.Group("/api/v1")
|
||||||
if auth.IsEnabled() && requireAuth {
|
if auth.IsEnabled() && requireAuth {
|
||||||
v1.Use(AuthMiddleware())
|
v1.Use(AuthMiddleware())
|
||||||
v1.Use(CSRFMiddleware())
|
|
||||||
}
|
}
|
||||||
if common.APISkipOriginCheck {
|
if common.APISkipOriginCheck {
|
||||||
v1.Use(SkipOriginCheckMiddleware())
|
v1.Use(SkipOriginCheckMiddleware())
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
// @Tags cert,websocket
|
// @Tags cert,websocket
|
||||||
// @Produce plain
|
// @Produce plain
|
||||||
// @Success 200 {object} apitypes.SuccessResponse
|
// @Success 200 {object} apitypes.SuccessResponse
|
||||||
// @Failure 400 {object} apitypes.ErrorResponse
|
|
||||||
// @Failure 403 {object} apitypes.ErrorResponse
|
// @Failure 403 {object} apitypes.ErrorResponse
|
||||||
// @Failure 500 {object} apitypes.ErrorResponse
|
// @Failure 500 {object} apitypes.ErrorResponse
|
||||||
// @Router /cert/renew [get]
|
// @Router /cert/renew [get]
|
||||||
|
|||||||
@@ -5125,7 +5125,10 @@
|
|||||||
"$ref": "#/definitions/MockResponse"
|
"$ref": "#/definitions/MockResponse"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"type": "string",
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/routeApi.RawRule"
|
||||||
|
},
|
||||||
"x-nullable": false,
|
"x-nullable": false,
|
||||||
"x-omitempty": false
|
"x-omitempty": false
|
||||||
}
|
}
|
||||||
@@ -6923,6 +6926,28 @@
|
|||||||
"x-nullable": false,
|
"x-nullable": false,
|
||||||
"x-omitempty": false
|
"x-omitempty": false
|
||||||
},
|
},
|
||||||
|
"routeApi.RawRule": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"do": {
|
||||||
|
"type": "string",
|
||||||
|
"x-nullable": false,
|
||||||
|
"x-omitempty": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"x-nullable": false,
|
||||||
|
"x-omitempty": false
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"type": "string",
|
||||||
|
"x-nullable": false,
|
||||||
|
"x-omitempty": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-nullable": false,
|
||||||
|
"x-omitempty": false
|
||||||
|
},
|
||||||
"routeApi.RoutesByProvider": {
|
"routeApi.RoutesByProvider": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
|
|||||||
@@ -905,7 +905,9 @@ definitions:
|
|||||||
mockResponse:
|
mockResponse:
|
||||||
$ref: '#/definitions/MockResponse'
|
$ref: '#/definitions/MockResponse'
|
||||||
rules:
|
rules:
|
||||||
type: string
|
items:
|
||||||
|
$ref: '#/definitions/routeApi.RawRule'
|
||||||
|
type: array
|
||||||
required:
|
required:
|
||||||
- rules
|
- rules
|
||||||
type: object
|
type: object
|
||||||
@@ -1856,6 +1858,15 @@ definitions:
|
|||||||
uptime:
|
uptime:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
routeApi.RawRule:
|
||||||
|
properties:
|
||||||
|
do:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
"on":
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
routeApi.RoutesByProvider:
|
routeApi.RoutesByProvider:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
items:
|
items:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package fileapi
|
package fileapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -45,14 +44,7 @@ func Get(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.OpenInRoot(".", request.FileType.GetPath(request.Filename))
|
content, err := os.ReadFile(request.FileType.GetPath(request.Filename))
|
||||||
if err != nil {
|
|
||||||
c.Error(apitypes.InternalServerError(err, "failed to open root"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
content, err := io.ReadAll(f)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error(apitypes.InternalServerError(err, "failed to read file"))
|
c.Error(apitypes.InternalServerError(err, "failed to read file"))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package fileapi_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
api "github.com/yusing/godoxy/internal/api"
|
|
||||||
fileapi "github.com/yusing/godoxy/internal/api/v1/file"
|
|
||||||
"github.com/yusing/goutils/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGet_PathTraversalBlocked(t *testing.T) {
|
|
||||||
gin.SetMode(gin.TestMode)
|
|
||||||
|
|
||||||
files, err := fs.ListFiles("..", 1, false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Greater(t, len(files), 0, "no files found")
|
|
||||||
|
|
||||||
relativePath := files[0]
|
|
||||||
|
|
||||||
fileContent, err := os.ReadFile(relativePath)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
r := gin.New()
|
|
||||||
r.Use(api.ErrorHandler())
|
|
||||||
r.GET("/api/v1/file/content", fileapi.Get)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
filename string
|
|
||||||
queryEscaped bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "dotdot_traversal",
|
|
||||||
filename: relativePath,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "url_encoded_dotdot_traversal",
|
|
||||||
filename: relativePath,
|
|
||||||
queryEscaped: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
filename := tt.filename
|
|
||||||
if tt.queryEscaped {
|
|
||||||
filename = url.QueryEscape(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
url := "/api/v1/file/content?type=config&filename=" + filename
|
|
||||||
req := httptest.NewRequest(http.MethodGet, url, nil)
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
r.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
// "Blocked" means we should never successfully read the outside file.
|
|
||||||
assert.NotEqual(t, http.StatusOK, w.Code)
|
|
||||||
assert.NotEqual(t, fileContent, w.Body.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/goccy/go-yaml"
|
|
||||||
"github.com/yusing/godoxy/internal/common"
|
"github.com/yusing/godoxy/internal/common"
|
||||||
"github.com/yusing/godoxy/internal/route/rules"
|
"github.com/yusing/godoxy/internal/route/rules"
|
||||||
apitypes "github.com/yusing/goutils/apitypes"
|
apitypes "github.com/yusing/goutils/apitypes"
|
||||||
@@ -24,7 +23,7 @@ type RawRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PlaygroundRequest struct {
|
type PlaygroundRequest struct {
|
||||||
Rules string `json:"rules" binding:"required"`
|
Rules []RawRule `json:"rules" binding:"required"`
|
||||||
MockRequest MockRequest `json:"mockRequest"`
|
MockRequest MockRequest `json:"mockRequest"`
|
||||||
MockResponse MockResponse `json:"mockResponse"`
|
MockResponse MockResponse `json:"mockResponse"`
|
||||||
} // @name PlaygroundRequest
|
} // @name PlaygroundRequest
|
||||||
@@ -256,35 +255,7 @@ func handlerWithRecover(w http.ResponseWriter, r *http.Request, h http.HandlerFu
|
|||||||
h(w, r)
|
h(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRules(config string) ([]ParsedRule, rules.Rules, error) {
|
func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, error) {
|
||||||
config = strings.TrimSpace(config)
|
|
||||||
if config == "" {
|
|
||||||
return []ParsedRule{}, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawRules []RawRule
|
|
||||||
if err := yaml.Unmarshal([]byte(config), &rawRules); err == nil && len(rawRules) > 0 {
|
|
||||||
return parseRawRules(rawRules)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rulesList rules.Rules
|
|
||||||
if err := rulesList.Parse(config); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedRules := make([]ParsedRule, 0, len(rulesList))
|
|
||||||
for _, rule := range rulesList {
|
|
||||||
parsedRules = append(parsedRules, ParsedRule{
|
|
||||||
Name: rule.Name,
|
|
||||||
On: rule.On.String(),
|
|
||||||
Do: rule.Do.String(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedRules, rulesList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRawRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, error) {
|
|
||||||
parsedRules := make([]ParsedRule, 0, len(rawRules))
|
parsedRules := make([]ParsedRule, 0, len(rawRules))
|
||||||
rulesList := make(rules.Rules, 0, len(rawRules))
|
rulesList := make(rules.Rules, 0, len(rawRules))
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,13 @@ func TestPlayground(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "simple path matching rule",
|
name: "simple path matching rule",
|
||||||
request: PlaygroundRequest{
|
request: PlaygroundRequest{
|
||||||
Rules: `- name: test rule
|
Rules: []RawRule{
|
||||||
on: path /api
|
{
|
||||||
do: pass
|
Name: "test rule",
|
||||||
`,
|
On: "path /api",
|
||||||
|
Do: "pass",
|
||||||
|
},
|
||||||
|
},
|
||||||
MockRequest: MockRequest{
|
MockRequest: MockRequest{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/api",
|
Path: "/api",
|
||||||
@@ -50,10 +53,13 @@ func TestPlayground(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "header matching rule",
|
name: "header matching rule",
|
||||||
request: PlaygroundRequest{
|
request: PlaygroundRequest{
|
||||||
Rules: `- name: check user agent
|
Rules: []RawRule{
|
||||||
on: header User-Agent Chrome
|
{
|
||||||
do: error 403 Forbidden
|
Name: "check user agent",
|
||||||
`,
|
On: "header User-Agent Chrome",
|
||||||
|
Do: "error 403 Forbidden",
|
||||||
|
},
|
||||||
|
},
|
||||||
MockRequest: MockRequest{
|
MockRequest: MockRequest{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/",
|
Path: "/",
|
||||||
@@ -84,10 +90,13 @@ func TestPlayground(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "invalid rule syntax",
|
name: "invalid rule syntax",
|
||||||
request: PlaygroundRequest{
|
request: PlaygroundRequest{
|
||||||
Rules: `- name: bad rule
|
Rules: []RawRule{
|
||||||
on: invalid_checker something
|
{
|
||||||
do: pass
|
Name: "bad rule",
|
||||||
`,
|
On: "invalid_checker something",
|
||||||
|
Do: "pass",
|
||||||
|
},
|
||||||
|
},
|
||||||
MockRequest: MockRequest{
|
MockRequest: MockRequest{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/",
|
Path: "/",
|
||||||
@@ -106,10 +115,13 @@ func TestPlayground(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "rewrite path rule",
|
name: "rewrite path rule",
|
||||||
request: PlaygroundRequest{
|
request: PlaygroundRequest{
|
||||||
Rules: `- name: rewrite rule
|
Rules: []RawRule{
|
||||||
on: path glob(/api/*)
|
{
|
||||||
do: rewrite /api/ /v1/
|
Name: "rewrite rule",
|
||||||
`,
|
On: "path glob(/api/*)",
|
||||||
|
Do: "rewrite /api/ /v1/",
|
||||||
|
},
|
||||||
|
},
|
||||||
MockRequest: MockRequest{
|
MockRequest: MockRequest{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/api/users",
|
Path: "/api/users",
|
||||||
@@ -136,10 +148,13 @@ func TestPlayground(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "method matching rule",
|
name: "method matching rule",
|
||||||
request: PlaygroundRequest{
|
request: PlaygroundRequest{
|
||||||
Rules: `- name: block POST
|
Rules: []RawRule{
|
||||||
on: method POST
|
{
|
||||||
do: error "405" "Method Not Allowed"
|
Name: "block POST",
|
||||||
`,
|
On: "method POST",
|
||||||
|
Do: `error "405" "Method Not Allowed"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
MockRequest: MockRequest{
|
MockRequest: MockRequest{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Path: "/api",
|
Path: "/api",
|
||||||
@@ -158,63 +173,6 @@ func TestPlayground(t *testing.T) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "block syntax default rule",
|
|
||||||
request: PlaygroundRequest{
|
|
||||||
Rules: `default {
|
|
||||||
pass
|
|
||||||
}`,
|
|
||||||
MockRequest: MockRequest{
|
|
||||||
Method: "GET",
|
|
||||||
Path: "/",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantStatusCode: http.StatusOK,
|
|
||||||
checkResponse: func(t *testing.T, resp PlaygroundResponse) {
|
|
||||||
if len(resp.ParsedRules) != 1 {
|
|
||||||
t.Errorf("expected 1 parsed rule, got %d", len(resp.ParsedRules))
|
|
||||||
}
|
|
||||||
if resp.ParsedRules[0].ValidationError != nil {
|
|
||||||
t.Errorf("expected rule to be valid, got error: %v", resp.ParsedRules[0].ValidationError)
|
|
||||||
}
|
|
||||||
if !resp.UpstreamCalled {
|
|
||||||
t.Error("expected upstream to be called")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "block syntax conditional rule",
|
|
||||||
request: PlaygroundRequest{
|
|
||||||
Rules: `header User-Agent Chrome {
|
|
||||||
error 403 Forbidden
|
|
||||||
}`,
|
|
||||||
MockRequest: MockRequest{
|
|
||||||
Method: "GET",
|
|
||||||
Path: "/",
|
|
||||||
Headers: map[string][]string{
|
|
||||||
"User-Agent": {"Chrome"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantStatusCode: http.StatusOK,
|
|
||||||
checkResponse: func(t *testing.T, resp PlaygroundResponse) {
|
|
||||||
if len(resp.ParsedRules) != 1 {
|
|
||||||
t.Errorf("expected 1 parsed rule, got %d", len(resp.ParsedRules))
|
|
||||||
}
|
|
||||||
if resp.ParsedRules[0].ValidationError != nil {
|
|
||||||
t.Errorf("expected rule to be valid, got error: %v", resp.ParsedRules[0].ValidationError)
|
|
||||||
}
|
|
||||||
if len(resp.MatchedRules) != 1 {
|
|
||||||
t.Errorf("expected 1 matched rule, got %d", len(resp.MatchedRules))
|
|
||||||
}
|
|
||||||
if resp.FinalResponse.StatusCode != http.StatusForbidden {
|
|
||||||
t.Errorf("expected status 403, got %d", resp.FinalResponse.StatusCode)
|
|
||||||
}
|
|
||||||
if resp.UpstreamCalled {
|
|
||||||
t.Error("expected upstream not to be called")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/yusing/godoxy/internal/common"
|
|
||||||
"golang.org/x/crypto/hkdf"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
CSRFCookieName = "godoxy_csrf"
|
|
||||||
CSRFHKDFSalt = "godoxy-csrf"
|
|
||||||
CSRFHeaderName = "X-CSRF-Token"
|
|
||||||
csrfTokenLength = 32
|
|
||||||
)
|
|
||||||
|
|
||||||
// csrfSecret is derived from API_JWT_SECRET via HKDF for cryptographic
|
|
||||||
// separation from JWT signing. Falls back to an ephemeral random key
|
|
||||||
// for OIDC-only setups where no JWT secret is configured.
|
|
||||||
var csrfSecret = func() []byte {
|
|
||||||
if common.APIJWTSecret != nil {
|
|
||||||
return hkdf.Extract(sha256.New, common.APIJWTSecret, []byte(CSRFHKDFSalt))
|
|
||||||
}
|
|
||||||
b := make([]byte, 32)
|
|
||||||
if _, err := rand.Read(b); err != nil {
|
|
||||||
panic("failed to generate CSRF secret: " + err.Error())
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}()
|
|
||||||
|
|
||||||
func GenerateCSRFToken() (string, error) {
|
|
||||||
nonce := make([]byte, csrfTokenLength)
|
|
||||||
if _, err := rand.Read(nonce); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
nonceHex := hex.EncodeToString(nonce)
|
|
||||||
return nonceHex + "." + csrfSign(nonceHex), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateCSRFToken checks the HMAC signature embedded in the token.
|
|
||||||
// This prevents subdomain cookie-injection attacks where an attacker
|
|
||||||
// sets a forged CSRF cookie — they cannot produce a valid signature
|
|
||||||
// without the ephemeral secret.
|
|
||||||
func ValidateCSRFToken(token string) bool {
|
|
||||||
nonce, sig, ok := strings.Cut(token, ".")
|
|
||||||
if !ok || len(nonce) != csrfTokenLength*2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return hmac.Equal([]byte(sig), []byte(csrfSign(nonce)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func csrfSign(nonce string) string {
|
|
||||||
mac := hmac.New(sha256.New, csrfSecret)
|
|
||||||
mac.Write([]byte(nonce))
|
|
||||||
return hex.EncodeToString(mac.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetCSRFCookie(w http.ResponseWriter, r *http.Request, token string) {
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: CSRFCookieName,
|
|
||||||
Value: token,
|
|
||||||
HttpOnly: false,
|
|
||||||
Secure: common.APIJWTSecure,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
Path: "/",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClearCSRFCookie(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: CSRFCookieName,
|
|
||||||
Value: "",
|
|
||||||
MaxAge: -1,
|
|
||||||
HttpOnly: false,
|
|
||||||
Secure: common.APIJWTSecure,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
Path: "/",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
module github.com/yusing/godoxy/internal/dnsproviders
|
module github.com/yusing/godoxy/internal/dnsproviders
|
||||||
|
|
||||||
go 1.26.1
|
go 1.26.0
|
||||||
|
|
||||||
replace github.com/yusing/godoxy => ../..
|
replace github.com/yusing/godoxy => ../..
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-acme/lego/v4 v4.32.0
|
github.com/go-acme/lego/v4 v4.32.0
|
||||||
github.com/yusing/godoxy v0.27.4
|
github.com/yusing/godoxy v0.26.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -19,11 +19,11 @@ require (
|
|||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 // indirect
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
|
||||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
|
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
|
||||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||||
github.com/boombuler/barcode v1.1.0 // indirect
|
github.com/boombuler/barcode v1.1.0 // indirect
|
||||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/bytedance/sonic v1.15.0 // indirect
|
github.com/bytedance/sonic v1.15.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
@@ -48,16 +48,16 @@ require (
|
|||||||
github.com/google/go-querystring v1.2.0 // indirect
|
github.com/google/go-querystring v1.2.0 // indirect
|
||||||
github.com/google/s2a-go v0.1.9 // indirect
|
github.com/google/s2a-go v0.1.9 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.19.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||||
github.com/gotify/server/v2 v2.9.1 // indirect
|
github.com/gotify/server/v2 v2.9.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/linode/linodego v1.66.0 // indirect
|
github.com/linode/linodego v1.65.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/maxatome/go-testdeep v1.14.0 // indirect
|
github.com/maxatome/go-testdeep v1.14.0 // indirect
|
||||||
@@ -65,8 +65,8 @@ require (
|
|||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/nrdcg/goacmedns v0.2.0 // indirect
|
github.com/nrdcg/goacmedns v0.2.0 // indirect
|
||||||
github.com/nrdcg/goinwx v0.12.0 // indirect
|
github.com/nrdcg/goinwx v0.12.0 // indirect
|
||||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2 // indirect
|
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 // indirect
|
||||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2 // indirect
|
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 // indirect
|
||||||
github.com/nrdcg/porkbun v0.4.0 // indirect
|
github.com/nrdcg/porkbun v0.4.0 // indirect
|
||||||
github.com/ovh/go-ovh v1.9.0 // indirect
|
github.com/ovh/go-ovh v1.9.0 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
@@ -79,28 +79,28 @@ require (
|
|||||||
github.com/stretchr/objx v0.5.3 // indirect
|
github.com/stretchr/objx v0.5.3 // indirect
|
||||||
github.com/stretchr/testify v1.11.1 // indirect
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/vultr/govultr/v3 v3.28.1 // indirect
|
github.com/vultr/govultr/v3 v3.27.0 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
github.com/yusing/gointernals v0.2.0 // indirect
|
github.com/yusing/gointernals v0.2.0 // indirect
|
||||||
github.com/yusing/goutils v0.7.0 // indirect
|
github.com/yusing/goutils v0.7.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.42.0 // indirect
|
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.42.0 // indirect
|
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||||
go.uber.org/ratelimit v0.3.1 // indirect
|
go.uber.org/ratelimit v0.3.1 // indirect
|
||||||
golang.org/x/arch v0.25.0 // indirect
|
golang.org/x/arch v0.24.0 // indirect
|
||||||
golang.org/x/crypto v0.49.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/mod v0.34.0 // indirect
|
golang.org/x/mod v0.33.0 // indirect
|
||||||
golang.org/x/net v0.52.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/oauth2 v0.36.0 // indirect
|
golang.org/x/oauth2 v0.35.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
golang.org/x/tools v0.43.0 // indirect
|
golang.org/x/tools v0.42.0 // indirect
|
||||||
google.golang.org/api v0.272.0 // indirect
|
google.golang.org/api v0.267.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
||||||
google.golang.org/grpc v1.79.3 // indirect
|
google.golang.org/grpc v1.79.1 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.1 // indirect
|
gopkg.in/ini.v1 v1.67.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.
|
|||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
|
||||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 h1:4iB+IesclUXdP0ICgAabvq2FYLXrJWKx1fJQ+GxSo3Y=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 h1:h/33OxYLqBk0BYmEbSUy7MlvgQR/m1w1/7OJFKoPL1I=
|
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 h1:h/33OxYLqBk0BYmEbSUy7MlvgQR/m1w1/7OJFKoPL1I=
|
||||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q=
|
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||||
@@ -37,8 +37,8 @@ github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx
|
|||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
|
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
|
||||||
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||||
@@ -103,12 +103,12 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
|||||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||||
github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
|
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||||
github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
|
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||||
github.com/gotify/server/v2 v2.9.1 h1:wsQUCdYJ4ZvP7RIRKDLtAtmFQc3kxbrv3QqccO5RWzs=
|
github.com/gotify/server/v2 v2.9.0 h1:2zRCl28wkq0oc6YNbyJS2n0dDOOVvOS3Oez5AG2ij54=
|
||||||
github.com/gotify/server/v2 v2.9.1/go.mod h1:8scw0hiExomp4rJDrXBwRIcgQm7kv74P4Z4B+iM4l8w=
|
github.com/gotify/server/v2 v2.9.0/go.mod h1:249wwlUqHTr0QsiKARGtFVqds0pNLIMjYLinHyMACdQ=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||||
@@ -131,8 +131,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
|||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/linode/linodego v1.66.0 h1:rK8QJFaV53LWOEJvb/evhTg/dP5ElvtuZmx4iv4RJds=
|
github.com/linode/linodego v1.65.0 h1:SdsuGD8VSsPWeShXpE7ihl5vec+fD3MgwhnfYC/rj7k=
|
||||||
github.com/linode/linodego v1.66.0/go.mod h1:12ykGs9qsvxE+OU3SXuW2w+DTruWF35FPlXC7gGk2tU=
|
github.com/linode/linodego v1.65.0/go.mod h1:tOFiTErdjkbVnV+4S0+NmIE9dqqZUEM2HsJaGu8wMh8=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
@@ -150,10 +150,10 @@ github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0
|
|||||||
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
||||||
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
|
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
|
||||||
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
|
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
|
||||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2 h1:SlJJlU2lgrB8dB9UFopLUNaO+JxtzLK4UWHYY3e3gvo=
|
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 h1:OWijzl3nHUApvTivl+3+78dbBwmyEHOnb+W9m6ixGbk=
|
||||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.109.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2 h1:c8B4nduK77OvP6WnsfzfZgnK6uHhZETZGYnxNfu23Go=
|
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 h1:9LsjN/zaIN7H8JE61NHpbWhxF0UGY96+kMlk3g8OvGU=
|
||||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.109.2/go.mod h1:twxIRVoHRrrMZp1A8Mh46CqCG/gyzLnImEb4dpzleIE=
|
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2/go.mod h1:32vZH06TuwZSn+IDMO1qcDvC2vHVlzUALCwXGWPA+dc=
|
||||||
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
|
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
|
||||||
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
|
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
|
||||||
github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
|
github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
|
||||||
@@ -193,8 +193,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
|||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/vultr/govultr/v3 v3.28.1 h1:KR3LhppYARlBujY7+dcrE7YKL0Yo9qXL+msxykKQrLI=
|
github.com/vultr/govultr/v3 v3.27.0 h1:J8etMyu/Jh5+idMsu2YZpOWmDXXHeW4VZnkYXmJYHx8=
|
||||||
github.com/vultr/govultr/v3 v3.28.1/go.mod h1:2zyUw9yADQaGwKnwDesmIOlBNLrm7edsCfWHFJpWKf8=
|
github.com/vultr/govultr/v3 v3.27.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||||
github.com/yusing/gointernals v0.2.0 h1:jyWB3kdUPkuU6s0r8QY/sS5h2WNBF4Kfisly8dtSVvg=
|
github.com/yusing/gointernals v0.2.0 h1:jyWB3kdUPkuU6s0r8QY/sS5h2WNBF4Kfisly8dtSVvg=
|
||||||
@@ -205,60 +205,60 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
|
|||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
|
||||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||||
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
|
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||||
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
|
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
|
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
|
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
|
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
|
||||||
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
|
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
|
||||||
golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
|
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
|
||||||
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
|
google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE=
|
||||||
google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
|
google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
|
||||||
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
|
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||||
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
|
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -122,9 +122,9 @@ classDiagram
|
|||||||
+accessLogger AccessLogger
|
+accessLogger AccessLogger
|
||||||
+findRouteFunc findRouteFunc
|
+findRouteFunc findRouteFunc
|
||||||
+shortLinkMatcher *ShortLinkMatcher
|
+shortLinkMatcher *ShortLinkMatcher
|
||||||
+streamRoutes *pool.Pool\[types.StreamRoute\]
|
+streamRoutes *pool.Pool[types.StreamRoute]
|
||||||
+excludedRoutes *pool.Pool\[types.Route\]
|
+excludedRoutes *pool.Pool[types.Route]
|
||||||
+servers *xsync.Map\[string, *httpServer\]
|
+servers *xsync.Map[string, *httpServer]
|
||||||
+SupportProxyProtocol() bool
|
+SupportProxyProtocol() bool
|
||||||
+StartAddRoute(r) error
|
+StartAddRoute(r) error
|
||||||
+IterRoutes(yield)
|
+IterRoutes(yield)
|
||||||
@@ -132,7 +132,7 @@ classDiagram
|
|||||||
}
|
}
|
||||||
|
|
||||||
class httpServer {
|
class httpServer {
|
||||||
+routes *pool.Pool\[types.HTTPRoute\]
|
+routes *pool.Pool[types.HTTPRoute]
|
||||||
+ServeHTTP(w, r)
|
+ServeHTTP(w, r)
|
||||||
+AddRoute(route)
|
+AddRoute(route)
|
||||||
+DelRoute(route)
|
+DelRoute(route)
|
||||||
@@ -154,8 +154,8 @@ classDiagram
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ShortLinkMatcher {
|
class ShortLinkMatcher {
|
||||||
+fqdnRoutes *xsync.Map\[string, string\]
|
+fqdnRoutes *xsync.Map[string, string]
|
||||||
+subdomainRoutes *xsync.Map\[string, emptyStruct\]
|
+subdomainRoutes *xsync.Map[string, struct{}]
|
||||||
+ServeHTTP(w, r)
|
+ServeHTTP(w, r)
|
||||||
+AddRoute(alias)
|
+AddRoute(alias)
|
||||||
+DelRoute(alias)
|
+DelRoute(alias)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -153,24 +152,19 @@ func (s *SystemInfo) collectDisksInfo(ctx context.Context, lastResult *SystemInf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partitions, err := disk.PartitionsWithContext(ctx, true)
|
partitions, err := disk.PartitionsWithContext(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Disks = make(map[string]disk.UsageStat, len(partitions))
|
s.Disks = make(map[string]disk.UsageStat, len(partitions))
|
||||||
errs := gperr.NewBuilder("failed to get disks info")
|
errs := gperr.NewBuilder("failed to get disks info")
|
||||||
for _, partition := range partitions {
|
for _, partition := range partitions {
|
||||||
if !shouldCollectPartition(partition) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
diskInfo, err := disk.UsageWithContext(ctx, partition.Mountpoint)
|
diskInfo, err := disk.UsageWithContext(ctx, partition.Mountpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs.Add(err)
|
errs.Add(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key := diskKey(partition)
|
s.Disks[partition.Device] = diskInfo
|
||||||
s.Disks[key] = diskInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if errs.HasError() {
|
if errs.HasError() {
|
||||||
@@ -182,41 +176,6 @@ func (s *SystemInfo) collectDisksInfo(ctx context.Context, lastResult *SystemInf
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldCollectPartition(partition disk.PartitionStat) bool {
|
|
||||||
if partition.Mountpoint == "/" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if partition.Mountpoint == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// includes WSL mounts like /mnt/c, but exclude /mnt/ itself and /mnt/wsl*
|
|
||||||
if len(partition.Mountpoint) >= len("/mnt/") &&
|
|
||||||
strings.HasPrefix(partition.Mountpoint, "/mnt/") &&
|
|
||||||
!strings.HasPrefix(partition.Mountpoint, "/mnt/wsl") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(partition.Device, "/dev/") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func diskKey(partition disk.PartitionStat) string {
|
|
||||||
if partition.Device == "" || partition.Device == "none" {
|
|
||||||
return partition.Mountpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
if partition.Device == "/dev/root" {
|
|
||||||
return partition.Mountpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
return partition.Device
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SystemInfo) collectNetworkInfo(ctx context.Context, lastResult *SystemInfo) error {
|
func (s *SystemInfo) collectNetworkInfo(ctx context.Context, lastResult *SystemInfo) error {
|
||||||
networkIO, err := net.IOCountersWithContext(ctx, false)
|
networkIO, err := net.IOCountersWithContext(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ This package implements a flexible HTTP middleware system for GoDoxy. Middleware
|
|||||||
- **Bypass Rules**: Skip middleware based on request properties
|
- **Bypass Rules**: Skip middleware based on request properties
|
||||||
- **Dynamic Loading**: Load middleware definitions from files at runtime
|
- **Dynamic Loading**: Load middleware definitions from files at runtime
|
||||||
|
|
||||||
Response body rewriting is only applied to unencoded, text-like content types (for example `text/*`, JSON, YAML, XML). Response status and headers can always be modified.
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ var CustomErrorPage = NewMiddleware[customErrorPage]()
|
|||||||
|
|
||||||
const StaticFilePathPrefix = "/$gperrorpage/"
|
const StaticFilePathPrefix = "/$gperrorpage/"
|
||||||
|
|
||||||
func (customErrorPage) isBodyResponseModifier() {}
|
|
||||||
|
|
||||||
// before implements RequestModifier.
|
// before implements RequestModifier.
|
||||||
func (customErrorPage) before(w http.ResponseWriter, r *http.Request) (proceed bool) {
|
func (customErrorPage) before(w http.ResponseWriter, r *http.Request) (proceed bool) {
|
||||||
return !ServeStaticErrorPageFile(w, r)
|
return !ServeStaticErrorPageFile(w, r)
|
||||||
|
|||||||
@@ -3,12 +3,9 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"mime"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bytedance/sonic"
|
"github.com/bytedance/sonic"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
@@ -19,12 +16,6 @@ import (
|
|||||||
"github.com/yusing/goutils/http/reverseproxy"
|
"github.com/yusing/goutils/http/reverseproxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
mimeEventStream = "text/event-stream"
|
|
||||||
headerContentType = "Content-Type"
|
|
||||||
maxModifiableBody = 4 * 1024 * 1024 // 4MB
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ReverseProxy = reverseproxy.ReverseProxy
|
ReverseProxy = reverseproxy.ReverseProxy
|
||||||
ProxyRequest = reverseproxy.ProxyRequest
|
ProxyRequest = reverseproxy.ProxyRequest
|
||||||
@@ -54,11 +45,7 @@ type (
|
|||||||
RequestModifier interface {
|
RequestModifier interface {
|
||||||
before(w http.ResponseWriter, r *http.Request) (proceed bool)
|
before(w http.ResponseWriter, r *http.Request) (proceed bool)
|
||||||
}
|
}
|
||||||
ResponseModifier interface{ modifyResponse(r *http.Response) error }
|
ResponseModifier interface{ modifyResponse(r *http.Response) error }
|
||||||
BodyResponseModifier interface {
|
|
||||||
ResponseModifier
|
|
||||||
isBodyResponseModifier()
|
|
||||||
}
|
|
||||||
MiddlewareWithSetup interface{ setup() }
|
MiddlewareWithSetup interface{ setup() }
|
||||||
MiddlewareFinalizer interface{ finalize() }
|
MiddlewareFinalizer interface{ finalize() }
|
||||||
MiddlewareFinalizerWithError interface {
|
MiddlewareFinalizerWithError interface {
|
||||||
@@ -202,154 +189,60 @@ func (m *Middleware) ServeHTTP(next http.HandlerFunc, w http.ResponseWriter, r *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if httpheaders.IsWebsocket(r.Header) || strings.Contains(strings.ToLower(r.Header.Get("Accept")), mimeEventStream) {
|
if httpheaders.IsWebsocket(r.Header) || r.Header.Get("Accept") == "text/event-stream" {
|
||||||
next(w, r)
|
next(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
exec, ok := m.impl.(ResponseModifier)
|
if exec, ok := m.impl.(ResponseModifier); ok {
|
||||||
if !ok {
|
lrm := httputils.NewLazyResponseModifier(w, needsBuffering)
|
||||||
next(w, r)
|
defer func() {
|
||||||
return
|
_, err := lrm.FlushRelease()
|
||||||
}
|
if err != nil {
|
||||||
isBodyModifier := isBodyResponseModifier(exec)
|
m.LogError(r).Err(err).Msg("failed to flush response")
|
||||||
|
|
||||||
shouldBuffer := canBufferAndModifyResponseBody
|
|
||||||
if !isBodyModifier {
|
|
||||||
// Header-only response modifiers do not need body rewrite capability checks.
|
|
||||||
// We still respect max buffer limits and may fall back to passthrough for large bodies.
|
|
||||||
shouldBuffer = func(http.Header) bool { return true }
|
|
||||||
}
|
|
||||||
lrm := httputils.NewLazyResponseModifier(w, shouldBuffer)
|
|
||||||
lrm.SetMaxBufferedBytes(maxModifiableBody)
|
|
||||||
defer func() {
|
|
||||||
_, err := lrm.FlushRelease()
|
|
||||||
if err != nil {
|
|
||||||
m.LogError(r).Err(err).Msg("failed to flush response")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
next(lrm, r)
|
|
||||||
|
|
||||||
// Skip modification if response wasn't buffered
|
|
||||||
if !lrm.IsBuffered() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rm := lrm.ResponseModifier()
|
|
||||||
if rm.IsPassthrough() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
currentBody := rm.BodyReader()
|
|
||||||
currentResp := &http.Response{
|
|
||||||
StatusCode: rm.StatusCode(),
|
|
||||||
Header: rm.Header(),
|
|
||||||
ContentLength: int64(rm.ContentLength()),
|
|
||||||
Body: currentBody,
|
|
||||||
Request: r,
|
|
||||||
}
|
|
||||||
respToModify := currentResp
|
|
||||||
if err := exec.modifyResponse(respToModify); err != nil {
|
|
||||||
log.Err(err).Str("middleware", m.Name()).Str("url", fullURL(r)).Msg("failed to modify response")
|
|
||||||
return // skip modification if failed
|
|
||||||
}
|
|
||||||
|
|
||||||
// override the response status code
|
|
||||||
rm.WriteHeader(respToModify.StatusCode)
|
|
||||||
|
|
||||||
// overriding the response header
|
|
||||||
maps.Copy(rm.Header(), respToModify.Header)
|
|
||||||
|
|
||||||
// override the body if changed
|
|
||||||
if isBodyModifier && respToModify.Body != currentBody {
|
|
||||||
err := rm.SetBody(respToModify.Body)
|
|
||||||
if err != nil {
|
|
||||||
m.LogError(r).Err(err).Msg("failed to set response body")
|
|
||||||
return // skip modification if failed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// canBufferAndModifyResponseBody checks if the response body can be buffered and modified.
|
|
||||||
//
|
|
||||||
// A body can be buffered and modified if:
|
|
||||||
// - The response is not a websocket and is not an event stream
|
|
||||||
// - The response has identity transfer encoding
|
|
||||||
// - The response has identity content encoding
|
|
||||||
// - The response has a content length
|
|
||||||
// - The content length is less than 4MB
|
|
||||||
// - The content type is text-like
|
|
||||||
func canBufferAndModifyResponseBody(respHeader http.Header) bool {
|
|
||||||
if httpheaders.IsWebsocket(respHeader) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
contentType := respHeader.Get("Content-Type")
|
|
||||||
if contentType == "" { // safe default: skip if no content type
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
contentType = strings.ToLower(contentType)
|
|
||||||
if strings.Contains(contentType, mimeEventStream) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// strip charset or any other parameters
|
|
||||||
contentType, _, err := mime.ParseMediaType(contentType)
|
|
||||||
if err != nil { // skip if invalid content type
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if hasNonIdentityEncoding(respHeader.Values("Transfer-Encoding")) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if hasNonIdentityEncoding(respHeader.Values("Content-Encoding")) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if contentLengthRaw := respHeader.Get("Content-Length"); contentLengthRaw != "" {
|
|
||||||
contentLength, err := strconv.ParseInt(contentLengthRaw, 10, 64)
|
|
||||||
if err != nil || contentLength >= maxModifiableBody {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isTextLikeMediaType(contentType) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasNonIdentityEncoding(values []string) bool {
|
|
||||||
for _, value := range values {
|
|
||||||
for token := range strings.SplitSeq(value, ",") {
|
|
||||||
token = strings.TrimSpace(token)
|
|
||||||
if token == "" || strings.EqualFold(token, "identity") {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
return true
|
}()
|
||||||
|
next(lrm, r)
|
||||||
|
|
||||||
|
// Skip modification if response wasn't buffered (non-HTML content)
|
||||||
|
if !lrm.IsBuffered() {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rm := lrm.ResponseModifier()
|
||||||
|
currentBody := rm.BodyReader()
|
||||||
|
currentResp := &http.Response{
|
||||||
|
StatusCode: rm.StatusCode(),
|
||||||
|
Header: rm.Header(),
|
||||||
|
ContentLength: int64(rm.ContentLength()),
|
||||||
|
Body: currentBody,
|
||||||
|
Request: r,
|
||||||
|
}
|
||||||
|
if err := exec.modifyResponse(currentResp); err != nil {
|
||||||
|
log.Err(err).Str("middleware", m.Name()).Str("url", fullURL(r)).Msg("failed to modify response")
|
||||||
|
}
|
||||||
|
|
||||||
|
// override the response status code
|
||||||
|
rm.WriteHeader(currentResp.StatusCode)
|
||||||
|
|
||||||
|
// overriding the response header
|
||||||
|
maps.Copy(rm.Header(), currentResp.Header)
|
||||||
|
|
||||||
|
// override the content length and body if changed
|
||||||
|
if currentResp.Body != currentBody {
|
||||||
|
if err := rm.SetBody(currentResp.Body); err != nil {
|
||||||
|
m.LogError(r).Err(err).Msg("failed to set response body")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next(w, r)
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isTextLikeMediaType(contentType string) bool {
|
// needsBuffering determines if a response should be buffered for modification.
|
||||||
if contentType == "" {
|
// Only HTML responses need buffering; streaming content (video, audio, etc.) should pass through.
|
||||||
return false
|
func needsBuffering(header http.Header) bool {
|
||||||
}
|
return httputils.GetContentType(header).IsHTML()
|
||||||
contentType = strings.ToLower(contentType)
|
|
||||||
if strings.HasPrefix(contentType, "text/") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if contentType == "application/json" || strings.HasSuffix(contentType, "+json") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if contentType == "application/xml" || strings.HasSuffix(contentType, "+xml") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.Contains(contentType, "yaml") || strings.Contains(contentType, "toml") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.Contains(contentType, "javascript") || strings.Contains(contentType, "ecmascript") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.Contains(contentType, "csv") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return contentType == "application/x-www-form-urlencoded"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Middleware) LogWarn(req *http.Request) *zerolog.Event {
|
func (m *Middleware) LogWarn(req *http.Request) *zerolog.Event {
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type middlewareChain struct {
|
type middlewareChain struct {
|
||||||
befores []RequestModifier
|
befores []RequestModifier
|
||||||
respHeader []ResponseModifier
|
modResps []ResponseModifier
|
||||||
respBody []ResponseModifier
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check conflict or duplicates.
|
// TODO: check conflict or duplicates.
|
||||||
@@ -23,11 +22,7 @@ func NewMiddlewareChain(name string, chain []*Middleware) *Middleware {
|
|||||||
chainMid.befores = append(chainMid.befores, before)
|
chainMid.befores = append(chainMid.befores, before)
|
||||||
}
|
}
|
||||||
if mr, ok := comp.impl.(ResponseModifier); ok {
|
if mr, ok := comp.impl.(ResponseModifier); ok {
|
||||||
if isBodyResponseModifier(mr) {
|
chainMid.modResps = append(chainMid.modResps, mr)
|
||||||
chainMid.respBody = append(chainMid.respBody, mr)
|
|
||||||
} else {
|
|
||||||
chainMid.respHeader = append(chainMid.respHeader, mr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
@@ -48,41 +43,13 @@ func (m *middlewareChain) before(w http.ResponseWriter, r *http.Request) (procee
|
|||||||
|
|
||||||
// modifyResponse implements ResponseModifier.
|
// modifyResponse implements ResponseModifier.
|
||||||
func (m *middlewareChain) modifyResponse(resp *http.Response) error {
|
func (m *middlewareChain) modifyResponse(resp *http.Response) error {
|
||||||
for i, mr := range m.respHeader {
|
if len(m.modResps) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for i, mr := range m.modResps {
|
||||||
if err := mr.modifyResponse(resp); err != nil {
|
if err := mr.modifyResponse(resp); err != nil {
|
||||||
return gperr.PrependSubject(err, strconv.Itoa(i))
|
return gperr.PrependSubject(err, strconv.Itoa(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(m.respBody) == 0 || !canBufferAndModifyResponseBody(responseHeaderForBodyRewriteGate(resp)) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
headerLen := len(m.respHeader)
|
|
||||||
for i, mr := range m.respBody {
|
|
||||||
if err := mr.modifyResponse(resp); err != nil {
|
|
||||||
return gperr.PrependSubject(err, strconv.Itoa(i+headerLen))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isBodyResponseModifier(mr ResponseModifier) bool {
|
|
||||||
if chain, ok := mr.(*middlewareChain); ok {
|
|
||||||
return len(chain.respBody) > 0
|
|
||||||
}
|
|
||||||
if bypass, ok := mr.(*checkBypass); ok {
|
|
||||||
return isBodyResponseModifier(bypass.modRes)
|
|
||||||
}
|
|
||||||
_, ok := mr.(BodyResponseModifier)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func responseHeaderForBodyRewriteGate(resp *http.Response) http.Header {
|
|
||||||
h := resp.Header.Clone()
|
|
||||||
if len(resp.TransferEncoding) > 0 && len(h.Values("Transfer-Encoding")) == 0 {
|
|
||||||
h["Transfer-Encoding"] = append([]string(nil), resp.TransferEncoding...)
|
|
||||||
}
|
|
||||||
if resp.ContentLength >= 0 && h.Get("Content-Length") == "" {
|
|
||||||
h.Set("Content-Length", strconv.FormatInt(resp.ContentLength, 10))
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -16,37 +14,12 @@ type testPriority struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var test = NewMiddleware[testPriority]()
|
var test = NewMiddleware[testPriority]()
|
||||||
var responseHeaderRewrite = NewMiddleware[testHeaderRewrite]()
|
|
||||||
var responseBodyRewrite = NewMiddleware[testBodyRewrite]()
|
|
||||||
|
|
||||||
func (t testPriority) before(w http.ResponseWriter, r *http.Request) bool {
|
func (t testPriority) before(w http.ResponseWriter, r *http.Request) bool {
|
||||||
w.Header().Add("Test-Value", strconv.Itoa(t.Value))
|
w.Header().Add("Test-Value", strconv.Itoa(t.Value))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type testHeaderRewrite struct {
|
|
||||||
StatusCode int `json:"status_code"`
|
|
||||||
HeaderKey string `json:"header_key"`
|
|
||||||
HeaderVal string `json:"header_val"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t testHeaderRewrite) modifyResponse(resp *http.Response) error {
|
|
||||||
resp.StatusCode = t.StatusCode
|
|
||||||
resp.Header.Set(t.HeaderKey, t.HeaderVal)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type testBodyRewrite struct {
|
|
||||||
Body string `json:"body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t testBodyRewrite) modifyResponse(resp *http.Response) error {
|
|
||||||
resp.Body = io.NopCloser(strings.NewReader(t.Body))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (testBodyRewrite) isBodyResponseModifier() {}
|
|
||||||
|
|
||||||
func TestMiddlewarePriority(t *testing.T) {
|
func TestMiddlewarePriority(t *testing.T) {
|
||||||
priorities := []int{4, 7, 9, 0}
|
priorities := []int{4, 7, 9, 0}
|
||||||
chain := make([]*Middleware, len(priorities))
|
chain := make([]*Middleware, len(priorities))
|
||||||
@@ -62,215 +35,3 @@ func TestMiddlewarePriority(t *testing.T) {
|
|||||||
expect.NoError(t, err)
|
expect.NoError(t, err)
|
||||||
expect.Equal(t, strings.Join(res.ResponseHeaders["Test-Value"], ","), "3,0,1,2")
|
expect.Equal(t, strings.Join(res.ResponseHeaders["Test-Value"], ","), "3,0,1,2")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMiddlewareResponseRewriteGate(t *testing.T) {
|
|
||||||
headerOpts := OptionsRaw{
|
|
||||||
"status_code": 418,
|
|
||||||
"header_key": "X-Rewrite",
|
|
||||||
"header_val": "1",
|
|
||||||
}
|
|
||||||
bodyOpts := OptionsRaw{
|
|
||||||
"body": "rewritten-body",
|
|
||||||
}
|
|
||||||
headerMid, err := responseHeaderRewrite.New(headerOpts)
|
|
||||||
expect.NoError(t, err)
|
|
||||||
bodyMid, err := responseBodyRewrite.New(bodyOpts)
|
|
||||||
expect.NoError(t, err)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
respHeaders http.Header
|
|
||||||
respBody []byte
|
|
||||||
expectStatus int
|
|
||||||
expectHeader string
|
|
||||||
expectBody string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "allow_body_rewrite_for_html",
|
|
||||||
respHeaders: http.Header{
|
|
||||||
"Content-Type": []string{"text/html; charset=utf-8"},
|
|
||||||
},
|
|
||||||
respBody: []byte("<html><body>original</body></html>"),
|
|
||||||
expectStatus: http.StatusTeapot,
|
|
||||||
expectHeader: "1",
|
|
||||||
expectBody: "rewritten-body",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "allow_body_rewrite_for_json",
|
|
||||||
respHeaders: http.Header{
|
|
||||||
"Content-Type": []string{"application/json"},
|
|
||||||
},
|
|
||||||
respBody: []byte(`{"message":"original"}`),
|
|
||||||
expectStatus: http.StatusTeapot,
|
|
||||||
expectHeader: "1",
|
|
||||||
expectBody: "rewritten-body",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "allow_body_rewrite_for_yaml",
|
|
||||||
respHeaders: http.Header{
|
|
||||||
"Content-Type": []string{"application/yaml"},
|
|
||||||
},
|
|
||||||
respBody: []byte("k: v"),
|
|
||||||
expectStatus: http.StatusTeapot,
|
|
||||||
expectHeader: "1",
|
|
||||||
expectBody: "rewritten-body",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "block_body_rewrite_for_binary_content",
|
|
||||||
respHeaders: http.Header{
|
|
||||||
"Content-Type": []string{"application/octet-stream"},
|
|
||||||
},
|
|
||||||
respBody: []byte("binary"),
|
|
||||||
expectStatus: http.StatusTeapot,
|
|
||||||
expectHeader: "1",
|
|
||||||
expectBody: "binary",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "block_body_rewrite_for_transfer_encoded_html",
|
|
||||||
respHeaders: http.Header{
|
|
||||||
"Content-Type": []string{"text/html"},
|
|
||||||
"Transfer-Encoding": []string{"chunked"},
|
|
||||||
},
|
|
||||||
respBody: []byte("<html><body>original</body></html>"),
|
|
||||||
expectStatus: http.StatusTeapot,
|
|
||||||
expectHeader: "1",
|
|
||||||
expectBody: "<html><body>original</body></html>",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "block_body_rewrite_for_content_encoded_html",
|
|
||||||
respHeaders: http.Header{
|
|
||||||
"Content-Type": []string{"text/html"},
|
|
||||||
"Content-Encoding": []string{"gzip"},
|
|
||||||
},
|
|
||||||
respBody: []byte("<html><body>original</body></html>"),
|
|
||||||
expectStatus: http.StatusTeapot,
|
|
||||||
expectHeader: "1",
|
|
||||||
expectBody: "<html><body>original</body></html>",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
result, err := newMiddlewaresTest([]*Middleware{headerMid, bodyMid}, &testArgs{
|
|
||||||
respHeaders: tc.respHeaders,
|
|
||||||
respBody: tc.respBody,
|
|
||||||
respStatus: http.StatusOK,
|
|
||||||
})
|
|
||||||
expect.NoError(t, err)
|
|
||||||
expect.Equal(t, result.ResponseStatus, tc.expectStatus)
|
|
||||||
expect.Equal(t, result.ResponseHeaders.Get("X-Rewrite"), tc.expectHeader)
|
|
||||||
expect.Equal(t, string(result.Data), tc.expectBody)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMiddlewareResponseRewriteGateServeHTTP(t *testing.T) {
|
|
||||||
headerOpts := OptionsRaw{
|
|
||||||
"status_code": 418,
|
|
||||||
"header_key": "X-Rewrite",
|
|
||||||
"header_val": "1",
|
|
||||||
}
|
|
||||||
bodyOpts := OptionsRaw{
|
|
||||||
"body": "rewritten-body",
|
|
||||||
}
|
|
||||||
headerMid, err := responseHeaderRewrite.New(headerOpts)
|
|
||||||
expect.NoError(t, err)
|
|
||||||
bodyMid, err := responseBodyRewrite.New(bodyOpts)
|
|
||||||
expect.NoError(t, err)
|
|
||||||
mid := NewMiddlewareChain("test", []*Middleware{headerMid, bodyMid})
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
respHeaders http.Header
|
|
||||||
respBody string
|
|
||||||
expectStatusCode int
|
|
||||||
expectHeader string
|
|
||||||
expectBody string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "allow_body_rewrite_for_html",
|
|
||||||
respHeaders: http.Header{
|
|
||||||
"Content-Type": []string{"text/html; charset=utf-8"},
|
|
||||||
},
|
|
||||||
respBody: "<html><body>original</body></html>",
|
|
||||||
expectStatusCode: http.StatusTeapot,
|
|
||||||
expectHeader: "1",
|
|
||||||
expectBody: "rewritten-body",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "block_body_rewrite_for_binary_content",
|
|
||||||
respHeaders: http.Header{
|
|
||||||
"Content-Type": []string{"application/octet-stream"},
|
|
||||||
},
|
|
||||||
respBody: "binary",
|
|
||||||
expectStatusCode: http.StatusOK,
|
|
||||||
expectHeader: "",
|
|
||||||
expectBody: "binary",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "block_body_rewrite_for_transfer_encoded_html",
|
|
||||||
respHeaders: http.Header{
|
|
||||||
"Content-Type": []string{"text/html"},
|
|
||||||
"Transfer-Encoding": []string{"chunked"},
|
|
||||||
},
|
|
||||||
respBody: "<html><body>original</body></html>",
|
|
||||||
expectStatusCode: http.StatusOK,
|
|
||||||
expectHeader: "",
|
|
||||||
expectBody: "<html><body>original</body></html>",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "block_body_rewrite_for_content_encoded_html",
|
|
||||||
respHeaders: http.Header{
|
|
||||||
"Content-Type": []string{"text/html"},
|
|
||||||
"Content-Encoding": []string{"gzip"},
|
|
||||||
},
|
|
||||||
respBody: "<html><body>original</body></html>",
|
|
||||||
expectStatusCode: http.StatusOK,
|
|
||||||
expectHeader: "",
|
|
||||||
expectBody: "<html><body>original</body></html>",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
|
||||||
rw := httptest.NewRecorder()
|
|
||||||
|
|
||||||
next := func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
for key, values := range tc.respHeaders {
|
|
||||||
for _, value := range values {
|
|
||||||
w.Header().Add(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
_, _ = w.Write([]byte(tc.respBody))
|
|
||||||
}
|
|
||||||
|
|
||||||
mid.ServeHTTP(next, rw, req)
|
|
||||||
|
|
||||||
resp := rw.Result()
|
|
||||||
defer resp.Body.Close()
|
|
||||||
data, readErr := io.ReadAll(resp.Body)
|
|
||||||
expect.NoError(t, readErr)
|
|
||||||
|
|
||||||
expect.Equal(t, resp.StatusCode, tc.expectStatusCode)
|
|
||||||
expect.Equal(t, resp.Header.Get("X-Rewrite"), tc.expectHeader)
|
|
||||||
expect.Equal(t, string(data), tc.expectBody)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestThemedSkipsBodyRewriteWhenRewriteBlocked(t *testing.T) {
|
|
||||||
result, err := newMiddlewareTest(Themed, &testArgs{
|
|
||||||
middlewareOpt: OptionsRaw{
|
|
||||||
"theme": DarkTheme,
|
|
||||||
},
|
|
||||||
respHeaders: http.Header{
|
|
||||||
"Content-Type": []string{"text/html; charset=utf-8"},
|
|
||||||
"Transfer-Encoding": []string{"chunked"},
|
|
||||||
},
|
|
||||||
respBody: []byte("<html><body>original</body></html>"),
|
|
||||||
})
|
|
||||||
expect.NoError(t, err)
|
|
||||||
expect.Equal(t, string(result.Data), "<html><body>original</body></html>")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ type modifyHTML struct {
|
|||||||
|
|
||||||
var ModifyHTML = NewMiddleware[modifyHTML]()
|
var ModifyHTML = NewMiddleware[modifyHTML]()
|
||||||
|
|
||||||
func (*modifyHTML) isBodyResponseModifier() {}
|
|
||||||
|
|
||||||
func (m *modifyHTML) before(_ http.ResponseWriter, req *http.Request) bool {
|
func (m *modifyHTML) before(_ http.ResponseWriter, req *http.Request) bool {
|
||||||
req.Header.Set("Accept-Encoding", "identity")
|
req.Header.Set("Accept-Encoding", "identity")
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"maps"
|
"maps"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bytedance/sonic"
|
"github.com/bytedance/sonic"
|
||||||
"github.com/yusing/godoxy/internal/common"
|
"github.com/yusing/godoxy/internal/common"
|
||||||
@@ -55,7 +54,7 @@ func (rt *requestRecorder) RoundTrip(req *http.Request) (resp *http.Response, er
|
|||||||
resp = &http.Response{
|
resp = &http.Response{
|
||||||
Status: http.StatusText(rt.args.respStatus),
|
Status: http.StatusText(rt.args.respStatus),
|
||||||
StatusCode: rt.args.respStatus,
|
StatusCode: rt.args.respStatus,
|
||||||
Header: maps.Clone(testHeaders),
|
Header: testHeaders,
|
||||||
Body: io.NopCloser(bytes.NewReader(rt.args.respBody)),
|
Body: io.NopCloser(bytes.NewReader(rt.args.respBody)),
|
||||||
ContentLength: int64(len(rt.args.respBody)),
|
ContentLength: int64(len(rt.args.respBody)),
|
||||||
Request: req,
|
Request: req,
|
||||||
@@ -66,27 +65,9 @@ func (rt *requestRecorder) RoundTrip(req *http.Request) (resp *http.Response, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
maps.Copy(resp.Header, rt.args.respHeaders)
|
maps.Copy(resp.Header, rt.args.respHeaders)
|
||||||
if transferEncoding := resp.Header.Values("Transfer-Encoding"); len(transferEncoding) > 0 {
|
|
||||||
resp.TransferEncoding = parseHeaderTokens(transferEncoding)
|
|
||||||
resp.ContentLength = -1
|
|
||||||
}
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHeaderTokens(values []string) []string {
|
|
||||||
var tokens []string
|
|
||||||
for _, value := range values {
|
|
||||||
for token := range strings.SplitSeq(value, ",") {
|
|
||||||
token = strings.TrimSpace(token)
|
|
||||||
if token == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tokens = append(tokens, token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestResult struct {
|
type TestResult struct {
|
||||||
RequestHeaders http.Header
|
RequestHeaders http.Header
|
||||||
ResponseHeaders http.Header
|
ResponseHeaders http.Header
|
||||||
|
|||||||
@@ -54,8 +54,6 @@ func (m *themed) modifyResponse(resp *http.Response) error {
|
|||||||
return m.m.modifyResponse(resp)
|
return m.m.modifyResponse(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*themed) isBodyResponseModifier() {}
|
|
||||||
|
|
||||||
func (m *themed) finalize() error {
|
func (m *themed) finalize() error {
|
||||||
m.m.Target = "body"
|
m.m.Target = "body"
|
||||||
if m.FontURL != "" && m.FontFamily != "" {
|
if m.FontURL != "" && m.FontFamily != "" {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ example: # matching `example.y.z`
|
|||||||
host: 10.0.0.254
|
host: 10.0.0.254
|
||||||
port: 80
|
port: 80
|
||||||
bind: 0.0.0.0
|
bind: 0.0.0.0
|
||||||
relay_proxy_protocol_header: false # tcp only, sends PROXY header to upstream
|
|
||||||
root: /var/www/example
|
root: /var/www/example
|
||||||
spa: true
|
spa: true
|
||||||
index: index.html
|
index: index.html
|
||||||
|
|||||||
@@ -123,24 +123,6 @@ func (r *ReverseProxyRoute) ReverseProxy() *reverseproxy.ReverseProxy {
|
|||||||
return r.rp
|
return r.rp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReverseProxyRoute) isSyntheticLoadBalancerRoute() bool {
|
|
||||||
return r.loadBalancer != nil && r.rp == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ReverseProxyRoute) Key() string {
|
|
||||||
if r.isSyntheticLoadBalancerRoute() {
|
|
||||||
return r.Alias
|
|
||||||
}
|
|
||||||
return r.Route.Key()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ReverseProxyRoute) ShouldExclude() bool {
|
|
||||||
if r.isSyntheticLoadBalancerRoute() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return r.Route.ShouldExclude()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start implements task.TaskStarter.
|
// Start implements task.TaskStarter.
|
||||||
func (r *ReverseProxyRoute) Start(parent task.Parent) error {
|
func (r *ReverseProxyRoute) Start(parent task.Parent) error {
|
||||||
r.task = parent.Subtask("http."+r.Name(), false)
|
r.task = parent.Subtask("http."+r.Name(), false)
|
||||||
@@ -224,7 +206,7 @@ func (r *ReverseProxyRoute) addToLoadBalancer(parent task.Parent, ep entrypoint.
|
|||||||
linked = l.(*ReverseProxyRoute) // it must be a reverse proxy route
|
linked = l.(*ReverseProxyRoute) // it must be a reverse proxy route
|
||||||
lb = linked.loadBalancer
|
lb = linked.loadBalancer
|
||||||
lb.UpdateConfigIfNeeded(cfg)
|
lb.UpdateConfigIfNeeded(cfg)
|
||||||
if linked.Homepage == nil || linked.Homepage.Name == "" {
|
if linked.Homepage.Name == "" {
|
||||||
linked.Homepage = r.Homepage
|
linked.Homepage = r.Homepage
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,165 +1,16 @@
|
|||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
|
||||||
"github.com/yusing/godoxy/internal/homepage"
|
|
||||||
route "github.com/yusing/godoxy/internal/route/types"
|
route "github.com/yusing/godoxy/internal/route/types"
|
||||||
"github.com/yusing/godoxy/internal/types"
|
"github.com/yusing/godoxy/internal/types"
|
||||||
"github.com/yusing/goutils/task"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type testPool[T interface{ Key() string }] struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
items map[string]T
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestPool[T interface{ Key() string }]() *testPool[T] {
|
|
||||||
return &testPool[T]{items: make(map[string]T)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *testPool[T]) Get(alias string) (T, bool) {
|
|
||||||
p.mu.RLock()
|
|
||||||
defer p.mu.RUnlock()
|
|
||||||
v, ok := p.items[alias]
|
|
||||||
return v, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *testPool[T]) Iter(yield func(alias string, r T) bool) {
|
|
||||||
p.mu.RLock()
|
|
||||||
defer p.mu.RUnlock()
|
|
||||||
for alias, r := range p.items {
|
|
||||||
if !yield(alias, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *testPool[T]) Size() int {
|
|
||||||
p.mu.RLock()
|
|
||||||
defer p.mu.RUnlock()
|
|
||||||
return len(p.items)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *testPool[T]) Add(r T) {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
p.items[r.Key()] = r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *testPool[T]) Del(r T) {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
delete(p.items, r.Key())
|
|
||||||
}
|
|
||||||
|
|
||||||
type testEntrypoint struct {
|
|
||||||
httpRoutes *testPool[types.HTTPRoute]
|
|
||||||
streamRoutes *testPool[types.StreamRoute]
|
|
||||||
excludedRoutes *testPool[types.Route]
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestEntrypoint() *testEntrypoint {
|
|
||||||
return &testEntrypoint{
|
|
||||||
httpRoutes: newTestPool[types.HTTPRoute](),
|
|
||||||
streamRoutes: newTestPool[types.StreamRoute](),
|
|
||||||
excludedRoutes: newTestPool[types.Route](),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) SupportProxyProtocol() bool { return false }
|
|
||||||
func (ep *testEntrypoint) DisablePoolsLog(bool) {}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) GetRoute(alias string) (types.Route, bool) {
|
|
||||||
if r, ok := ep.httpRoutes.Get(alias); ok {
|
|
||||||
return r, true
|
|
||||||
}
|
|
||||||
if r, ok := ep.streamRoutes.Get(alias); ok {
|
|
||||||
return r, true
|
|
||||||
}
|
|
||||||
if r, ok := ep.excludedRoutes.Get(alias); ok {
|
|
||||||
return r, true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) StartAddRoute(r types.Route) error {
|
|
||||||
if r.ShouldExclude() {
|
|
||||||
ep.excludedRoutes.Add(r)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch rt := r.(type) {
|
|
||||||
case types.HTTPRoute:
|
|
||||||
ep.httpRoutes.Add(rt)
|
|
||||||
return nil
|
|
||||||
case types.StreamRoute:
|
|
||||||
ep.streamRoutes.Add(rt)
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown route type: %T", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) IterRoutes(yield func(r types.Route) bool) {
|
|
||||||
ep.httpRoutes.Iter(func(_ string, r types.HTTPRoute) bool {
|
|
||||||
return yield(r)
|
|
||||||
})
|
|
||||||
ep.streamRoutes.Iter(func(_ string, r types.StreamRoute) bool {
|
|
||||||
return yield(r)
|
|
||||||
})
|
|
||||||
ep.excludedRoutes.Iter(func(_ string, r types.Route) bool {
|
|
||||||
return yield(r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) NumRoutes() int {
|
|
||||||
return ep.httpRoutes.Size() + ep.streamRoutes.Size() + ep.excludedRoutes.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) RoutesByProvider() map[string][]types.Route {
|
|
||||||
return map[string][]types.Route{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) HTTPRoutes() entrypoint.PoolLike[types.HTTPRoute] {
|
|
||||||
return ep.httpRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) StreamRoutes() entrypoint.PoolLike[types.StreamRoute] {
|
|
||||||
return ep.streamRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) ExcludedRoutes() entrypoint.RWPoolLike[types.Route] {
|
|
||||||
return ep.excludedRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) GetHealthInfo() map[string]types.HealthInfo {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) GetHealthInfoWithoutDetail() map[string]types.HealthInfoWithoutDetail {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ep *testEntrypoint) GetHealthInfoSimple() map[string]types.HealthStatus {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReverseProxyRoute(t *testing.T) {
|
func TestReverseProxyRoute(t *testing.T) {
|
||||||
t.Run("LinkToLoadBalancer", func(t *testing.T) {
|
t.Run("LinkToLoadBalancer", func(t *testing.T) {
|
||||||
testTask := task.GetTestTask(t)
|
|
||||||
entrypoint.SetCtx(testTask, newTestEntrypoint())
|
|
||||||
|
|
||||||
cfg := Route{
|
cfg := Route{
|
||||||
Alias: "test",
|
Alias: "test",
|
||||||
Scheme: route.SchemeHTTP,
|
Scheme: route.SchemeHTTP,
|
||||||
@@ -185,75 +36,4 @@ func TestReverseProxyRoute(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, r2)
|
assert.NotNil(t, r2)
|
||||||
})
|
})
|
||||||
t.Run("LoadBalancerRoute", func(t *testing.T) {
|
|
||||||
testTask := task.GetTestTask(t)
|
|
||||||
entrypoint.SetCtx(testTask, newTestEntrypoint())
|
|
||||||
|
|
||||||
newServer := func() *httptest.Server {
|
|
||||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
srv1 := newServer()
|
|
||||||
t.Cleanup(srv1.Close)
|
|
||||||
srv2 := newServer()
|
|
||||||
t.Cleanup(srv2.Close)
|
|
||||||
srv3 := newServer()
|
|
||||||
t.Cleanup(srv3.Close)
|
|
||||||
|
|
||||||
makeRoute := func(alias string, target *httptest.Server) *Route {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
targetURL, err := url.Parse(target.URL)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
host, portStr, err := net.SplitHostPort(targetURL.Host)
|
|
||||||
require.NoError(t, err)
|
|
||||||
port, err := strconv.Atoi(portStr)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
return &Route{
|
|
||||||
Alias: alias,
|
|
||||||
Scheme: route.SchemeHTTP,
|
|
||||||
Host: host,
|
|
||||||
Port: Port{Proxy: port},
|
|
||||||
Homepage: &homepage.ItemConfig{
|
|
||||||
Show: true,
|
|
||||||
},
|
|
||||||
LoadBalance: &types.LoadBalancerConfig{
|
|
||||||
Link: "lb-test",
|
|
||||||
},
|
|
||||||
HealthCheck: types.HealthCheckConfig{
|
|
||||||
Path: "/",
|
|
||||||
Interval: 2 * time.Second,
|
|
||||||
Timeout: time.Second,
|
|
||||||
UseGet: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := NewStartedTestRoute(t, makeRoute("lb-1", srv1))
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = NewStartedTestRoute(t, makeRoute("lb-2", srv2))
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = NewStartedTestRoute(t, makeRoute("lb-3", srv3))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ep := entrypoint.FromCtx(testTask.Context())
|
|
||||||
require.NotNil(t, ep)
|
|
||||||
|
|
||||||
lbRoute, ok := ep.HTTPRoutes().Get("lb-test")
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
lb, ok := lbRoute.(*ReverseProxyRoute)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.False(t, lb.ShouldExclude())
|
|
||||||
require.NotNil(t, lb.loadBalancer)
|
|
||||||
require.NotNil(t, lb.HealthMonitor())
|
|
||||||
assert.Equal(t, route.SchemeNone, lb.Scheme)
|
|
||||||
assert.Empty(t, lb.Host)
|
|
||||||
assert.Zero(t, lb.Port.Proxy)
|
|
||||||
assert.Equal(t, "3/3 servers are healthy", lb.HealthMonitor().Detail())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,16 +54,15 @@ type (
|
|||||||
Index string `json:"index,omitempty"` // Index file to serve for single-page app mode
|
Index string `json:"index,omitempty"` // Index file to serve for single-page app mode
|
||||||
|
|
||||||
route.HTTPConfig
|
route.HTTPConfig
|
||||||
PathPatterns []string `json:"path_patterns,omitempty" extensions:"x-nullable"`
|
PathPatterns []string `json:"path_patterns,omitempty" extensions:"x-nullable"`
|
||||||
Rules rules.Rules `json:"rules,omitempty" extensions:"x-nullable"`
|
Rules rules.Rules `json:"rules,omitempty" extensions:"x-nullable"`
|
||||||
RuleFile string `json:"rule_file,omitempty" extensions:"x-nullable"`
|
RuleFile string `json:"rule_file,omitempty" extensions:"x-nullable"`
|
||||||
HealthCheck types.HealthCheckConfig `json:"healthcheck,omitzero" extensions:"x-nullable"` // null on load-balancer routes
|
HealthCheck types.HealthCheckConfig `json:"healthcheck,omitzero" extensions:"x-nullable"` // null on load-balancer routes
|
||||||
LoadBalance *types.LoadBalancerConfig `json:"load_balance,omitempty" extensions:"x-nullable"`
|
LoadBalance *types.LoadBalancerConfig `json:"load_balance,omitempty" extensions:"x-nullable"`
|
||||||
Middlewares map[string]types.LabelMap `json:"middlewares,omitempty" extensions:"x-nullable"`
|
Middlewares map[string]types.LabelMap `json:"middlewares,omitempty" extensions:"x-nullable"`
|
||||||
Homepage *homepage.ItemConfig `json:"homepage"`
|
Homepage *homepage.ItemConfig `json:"homepage"`
|
||||||
AccessLog *accesslog.RequestLoggerConfig `json:"access_log,omitempty" extensions:"x-nullable"`
|
AccessLog *accesslog.RequestLoggerConfig `json:"access_log,omitempty" extensions:"x-nullable"`
|
||||||
RelayProxyProtocolHeader bool `json:"relay_proxy_protocol_header,omitempty"` // TCP only: relay PROXY protocol header to the destination
|
Agent string `json:"agent,omitempty"`
|
||||||
Agent string `json:"agent,omitempty"`
|
|
||||||
|
|
||||||
Proxmox *proxmox.NodeConfig `json:"proxmox,omitempty" extensions:"x-nullable"`
|
Proxmox *proxmox.NodeConfig `json:"proxmox,omitempty" extensions:"x-nullable"`
|
||||||
|
|
||||||
@@ -311,9 +310,6 @@ func (r *Route) validate() error {
|
|||||||
if !r.UseHealthCheck() && (r.UseLoadBalance() || r.UseIdleWatcher()) {
|
if !r.UseHealthCheck() && (r.UseLoadBalance() || r.UseIdleWatcher()) {
|
||||||
errs.Adds("cannot disable healthcheck when loadbalancer or idle watcher is enabled")
|
errs.Adds("cannot disable healthcheck when loadbalancer or idle watcher is enabled")
|
||||||
}
|
}
|
||||||
if r.RelayProxyProtocolHeader && r.Scheme != route.SchemeTCP {
|
|
||||||
errs.Adds("relay_proxy_protocol_header is only supported for tcp routes")
|
|
||||||
}
|
|
||||||
|
|
||||||
if errs.HasError() {
|
if errs.HasError() {
|
||||||
return errs.Error()
|
return errs.Error()
|
||||||
|
|||||||
@@ -78,19 +78,6 @@ func TestRouteValidate(t *testing.T) {
|
|||||||
require.NotNil(t, r.impl, "Impl should be initialized")
|
require.NotNil(t, r.impl, "Impl should be initialized")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("RelayProxyProtocolHeaderTCPOnly", func(t *testing.T) {
|
|
||||||
r := &Route{
|
|
||||||
Alias: "test-udp-relay",
|
|
||||||
Scheme: route.SchemeUDP,
|
|
||||||
Host: "127.0.0.1",
|
|
||||||
Port: route.Port{Proxy: 53, Listening: 53},
|
|
||||||
RelayProxyProtocolHeader: true,
|
|
||||||
}
|
|
||||||
err := r.Validate()
|
|
||||||
require.Error(t, err, "Validate should reject proxy protocol relay on UDP routes")
|
|
||||||
require.ErrorContains(t, err, "relay_proxy_protocol_header is only supported for tcp routes")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("DockerContainer", func(t *testing.T) {
|
t.Run("DockerContainer", func(t *testing.T) {
|
||||||
r := &Route{
|
r := &Route{
|
||||||
Alias: "test",
|
Alias: "test",
|
||||||
|
|||||||
@@ -309,8 +309,7 @@ nested_block := on_expr ws* '{' do_body '}'
|
|||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- A nested block is recognized when a logical header ends with an unquoted `{`.
|
- A nested block is recognized when a line ends with an unquoted `{` (ignoring trailing whitespace).
|
||||||
- Logical headers can continue to the next line when the current line ends with `|` or `&`.
|
|
||||||
- `on_expr` uses the same syntax as rule `on` (supports `|`, `&`, quoting/backticks, matcher functions, etc.).
|
- `on_expr` uses the same syntax as rule `on` (supports `|`, `&`, quoting/backticks, matcher functions, etc.).
|
||||||
- The nested block executes **in sequence**, at the point where it appears in the parent `do` list.
|
- The nested block executes **in sequence**, at the point where it appears in the parent `do` list.
|
||||||
- Nested blocks are evaluated in the same phase the parent rule runs (no special phase promotion).
|
- Nested blocks are evaluated in the same phase the parent rule runs (no special phase promotion).
|
||||||
@@ -425,15 +424,6 @@ path !glob("/public/*")
|
|||||||
|
|
||||||
# OR within a line
|
# OR within a line
|
||||||
method GET | method POST
|
method GET | method POST
|
||||||
|
|
||||||
# OR across multiple lines (line continuation)
|
|
||||||
method GET |
|
|
||||||
method POST |
|
|
||||||
method PUT
|
|
||||||
|
|
||||||
# AND across multiple lines
|
|
||||||
header Connection Upgrade &
|
|
||||||
header Upgrade websocket
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Variable Substitution
|
### Variable Substitution
|
||||||
@@ -449,19 +439,8 @@ $remote_host # Client IP
|
|||||||
# Dynamic variables
|
# Dynamic variables
|
||||||
$header(Name) # Request header
|
$header(Name) # Request header
|
||||||
$header(Name, index) # Header at index
|
$header(Name, index) # Header at index
|
||||||
$resp_header(Name) # Response header
|
|
||||||
$arg(Name) # Query argument
|
$arg(Name) # Query argument
|
||||||
$form(Name) # Form field
|
$form(Name) # Form field
|
||||||
$postform(Name) # POST form field
|
|
||||||
$cookie(Name) # Cookie value
|
|
||||||
|
|
||||||
# Function composition: pass result of one function to another
|
|
||||||
$redacted($header(Authorization)) # Redact the Authorization header value
|
|
||||||
$redacted($arg(token)) # Redact a query parameter value
|
|
||||||
$redacted($cookie(session)) # Redact a cookie value
|
|
||||||
|
|
||||||
# $redacted: masks a value, showing only first 2 and last 2 characters
|
|
||||||
$redacted(value) # Redact a plain string
|
|
||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
${ENV_VAR}
|
${ENV_VAR}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package rules
|
package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -234,9 +233,6 @@ var commands = map[string]struct {
|
|||||||
route := args.(string)
|
route := args.(string)
|
||||||
return func(w *httputils.ResponseModifier, req *http.Request, upstream http.HandlerFunc) error {
|
return func(w *httputils.ResponseModifier, req *http.Request, upstream http.HandlerFunc) error {
|
||||||
ep := entrypoint.FromCtx(req.Context())
|
ep := entrypoint.FromCtx(req.Context())
|
||||||
if ep == nil {
|
|
||||||
return errors.New("entrypoint not found")
|
|
||||||
}
|
|
||||||
r, ok := ep.HTTPRoutes().Get(route)
|
r, ok := ep.HTTPRoutes().Get(route)
|
||||||
if !ok {
|
if !ok {
|
||||||
excluded, has := ep.ExcludedRoutes().Get(route)
|
excluded, has := ep.ExcludedRoutes().Get(route)
|
||||||
|
|||||||
@@ -292,20 +292,6 @@ func parseAtBlockChain(src string, blockPos int) (CommandHandler, int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func lineEndsWithUnquotedOpenBrace(src string, lineStart int, lineEnd int) bool {
|
func lineEndsWithUnquotedOpenBrace(src string, lineStart int, lineEnd int) bool {
|
||||||
return lineEndsWithUnquotedToken(src, lineStart, lineEnd) == '{'
|
|
||||||
}
|
|
||||||
|
|
||||||
func lineContinuationOperator(src string, lineStart int, lineEnd int) byte {
|
|
||||||
token := lineEndsWithUnquotedToken(src, lineStart, lineEnd)
|
|
||||||
switch token {
|
|
||||||
case '|', '&':
|
|
||||||
return token
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lineEndsWithUnquotedToken(src string, lineStart int, lineEnd int) byte {
|
|
||||||
quote := byte(0)
|
quote := byte(0)
|
||||||
lastSignificant := byte(0)
|
lastSignificant := byte(0)
|
||||||
atLineStart := true
|
atLineStart := true
|
||||||
@@ -348,22 +334,13 @@ func lineEndsWithUnquotedToken(src string, lineStart int, lineEnd int) byte {
|
|||||||
atLineStart = false
|
atLineStart = false
|
||||||
prevIsSpace = false
|
prevIsSpace = false
|
||||||
}
|
}
|
||||||
if quote != 0 {
|
return quote == 0 && lastSignificant == '{'
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return lastSignificant
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseDoWithBlocks parses a do-body containing plain command lines and nested blocks.
|
// parseDoWithBlocks parses a do-body containing plain command lines and nested blocks.
|
||||||
// It returns the outer command handlers and the require phase.
|
// It returns the outer command handlers and the require phase.
|
||||||
//
|
//
|
||||||
// A nested block is recognized when a logical header ends with an unquoted '{'.
|
// A nested block is recognized when a line ends with an unquoted '{' (ignoring trailing whitespace).
|
||||||
// Logical headers may span lines using trailing '|' or '&', for example:
|
|
||||||
//
|
|
||||||
// remote 127.0.0.1 |
|
|
||||||
// remote 192.168.0.0/16 {
|
|
||||||
// set header X-Remote-Type private
|
|
||||||
// }
|
|
||||||
func parseDoWithBlocks(src string) (handlers []CommandHandler, err error) {
|
func parseDoWithBlocks(src string) (handlers []CommandHandler, err error) {
|
||||||
pos := 0
|
pos := 0
|
||||||
length := len(src)
|
length := len(src)
|
||||||
@@ -423,38 +400,12 @@ func parseDoWithBlocks(src string) (handlers []CommandHandler, err error) {
|
|||||||
linePos++
|
linePos++
|
||||||
}
|
}
|
||||||
|
|
||||||
logicalEnd := linePos
|
lineEnd := linePos
|
||||||
for logicalEnd < length && src[logicalEnd] != '\n' {
|
for lineEnd < length && src[lineEnd] != '\n' {
|
||||||
logicalEnd++
|
lineEnd++
|
||||||
}
|
}
|
||||||
|
|
||||||
for linePos < length && lineContinuationOperator(src, linePos, logicalEnd) != 0 {
|
if linePos < length && lineEndsWithUnquotedOpenBrace(src, linePos, lineEnd) {
|
||||||
nextPos := logicalEnd
|
|
||||||
if nextPos < length && src[nextPos] == '\n' {
|
|
||||||
nextPos++
|
|
||||||
}
|
|
||||||
for nextPos < length {
|
|
||||||
c := rune(src[nextPos])
|
|
||||||
if c == '\n' {
|
|
||||||
nextPos++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c == '\r' || unicode.IsSpace(c) {
|
|
||||||
nextPos++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if nextPos >= length {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
logicalEnd = nextPos
|
|
||||||
for logicalEnd < length && src[logicalEnd] != '\n' {
|
|
||||||
logicalEnd++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if linePos < length && lineEndsWithUnquotedOpenBrace(src, linePos, logicalEnd) {
|
|
||||||
h, next, err := parseAtBlockChain(src, linePos)
|
h, next, err := parseAtBlockChain(src, linePos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -466,10 +417,10 @@ func parseDoWithBlocks(src string) (handlers []CommandHandler, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Not a nested block; parse the rest of this line as a command.
|
// Not a nested block; parse the rest of this line as a command.
|
||||||
if lerr := appendLineCommand(src[pos:logicalEnd]); lerr != nil {
|
if lerr := appendLineCommand(src[pos:lineEnd]); lerr != nil {
|
||||||
return nil, lerr
|
return nil, lerr
|
||||||
}
|
}
|
||||||
pos = logicalEnd
|
pos = lineEnd
|
||||||
lineStart = true
|
lineStart = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,38 +71,3 @@ func TestIfElseBlockCommandServeHTTP_ConditionalMatchedNilDoNotFallsThrough(t *t
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.False(t, elseCalled)
|
assert.False(t, elseCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseDoWithBlocks_MultilineBlockHeaderContinuation(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
src string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "or continuation",
|
|
||||||
src: `
|
|
||||||
remote 127.0.0.1 |
|
|
||||||
remote 192.168.0.0/16 {
|
|
||||||
set header X-Remote-Type private
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "and continuation",
|
|
||||||
src: `
|
|
||||||
method GET &
|
|
||||||
remote 127.0.0.1 {
|
|
||||||
set header X-Remote-Type private
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
handlers, err := parseDoWithBlocks(tt.src)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, handlers, 1)
|
|
||||||
require.IsType(t, IfBlockCommand{}, handlers[0])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -456,8 +456,7 @@ func TestHTTPFlow_NestedBlocks_RemoteOverride(t *testing.T) {
|
|||||||
err := parseRules(`
|
err := parseRules(`
|
||||||
header X-Test-Header {
|
header X-Test-Header {
|
||||||
set header X-Remote-Type public
|
set header X-Remote-Type public
|
||||||
remote 127.0.0.1 |
|
remote 127.0.0.1 | remote 192.168.0.0/16 {
|
||||||
remote 192.168.0.0/16 {
|
|
||||||
set header X-Remote-Type private
|
set header X-Remote-Type private
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -505,70 +505,62 @@ var (
|
|||||||
andSeps = [256]uint8{'&': 1, '\n': 1}
|
andSeps = [256]uint8{'&': 1, '\n': 1}
|
||||||
)
|
)
|
||||||
|
|
||||||
// splitAnd splits a condition string into AND parts.
|
func indexAnd(s string) int {
|
||||||
// It treats '&' and newline as AND separators, except when a line ends with
|
for i := range s {
|
||||||
// an unescaped '|' (OR continuation), where the newline stays in the same part.
|
if andSeps[s[i]] != 0 {
|
||||||
// Empty parts are omitted.
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func countAnd(s string) int {
|
||||||
|
n := 0
|
||||||
|
for i := range s {
|
||||||
|
if andSeps[s[i]] != 0 {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitAnd splits a string by "&" and "\n" with all spaces removed.
|
||||||
|
// empty strings are not included in the result.
|
||||||
func splitAnd(s string) []string {
|
func splitAnd(s string) []string {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
result := []string{}
|
n := countAnd(s)
|
||||||
forEachAndPart(s, func(part string) {
|
a := make([]string, n+1)
|
||||||
result = append(result, part)
|
i := 0
|
||||||
})
|
for i < n {
|
||||||
return result
|
end := indexAnd(s)
|
||||||
}
|
if end == -1 {
|
||||||
|
break
|
||||||
func lineEndsWithUnescapedPipe(s string, start, end int) bool {
|
|
||||||
for i := end - 1; i >= start; i-- {
|
|
||||||
if asciiSpace[s[i]] != 0 {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if s[i] != '|' {
|
beg := 0
|
||||||
return false
|
// trim leading spaces
|
||||||
|
for beg < end && asciiSpace[s[beg]] != 0 {
|
||||||
|
beg++
|
||||||
}
|
}
|
||||||
escapes := 0
|
// trim trailing spaces
|
||||||
for j := i - 1; j >= start && s[j] == '\\'; j-- {
|
next := end + 1
|
||||||
escapes++
|
for end-1 > beg && asciiSpace[s[end-1]] != 0 {
|
||||||
|
end--
|
||||||
}
|
}
|
||||||
return escapes%2 == 0
|
// skip empty segments
|
||||||
|
if end > beg {
|
||||||
|
a[i] = s[beg:end]
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
s = s[next:]
|
||||||
}
|
}
|
||||||
return false
|
s = strings.TrimSpace(s)
|
||||||
}
|
if s != "" {
|
||||||
|
a[i] = s
|
||||||
func advanceSplitState(s string, i *int, quote *byte, brackets *int) bool {
|
i++
|
||||||
c := s[*i]
|
|
||||||
if *quote != 0 {
|
|
||||||
if c == '\\' && *i+1 < len(s) {
|
|
||||||
*i++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if c == *quote {
|
|
||||||
*quote = 0
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
return a[:i]
|
||||||
switch c {
|
|
||||||
case '\\':
|
|
||||||
if *i+1 < len(s) {
|
|
||||||
*i++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case '"', '\'', '`':
|
|
||||||
*quote = c
|
|
||||||
return true
|
|
||||||
case '(':
|
|
||||||
*brackets++
|
|
||||||
return true
|
|
||||||
case ')':
|
|
||||||
if *brackets > 0 {
|
|
||||||
*brackets--
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitPipe splits a string by "|" but respects quotes, brackets, and escaped characters.
|
// splitPipe splits a string by "|" but respects quotes, brackets, and escaped characters.
|
||||||
@@ -586,26 +578,8 @@ func splitPipe(s string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func forEachAndPart(s string, fn func(part string)) {
|
func forEachAndPart(s string, fn func(part string)) {
|
||||||
quote := byte(0)
|
|
||||||
brackets := 0
|
|
||||||
start := 0
|
start := 0
|
||||||
|
|
||||||
for i := 0; i <= len(s); i++ {
|
for i := 0; i <= len(s); i++ {
|
||||||
if i < len(s) {
|
|
||||||
c := s[i]
|
|
||||||
if advanceSplitState(s, &i, "e, &brackets) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if c == '\n' {
|
|
||||||
if brackets > 0 || lineEndsWithUnescapedPipe(s, start, i) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else if c != '&' || brackets > 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i < len(s) && andSeps[s[i]] == 0 {
|
if i < len(s) && andSeps[s[i]] == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -623,14 +597,30 @@ func forEachPipePart(s string, fn func(part string)) {
|
|||||||
start := 0
|
start := 0
|
||||||
|
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if advanceSplitState(s, &i, "e, &brackets) {
|
switch s[i] {
|
||||||
continue
|
case '\\':
|
||||||
}
|
if i+1 < len(s) {
|
||||||
if s[i] == '|' && brackets == 0 {
|
i++
|
||||||
if part := strings.TrimSpace(s[start:i]); part != "" {
|
}
|
||||||
fn(part)
|
case '"', '\'', '`':
|
||||||
|
if quote == 0 && brackets == 0 {
|
||||||
|
quote = s[i]
|
||||||
|
} else if s[i] == quote {
|
||||||
|
quote = 0
|
||||||
|
}
|
||||||
|
case '(':
|
||||||
|
brackets++
|
||||||
|
case ')':
|
||||||
|
if brackets > 0 {
|
||||||
|
brackets--
|
||||||
|
}
|
||||||
|
case '|':
|
||||||
|
if quote == 0 && brackets == 0 {
|
||||||
|
if part := strings.TrimSpace(s[start:i]); part != "" {
|
||||||
|
fn(part)
|
||||||
|
}
|
||||||
|
start = i + 1
|
||||||
}
|
}
|
||||||
start = i + 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if start < len(s) {
|
if start < len(s) {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package rules
|
package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
gperr "github.com/yusing/goutils/errs"
|
gperr "github.com/yusing/goutils/errs"
|
||||||
@@ -135,16 +133,6 @@ func TestSplitAnd(t *testing.T) {
|
|||||||
input: " rule1\nrule2 & rule3 ",
|
input: " rule1\nrule2 & rule3 ",
|
||||||
want: []string{"rule1", "rule2", "rule3"},
|
want: []string{"rule1", "rule2", "rule3"},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "newline_after_pipe_is_or_continuation",
|
|
||||||
input: "path /abc |\npath /bcd",
|
|
||||||
want: []string{"path /abc |\npath /bcd"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "newline_after_pipe_with_spaces_is_or_continuation",
|
|
||||||
input: "path /abc | \n path /bcd",
|
|
||||||
want: []string{"path /abc | \n path /bcd"},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@@ -292,11 +280,6 @@ func TestParseOn(t *testing.T) {
|
|||||||
input: `method GET | path regex("^(_next/static|_next/image|favicon.ico).*$") | header Authorization`,
|
input: `method GET | path regex("^(_next/static|_next/image|favicon.ico).*$") | header Authorization`,
|
||||||
wantErr: nil,
|
wantErr: nil,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "pipe_multiline_continuation",
|
|
||||||
input: "path /abc |\npath /bcd |",
|
|
||||||
wantErr: nil,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -311,18 +294,3 @@ func TestParseOn(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRuleOnParse_MultilineOrContinuation(t *testing.T) {
|
|
||||||
var on RuleOn
|
|
||||||
err := on.Parse("path /abc |\npath /bcd |")
|
|
||||||
expect.NoError(t, err)
|
|
||||||
|
|
||||||
w := http.ResponseWriter(nil)
|
|
||||||
reqABC := &http.Request{URL: &url.URL{Path: "/abc"}}
|
|
||||||
reqBCD := &http.Request{URL: &url.URL{Path: "/bcd"}}
|
|
||||||
reqXYZ := &http.Request{URL: &url.URL{Path: "/xyz"}}
|
|
||||||
|
|
||||||
expect.Equal(t, on.Check(w, reqABC), true)
|
|
||||||
expect.Equal(t, on.Check(w, reqBCD), true)
|
|
||||||
expect.Equal(t, on.Check(w, reqXYZ), false)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -27,21 +27,22 @@ type (
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
proxy.app1.rules: |
|
proxy.app1.rules: |
|
||||||
default {
|
- name: default
|
||||||
rewrite / /index.html
|
do: |
|
||||||
serve /var/www/goaccess
|
rewrite / /index.html
|
||||||
}
|
serve /var/www/goaccess
|
||||||
header Connection Upgrade & header Upgrade websocket {
|
- name: ws
|
||||||
bypass
|
on: |
|
||||||
}
|
header Connection Upgrade
|
||||||
|
header Upgrade websocket
|
||||||
|
do: bypass
|
||||||
|
|
||||||
proxy.app2.rules: |
|
proxy.app2.rules: |
|
||||||
default {
|
- name: default
|
||||||
bypass
|
do: bypass
|
||||||
}
|
- name: block POST and PUT
|
||||||
method POST | method PUT {
|
on: method POST | method PUT
|
||||||
error 403 Forbidden
|
do: error 403 Forbidden
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
//nolint:recvcheck
|
//nolint:recvcheck
|
||||||
Rules []Rule
|
Rules []Rule
|
||||||
|
|||||||
@@ -152,12 +152,6 @@ func ExpandVars(w *httputils.ResponseModifier, req *http.Request, src string, ds
|
|||||||
return phase, err
|
return phase, err
|
||||||
}
|
}
|
||||||
i = nextIdx
|
i = nextIdx
|
||||||
// Expand any nested $func(...) expressions in args
|
|
||||||
args, argPhase, err := expandArgs(args, w, req)
|
|
||||||
if err != nil {
|
|
||||||
return phase, err
|
|
||||||
}
|
|
||||||
phase |= argPhase
|
|
||||||
actual, err = getter.get(args, w, req)
|
actual, err = getter.get(args, w, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return phase, err
|
return phase, err
|
||||||
@@ -227,18 +221,6 @@ func extractArgs(src string, i int, funcName string) (args []string, nextIdx int
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nested function call: $func(...) as an argument
|
|
||||||
if ch == '$' && arg.Len() == 0 {
|
|
||||||
// Capture the entire $func(...) expression as a raw argument token
|
|
||||||
nestedEnd, nestedErr := extractNestedFuncExpr(src, nextIdx)
|
|
||||||
if nestedErr != nil {
|
|
||||||
return nil, 0, nestedErr
|
|
||||||
}
|
|
||||||
args = append(args, src[nextIdx:nestedEnd+1])
|
|
||||||
nextIdx = nestedEnd + 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ch == ')' {
|
if ch == ')' {
|
||||||
// End of arguments
|
// End of arguments
|
||||||
if arg.Len() > 0 {
|
if arg.Len() > 0 {
|
||||||
@@ -274,70 +256,3 @@ func extractArgs(src string, i int, funcName string) (args []string, nextIdx int
|
|||||||
}
|
}
|
||||||
return nil, 0, ErrUnterminatedParenthesis.Withf("func %q", funcName)
|
return nil, 0, ErrUnterminatedParenthesis.Withf("func %q", funcName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractNestedFuncExpr finds the end index (inclusive) of a $func(...) expression
|
|
||||||
// starting at position start in src. It handles nested parentheses.
|
|
||||||
func extractNestedFuncExpr(src string, start int) (endIdx int, err error) {
|
|
||||||
// src[start] must be '$'
|
|
||||||
i := start + 1
|
|
||||||
// skip the function name (valid var name chars)
|
|
||||||
for i < len(src) && validVarNameCharset[src[i]] {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if i >= len(src) || src[i] != '(' {
|
|
||||||
return 0, ErrUnterminatedParenthesis.Withf("nested func at position %d", start)
|
|
||||||
}
|
|
||||||
// Now find the matching closing parenthesis, respecting quotes and nesting
|
|
||||||
depth := 0
|
|
||||||
var quote byte
|
|
||||||
for i < len(src) {
|
|
||||||
ch := src[i]
|
|
||||||
if quote != 0 {
|
|
||||||
if ch == quote {
|
|
||||||
quote = 0
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if quoteChars[ch] {
|
|
||||||
quote = ch
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch ch {
|
|
||||||
case '(':
|
|
||||||
depth++
|
|
||||||
case ')':
|
|
||||||
depth--
|
|
||||||
if depth == 0 {
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if quote != 0 {
|
|
||||||
return 0, ErrUnterminatedQuotes.Withf("nested func at position %d", start)
|
|
||||||
}
|
|
||||||
return 0, ErrUnterminatedParenthesis.Withf("nested func at position %d", start)
|
|
||||||
}
|
|
||||||
|
|
||||||
// expandArgs expands any args that are nested dynamic var expressions (starting with '$').
|
|
||||||
// It returns the expanded args and the combined phase flags.
|
|
||||||
func expandArgs(args []string, w *httputils.ResponseModifier, req *http.Request) (expanded []string, phase PhaseFlag, err error) {
|
|
||||||
expanded = make([]string, len(args))
|
|
||||||
for i, arg := range args {
|
|
||||||
if len(arg) > 0 && arg[0] == '$' {
|
|
||||||
var buf strings.Builder
|
|
||||||
var argPhase PhaseFlag
|
|
||||||
argPhase, err = ExpandVars(w, req, arg, &buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, phase, err
|
|
||||||
}
|
|
||||||
phase |= argPhase
|
|
||||||
expanded[i] = buf.String()
|
|
||||||
} else {
|
|
||||||
expanded[i] = arg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expanded, phase, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
httputils "github.com/yusing/goutils/http"
|
httputils "github.com/yusing/goutils/http"
|
||||||
strutils "github.com/yusing/goutils/strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -16,7 +15,6 @@ var (
|
|||||||
VarQuery = "arg"
|
VarQuery = "arg"
|
||||||
VarForm = "form"
|
VarForm = "form"
|
||||||
VarPostForm = "postform"
|
VarPostForm = "postform"
|
||||||
VarRedacted = "redacted"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type dynamicVarGetter struct {
|
type dynamicVarGetter struct {
|
||||||
@@ -96,17 +94,6 @@ var dynamicVarSubsMap = map[string]dynamicVarGetter{
|
|||||||
return getValueByKeyAtIndex(req.PostForm, key, index)
|
return getValueByKeyAtIndex(req.PostForm, key, index)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// VarRedacted wraps the result of its single argument (which may be another dynamic var
|
|
||||||
// expression, already expanded by expandArgs) with strutils.Redact.
|
|
||||||
VarRedacted: {
|
|
||||||
phase: PhaseNone,
|
|
||||||
get: func(args []string, w *httputils.ResponseModifier, req *http.Request) (string, error) {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return "", ErrExpectOneArg
|
|
||||||
}
|
|
||||||
return strutils.Redact(args[0]), nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getValueByKeyAtIndex[Values http.Header | url.Values](values Values, key string, index int) (string, error) {
|
func getValueByKeyAtIndex[Values http.Header | url.Values](values Values, key string, index int) (string, error) {
|
||||||
|
|||||||
@@ -189,64 +189,6 @@ func TestExtractArgs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractArgs_NestedFunc(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
src string
|
|
||||||
startPos int
|
|
||||||
funcName string
|
|
||||||
wantArgs []string
|
|
||||||
wantNextIdx int
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nested func as single arg",
|
|
||||||
src: "redacted($header(Authorization))",
|
|
||||||
startPos: 0,
|
|
||||||
funcName: "redacted",
|
|
||||||
wantArgs: []string{"$header(Authorization)"},
|
|
||||||
wantNextIdx: 31,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nested func with quoted arg inside",
|
|
||||||
src: `redacted($header("X-Secret"))`,
|
|
||||||
startPos: 0,
|
|
||||||
funcName: "redacted",
|
|
||||||
wantArgs: []string{`$header("X-Secret")`},
|
|
||||||
wantNextIdx: 28,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nested func with two args inside",
|
|
||||||
src: "redacted($header(X-Multi, 1))",
|
|
||||||
startPos: 0,
|
|
||||||
funcName: "redacted",
|
|
||||||
wantArgs: []string{"$header(X-Multi, 1)"},
|
|
||||||
wantNextIdx: 28,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nested func missing closing paren",
|
|
||||||
src: "redacted($header(Authorization)",
|
|
||||||
startPos: 0,
|
|
||||||
funcName: "redacted",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
args, nextIdx, err := extractArgs(tt.src, tt.startPos, tt.funcName)
|
|
||||||
|
|
||||||
if tt.wantErr {
|
|
||||||
require.Error(t, err)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, tt.wantArgs, args)
|
|
||||||
require.Equal(t, tt.wantNextIdx, nextIdx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExpandVars(t *testing.T) {
|
func TestExpandVars(t *testing.T) {
|
||||||
// Create a comprehensive test request with form data
|
// Create a comprehensive test request with form data
|
||||||
formData := url.Values{}
|
formData := url.Values{}
|
||||||
@@ -504,27 +446,6 @@ func TestExpandVars(t *testing.T) {
|
|||||||
input: "Header: $header(User-Agent), Status: $status_code",
|
input: "Header: $header(User-Agent), Status: $status_code",
|
||||||
want: "Header: test-agent/1.0, Status: 200",
|
want: "Header: test-agent/1.0, Status: 200",
|
||||||
},
|
},
|
||||||
// $redacted function
|
|
||||||
{
|
|
||||||
name: "redacted with plain string arg",
|
|
||||||
input: "$redacted(secret)",
|
|
||||||
want: "se**et",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "redacted wrapping header",
|
|
||||||
input: "$redacted($header(User-Agent))",
|
|
||||||
want: "te**********.0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "redacted wrapping arg",
|
|
||||||
input: "$redacted($arg(param1))",
|
|
||||||
want: "va**e1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "redacted with no args",
|
|
||||||
input: "$redacted()",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
// Escaped dollar signs
|
// Escaped dollar signs
|
||||||
{
|
{
|
||||||
name: "escaped dollar",
|
name: "escaped dollar",
|
||||||
|
|||||||
@@ -110,14 +110,7 @@ func (r *StreamRoute) initStream() (nettypes.Stream, error) {
|
|||||||
|
|
||||||
switch rScheme {
|
switch rScheme {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
return stream.NewTCPTCPStream(
|
return stream.NewTCPTCPStream(lurl.Scheme, rurl.Scheme, laddr, rurl.Host, r.GetAgent())
|
||||||
lurl.Scheme,
|
|
||||||
rurl.Scheme,
|
|
||||||
laddr,
|
|
||||||
rurl.Host,
|
|
||||||
r.GetAgent(),
|
|
||||||
r.RelayProxyProtocolHeader,
|
|
||||||
)
|
|
||||||
case "udp":
|
case "udp":
|
||||||
return stream.NewUDPUDPStream(lurl.Scheme, rurl.Scheme, laddr, rurl.Host, r.GetAgent())
|
return stream.NewUDPUDPStream(lurl.Scheme, rurl.Scheme, laddr, rurl.Host, r.GetAgent())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,7 +181,6 @@ routes:
|
|||||||
scheme: tcp4
|
scheme: tcp4
|
||||||
bind: 0.0.0.0 # optional
|
bind: 0.0.0.0 # optional
|
||||||
port: 2222:22 # listening port: target port
|
port: 2222:22 # listening port: target port
|
||||||
relay_proxy_protocol_header: true # optional, tcp only
|
|
||||||
|
|
||||||
dns-proxy:
|
dns-proxy:
|
||||||
scheme: udp4
|
scheme: udp4
|
||||||
@@ -224,7 +223,6 @@ Log context includes: `protocol`, `listen`, `dst`, `action`
|
|||||||
|
|
||||||
- ACL wrapping available for TCP and UDP listeners
|
- ACL wrapping available for TCP and UDP listeners
|
||||||
- PROXY protocol support for original client IP
|
- PROXY protocol support for original client IP
|
||||||
- TCP routes can optionally emit a fresh upstream PROXY v2 header with `relay_proxy_protocol_header: true`
|
|
||||||
- No protocol validation (relies on upstream)
|
- No protocol validation (relies on upstream)
|
||||||
- Connection limits managed by OS
|
- Connection limits managed by OS
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
package stream
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/pires/go-proxyproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func writeProxyProtocolHeader(dst io.Writer, src net.Conn) error {
|
|
||||||
srcAddr, ok := src.RemoteAddr().(*net.TCPAddr)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unexpected source address type %T", src.RemoteAddr())
|
|
||||||
}
|
|
||||||
dstAddr, ok := src.LocalAddr().(*net.TCPAddr)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unexpected destination address type %T", src.LocalAddr())
|
|
||||||
}
|
|
||||||
|
|
||||||
header := &proxyproto.Header{
|
|
||||||
Version: 2,
|
|
||||||
Command: proxyproto.PROXY,
|
|
||||||
TransportProtocol: transportProtocol(srcAddr, dstAddr),
|
|
||||||
SourceAddr: srcAddr,
|
|
||||||
DestinationAddr: dstAddr,
|
|
||||||
}
|
|
||||||
_, err := header.WriteTo(dst)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func transportProtocol(src, dst *net.TCPAddr) proxyproto.AddressFamilyAndProtocol {
|
|
||||||
if src.IP.To4() != nil && dst.IP.To4() != nil {
|
|
||||||
return proxyproto.TCPv4
|
|
||||||
}
|
|
||||||
return proxyproto.TCPv6
|
|
||||||
}
|
|
||||||
@@ -25,15 +25,13 @@ type TCPTCPStream struct {
|
|||||||
dst *net.TCPAddr
|
dst *net.TCPAddr
|
||||||
agent *agentpool.Agent
|
agent *agentpool.Agent
|
||||||
|
|
||||||
relayProxyProtocolHeader bool
|
|
||||||
|
|
||||||
preDial nettypes.HookFunc
|
preDial nettypes.HookFunc
|
||||||
onRead nettypes.HookFunc
|
onRead nettypes.HookFunc
|
||||||
|
|
||||||
closed atomic.Bool
|
closed atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCPTCPStream(network, dstNetwork, listenAddr, dstAddr string, agent *agentpool.Agent, relayProxyProtocolHeader bool) (nettypes.Stream, error) {
|
func NewTCPTCPStream(network, dstNetwork, listenAddr, dstAddr string, agent *agentpool.Agent) (nettypes.Stream, error) {
|
||||||
dst, err := net.ResolveTCPAddr(dstNetwork, dstAddr)
|
dst, err := net.ResolveTCPAddr(dstNetwork, dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -42,14 +40,7 @@ func NewTCPTCPStream(network, dstNetwork, listenAddr, dstAddr string, agent *age
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &TCPTCPStream{
|
return &TCPTCPStream{network: network, dstNetwork: dstNetwork, laddr: laddr, dst: dst, agent: agent}, nil
|
||||||
network: network,
|
|
||||||
dstNetwork: dstNetwork,
|
|
||||||
laddr: laddr,
|
|
||||||
dst: dst,
|
|
||||||
agent: agent,
|
|
||||||
relayProxyProtocolHeader: relayProxyProtocolHeader,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TCPTCPStream) ListenAndServe(ctx context.Context, preDial, onRead nettypes.HookFunc) error {
|
func (s *TCPTCPStream) ListenAndServe(ctx context.Context, preDial, onRead nettypes.HookFunc) error {
|
||||||
@@ -167,14 +158,6 @@ func (s *TCPTCPStream) handle(ctx context.Context, conn net.Conn) {
|
|||||||
if s.closed.Load() {
|
if s.closed.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if s.relayProxyProtocolHeader {
|
|
||||||
if err := writeProxyProtocolHeader(dstConn, conn); err != nil {
|
|
||||||
if !s.closed.Load() {
|
|
||||||
logErr(s, err, "failed to write proxy protocol header")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
src := conn
|
src := conn
|
||||||
dst := dstConn
|
dst := dstConn
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
package stream
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/pires/go-proxyproto"
|
|
||||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint"
|
|
||||||
entrypointtypes "github.com/yusing/godoxy/internal/entrypoint/types"
|
|
||||||
"github.com/yusing/goutils/task"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTCPTCPStreamRelayProxyProtocolHeader(t *testing.T) {
|
|
||||||
t.Run("Disabled", func(t *testing.T) {
|
|
||||||
upstreamLn, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer upstreamLn.Close()
|
|
||||||
|
|
||||||
s, err := NewTCPTCPStream("tcp", "tcp", "127.0.0.1:0", upstreamLn.Addr().String(), nil, false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(t.Context())
|
|
||||||
defer cancel()
|
|
||||||
require.NoError(t, s.ListenAndServe(ctx, nil, nil))
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
client, err := net.Dial("tcp", s.LocalAddr().String())
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
_, err = client.Write([]byte("ping"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
upstreamConn, err := upstreamLn.Accept()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer upstreamConn.Close()
|
|
||||||
|
|
||||||
payload := make([]byte, 4)
|
|
||||||
_, err = io.ReadFull(upstreamConn, payload)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, []byte("ping"), payload)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Enabled", func(t *testing.T) {
|
|
||||||
upstreamLn, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer upstreamLn.Close()
|
|
||||||
|
|
||||||
s, err := NewTCPTCPStream("tcp", "tcp", "127.0.0.1:0", upstreamLn.Addr().String(), nil, true)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(t.Context())
|
|
||||||
defer cancel()
|
|
||||||
require.NoError(t, s.ListenAndServe(ctx, nil, nil))
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
client, err := net.Dial("tcp", s.LocalAddr().String())
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
_, err = client.Write([]byte("ping"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
upstreamConn, err := upstreamLn.Accept()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer upstreamConn.Close()
|
|
||||||
|
|
||||||
reader := bufio.NewReader(upstreamConn)
|
|
||||||
header, err := proxyproto.Read(reader)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, proxyproto.PROXY, header.Command)
|
|
||||||
|
|
||||||
srcAddr, ok := header.SourceAddr.(*net.TCPAddr)
|
|
||||||
require.True(t, ok)
|
|
||||||
dstAddr, ok := header.DestinationAddr.(*net.TCPAddr)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, client.LocalAddr().String(), srcAddr.String())
|
|
||||||
require.Equal(t, s.LocalAddr().String(), dstAddr.String())
|
|
||||||
|
|
||||||
payload := make([]byte, 4)
|
|
||||||
_, err = io.ReadFull(reader, payload)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, []byte("ping"), payload)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTCPTCPStreamRelayProxyProtocolUsesIncomingProxyHeader(t *testing.T) {
|
|
||||||
upstreamLn, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer upstreamLn.Close()
|
|
||||||
|
|
||||||
s, err := NewTCPTCPStream("tcp", "tcp", "127.0.0.1:0", upstreamLn.Addr().String(), nil, true)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
parent := task.GetTestTask(t)
|
|
||||||
ep := entrypoint.NewEntrypoint(parent, &entrypoint.Config{
|
|
||||||
SupportProxyProtocol: true,
|
|
||||||
})
|
|
||||||
entrypointtypes.SetCtx(parent, ep)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(parent.Context())
|
|
||||||
defer cancel()
|
|
||||||
require.NoError(t, s.ListenAndServe(ctx, nil, nil))
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
client, err := net.Dial("tcp", s.LocalAddr().String())
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
downstreamHeader := &proxyproto.Header{
|
|
||||||
Version: 2,
|
|
||||||
Command: proxyproto.PROXY,
|
|
||||||
TransportProtocol: proxyproto.TCPv4,
|
|
||||||
SourceAddr: &net.TCPAddr{
|
|
||||||
IP: net.ParseIP("203.0.113.10"),
|
|
||||||
Port: 42300,
|
|
||||||
},
|
|
||||||
DestinationAddr: &net.TCPAddr{
|
|
||||||
IP: net.ParseIP("127.0.0.1"),
|
|
||||||
Port: s.LocalAddr().(*net.TCPAddr).Port,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, err = downstreamHeader.WriteTo(client)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = client.Write([]byte("pong"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
upstreamConn, err := upstreamLn.Accept()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer upstreamConn.Close()
|
|
||||||
|
|
||||||
reader := bufio.NewReader(upstreamConn)
|
|
||||||
header, err := proxyproto.Read(reader)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, downstreamHeader.SourceAddr.String(), header.SourceAddr.String())
|
|
||||||
require.Equal(t, downstreamHeader.DestinationAddr.String(), header.DestinationAddr.String())
|
|
||||||
|
|
||||||
payload := make([]byte, 4)
|
|
||||||
_, err = io.ReadFull(reader, payload)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, []byte("pong"), payload)
|
|
||||||
}
|
|
||||||
@@ -26,9 +26,3 @@ app2:
|
|||||||
scheme: udp
|
scheme: udp
|
||||||
host: 10.0.0.2
|
host: 10.0.0.2
|
||||||
port: 2223:dns
|
port: 2223:dns
|
||||||
|
|
||||||
ssh-with-proxy-protocol:
|
|
||||||
scheme: tcp
|
|
||||||
host: 10.0.0.3
|
|
||||||
port: 2222:22
|
|
||||||
relay_proxy_protocol_header: true
|
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
||||||
echo "Working tree is not clean. Commit or stash changes before running refresh-compat.sh." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
git fetch origin main compat
|
|
||||||
git checkout -B compat origin/compat
|
|
||||||
patch_file="$(mktemp)"
|
|
||||||
trap 'rm -f "$patch_file"' EXIT
|
|
||||||
git diff origin/main -- ':(glob)**/*.go' >"$patch_file"
|
|
||||||
git checkout -B main origin/main
|
|
||||||
git branch -D compat
|
|
||||||
git checkout -b compat
|
|
||||||
git apply "$patch_file"
|
|
||||||
mapfile -t changed_go_files < <(git diff --name-only -- '*.go')
|
|
||||||
fmt_go_files=()
|
|
||||||
for file in "${changed_go_files[@]}"; do
|
|
||||||
[ -f "$file" ] || continue
|
|
||||||
sed -i 's/sonic\./json\./g' "$file"
|
|
||||||
sed -i 's/"github.com\/bytedance\/sonic"/"encoding\/json"/g' "$file"
|
|
||||||
sed -E -i 's/\bsonic[[:space:]]+"encoding\/json"/json "encoding\/json"/g' "$file"
|
|
||||||
fmt_go_files+=("$file")
|
|
||||||
done
|
|
||||||
if [ "${#fmt_go_files[@]}" -gt 0 ]; then
|
|
||||||
gofmt -w "${fmt_go_files[@]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# create placeholder files for minified JS files so go vet won't complain
|
|
||||||
while IFS= read -r file; do
|
|
||||||
ext="${file##*.}"
|
|
||||||
base="${file%.*}"
|
|
||||||
min_file="${base}-min.${ext}"
|
|
||||||
[ -f "$min_file" ] || : >"$min_file"
|
|
||||||
done < <(find internal/ -name '*.js' ! -name '*-min.js')
|
|
||||||
|
|
||||||
docker_version="$(
|
|
||||||
git show origin/compat:go.mod |
|
|
||||||
sed -n 's/^[[:space:]]*github.com\/docker\/docker[[:space:]]\+\(v[^[:space:]]\+\).*/\1/p' |
|
|
||||||
head -n 1
|
|
||||||
)"
|
|
||||||
if [ -n "$docker_version" ]; then
|
|
||||||
go mod edit -droprequire=github.com/docker/docker/api || true
|
|
||||||
go mod edit -droprequire=github.com/docker/docker/client || true
|
|
||||||
go mod edit -require="github.com/docker/docker@${docker_version}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
go mod tidy
|
|
||||||
go mod -C agent tidy
|
|
||||||
git add -A
|
|
||||||
git commit -m "Apply compat patch"
|
|
||||||
go vet ./...
|
|
||||||
go vet -C agent ./...
|
|
||||||
@@ -4,336 +4,335 @@ import { Glob } from "bun";
|
|||||||
import { md2mdx } from "./api-md2mdx";
|
import { md2mdx } from "./api-md2mdx";
|
||||||
|
|
||||||
type ImplDoc = {
|
type ImplDoc = {
|
||||||
/** Directory path relative to this repo, e.g. "internal/health/check" */
|
/** Directory path relative to this repo, e.g. "internal/health/check" */
|
||||||
pkgPath: string;
|
pkgPath: string;
|
||||||
/** File name in wiki `src/impl/`, e.g. "internal-health-check.md" */
|
/** File name in wiki `src/impl/`, e.g. "internal-health-check.md" */
|
||||||
docFileName: string;
|
docFileName: string;
|
||||||
/** VitePress route path (extensionless), e.g. "/impl/internal-health-check" */
|
/** VitePress route path (extensionless), e.g. "/impl/internal-health-check" */
|
||||||
docRoute: string;
|
docRoute: string;
|
||||||
/** Absolute source README path */
|
/** Absolute source README path */
|
||||||
srcPathAbs: string;
|
srcPathAbs: string;
|
||||||
/** Absolute destination doc path */
|
/** Absolute destination doc path */
|
||||||
dstPathAbs: string;
|
dstPathAbs: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const skipSubmodules = [
|
const skipSubmodules = [
|
||||||
"internal/go-oidc/",
|
"internal/go-oidc/",
|
||||||
"internal/gopsutil/",
|
"internal/gopsutil/",
|
||||||
"internal/go-proxmox/",
|
"internal/go-proxmox/",
|
||||||
];
|
];
|
||||||
|
|
||||||
function normalizeRepoUrl(raw: string) {
|
function normalizeRepoUrl(raw: string) {
|
||||||
let url = (raw ?? "").trim();
|
let url = (raw ?? "").trim();
|
||||||
if (!url) return "";
|
if (!url) return "";
|
||||||
// Common typo: "https://https://github.com/..."
|
// Common typo: "https://https://github.com/..."
|
||||||
url = url.replace(/^https?:\/\/https?:\/\//i, "https://");
|
url = url.replace(/^https?:\/\/https?:\/\//i, "https://");
|
||||||
if (!/^https?:\/\//i.test(url)) url = `https://${url}`;
|
if (!/^https?:\/\//i.test(url)) url = `https://${url}`;
|
||||||
url = url.replace(/\/+$/, "");
|
url = url.replace(/\/+$/, "");
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeFileStemFromPkgPath(pkgPath: string) {
|
function sanitizeFileStemFromPkgPath(pkgPath: string) {
|
||||||
// Convert a package path into a stable filename.
|
// Convert a package path into a stable filename.
|
||||||
// Example: "internal/go-oidc/example" -> "internal-go-oidc-example"
|
// Example: "internal/go-oidc/example" -> "internal-go-oidc-example"
|
||||||
// Keep it readable and unique (uses full path).
|
// Keep it readable and unique (uses full path).
|
||||||
const parts = pkgPath
|
const parts = pkgPath
|
||||||
.split("/")
|
.split("/")
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((p) => p.replace(/[^A-Za-z0-9._-]+/g, "-"));
|
.map((p) => p.replace(/[^A-Za-z0-9._-]+/g, "-"));
|
||||||
const joined = parts.join("-");
|
const joined = parts.join("-");
|
||||||
return joined.replace(/-+/g, "-").replace(/^-|-$/g, "");
|
return joined.replace(/-+/g, "-").replace(/^-|-$/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitUrlAndFragment(url: string): {
|
function splitUrlAndFragment(url: string): {
|
||||||
urlNoFragment: string;
|
urlNoFragment: string;
|
||||||
fragment: string;
|
fragment: string;
|
||||||
} {
|
} {
|
||||||
const i = url.indexOf("#");
|
const i = url.indexOf("#");
|
||||||
if (i === -1) return { urlNoFragment: url, fragment: "" };
|
if (i === -1) return { urlNoFragment: url, fragment: "" };
|
||||||
return { urlNoFragment: url.slice(0, i), fragment: url.slice(i) };
|
return { urlNoFragment: url.slice(0, i), fragment: url.slice(i) };
|
||||||
}
|
}
|
||||||
|
|
||||||
function isExternalOrAbsoluteUrl(url: string) {
|
function isExternalOrAbsoluteUrl(url: string) {
|
||||||
// - absolute site links: "/foo"
|
// - absolute site links: "/foo"
|
||||||
// - pure fragments: "#bar"
|
// - pure fragments: "#bar"
|
||||||
// - external schemes: "https:", "mailto:", "vscode:", etc.
|
// - external schemes: "https:", "mailto:", "vscode:", etc.
|
||||||
// IMPORTANT: don't treat "config.go:29" as a scheme.
|
// IMPORTANT: don't treat "config.go:29" as a scheme.
|
||||||
if (url.startsWith("/") || url.startsWith("#")) return true;
|
if (url.startsWith("/") || url.startsWith("#")) return true;
|
||||||
if (url.includes("://")) return true;
|
if (url.includes("://")) return true;
|
||||||
return /^(https?|mailto|tel|vscode|file|data|ssh|git):/i.test(url);
|
return /^(https?|mailto|tel|vscode|file|data|ssh|git):/i.test(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRepoSourceFilePath(filePath: string) {
|
function isRepoSourceFilePath(filePath: string) {
|
||||||
// Conservative allow-list: avoid rewriting .md (non-README) which may be VitePress docs.
|
// Conservative allow-list: avoid rewriting .md (non-README) which may be VitePress docs.
|
||||||
return /\.(go|ts|tsx|js|jsx|py|sh|yml|yaml|json|toml|env|css|html|txt)$/i.test(
|
return /\.(go|ts|tsx|js|jsx|py|sh|yml|yaml|json|toml|env|css|html|txt)$/i.test(
|
||||||
filePath,
|
filePath,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFileLineSuffix(urlNoFragment: string): {
|
function parseFileLineSuffix(urlNoFragment: string): {
|
||||||
filePath: string;
|
filePath: string;
|
||||||
line?: string;
|
line?: string;
|
||||||
} {
|
} {
|
||||||
// Match "file.ext:123" (line suffix), while leaving "file.ext" untouched.
|
// Match "file.ext:123" (line suffix), while leaving "file.ext" untouched.
|
||||||
const m = urlNoFragment.match(/^(.*?):(\d+)$/);
|
const m = urlNoFragment.match(/^(.*?):(\d+)$/);
|
||||||
if (!m) return { filePath: urlNoFragment };
|
if (!m) return { filePath: urlNoFragment };
|
||||||
return { filePath: m[1] ?? urlNoFragment, line: m[2] };
|
return { filePath: m[1] ?? urlNoFragment, line: m[2] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function rewriteMarkdownLinksOutsideFences(
|
function rewriteMarkdownLinksOutsideFences(
|
||||||
md: string,
|
md: string,
|
||||||
rewriteInline: (url: string) => string,
|
rewriteInline: (url: string) => string,
|
||||||
) {
|
) {
|
||||||
const lines = md.split("\n");
|
const lines = md.split("\n");
|
||||||
let inFence = false;
|
let inFence = false;
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i] ?? "";
|
const line = lines[i] ?? "";
|
||||||
const trimmed = line.trimStart();
|
const trimmed = line.trimStart();
|
||||||
if (trimmed.startsWith("```")) {
|
if (trimmed.startsWith("```")) {
|
||||||
inFence = !inFence;
|
inFence = !inFence;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (inFence) continue;
|
if (inFence) continue;
|
||||||
|
|
||||||
// Inline markdown links/images: [text](url "title") / 
|
// Inline markdown links/images: [text](url "title") / 
|
||||||
lines[i] = line.replace(
|
lines[i] = line.replace(
|
||||||
/\]\(([^)\s]+)(\s+"[^"]*")?\)/g,
|
/\]\(([^)\s]+)(\s+"[^"]*")?\)/g,
|
||||||
(_full, urlRaw: string, maybeTitle: string | undefined) => {
|
(_full, urlRaw: string, maybeTitle: string | undefined) => {
|
||||||
const rewritten = rewriteInline(urlRaw);
|
const rewritten = rewriteInline(urlRaw);
|
||||||
return `](${rewritten}${maybeTitle ?? ""})`;
|
return `](${rewritten}${maybeTitle ?? ""})`;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function rewriteImplMarkdown(params: {
|
function rewriteImplMarkdown(params: {
|
||||||
md: string;
|
md: string;
|
||||||
pkgPath: string;
|
pkgPath: string;
|
||||||
readmeRelToDocRoute: Map<string, string>;
|
readmeRelToDocRoute: Map<string, string>;
|
||||||
dirPathToDocRoute: Map<string, string>;
|
dirPathToDocRoute: Map<string, string>;
|
||||||
repoUrl: string;
|
repoUrl: string;
|
||||||
}) {
|
}) {
|
||||||
const { md, pkgPath, readmeRelToDocRoute, dirPathToDocRoute, repoUrl } =
|
const { md, pkgPath, readmeRelToDocRoute, dirPathToDocRoute, repoUrl } =
|
||||||
params;
|
params;
|
||||||
|
|
||||||
return rewriteMarkdownLinksOutsideFences(md, (urlRaw) => {
|
return rewriteMarkdownLinksOutsideFences(md, (urlRaw) => {
|
||||||
// Handle angle-bracketed destinations: (<./foo/README.md>)
|
// Handle angle-bracketed destinations: (<./foo/README.md>)
|
||||||
const angleWrapped =
|
const angleWrapped =
|
||||||
urlRaw.startsWith("<") && urlRaw.endsWith(">")
|
urlRaw.startsWith("<") && urlRaw.endsWith(">")
|
||||||
? urlRaw.slice(1, -1)
|
? urlRaw.slice(1, -1)
|
||||||
: urlRaw;
|
: urlRaw;
|
||||||
|
|
||||||
const { urlNoFragment, fragment } = splitUrlAndFragment(angleWrapped);
|
const { urlNoFragment, fragment } = splitUrlAndFragment(angleWrapped);
|
||||||
if (!urlNoFragment) return urlRaw;
|
if (!urlNoFragment) return urlRaw;
|
||||||
if (isExternalOrAbsoluteUrl(urlNoFragment)) return urlRaw;
|
if (isExternalOrAbsoluteUrl(urlNoFragment)) return urlRaw;
|
||||||
|
|
||||||
// 1) Directory links like "common" or "common/" that have a README
|
// 1) Directory links like "common" or "common/" that have a README
|
||||||
const dirPathNormalized = urlNoFragment.replace(/\/+$/, "");
|
const dirPathNormalized = urlNoFragment.replace(/\/+$/, "");
|
||||||
let rewritten: string | undefined;
|
let rewritten: string | undefined;
|
||||||
// First try exact match
|
// First try exact match
|
||||||
if (dirPathToDocRoute.has(dirPathNormalized)) {
|
if (dirPathToDocRoute.has(dirPathNormalized)) {
|
||||||
rewritten = `${dirPathToDocRoute.get(dirPathNormalized)}${fragment}`;
|
rewritten = `${dirPathToDocRoute.get(dirPathNormalized)}${fragment}`;
|
||||||
} else {
|
} else {
|
||||||
// Fallback: check parent directories for a README
|
// Fallback: check parent directories for a README
|
||||||
// This handles paths like "internal/watcher/events" where only the parent has a README
|
// This handles paths like "internal/watcher/events" where only the parent has a README
|
||||||
let parentPath = dirPathNormalized;
|
let parentPath = dirPathNormalized;
|
||||||
while (parentPath.includes("/")) {
|
while (parentPath.includes("/")) {
|
||||||
parentPath = parentPath.slice(0, parentPath.lastIndexOf("/"));
|
parentPath = parentPath.slice(0, parentPath.lastIndexOf("/"));
|
||||||
if (dirPathToDocRoute.has(parentPath)) {
|
if (dirPathToDocRoute.has(parentPath)) {
|
||||||
rewritten = `${dirPathToDocRoute.get(parentPath)}${fragment}`;
|
rewritten = `${dirPathToDocRoute.get(parentPath)}${fragment}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rewritten) {
|
if (rewritten) {
|
||||||
return angleWrapped === urlRaw ? rewritten : `<${rewritten}>`;
|
return angleWrapped === urlRaw ? rewritten : `<${rewritten}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Intra-repo README links -> VitePress impl routes
|
// 2) Intra-repo README links -> VitePress impl routes
|
||||||
if (/(^|\/)README\.md$/.test(urlNoFragment)) {
|
if (/(^|\/)README\.md$/.test(urlNoFragment)) {
|
||||||
const targetReadmeRel = path.posix.normalize(
|
const targetReadmeRel = path.posix.normalize(
|
||||||
path.posix.join(pkgPath, urlNoFragment),
|
path.posix.join(pkgPath, urlNoFragment),
|
||||||
);
|
);
|
||||||
const route = readmeRelToDocRoute.get(targetReadmeRel);
|
const route = readmeRelToDocRoute.get(targetReadmeRel);
|
||||||
if (route) {
|
if (route) {
|
||||||
const rewritten = `${route}${fragment}`;
|
const rewritten = `${route}${fragment}`;
|
||||||
return angleWrapped === urlRaw ? rewritten : `<${rewritten}>`;
|
return angleWrapped === urlRaw ? rewritten : `<${rewritten}>`;
|
||||||
}
|
}
|
||||||
return urlRaw;
|
return urlRaw;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Local source-file references like "config.go:29" -> GitHub blob link
|
// 3) Local source-file references like "config.go:29" -> GitHub blob link
|
||||||
if (repoUrl) {
|
if (repoUrl) {
|
||||||
const { filePath, line } = parseFileLineSuffix(urlNoFragment);
|
const { filePath, line } = parseFileLineSuffix(urlNoFragment);
|
||||||
if (isRepoSourceFilePath(filePath)) {
|
if (isRepoSourceFilePath(filePath)) {
|
||||||
const repoRel = path.posix.normalize(
|
const repoRel = path.posix.normalize(
|
||||||
path.posix.join(pkgPath, filePath),
|
path.posix.join(pkgPath, filePath),
|
||||||
);
|
);
|
||||||
const githubUrl = `${repoUrl}/blob/main/${repoRel}${
|
const githubUrl = `${repoUrl}/blob/main/${repoRel}${
|
||||||
line ? `#L${line}` : ""
|
line ? `#L${line}` : ""
|
||||||
}`;
|
}`;
|
||||||
const rewritten = `${githubUrl}${fragment}`;
|
const rewritten = `${githubUrl}${fragment}`;
|
||||||
return angleWrapped === urlRaw ? rewritten : `<${rewritten}>`;
|
return angleWrapped === urlRaw ? rewritten : `<${rewritten}>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlRaw;
|
return urlRaw;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listRepoReadmes(repoRootAbs: string): Promise<string[]> {
|
async function listRepoReadmes(repoRootAbs: string): Promise<string[]> {
|
||||||
const glob = new Glob("**/README.md");
|
const glob = new Glob("**/README.md");
|
||||||
const readmes: string[] = [];
|
const readmes: string[] = [];
|
||||||
|
|
||||||
for await (const rel of glob.scan({
|
for await (const rel of glob.scan({
|
||||||
cwd: repoRootAbs,
|
cwd: repoRootAbs,
|
||||||
onlyFiles: true,
|
onlyFiles: true,
|
||||||
dot: false,
|
dot: false,
|
||||||
})) {
|
})) {
|
||||||
// Bun returns POSIX-style rel paths.
|
// Bun returns POSIX-style rel paths.
|
||||||
if (rel === "README.md") continue; // exclude root README
|
if (rel === "README.md") continue; // exclude root README
|
||||||
if (rel.startsWith(".git/") || rel.includes("/.git/")) continue;
|
if (rel.startsWith(".git/") || rel.includes("/.git/")) continue;
|
||||||
if (rel.startsWith("node_modules/") || rel.includes("/node_modules/"))
|
if (rel.startsWith("node_modules/") || rel.includes("/node_modules/"))
|
||||||
continue;
|
continue;
|
||||||
let skip = false;
|
let skip = false;
|
||||||
for (const submodule of skipSubmodules) {
|
for (const submodule of skipSubmodules) {
|
||||||
if (rel.startsWith(submodule)) {
|
if (rel.startsWith(submodule)) {
|
||||||
skip = true;
|
skip = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (skip) continue;
|
if (skip) continue;
|
||||||
readmes.push(rel);
|
readmes.push(rel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deterministic order.
|
// Deterministic order.
|
||||||
readmes.sort((a, b) => a.localeCompare(b));
|
readmes.sort((a, b) => a.localeCompare(b));
|
||||||
return readmes;
|
return readmes;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeImplDocToMdx(params: {
|
async function writeImplDocCopy(params: {
|
||||||
srcAbs: string;
|
srcAbs: string;
|
||||||
dstAbs: string;
|
dstAbs: string;
|
||||||
pkgPath: string;
|
pkgPath: string;
|
||||||
readmeRelToDocRoute: Map<string, string>;
|
readmeRelToDocRoute: Map<string, string>;
|
||||||
dirPathToDocRoute: Map<string, string>;
|
dirPathToDocRoute: Map<string, string>;
|
||||||
repoUrl: string;
|
repoUrl: string;
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
srcAbs,
|
srcAbs,
|
||||||
dstAbs,
|
dstAbs,
|
||||||
pkgPath,
|
pkgPath,
|
||||||
readmeRelToDocRoute,
|
readmeRelToDocRoute,
|
||||||
dirPathToDocRoute,
|
dirPathToDocRoute,
|
||||||
repoUrl,
|
repoUrl,
|
||||||
} = params;
|
} = params;
|
||||||
await mkdir(path.dirname(dstAbs), { recursive: true });
|
await mkdir(path.dirname(dstAbs), { recursive: true });
|
||||||
|
await rm(dstAbs, { force: true });
|
||||||
|
|
||||||
const original = await readFile(srcAbs, "utf8");
|
const original = await readFile(srcAbs, "utf8");
|
||||||
const current = await readFile(dstAbs, "utf-8");
|
const rewritten = rewriteImplMarkdown({
|
||||||
const rewritten = md2mdx(
|
md: original,
|
||||||
rewriteImplMarkdown({
|
pkgPath,
|
||||||
md: original,
|
readmeRelToDocRoute,
|
||||||
pkgPath,
|
dirPathToDocRoute,
|
||||||
readmeRelToDocRoute,
|
repoUrl,
|
||||||
dirPathToDocRoute,
|
});
|
||||||
repoUrl,
|
await writeFile(dstAbs, md2mdx(rewritten));
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (current === rewritten) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeFile(dstAbs, rewritten, "utf-8");
|
|
||||||
console.log(`[W] ${srcAbs} -> ${dstAbs}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncImplDocs(
|
async function syncImplDocs(
|
||||||
repoRootAbs: string,
|
repoRootAbs: string,
|
||||||
wikiRootAbs: string,
|
wikiRootAbs: string,
|
||||||
): Promise<void> {
|
): Promise<ImplDoc[]> {
|
||||||
const implDirAbs = path.join(wikiRootAbs, "content", "docs", "impl");
|
const implDirAbs = path.join(wikiRootAbs, "content", "docs", "impl");
|
||||||
await mkdir(implDirAbs, { recursive: true });
|
await mkdir(implDirAbs, { recursive: true });
|
||||||
|
|
||||||
const readmes = await listRepoReadmes(repoRootAbs);
|
const readmes = await listRepoReadmes(repoRootAbs);
|
||||||
const expectedFileNames = new Set<string>();
|
const docs: ImplDoc[] = [];
|
||||||
expectedFileNames.add("index.mdx");
|
const expectedFileNames = new Set<string>();
|
||||||
expectedFileNames.add("meta.json");
|
expectedFileNames.add("index.mdx");
|
||||||
|
expectedFileNames.add("meta.json");
|
||||||
|
|
||||||
const repoUrl = normalizeRepoUrl(
|
const repoUrl = normalizeRepoUrl(
|
||||||
Bun.env.REPO_URL ?? "https://github.com/yusing/godoxy",
|
Bun.env.REPO_URL ?? "https://github.com/yusing/godoxy",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Precompute mapping from repo-relative README path -> VitePress route.
|
// Precompute mapping from repo-relative README path -> VitePress route.
|
||||||
// This lets us rewrite intra-repo README links when copying content.
|
// This lets us rewrite intra-repo README links when copying content.
|
||||||
const readmeRelToDocRoute = new Map<string, string>();
|
const readmeRelToDocRoute = new Map<string, string>();
|
||||||
|
|
||||||
// Also precompute mapping from directory path -> VitePress route.
|
// Also precompute mapping from directory path -> VitePress route.
|
||||||
// This handles links like "[`common/`](common)" that point to directories with READMEs.
|
// This handles links like "[`common/`](common)" that point to directories with READMEs.
|
||||||
const dirPathToDocRoute = new Map<string, string>();
|
const dirPathToDocRoute = new Map<string, string>();
|
||||||
|
|
||||||
for (const readmeRel of readmes) {
|
for (const readmeRel of readmes) {
|
||||||
const pkgPath = path.posix.dirname(readmeRel);
|
const pkgPath = path.posix.dirname(readmeRel);
|
||||||
if (!pkgPath || pkgPath === ".") continue;
|
if (!pkgPath || pkgPath === ".") continue;
|
||||||
|
|
||||||
const docStem = sanitizeFileStemFromPkgPath(pkgPath);
|
const docStem = sanitizeFileStemFromPkgPath(pkgPath);
|
||||||
if (!docStem) continue;
|
if (!docStem) continue;
|
||||||
const route = `/impl/${docStem}`;
|
const route = `/impl/${docStem}`;
|
||||||
readmeRelToDocRoute.set(readmeRel, route);
|
readmeRelToDocRoute.set(readmeRel, route);
|
||||||
dirPathToDocRoute.set(pkgPath, route);
|
dirPathToDocRoute.set(pkgPath, route);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const readmeRel of readmes) {
|
for (const readmeRel of readmes) {
|
||||||
const pkgPath = path.posix.dirname(readmeRel);
|
const pkgPath = path.posix.dirname(readmeRel);
|
||||||
if (!pkgPath || pkgPath === ".") continue;
|
if (!pkgPath || pkgPath === ".") continue;
|
||||||
|
|
||||||
const docStem = sanitizeFileStemFromPkgPath(pkgPath);
|
const docStem = sanitizeFileStemFromPkgPath(pkgPath);
|
||||||
if (!docStem) continue;
|
if (!docStem) continue;
|
||||||
const docFileName = `${docStem}.mdx`;
|
const docFileName = `${docStem}.mdx`;
|
||||||
|
const docRoute = `/impl/${docStem}`;
|
||||||
|
|
||||||
const srcPathAbs = path.join(repoRootAbs, readmeRel);
|
const srcPathAbs = path.join(repoRootAbs, readmeRel);
|
||||||
const dstPathAbs = path.join(implDirAbs, docFileName);
|
const dstPathAbs = path.join(implDirAbs, docFileName);
|
||||||
|
|
||||||
await writeImplDocToMdx({
|
await writeImplDocCopy({
|
||||||
srcAbs: srcPathAbs,
|
srcAbs: srcPathAbs,
|
||||||
dstAbs: dstPathAbs,
|
dstAbs: dstPathAbs,
|
||||||
pkgPath,
|
pkgPath,
|
||||||
readmeRelToDocRoute,
|
readmeRelToDocRoute,
|
||||||
dirPathToDocRoute,
|
dirPathToDocRoute,
|
||||||
repoUrl,
|
repoUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
expectedFileNames.add(docFileName);
|
docs.push({ pkgPath, docFileName, docRoute, srcPathAbs, dstPathAbs });
|
||||||
}
|
expectedFileNames.add(docFileName);
|
||||||
|
}
|
||||||
|
|
||||||
// Clean orphaned impl docs.
|
// Clean orphaned impl docs.
|
||||||
const existing = await readdir(implDirAbs, { withFileTypes: true });
|
const existing = await readdir(implDirAbs, { withFileTypes: true });
|
||||||
for (const ent of existing) {
|
for (const ent of existing) {
|
||||||
if (!ent.isFile()) continue;
|
if (!ent.isFile()) continue;
|
||||||
if (!ent.name.endsWith(".md")) continue;
|
if (!ent.name.endsWith(".md")) continue;
|
||||||
if (expectedFileNames.has(ent.name)) continue;
|
if (expectedFileNames.has(ent.name)) continue;
|
||||||
await rm(path.join(implDirAbs, ent.name), { force: true });
|
await rm(path.join(implDirAbs, ent.name), { force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deterministic for sidebar.
|
||||||
|
docs.sort((a, b) => a.pkgPath.localeCompare(b.pkgPath));
|
||||||
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// This script lives in `scripts/update-wiki/`, so repo root is two levels up.
|
// This script lives in `scripts/update-wiki/`, so repo root is two levels up.
|
||||||
const repoRootAbs = path.resolve(import.meta.dir, "../..");
|
const repoRootAbs = path.resolve(import.meta.dir);
|
||||||
|
|
||||||
// Required by task, but allow overriding via env for convenience.
|
// Required by task, but allow overriding via env for convenience.
|
||||||
const wikiRootAbs = Bun.env.DOCS_DIR
|
const wikiRootAbs = Bun.env.DOCS_DIR
|
||||||
? path.resolve(repoRootAbs, Bun.env.DOCS_DIR)
|
? path.resolve(repoRootAbs, Bun.env.DOCS_DIR)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
if (!wikiRootAbs) {
|
if (!wikiRootAbs) {
|
||||||
throw new Error("DOCS_DIR is not set");
|
throw new Error("DOCS_DIR is not set");
|
||||||
}
|
}
|
||||||
|
|
||||||
await syncImplDocs(repoRootAbs, wikiRootAbs);
|
await syncImplDocs(repoRootAbs, wikiRootAbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
await main();
|
await main();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Stage 1: deps
|
# Stage 1: deps
|
||||||
FROM golang:1.26.1-alpine AS deps
|
FROM golang:1.26.0-alpine AS deps
|
||||||
HEALTHCHECK NONE
|
HEALTHCHECK NONE
|
||||||
|
|
||||||
# package version does not matter
|
# package version does not matter
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
module github.com/yusing/godoxy/socketproxy
|
module github.com/yusing/godoxy/socketproxy
|
||||||
|
|
||||||
go 1.26.1
|
go 1.26.0
|
||||||
|
|
||||||
replace github.com/yusing/goutils => ../goutils
|
replace github.com/yusing/goutils => ../goutils
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/yusing/goutils v0.7.0
|
github.com/yusing/goutils v0.7.0
|
||||||
golang.org/x/net v0.52.0
|
golang.org/x/net v0.50.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -17,6 +17,6 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
|
github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect
|
||||||
github.com/rs/zerolog v1.34.0 // indirect
|
github.com/rs/zerolog v1.34.0 // indirect
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
|||||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
Reference in New Issue
Block a user