Compare commits

..

10 Commits

Author SHA1 Message Date
yusing
52e949de85 feat: Add development environment configuration with Docker Compose and Dockerfile 2025-09-08 09:15:24 +08:00
yusing
abeb26b556 fix(monitor): prevent nil pointer dereference in Finish method 2025-09-08 09:02:19 +08:00
yusing
23d392d88b fix(route): improve error handling in route.Start method 2025-09-08 09:02:19 +08:00
yusing
d588664bfa fix: prevent panicking on misconfigurations 2025-09-08 09:02:19 +08:00
DeAndre Harris
41ce784a7f feat: Add per-route OIDC client ID and secret support (#145) 2025-09-08 08:16:30 +08:00
yusing
577169d03c refactor(idlewatcher): improve container readiness handling and health check logic
- Simplified the wakeFromHTTP and wakeFromStream methods by removing unnecessary loops and integrating direct checks for container readiness.
- Introduced a waitForReady method to streamline the waiting process for container readiness notifications.
- Enhanced the checkUpdateState method to include timeout detection for container startup.
- Added health check retries and logging for better monitoring of container state transitions.
2025-09-06 07:51:28 +08:00
yusing
b43274e9e6 refactor(idlewatcher): replace map with ordered.Map for deduplicating dependencies 2025-09-06 07:49:50 +08:00
yusing
d83c367e7f chore: update Go version to 1.25.1 in Dockerfile and module files 2025-09-06 07:48:57 +08:00
yusing
d9fbd53870 refactor(api): remove unused Swagger docs.go and clean up dependencies; Makefile update 2025-09-06 07:48:23 +08:00
yusing
7f54f50af8 docs(README): add announcement for new WebUI availability in nightly tag 2025-09-06 07:46:09 +08:00
30 changed files with 445 additions and 3826 deletions

View File

@@ -1,5 +1,5 @@
# Stage 1: deps
FROM golang:1.25.0-alpine AS deps
FROM golang:1.25.1-alpine AS deps
HEALTHCHECK NONE
# package version does not matter

View File

@@ -2,6 +2,7 @@ shell := /bin/sh
export VERSION ?= $(shell git describe --tags --abbrev=0)
export BUILD_DATE ?= $(shell date -u +'%Y%m%d-%H%M')
export GOOS = linux
export GOARCH ?= amd64
WEBUI_DIR ?= ../godoxy-frontend
DOCS_DIR ?= ../godoxy-wiki
@@ -113,9 +114,11 @@ build:
run:
cd ${PWD} && [ -f .env ] && godotenv -f .env go run ${BUILD_FLAGS} ./cmd
debug:
make NAME="godoxy-test" debug=1 build
sh -c 'HTTP_ADDR=:81 HTTPS_ADDR=:8443 API_ADDR=:8899 DEBUG=1 bin/godoxy-test'
dev:
docker compose -f dev.compose.yml up -t 0 -d
dev-build: build
docker compose -f dev.compose.yml up -t 0 -d --build
mtrace:
${BIN_PATH} debug-ls-mtrace > mtrace.json
@@ -141,6 +144,8 @@ push-github:
gen-swagger:
swag init --parseDependency --parseInternal -g handler.go -d internal/api -o internal/api/v1/docs
python3 scripts/fix-swagger-json.py
# we don't need this
rm internal/api/v1/docs/docs.go
gen-swagger-markdown: gen-swagger
swagger generate markdown -f internal/api/v1/docs/swagger.yaml --skip-validation --output ${DOCS_DIR}/src/API.md

View File

@@ -20,6 +20,8 @@ Have questions? Ask [ChatGPT](https://chatgpt.com/g/g-6825390374b481919ad482f2e4
<img src="screenshots/webui.jpg" style="max-width: 650">
**New WebUI and is now available in nightly tag [(Demo)](https://nightly.demo.godoxy.dev), feedbacks are welcomed!**
</div>
## Table of content

View File

@@ -1,6 +1,6 @@
module github.com/yusing/go-proxy/agent
go 1.25.0
go 1.25.1
replace github.com/yusing/go-proxy => ..

View File

@@ -314,10 +314,10 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
google.golang.org/genproto v0.0.0-20250811230008-5f3141c8851a h1:V8Zj/61zlL7B+VH151iV5hJlUnYc3fUNTEhLtyr9Kzc=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

33
dev.Dockerfile Normal file
View File

@@ -0,0 +1,33 @@
# Stage 1: deps
FROM golang:1.25.0-alpine AS deps
HEALTHCHECK NONE
# package version does not matter
# trunk-ignore(hadolint/DL3018)
RUN apk add --no-cache tzdata make libcap-setcap
# Stage 3: Final image
FROM alpine:3.22
LABEL maintainer="yusing@6uo.me"
LABEL proxy.exclude=1
# copy timezone data
COPY --from=deps /usr/share/zoneinfo /usr/share/zoneinfo
# copy certs
COPY --from=deps /etc/ssl/certs /etc/ssl/certs
ARG TARGET
ENV TARGET=${TARGET}
ENV DOCKER_HOST=unix:///var/run/docker.sock
# copy binary
COPY bin/${TARGET} /app/run
WORKDIR /app
RUN chown -R 1000:1000 /app
CMD ["/app/run"]

44
dev.compose.yml Normal file
View File

@@ -0,0 +1,44 @@
services:
app:
image: godoxy-dev
build:
context: .
dockerfile: dev.Dockerfile
args:
- TARGET=godoxy
container_name: godoxy-proxy-dev
restart: unless-stopped
environment:
TZ: Asia/Hong_Kong
API_ADDR: :8999
API_USER: dev
API_PASSWORD: 1234
API_SKIP_ORIGIN_CHECK: true
API_JWT_SECURE: false
API_JWT_TTL: 24h
DEBUG: true
API_SECRET: 1234567891234567
labels:
proxy.exclude: true
proxy.#1.healthcheck.disable: true
ipc: host
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./dev-data/config:/app/config
- ./dev-data/certs:/app/certs
- ./dev-data/error_pages:/app/error_pages:ro
- ./dev-data/data:/app/data
- ./dev-data/logs:/app/logs
depends_on:
- tinyauth
tinyauth:
image: ghcr.io/steveiliop56/tinyauth:v3
container_name: tinyauth
restart: unless-stopped
environment:
- SECRET=12345678912345671234567891234567
- APP_URL=https://tinyauth.my.app
- USERS=user:$$2a$$10$$UdLYoJ5lgPsC0RKqYH/jMua7zIn0g9kPqWmhYayJYLaZQ/FTmH2/u # user:password
labels:
proxy.tinyauth.port: "3000"

40
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/yusing/go-proxy
go 1.25.0
go 1.25.1
replace github.com/yusing/go-proxy/agent => ./agent
@@ -213,7 +213,7 @@ require (
golang.org/x/tools v0.36.0 // indirect
google.golang.org/api v0.248.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
google.golang.org/grpc v1.75.0
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.15.0 // indirect
@@ -222,16 +222,12 @@ require (
)
require (
github.com/containerd/containerd/v2 v2.1.4
github.com/containerd/nerdctl/v2 v2.1.3
github.com/gin-gonic/gin v1.10.1
github.com/swaggo/swag v1.16.6
github.com/yusing/ds v0.1.0
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect
@@ -243,53 +239,25 @@ require (
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect
github.com/containerd/containerd/api v1.9.0 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/go-cni v1.1.13 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v1.0.0-rc.1 // indirect
github.com/containerd/plugin v1.0.0 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/containernetworking/cni v1.3.0 // indirect
github.com/dnsimple/dnsimple-go/v4 v4.0.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-acme/alidns-20150109/v4 v4.5.11 // indirect
github.com/go-acme/tencentclouddnspod v1.0.1208 // indirect
github.com/go-openapi/jsonpointer v0.21.2 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/namedotcom/go/v4 v4.0.2 // indirect
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.99.1 // indirect
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.99.1 // indirect
github.com/opencontainers/runtime-spec v1.2.1 // indirect
github.com/opencontainers/selinux v1.12.0 // indirect
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
github.com/sasha-s/go-deadlock v0.3.5 // indirect
github.com/selectel/go-selvpcclient/v4 v4.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
golang.org/x/arch v0.20.0 // indirect
google.golang.org/genproto v0.0.0-20250811230008-5f3141c8851a // indirect

70
go.sum
View File

@@ -601,8 +601,6 @@ cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcP
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE=
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
@@ -637,12 +635,8 @@ github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXY
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
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/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
@@ -824,36 +818,12 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0=
github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI=
github.com/containerd/containerd/v2 v2.1.4 h1:/hXWjiSFd6ftrBOBGfAZ6T30LJcx1dBjdKEeI8xucKQ=
github.com/containerd/containerd/v2 v2.1.4/go.mod h1:8C5QV9djwsYDNhxfTCFjWtTBZrqjditQ4/ghHSYjnHM=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
github.com/containerd/go-cni v1.1.13 h1:eFSGOKlhoYNxpJ51KRIMHZNlg5UgocXEIEBGkY7Hnis=
github.com/containerd/go-cni v1.1.13/go.mod h1:nTieub0XDRmvCZ9VI/SBG6PyqT95N4FIhxsauF1vSBI=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/nerdctl/v2 v2.1.3 h1:8QpZrEdTFE0V26kVpeKz8/EyQQfh9VAQTx+PdjSJF1A=
github.com/containerd/nerdctl/v2 v2.1.3/go.mod h1:1Xs+sxCETHjwvNiL80QEzAyGxbWK3FHrNEm4T4nHNBo=
github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E=
github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=
github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ=
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
github.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEmnuFjskwo=
github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -987,14 +957,6 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA=
github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
@@ -1054,8 +1016,6 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -1281,8 +1241,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -1364,8 +1322,6 @@ github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXq
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -1424,20 +1380,10 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -1516,10 +1462,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
@@ -1538,9 +1480,6 @@ github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8
github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM=
github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE=
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
@@ -1602,8 +1541,6 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/puzpuzpuz/xsync/v4 v4.1.0 h1:x9eHRl4QhZFIPJ17yl4KKW9xLyVWbb3/Yq4SXpjF71U=
github.com/puzpuzpuz/xsync/v4 v4.1.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
@@ -1649,8 +1586,6 @@ github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89
github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M=
github.com/samber/slog-zerolog/v2 v2.7.3 h1:/MkPDl/tJhijN2GvB1MWwBn2FU8RiL3rQ8gpXkQm2EY=
github.com/samber/slog-zerolog/v2 v2.7.3/go.mod h1:oWU7WHof4Xp8VguiNO02r1a4VzkgoOyOZhY5CuRke60=
github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU=
github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34 h1:48+VFHsyVcAHIN2v1Ao9v1/RkjJS5AwctFucBrfYNIA=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34/go.mod h1:zFWiHphneiey3s8HOtAEnGrRlWivNaxW5T6d5Xfco7g=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
@@ -1740,8 +1675,6 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1208/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.18 h1:Pkyu9oYcYkp0GFKWa/Jyf8IeS6AADgUoUoRzP2eDq2Q=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.18/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
@@ -1769,8 +1702,6 @@ github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8A
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ=
github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/volcengine/volc-sdk-golang v1.0.219 h1:IqMCdpJ6uuqS2ZZQYUVHKVd+2H1au0NDsSt0wx6hv9k=
github.com/volcengine/volc-sdk-golang v1.0.219/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM=
github.com/vultr/govultr/v3 v3.23.0 h1:0jZo4FI+oMkPXFez1bvhsb5Ql0EZUFbe3SNLq3d8IIY=
@@ -1822,7 +1753,6 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=

View File

@@ -14,7 +14,6 @@ import (
authApi "github.com/yusing/go-proxy/internal/api/v1/auth"
certApi "github.com/yusing/go-proxy/internal/api/v1/cert"
dockerApi "github.com/yusing/go-proxy/internal/api/v1/docker"
"github.com/yusing/go-proxy/internal/api/v1/docs"
fileApi "github.com/yusing/go-proxy/internal/api/v1/file"
homepageApi "github.com/yusing/go-proxy/internal/api/v1/homepage"
metricsApi "github.com/yusing/go-proxy/internal/api/v1/metrics"
@@ -44,9 +43,6 @@ func NewHandler() *gin.Engine {
r.Use(ErrorHandler())
r.Use(ErrorLoggingMiddleware())
docs.SwaggerInfo.Title = "GoDoxy API"
docs.SwaggerInfo.BasePath = "/api/v1"
r.GET("/api/v1/version", apiV1.Version)
v1Auth := r.Group("/api/v1/auth")

File diff suppressed because it is too large Load Diff

View File

@@ -130,7 +130,7 @@ func (auth *OIDCProvider) setSessionTokenCookie(w http.ResponseWriter, r *http.R
log.Err(err).Msg("failed to sign session token")
return
}
SetTokenCookie(w, r, CookieOauthSessionToken, signed, common.APIJWTTokenTTL)
SetTokenCookie(w, r, auth.getAppScopedCookieName(CookieOauthSessionToken), signed, common.APIJWTTokenTTL)
}
func (auth *OIDCProvider) parseSessionJWT(sessionJWT string) (claims *sessionClaims, valid bool, err error) {

View File

@@ -3,6 +3,7 @@ package auth
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
@@ -39,12 +40,27 @@ type (
var _ Provider = (*OIDCProvider)(nil)
// Cookie names for OIDC authentication
const (
CookieOauthState = "godoxy_oidc_state"
CookieOauthToken = "godoxy_oauth_token" //nolint:gosec
CookieOauthSessionToken = "godoxy_session_token" //nolint:gosec
)
// getAppScopedCookieName returns a cookie name scoped to the specific application
// to prevent conflicts between different OIDC clients
func (auth *OIDCProvider) getAppScopedCookieName(baseName string) string {
// Use the client ID to scope the cookie name
// This prevents conflicts when multiple apps use different client IDs
if auth.oauthConfig.ClientID != "" {
// Create a hash of the client ID to keep cookie names short
hash := sha256.Sum256([]byte(auth.oauthConfig.ClientID))
clientHash := base64.URLEncoding.EncodeToString(hash[:])[:8]
return fmt.Sprintf("%s_%s", baseName, clientHash)
}
return baseName
}
const (
OIDCAuthInitPath = "/"
OIDCPostAuthPath = "/auth/callback"
@@ -117,6 +133,37 @@ func NewOIDCProviderFromEnv() (*OIDCProvider, error) {
)
}
// NewOIDCProviderWithCustomClient creates a new OIDCProvider with custom client credentials
// based on an existing provider (for issuer discovery)
func NewOIDCProviderWithCustomClient(baseProvider *OIDCProvider, clientID, clientSecret string) (*OIDCProvider, error) {
if clientID == "" || clientSecret == "" {
return nil, errors.New("client ID and client secret are required")
}
// Create a new OIDC verifier with the custom client ID
oidcVerifier := baseProvider.oidcProvider.Verifier(&oidc.Config{
ClientID: clientID,
})
// Create new OAuth config with custom credentials
oauthConfig := &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: "",
Endpoint: baseProvider.oauthConfig.Endpoint,
Scopes: baseProvider.oauthConfig.Scopes,
}
return &OIDCProvider{
oauthConfig: oauthConfig,
oidcProvider: baseProvider.oidcProvider,
oidcVerifier: oidcVerifier,
endSessionURL: baseProvider.endSessionURL,
allowedUsers: baseProvider.allowedUsers,
allowedGroups: baseProvider.allowedGroups,
}, nil
}
func (auth *OIDCProvider) SetAllowedUsers(users []string) {
auth.allowedUsers = users
}
@@ -125,6 +172,10 @@ func (auth *OIDCProvider) SetAllowedGroups(groups []string) {
auth.allowedGroups = groups
}
func (auth *OIDCProvider) SetScopes(scopes []string) {
auth.oauthConfig.Scopes = scopes
}
// optRedirectPostAuth returns an oauth2 option that sets the "redirect_uri"
// parameter of the authorization URL to the post auth path of the current
// request host.
@@ -169,7 +220,7 @@ var rateLimit = rate.NewLimiter(rate.Every(time.Second), 1)
func (auth *OIDCProvider) LoginHandler(w http.ResponseWriter, r *http.Request) {
// check for session token
sessionToken, err := r.Cookie(CookieOauthSessionToken)
sessionToken, err := r.Cookie(auth.getAppScopedCookieName(CookieOauthSessionToken))
if err == nil { // session token exists
result, err := auth.TryRefreshToken(r.Context(), sessionToken.Value)
// redirect back to where they requested
@@ -193,7 +244,7 @@ func (auth *OIDCProvider) LoginHandler(w http.ResponseWriter, r *http.Request) {
}
state := generateState()
SetTokenCookie(w, r, CookieOauthState, state, 300*time.Second)
SetTokenCookie(w, r, auth.getAppScopedCookieName(CookieOauthState), state, 300*time.Second)
// redirect user to Idp
url := auth.oauthConfig.AuthCodeURL(state, optRedirectPostAuth(r))
if IsFrontend(r) {
@@ -209,7 +260,8 @@ func parseClaims(idToken *oidc.IDToken) (*IDTokenClaims, error) {
if err := idToken.Claims(&claim); err != nil {
return nil, fmt.Errorf("failed to parse claims: %w", err)
}
if claim.Username == "" {
// Username is optional if groups are present
if claim.Username == "" && len(claim.Groups) == 0 {
return nil, errors.New("missing username in ID token")
}
return &claim, nil
@@ -228,7 +280,7 @@ func (auth *OIDCProvider) checkAllowed(user string, groups []string) bool {
}
func (auth *OIDCProvider) CheckToken(r *http.Request) error {
tokenCookie, err := r.Cookie(CookieOauthToken)
tokenCookie, err := r.Cookie(auth.getAppScopedCookieName(CookieOauthToken))
if err != nil {
return ErrMissingOAuthToken
}
@@ -257,7 +309,7 @@ func (auth *OIDCProvider) PostAuthCallbackHandler(w http.ResponseWriter, r *http
}
// verify state
state, err := r.Cookie(CookieOauthState)
state, err := r.Cookie(auth.getAppScopedCookieName(CookieOauthState))
if err != nil {
http.Error(w, "missing state cookie", http.StatusBadRequest)
return
@@ -297,8 +349,8 @@ func (auth *OIDCProvider) PostAuthCallbackHandler(w http.ResponseWriter, r *http
}
func (auth *OIDCProvider) LogoutHandler(w http.ResponseWriter, r *http.Request) {
oauthToken, _ := r.Cookie(CookieOauthToken)
sessionToken, _ := r.Cookie(CookieOauthSessionToken)
oauthToken, _ := r.Cookie(auth.getAppScopedCookieName(CookieOauthToken))
sessionToken, _ := r.Cookie(auth.getAppScopedCookieName(CookieOauthSessionToken))
auth.clearCookie(w, r)
if sessionToken != nil {
@@ -325,17 +377,17 @@ func (auth *OIDCProvider) LogoutHandler(w http.ResponseWriter, r *http.Request)
}
func (auth *OIDCProvider) setIDTokenCookie(w http.ResponseWriter, r *http.Request, jwt string, ttl time.Duration) {
SetTokenCookie(w, r, CookieOauthToken, jwt, ttl)
SetTokenCookie(w, r, auth.getAppScopedCookieName(CookieOauthToken), jwt, ttl)
}
func (auth *OIDCProvider) clearCookie(w http.ResponseWriter, r *http.Request) {
ClearTokenCookie(w, r, CookieOauthToken)
ClearTokenCookie(w, r, CookieOauthSessionToken)
ClearTokenCookie(w, r, auth.getAppScopedCookieName(CookieOauthToken))
ClearTokenCookie(w, r, auth.getAppScopedCookieName(CookieOauthSessionToken))
}
// handleTestCallback handles OIDC callback in test environment.
func (auth *OIDCProvider) handleTestCallback(w http.ResponseWriter, r *http.Request) {
state, err := r.Cookie(CookieOauthState)
state, err := r.Cookie(auth.getAppScopedCookieName(CookieOauthState))
if err != nil {
http.Error(w, "missing state cookie", http.StatusBadRequest)
return
@@ -347,7 +399,7 @@ func (auth *OIDCProvider) handleTestCallback(w http.ResponseWriter, r *http.Requ
}
// Create test JWT token
SetTokenCookie(w, r, CookieOauthToken, "test", time.Hour)
SetTokenCookie(w, r, auth.getAppScopedCookieName(CookieOauthToken), "test", time.Hour)
http.Redirect(w, r, "/", http.StatusFound)
}

View File

@@ -426,6 +426,9 @@ func TestCheckToken(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
// Create the Auth Provider.
auth := &OIDCProvider{
oauthConfig: &oauth2.Config{
ClientID: clientID,
},
oidcVerifier: provider.verifier,
allowedUsers: tc.allowedUsers,
allowedGroups: tc.allowedGroups,
@@ -435,7 +438,7 @@ func TestCheckToken(t *testing.T) {
// Craft a test HTTP request that includes the token as a cookie.
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(&http.Cookie{
Name: CookieOauthToken,
Name: auth.getAppScopedCookieName(CookieOauthToken),
Value: signedToken,
})

View File

@@ -1,6 +1,6 @@
module github.com/yusing/go-proxy/internal/dnsproviders
go 1.25.0
go 1.25.1
replace github.com/yusing/go-proxy => ../..

View File

@@ -1553,8 +1553,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"net/http"
"strconv"
"time"
api "github.com/yusing/go-proxy/internal/api/v1"
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
@@ -112,34 +111,24 @@ func (w *Watcher) wakeFromHTTP(rw http.ResponseWriter, r *http.Request) (shouldN
return false
}
for {
w.resetIdleTimer()
// Wait for route to be started
if !w.waitStarted(ctx) {
return false
}
// Wait for container to become ready
if !w.waitForReady(ctx) {
if w.canceled(ctx) {
w.redirectToStartEndpoint(rw, r)
return false
}
if !w.waitStarted(ctx) {
return false
}
ready, err := w.checkUpdateState()
if err != nil {
gphttp.ServerError(rw, r, err)
return false
}
if ready {
if isCheckRedirect {
w.l.Debug().Stringer("url", w.hc.URL()).Msg("container is ready, redirecting")
rw.WriteHeader(http.StatusOK)
return false
}
w.l.Debug().Stringer("url", w.hc.URL()).Msg("container is ready, passing through")
return true
}
// retry until the container is ready or timeout
time.Sleep(idleWakerCheckInterval)
return false
}
if isCheckRedirect {
w.l.Debug().Stringer("url", w.hc.URL()).Msg("container is ready, redirecting")
rw.WriteHeader(http.StatusOK)
return false
}
w.l.Debug().Stringer("url", w.hc.URL()).Msg("container is ready, passing through")
return true
}

View File

@@ -3,7 +3,6 @@ package idlewatcher
import (
"context"
"net"
"time"
nettypes "github.com/yusing/go-proxy/internal/net/types"
)
@@ -63,27 +62,18 @@ func (w *Watcher) wakeFromStream(ctx context.Context) error {
return err
}
for {
w.resetIdleTimer()
if w.canceled(ctx) {
return nil
}
if !w.waitStarted(ctx) {
return nil
}
ready, err := w.checkUpdateState()
if err != nil {
return err
}
if ready {
w.l.Debug().Stringer("url", w.hc.URL()).Msg("container is ready, passing through")
return nil
}
// retry until the container is ready or timeout
time.Sleep(idleWakerCheckInterval)
// Wait for route to be started
if !w.waitStarted(ctx) {
return nil
}
// Wait for container to become ready
if !w.waitForReady(ctx) {
return nil // canceled or failed
}
// Container is ready
w.resetIdleTimer()
w.l.Debug().Stringer("url", w.hc.URL()).Msg("container is ready, passing through")
return nil
}

View File

@@ -92,37 +92,69 @@ func (w *Watcher) MarshalJSON() ([]byte, error) {
return (&types.HealthJSONRepr{
Name: w.Name(),
Status: w.Status(),
Config: dummyHealthCheckConfig,
Config: &types.HealthCheckConfig{
Interval: idleWakerCheckInterval,
Timeout: idleWakerCheckTimeout,
},
URL: url,
Detail: detail,
}).MarshalJSON()
}
func (w *Watcher) checkUpdateState() (ready bool, err error) {
// already ready
if w.ready() {
return true, nil
}
if !w.running() {
return false, nil
}
// the new container info not yet updated
if w.hc.URL().Host == "" {
return false, nil
}
state := w.state.Load()
// Check if container has been starting for too long (timeout after WakeTimeout)
if !state.startedAt.IsZero() {
elapsed := time.Since(state.startedAt)
if elapsed > w.cfg.WakeTimeout {
err := gperr.Errorf("container failed to become ready within %v (started at %v, %d health check attempts)",
w.cfg.WakeTimeout, state.startedAt, state.healthTries)
w.l.Error().
Dur("elapsed", elapsed).
Time("started_at", state.startedAt).
Int("health_tries", state.healthTries).
Msg("container startup timeout")
w.setError(err)
return false, err
}
}
res, err := w.hc.CheckHealth()
if err != nil {
w.l.Debug().Err(err).Msg("health check error")
w.setError(err)
return false, err
}
if res.Healthy {
w.l.Debug().
Dur("startup_time", time.Since(state.startedAt)).
Int("health_tries", state.healthTries+1).
Msg("container ready")
w.setReady()
return true, nil
}
w.setStarting()
// Health check failed, increment counter and log
newHealthTries := state.healthTries + 1
w.state.Store(&containerState{
status: state.status,
ready: false,
err: state.err,
startedAt: state.startedAt,
healthTries: newHealthTries,
})
w.l.Debug().
Int("health_tries", newHealthTries).
Dur("elapsed", time.Since(state.startedAt)).
Msg("health check failed, still starting")
return false, nil
}

View File

@@ -1,6 +1,11 @@
package idlewatcher
import idlewatcher "github.com/yusing/go-proxy/internal/idlewatcher/types"
import (
"context"
"time"
idlewatcher "github.com/yusing/go-proxy/internal/idlewatcher/types"
)
func (w *Watcher) running() bool {
return w.state.Load().status == idlewatcher.ContainerStatusRunning
@@ -19,26 +24,55 @@ func (w *Watcher) setReady() {
status: idlewatcher.ContainerStatusRunning,
ready: true,
})
// Notify waiting handlers that container is ready
select {
case w.readyNotifyCh <- struct{}{}:
default: // channel full, notification already pending
}
}
func (w *Watcher) setStarting() {
now := time.Now()
w.state.Store(&containerState{
status: idlewatcher.ContainerStatusRunning,
ready: false,
status: idlewatcher.ContainerStatusRunning,
ready: false,
startedAt: now,
})
w.l.Debug().Time("started_at", now).Msg("container starting")
}
func (w *Watcher) setNapping(status idlewatcher.ContainerStatus) {
w.state.Store(&containerState{
status: status,
ready: false,
status: status,
ready: false,
startedAt: time.Time{},
healthTries: 0,
})
}
func (w *Watcher) setError(err error) {
w.state.Store(&containerState{
status: idlewatcher.ContainerStatusError,
ready: false,
err: err,
status: idlewatcher.ContainerStatusError,
ready: false,
err: err,
startedAt: time.Time{},
healthTries: 0,
})
}
// waitForReady waits for the container to become ready or context to be canceled.
// Returns true if ready, false if canceled.
func (w *Watcher) waitForReady(ctx context.Context) bool {
// Check if already ready
if w.ready() {
return true
}
// Wait for ready notification or context cancellation
select {
case <-w.readyNotifyCh:
return w.ready() // double-check in case of race condition
case <-ctx.Done():
return false
}
}

View File

@@ -3,13 +3,14 @@ package idlewatcher
import (
"context"
"errors"
"maps"
"math"
"strings"
"sync"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/yusing/ds/ordered"
"github.com/yusing/go-proxy/internal/docker"
"github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/idlewatcher/provider"
@@ -36,9 +37,11 @@ type (
}
containerState struct {
status idlewatcher.ContainerStatus
ready bool
err error
status idlewatcher.ContainerStatus
ready bool
err error
startedAt time.Time // when container started (for timeout detection)
healthTries int // number of failed health check attempts
}
Watcher struct {
@@ -54,8 +57,10 @@ type (
state atomic.Value[*containerState]
lastReset atomic.Value[time.Time]
idleTicker *time.Ticker
task *task.Task
idleTicker *time.Ticker
healthTicker *time.Ticker
readyNotifyCh chan struct{} // notifies when container becomes ready
task *task.Task
dependsOn []*dependency
}
@@ -77,15 +82,10 @@ var (
)
const (
idleWakerCheckInterval = 100 * time.Millisecond
idleWakerCheckInterval = 200 * time.Millisecond
idleWakerCheckTimeout = time.Second
)
var dummyHealthCheckConfig = &types.HealthCheckConfig{
Interval: idleWakerCheckInterval,
Timeout: idleWakerCheckTimeout,
}
var (
causeReload = gperr.New("reloaded") //nolint:errname
causeContainerDestroy = gperr.New("container destroyed") //nolint:errname
@@ -115,8 +115,10 @@ func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig)
w.resetIdleTimer()
} else {
w = &Watcher{
idleTicker: time.NewTicker(cfg.IdleTimeout),
cfg: cfg,
idleTicker: time.NewTicker(cfg.IdleTimeout),
healthTicker: time.NewTicker(idleWakerCheckInterval),
readyNotifyCh: make(chan struct{}, 1), // buffered to avoid blocking
cfg: cfg,
routeHelper: routeHelper{
hc: monitor.NewMonitor(r),
},
@@ -303,6 +305,8 @@ func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig)
}
w.idleTicker.Stop()
w.healthTicker.Stop()
close(w.readyNotifyCh)
w.provider.Close()
w.task.Finish(cause)
}()
@@ -372,17 +376,25 @@ func (w *Watcher) wakeDependencies(ctx context.Context) error {
return err
}
if dep.waitHealthy {
// initial health check before starting the ticker
if h, err := dep.hc.CheckHealth(); err != nil {
return err
} else if h.Healthy {
return nil
}
tick := time.NewTicker(idleWakerCheckInterval)
defer tick.Stop()
for {
select {
case <-ctx.Done():
return w.newDepError("wait_healthy", dep, context.Cause(ctx))
default:
case <-tick.C:
if h, err := dep.hc.CheckHealth(); err != nil {
return err
} else if h.Healthy {
return nil
}
time.Sleep(idleWakerCheckInterval)
}
}
}
@@ -446,7 +458,7 @@ func (w *Watcher) stopByMethod() error {
case types.ContainerStopMethodPause:
err = w.provider.ContainerPause(ctx)
case types.ContainerStopMethodStop:
err = w.provider.ContainerStop(ctx, cfg.StopSignal, int(cfg.StopTimeout.Seconds()))
err = w.provider.ContainerStop(ctx, cfg.StopSignal, int(math.Ceil(cfg.StopTimeout.Seconds())))
case types.ContainerStopMethodKill:
err = w.provider.ContainerKill(ctx, cfg.StopSignal)
default:
@@ -510,16 +522,39 @@ func (w *Watcher) watchUntilDestroy() (returnCause error) {
switch {
case e.Action.IsContainerStart(): // create / start / unpause
w.setStarting()
w.healthTicker.Reset(idleWakerCheckInterval) // start health checking
w.l.Info().Msg("awaken")
case e.Action.IsContainerStop(): // stop / kill / die
w.setNapping(idlewatcher.ContainerStatusStopped)
w.idleTicker.Stop()
w.healthTicker.Stop() // stop health checking
case e.Action.IsContainerPause(): // pause
w.setNapping(idlewatcher.ContainerStatusPaused)
w.idleTicker.Stop()
w.healthTicker.Stop() // stop health checking
default:
w.l.Debug().Stringer("action", e.Action).Msg("unexpected container action")
}
case <-w.healthTicker.C:
// Only check health if container is starting (not ready yet)
if w.running() && !w.ready() {
ready, err := w.checkUpdateState()
if err != nil {
// Health check failed with error, stop health checking
w.healthTicker.Stop()
continue
}
if ready {
// Container is now ready, notify waiting handlers
w.healthTicker.Stop()
select {
case w.readyNotifyCh <- struct{}{}:
default: // channel full, notification already pending
}
w.resetIdleTimer()
}
// If not ready yet, keep checking on next tick
}
case <-w.idleTicker.C:
w.idleTicker.Stop()
if w.running() {
@@ -545,13 +580,13 @@ func (w *Watcher) dedupDependencies() {
deps := w.dependencies()
for _, dep := range w.dependsOn {
depdeps := dep.dependencies()
for depdep := range depdeps {
delete(deps, depdep)
for depdep := range depdeps.Iter {
deps.Del(depdep)
}
}
newDepOn := make([]string, 0, len(deps))
newDeps := make([]*dependency, 0, len(deps))
for _, dep := range deps {
newDepOn := make([]string, 0, deps.Len())
newDeps := make([]*dependency, 0, deps.Len())
for _, dep := range deps.Iter {
newDepOn = append(newDepOn, dep.cfg.ContainerName())
newDeps = append(newDeps, dep)
}
@@ -559,11 +594,13 @@ func (w *Watcher) dedupDependencies() {
w.dependsOn = newDeps
}
func (w *Watcher) dependencies() map[string]*dependency {
deps := make(map[string]*dependency)
func (w *Watcher) dependencies() *ordered.Map[string, *dependency] {
deps := ordered.NewMap[string, *dependency]()
for _, dep := range w.dependsOn {
deps[dep.Key()] = dep
maps.Copy(deps, dep.dependencies())
deps.Set(dep.Key(), dep)
for _, depdep := range dep.dependencies().Iter {
deps.Set(depdep.Key(), depdep)
}
}
return deps
}

View File

@@ -3,6 +3,7 @@ package middleware
import (
"errors"
"net/http"
"strings"
"sync"
"sync/atomic"
@@ -13,6 +14,9 @@ import (
type oidcMiddleware struct {
AllowedUsers []string `json:"allowed_users"`
AllowedGroups []string `json:"allowed_groups"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Scopes string `json:"scopes"`
auth *auth.OIDCProvider
@@ -49,11 +53,28 @@ func (amw *oidcMiddleware) initSlow() error {
amw.initMu.Unlock()
}()
// Always start with the global OIDC provider (for issuer discovery)
authProvider, err := auth.NewOIDCProviderFromEnv()
if err != nil {
return err
}
// Check if custom client credentials are provided
if amw.ClientID != "" && amw.ClientSecret != "" {
// Use custom client credentials
customProvider, err := auth.NewOIDCProviderWithCustomClient(
authProvider,
amw.ClientID,
amw.ClientSecret,
)
if err != nil {
return err
}
authProvider = customProvider
}
// If no custom credentials, authProvider remains the global one
// Apply per-route user/group restrictions (these always override global)
if len(amw.AllowedUsers) > 0 {
authProvider.SetAllowedUsers(amw.AllowedUsers)
}
@@ -61,6 +82,11 @@ func (amw *oidcMiddleware) initSlow() error {
authProvider.SetAllowedGroups(amw.AllowedGroups)
}
// Apply custom scopes if provided
if amw.Scopes != "" {
authProvider.SetScopes(strings.Split(amw.Scopes, ","))
}
amw.auth = authProvider
return nil
}

View File

@@ -0,0 +1,35 @@
package middleware
import (
"testing"
. "github.com/yusing/go-proxy/internal/utils/testing"
)
func TestOIDCMiddlewarePerRouteConfig(t *testing.T) {
t.Run("middleware struct has correct fields", func(t *testing.T) {
middleware := &oidcMiddleware{
AllowedUsers: []string{"custom-user"},
AllowedGroups: []string{"custom-group"},
ClientID: "custom-client-id",
ClientSecret: "custom-client-secret",
Scopes: "openid,profile,email,groups",
}
ExpectEqual(t, middleware.AllowedUsers, []string{"custom-user"})
ExpectEqual(t, middleware.AllowedGroups, []string{"custom-group"})
ExpectEqual(t, middleware.ClientID, "custom-client-id")
ExpectEqual(t, middleware.ClientSecret, "custom-client-secret")
ExpectEqual(t, middleware.Scopes, "openid,profile,email,groups")
})
t.Run("middleware struct handles empty values", func(t *testing.T) {
middleware := &oidcMiddleware{}
ExpectEqual(t, middleware.AllowedUsers, nil)
ExpectEqual(t, middleware.AllowedGroups, nil)
ExpectEqual(t, middleware.ClientID, "")
ExpectEqual(t, middleware.ClientSecret, "")
ExpectEqual(t, middleware.Scopes, "")
})
}

View File

@@ -179,13 +179,13 @@ func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent) {
_ = lb.Start(parent) // always return nil
linked = &ReveseProxyRoute{
Route: &Route{
Alias: cfg.Link,
Homepage: r.Homepage,
HealthMon: lb,
Alias: cfg.Link,
Homepage: r.Homepage,
},
loadBalancer: lb,
handler: lb,
}
linked.SetHealthMonitor(lb)
routes.HTTP.AddKey(cfg.Link, linked)
routes.All.AddKey(cfg.Link, linked)
r.task.OnFinished("remove_loadbalancer_route", func() {

View File

@@ -51,9 +51,6 @@ type (
Agent string `json:"agent,omitempty"`
Idlewatcher *types.IdlewatcherConfig `json:"idlewatcher,omitempty" extensions:"x-nullable"`
HealthMon types.HealthMonitor `json:"health,omitempty" swaggerignore:"true"`
// for swagger
HealthJSON *types.HealthJSON `json:",omitempty" form:"health"`
Metadata `deserialize:"-"`
}
@@ -70,6 +67,10 @@ type (
Excluded *bool `json:"excluded"`
HealthMon types.HealthMonitor `json:"health,omitempty" swaggerignore:"true"`
// for swagger
HealthJSON *types.HealthJSON `json:",omitempty" form:"health"`
impl types.Route
task *task.Task
@@ -271,11 +272,14 @@ func (r *Route) Task() *task.Task {
return r.task
}
func (r *Route) Start(parent task.Parent) (err gperr.Error) {
func (r *Route) Start(parent task.Parent) gperr.Error {
if r.lastError != nil {
return r.lastError
}
r.once.Do(func() {
err = r.start(parent)
r.lastError = r.start(parent)
})
return
return r.lastError
}
func (r *Route) start(parent task.Parent) gperr.Error {
@@ -466,7 +470,7 @@ func (r *Route) UseLoadBalance() bool {
}
func (r *Route) UseIdleWatcher() bool {
return r.Idlewatcher != nil && r.Idlewatcher.IdleTimeout > 0
return r.Idlewatcher != nil && r.Idlewatcher.IdleTimeout > 0 && r.Idlewatcher.ValErr() == nil
}
func (r *Route) UseHealthCheck() bool {
@@ -582,13 +586,11 @@ func (r *Route) Finalize() {
r.HealthCheck = types.DefaultHealthConfig()
}
if !r.HealthCheck.Disable {
if r.HealthCheck.Interval == 0 {
r.HealthCheck.Interval = common.HealthCheckIntervalDefault
}
if r.HealthCheck.Timeout == 0 {
r.HealthCheck.Timeout = common.HealthCheckTimeoutDefault
}
if r.HealthCheck.Interval == 0 {
r.HealthCheck.Interval = common.HealthCheckIntervalDefault
}
if r.HealthCheck.Timeout == 0 {
r.HealthCheck.Timeout = common.HealthCheckTimeoutDefault
}
}

View File

@@ -30,6 +30,8 @@ type (
StartEndpoint string `json:"start_endpoint,omitempty"` // Optional path that must be hit to start container
DependsOn []string `json:"depends_on,omitempty"`
valErr gperr.Error
} // @name IdlewatcherConfig
ContainerStopMethod string // @name ContainerStopMethod
ContainerSignal string // @name ContainerSignal
@@ -70,9 +72,10 @@ func (c *IdlewatcherConfig) ContainerName() string {
func (c *IdlewatcherConfig) Validate() gperr.Error {
if c.IdleTimeout == 0 { // zero idle timeout means no idle watcher
c.valErr = nil
return nil
}
errs := gperr.NewBuilder("idlewatcher config validation error")
errs := gperr.NewBuilder()
errs.AddRange(
c.validateProvider(),
c.validateTimeouts(),
@@ -80,7 +83,12 @@ func (c *IdlewatcherConfig) Validate() gperr.Error {
c.validateStopSignal(),
c.validateStartEndpoint(),
)
return errs.Error()
c.valErr = errs.Error()
return c.valErr
}
func (c *IdlewatcherConfig) ValErr() gperr.Error {
return c.valErr
}
func (c *IdlewatcherConfig) validateProvider() error {

View File

@@ -1,6 +1,6 @@
module github.com/yusing/go-proxy/internal/utils
go 1.25.0
go 1.25.1
require (
github.com/goccy/go-yaml v1.18.0

View File

@@ -72,6 +72,9 @@ func NewMonitor(r types.Route) types.HealthMonCheck {
func newMonitor(u *url.URL, config *types.HealthCheckConfig, healthCheckFunc HealthCheckFunc) *monitor {
if config.Retries == 0 {
if config.Interval == 0 {
config.Interval = common.HealthCheckIntervalDefault
}
config.Retries = int64(common.HealthCheckDownNotifyDelayDefault / config.Interval)
}
mon := &monitor{
@@ -171,7 +174,9 @@ func (mon *monitor) Task() *task.Task {
// Finish implements task.TaskFinisher.
func (mon *monitor) Finish(reason any) {
mon.task.Finish(reason)
if mon.task != nil {
mon.task.Finish(reason)
}
}
// UpdateURL implements HealthChecker.

View File

@@ -1,6 +1,6 @@
module github.com/yusing/go-proxy/socketproxy
go 1.25.0
go 1.25.1
replace github.com/yusing/go-proxy/internal/utils => ../internal/utils

View File

@@ -17,8 +17,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
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/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=