mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-15 23:03:50 +01:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61c8ac04e8 | ||
|
|
cc27942c4d | ||
|
|
1c2515cb29 | ||
|
|
45720db754 |
2
Makefile
2
Makefile
@@ -75,7 +75,7 @@ endif
|
||||
.PHONY: debug
|
||||
|
||||
test:
|
||||
go test -v -race ./internal/...
|
||||
CGO_ENABLED=1 go test -v -race ${BUILD_FLAGS} ./internal/...
|
||||
|
||||
docker-build-test:
|
||||
docker build -t godoxy .
|
||||
|
||||
12
agent/go.mod
12
agent/go.mod
@@ -25,8 +25,8 @@ require (
|
||||
github.com/yusing/godoxy 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/http/reverseproxy v0.0.0-20251217162119-cb0f79b51ce2
|
||||
github.com/yusing/goutils/server v0.0.0-20251217162119-cb0f79b51ce2
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260103043911-785deb23bd64
|
||||
github.com/yusing/goutils/server v0.0.0-20260103043911-785deb23bd64
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -65,7 +65,7 @@ require (
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.19.1 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gotify/server/v2 v2.7.3 // indirect
|
||||
github.com/gotify/server/v2 v2.8.0 // indirect
|
||||
github.com/jinzhu/copier v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
@@ -73,7 +73,7 @@ require (
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
|
||||
github.com/luthermonson/go-proxmox v0.2.4 // indirect
|
||||
github.com/luthermonson/go-proxmox v0.3.1 // indirect
|
||||
github.com/magefile/mage v1.15.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
@@ -95,7 +95,7 @@ require (
|
||||
github.com/samber/lo v1.52.0 // indirect
|
||||
github.com/samber/slog-common v0.19.0 // indirect
|
||||
github.com/samber/slog-zerolog/v2 v2.9.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.11 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.12 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
@@ -106,7 +106,7 @@ require (
|
||||
github.com/vincent-petithory/dataurl v1.0.0 // indirect
|
||||
github.com/yusing/ds v0.3.1 // indirect
|
||||
github.com/yusing/gointernals v0.1.16 // indirect
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20251217162119-cb0f79b51ce2 // indirect
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260103043911-785deb23bd64 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
|
||||
|
||||
93
agent/go.sum
93
agent/go.sum
@@ -1,13 +1,38 @@
|
||||
cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=
|
||||
cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 h1:yzrctSl9GMIQ5lHu7jc8olOsGjWDCsBpJhWqfGa/YIM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0/go.mod h1:GE4m0rnnfwLGX0Y9A9A25Zx5N/90jneT5ABevqzhuFQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 h1:zLzoX5+W2l95UJoVwiyNS4dX8vHyQ6x2xRLoBBL9wMk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||
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/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
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/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q=
|
||||
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
|
||||
github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
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/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
|
||||
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
@@ -71,6 +96,8 @@ 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-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
@@ -79,6 +106,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4=
|
||||
github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
@@ -88,24 +117,38 @@ github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU
|
||||
github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE=
|
||||
github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
||||
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=
|
||||
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
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/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gotify/server/v2 v2.7.3 h1:nro/ZnxdlZFvxFcw9LREGA8zdk6CK744azwhuhX/A4g=
|
||||
github.com/gotify/server/v2 v2.7.3/go.mod h1:VAtE1RIc/2j886PYs9WPQbMjqbFsoyQ0G8IdFtnAxU0=
|
||||
github.com/gotify/server/v2 v2.8.0 h1:E3UDDn/3rFZi1sjZfbuhXNnxJP3ACZhdcw/iySegPRA=
|
||||
github.com/gotify/server/v2 v2.8.0/go.mod h1:6ci5adxcE2hf1v+2oowKiQmixOxXV8vU+CRLKP6sqZA=
|
||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
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/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
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-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
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=
|
||||
@@ -118,14 +161,18 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
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/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/linode/linodego v1.63.0 h1:MdjizfXNJDVJU6ggoJmMO5O9h4KGPGivNX0fzrAnstk=
|
||||
github.com/linode/linodego v1.63.0/go.mod h1:GoiwLVuLdBQcAebxAVKVL3mMYUgJZR/puOUSla04xBE=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/luthermonson/go-proxmox v0.2.4 h1:XQ6YNUTVvHS7N4EJxWpuqWLW2s1VPtsIblxLV/rGHLw=
|
||||
github.com/luthermonson/go-proxmox v0.2.4/go.mod h1:oyFgg2WwTEIF0rP6ppjiixOHa5ebK1p8OaRiFhvICBQ=
|
||||
github.com/luthermonson/go-proxmox v0.3.1 h1:h64s4/zIEQ06TBo0phFKcckV441YpvUPgLfRAptYsjY=
|
||||
github.com/luthermonson/go-proxmox v0.3.1/go.mod h1:oyFgg2WwTEIF0rP6ppjiixOHa5ebK1p8OaRiFhvICBQ=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
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=
|
||||
@@ -137,6 +184,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
|
||||
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
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/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
|
||||
@@ -148,18 +197,30 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
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/oci-go-sdk/common/v1065 v1065.105.2 h1:l0tH15ACQADZAzC+LZ+mo2tIX4H6uZu0ulrVmG5Tqz0=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2 h1:gzB4c6ztb38C/jYiqEaFC+mCGcWFHDji9e6jwymY9d4=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2/go.mod h1:l1qIPIq2uRV5WTSvkbhbl/ndbeOu7OCb3UZ+0+2ZSb8=
|
||||
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/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
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/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
|
||||
github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
|
||||
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
@@ -185,14 +246,20 @@ 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.9.0 h1:6LkOabJmZdNLaUWkTC3IVVA+dq7b/V0FM6lz6/7+THI=
|
||||
github.com/samber/slog-zerolog/v2 v2.9.0/go.mod h1:gnQW9VnCfM34v2pRMUIGMsZOVbYLqY/v0Wxu6atSVGc=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
|
||||
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
@@ -216,11 +283,17 @@ github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFn
|
||||
github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4=
|
||||
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/vultr/govultr/v3 v3.26.1 h1:G/M0rMQKwVSmL+gb0UgETbW5mcQi0Vf/o/ZSGdBCxJw=
|
||||
github.com/vultr/govultr/v3 v3.26.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
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/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusing/ds v0.3.1 h1:mCqTgTQD8RhiBpcysvii5kZ7ZBmqcknVsFubNALGLbY=
|
||||
github.com/yusing/ds v0.3.1/go.mod h1:XhKV4l7cZwBbbl7lRzNC9zX27zvCM0frIwiuD40ULRk=
|
||||
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260104140148-1c2515cb298d h1:O6umnEZyKot6IqyOCuLMUuCT8/K8n+lKiQJ+UjmSfVc=
|
||||
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260104140148-1c2515cb298d/go.mod h1:84uz4o4GfD4FhXv3v7620Vj7LtXL0gnxDgL9LA+KmEI=
|
||||
github.com/yusing/gointernals v0.1.16 h1:GrhZZdxzA+jojLEqankctJrOuAYDb7kY1C93S1pVR34=
|
||||
github.com/yusing/gointernals v0.1.16/go.mod h1:B/0FVXt4WPmgzVy3ynzkqKi+BSGaJVmwCJBRXYapo34=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
@@ -243,6 +316,8 @@ 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/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
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/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
|
||||
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -334,11 +409,21 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.258.0 h1:IKo1j5FBlN74fe5isA2PVozN3Y5pwNKriEgAXPOkDAc=
|
||||
google.golang.org/api v0.258.0/go.mod h1:qhOMTQEZ6lUps63ZNq9jhODswwjkjYYguA7fA3TBFww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
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 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
22
go.mod
22
go.mod
@@ -22,7 +22,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.30.1 // validator
|
||||
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/gotify/server/v2 v2.7.3 // reference the Message struct for json response
|
||||
github.com/gotify/server/v2 v2.8.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/pires/go-proxyproto v0.8.1 // proxy protocol support
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0 // lock free map for concurrent operations
|
||||
@@ -41,23 +41,23 @@ require (
|
||||
github.com/docker/cli v29.1.3+incompatible // needs docker/cli/cli/connhelper connection helper for docker client
|
||||
github.com/goccy/go-yaml v1.19.1 // yaml parsing for different config files
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // jwt authentication
|
||||
github.com/luthermonson/go-proxmox v0.2.4 // proxmox API client
|
||||
github.com/luthermonson/go-proxmox v0.3.1 // proxmox API client
|
||||
github.com/moby/moby/api v1.52.0 // docker API
|
||||
github.com/moby/moby/client v0.2.1 // docker client
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 // maxminddb for geoip database
|
||||
github.com/quic-go/quic-go v0.58.0 // http3 support
|
||||
github.com/shirou/gopsutil/v4 v4.25.11 // system information
|
||||
github.com/shirou/gopsutil/v4 v4.25.12 // system information
|
||||
github.com/spf13/afero v1.15.0 // afero for file system operations
|
||||
github.com/stretchr/testify v1.11.1 // testing framework
|
||||
github.com/valyala/fasthttp v1.68.0 // fast http for health check
|
||||
github.com/yusing/ds v0.3.1 // data structures and algorithms
|
||||
github.com/yusing/godoxy/agent v0.0.0-20251230135310-5087800fd763
|
||||
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20251230043958-dba8441e8a5d
|
||||
github.com/yusing/godoxy/agent v0.0.0-20260104140148-1c2515cb298d
|
||||
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260104140148-1c2515cb298d
|
||||
github.com/yusing/gointernals v0.1.16
|
||||
github.com/yusing/goutils v0.7.0
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20251217162119-cb0f79b51ce2
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20251217162119-cb0f79b51ce2
|
||||
github.com/yusing/goutils/server v0.0.0-20251217162119-cb0f79b51ce2
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260103043911-785deb23bd64
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260103043911-785deb23bd64
|
||||
github.com/yusing/goutils/server v0.0.0-20260103043911-785deb23bd64
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -147,6 +147,7 @@ require (
|
||||
require (
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@@ -154,20 +155,25 @@ require (
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.17.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/linode/linodego v1.63.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
|
||||
github.com/nrdcg/goinwx v0.12.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/pquerna/otp v1.5.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
|
||||
21
go.sum
21
go.sum
@@ -44,6 +44,9 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
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/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY=
|
||||
github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
@@ -85,6 +88,8 @@ github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1Ugj
|
||||
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/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
@@ -121,6 +126,8 @@ github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6x
|
||||
github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
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/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
@@ -150,8 +157,8 @@ github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5
|
||||
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
|
||||
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/gotify/server/v2 v2.7.3 h1:nro/ZnxdlZFvxFcw9LREGA8zdk6CK744azwhuhX/A4g=
|
||||
github.com/gotify/server/v2 v2.7.3/go.mod h1:VAtE1RIc/2j886PYs9WPQbMjqbFsoyQ0G8IdFtnAxU0=
|
||||
github.com/gotify/server/v2 v2.8.0 h1:E3UDDn/3rFZi1sjZfbuhXNnxJP3ACZhdcw/iySegPRA=
|
||||
github.com/gotify/server/v2 v2.8.0/go.mod h1:6ci5adxcE2hf1v+2oowKiQmixOxXV8vU+CRLKP6sqZA=
|
||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
@@ -174,6 +181,8 @@ github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uq
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -188,8 +197,8 @@ github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/luthermonson/go-proxmox v0.2.4 h1:XQ6YNUTVvHS7N4EJxWpuqWLW2s1VPtsIblxLV/rGHLw=
|
||||
github.com/luthermonson/go-proxmox v0.2.4/go.mod h1:oyFgg2WwTEIF0rP6ppjiixOHa5ebK1p8OaRiFhvICBQ=
|
||||
github.com/luthermonson/go-proxmox v0.3.1 h1:h64s4/zIEQ06TBo0phFKcckV441YpvUPgLfRAptYsjY=
|
||||
github.com/luthermonson/go-proxmox v0.3.1/go.mod h1:oyFgg2WwTEIF0rP6ppjiixOHa5ebK1p8OaRiFhvICBQ=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
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=
|
||||
@@ -218,6 +227,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
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/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
|
||||
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2 h1:l0tH15ACQADZAzC+LZ+mo2tIX4H6uZu0ulrVmG5Tqz0=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2 h1:gzB4c6ztb38C/jYiqEaFC+mCGcWFHDji9e6jwymY9d4=
|
||||
@@ -248,6 +259,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
|
||||
560
internal/autocert/README.md
Normal file
560
internal/autocert/README.md
Normal file
@@ -0,0 +1,560 @@
|
||||
# Autocert Package
|
||||
|
||||
Automated SSL certificate management using the ACME protocol (Let's Encrypt and compatible CAs).
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────────┐
|
||||
│ GoDoxy Proxy │
|
||||
├────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ┌──────────────────────┐ ┌─────────────────────────────────────────┐ │
|
||||
│ │ Config.State │────▶│ autocert.Provider │ │
|
||||
│ │ (config loading) │ │ ┌───────────────────────────────────┐ │ │
|
||||
│ └──────────────────────┘ │ │ main Provider │ │ │
|
||||
│ │ │ - Primary certificate │ │ │
|
||||
│ │ │ - SNI matcher │ │ │
|
||||
│ │ │ - Renewal scheduler │ │ │
|
||||
│ │ └───────────────────────────────────┘ │ │
|
||||
│ │ ┌───────────────────────────────────┐ │ │
|
||||
│ │ │ extraProviders[] │ │ │
|
||||
│ │ │ - Additional certifictes │ │ │
|
||||
│ │ │ - Different domains/A │ │ │
|
||||
│ │ └───────────────────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────────────────┐ │
|
||||
│ │ TLS Handshake │ │
|
||||
│ │ GetCert(ClientHelloInf) │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
└────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Certificate Lifecycle
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
theme: redux-dark-color
|
||||
---
|
||||
flowchart TD
|
||||
A[Start] --> B[Load Existing Cert]
|
||||
B --> C{Cert Exists?}
|
||||
C -->|Yes| D[Load Cert from Disk]
|
||||
C -->|No| E[Obtain New Cert]
|
||||
|
||||
D --> F{Valid & Not Expired?}
|
||||
F -->|Yes| G[Schedule Renewal]
|
||||
F -->|No| H{Renewal Time?}
|
||||
H -->|Yes| I[Renew Certificate]
|
||||
H -->|No| G
|
||||
|
||||
E --> J[Init ACME Client]
|
||||
J --> K[Register Account]
|
||||
K --> L[DNS-01 Challenge]
|
||||
L --> M[Complete Challenge]
|
||||
M --> N[Download Certificate]
|
||||
N --> O[Save to Disk]
|
||||
O --> G
|
||||
|
||||
G --> P[Wait Until Renewal Time]
|
||||
P --> Q[Trigger Renewal]
|
||||
Q --> I
|
||||
|
||||
I --> R[Renew via ACME]
|
||||
R --> S{Same Domains?}
|
||||
S -->|Yes| T[Bundle & Save]
|
||||
S -->|No| U[Re-obtain Certificate]
|
||||
U --> T
|
||||
|
||||
T --> V[Update SNI Matcher]
|
||||
V --> G
|
||||
|
||||
style E fill:#90EE90
|
||||
style I fill:#FFD700
|
||||
style N fill:#90EE90
|
||||
style U fill:#FFA07A
|
||||
```
|
||||
|
||||
## SNI Matching Flow
|
||||
|
||||
When a TLS client connects with Server Name Indication (SNI), the proxy needs to select the correct certificate.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Client["TLS Client"] -->|ClientHello SNI| Proxy["GoDoxy Proxy"]
|
||||
Proxy -->|Certificate| Client
|
||||
|
||||
subgraph "SNI Matching Process"
|
||||
direction TB
|
||||
A[Extract SNI from ClientHello] --> B{Normalize SNI}
|
||||
B --> C{Exact Match?}
|
||||
C -->|Yes| D[Return cert]
|
||||
C -->|No| E[Wildcard Suffix Tree]
|
||||
E --> F{Match Found?}
|
||||
F -->|Yes| D
|
||||
F -->|No| G[Return default cert]
|
||||
end
|
||||
|
||||
style C fill:#90EE90
|
||||
style E fill:#87CEEB
|
||||
style F fill:#FFD700
|
||||
```
|
||||
|
||||
### Suffix Tree Structure
|
||||
|
||||
The `sniMatcher` uses an optimized suffix tree for efficient wildcard matching:
|
||||
|
||||
```
|
||||
Certificate: *.example.com, example.com, *.api.example.com
|
||||
|
||||
exact:
|
||||
"example.com" → Provider_A
|
||||
|
||||
root:
|
||||
└── "com"
|
||||
└── "example"
|
||||
├── "*" → Provider_A [wildcard at *.example.com]
|
||||
└── "api"
|
||||
└── "*" → Provider_B [wildcard at *.api.example.com]
|
||||
```
|
||||
|
||||
## Key Components
|
||||
|
||||
### Config
|
||||
|
||||
Configuration for certificate management, loaded from `config/autocert.yml`.
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Email string // ACME account email
|
||||
Domains []string // Domains to certifiy
|
||||
CertPath string // Output cert path
|
||||
KeyPath string // Output key path
|
||||
Extra []ConfigExtra // Additional cert configs
|
||||
ACMEKeyPath string // ACME account private key (shared by all extras)
|
||||
Provider string // DNS provider name
|
||||
Options map[string]strutils.Redacted // Provider-specific options
|
||||
Resolvers []string // DNS resolvers for DNS-01
|
||||
CADirURL string // Custom ACME CA directory
|
||||
CACerts []string // Custom CA certificates
|
||||
EABKid string // External Account Binding Key ID
|
||||
EABHmac string // External Account Binding HMAC
|
||||
|
||||
idx int // 0: main, 1+: extra[i]
|
||||
}
|
||||
|
||||
type ConfigExtra Config
|
||||
```
|
||||
|
||||
**Extra Provider Merging:** Extra configurations are merged with the main config using `MergeExtraConfig()`, inheriting most settings from the main provider while allowing per-certificate overrides for `Provider`, `Email`, `Domains`, `Options`, `Resolvers`, `CADirURL`, `CACerts`, `EABKid`, `EABHmac`, and `HTTPClient`. The `ACMEKeyPath` is shared across all providers.
|
||||
|
||||
**Validation:**
|
||||
|
||||
- Extra configs must have unique `cert_path` and `key_path` values (no duplicates across main or any extra provider)
|
||||
|
||||
### ConfigExtra
|
||||
|
||||
Extra certificate configuration type. Uses `MergeExtraConfig()` to inherit settings from the main provider:
|
||||
|
||||
```go
|
||||
func MergeExtraConfig(mainCfg *Config, extraCfg *ConfigExtra) ConfigExtra
|
||||
```
|
||||
|
||||
Fields that can be overridden per extra provider:
|
||||
|
||||
- `Provider` - DNS provider name
|
||||
- `Email` - ACME account email
|
||||
- `Domains` - Certificate domains
|
||||
- `Options` - Provider-specific options
|
||||
- `Resolvers` - DNS resolvers
|
||||
- `CADirURL` - Custom ACME CA directory
|
||||
- `CACerts` - Custom CA certificates
|
||||
- `EABKid` / `EABHmac` - External Account Binding credentials
|
||||
- `HTTPClient` - Custom HTTP client
|
||||
|
||||
Fields inherited from main config (shared):
|
||||
|
||||
- `ACMEKeyPath` - ACME account private key (same for all)
|
||||
|
||||
**Provider Types:**
|
||||
|
||||
- `local` - No ACME, use existing certificate (default)
|
||||
- `pseudo` - Mock provider for testing
|
||||
- `custom` - Custom ACME CA with `CADirURL`
|
||||
|
||||
### Provider
|
||||
|
||||
Main certificate management struct that handles:
|
||||
|
||||
- Certificate issuance and renewal
|
||||
- Loading certificates from disk
|
||||
- SNI-based certificate selection
|
||||
- Renewal scheduling
|
||||
|
||||
```go
|
||||
type Provider struct {
|
||||
logger zerolog.Logger // Provider-scoped logger
|
||||
|
||||
cfg *Config // Configuration
|
||||
user *User // ACME account
|
||||
legoCfg *lego.Config // LEGO client config
|
||||
client *lego.Client // ACME client
|
||||
lastFailure time.Time // Last renewal failure
|
||||
legoCert *certificate.Resource // Cached cert resource
|
||||
tlsCert *tls.Certificate // Parsed TLS certificate
|
||||
certExpiries CertExpiries // Domain → expiry map
|
||||
extraProviders []*Provider // Additional certificates
|
||||
sniMatcher sniMatcher // SNI → Provider mapping
|
||||
forceRenewalCh chan struct{} // Force renewal trigger channel
|
||||
scheduleRenewalOnce sync.Once // Prevents duplicate renewal scheduling
|
||||
}
|
||||
```
|
||||
|
||||
**Logging:** Each provider has a scoped logger with provider name ("main" or "extra[N]") for consistent log context.
|
||||
|
||||
**Key Methods:**
|
||||
|
||||
- `NewProvider(cfg *Config, user *User, legoCfg *lego.Config) (*Provider, error)` - Creates provider and initializes extra providers atomically
|
||||
- `GetCert(hello *tls.ClientHelloInfo)` - Returns certificate for TLS handshake
|
||||
- `GetName()` - Returns provider name ("main" or "extra[N]")
|
||||
- `ObtainCert()` - Obtains new certificate via ACME
|
||||
- `ObtainCertAll()` - Renews/obtains certificates for main and all extra providers
|
||||
- `ObtainCertIfNotExistsAll()` - Obtains certificates only if they don't exist on disk
|
||||
- `ForceExpiryAll()` - Triggers forced certificate renewal for main and all extra providers
|
||||
- `ScheduleRenewalAll(parent task.Parent)` - Schedules automatic renewal for all providers
|
||||
- `PrintCertExpiriesAll()` - Logs certificate expiry dates for all providers
|
||||
|
||||
### User
|
||||
|
||||
ACME account representation implementing lego's `acme.User` interface.
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
Email string // Account email
|
||||
Registration *registration.Resource // ACME registration
|
||||
Key crypto.PrivateKey // Account key
|
||||
}
|
||||
```
|
||||
|
||||
### sniMatcher
|
||||
|
||||
Efficient SNI-to-Provider lookup with exact and wildcard matching.
|
||||
|
||||
```go
|
||||
type sniMatcher struct {
|
||||
exact map[string]*Provider // Exact domain matches
|
||||
root sniTreeNode // Wildcard suffix tree
|
||||
}
|
||||
|
||||
type sniTreeNode struct {
|
||||
children map[string]*sniTreeNode // DNS label → child node
|
||||
wildcard *Provider // Wildcard match at this level
|
||||
}
|
||||
```
|
||||
|
||||
## DNS Providers
|
||||
|
||||
Supported DNS providers for DNS-01 challenge validation:
|
||||
|
||||
| Provider | Name | Description |
|
||||
| ------------ | -------------- | ---------------------------------------- |
|
||||
| Cloudflare | `cloudflare` | Cloudflare DNS |
|
||||
| Route 53 | `route53` | AWS Route 53 |
|
||||
| DigitalOcean | `digitalocean` | DigitalOcean DNS |
|
||||
| GoDaddy | `godaddy` | GoDaddy DNS |
|
||||
| OVH | `ovh` | OVHcloud DNS |
|
||||
| CloudDNS | `clouddns` | Google Cloud DNS |
|
||||
| AzureDNS | `azuredns` | Azure DNS |
|
||||
| DuckDNS | `duckdns` | DuckDNS |
|
||||
| and more... | | See `internal/dnsproviders/providers.go` |
|
||||
|
||||
### Provider Configuration
|
||||
|
||||
Each provider accepts configuration via the `options` map:
|
||||
|
||||
```yaml
|
||||
autocert:
|
||||
provider: cloudflare
|
||||
email: admin@example.com
|
||||
domains:
|
||||
- example.com
|
||||
- "*.example.com"
|
||||
options:
|
||||
CF_API_TOKEN: your-api-token
|
||||
CF_ZONE_API_TOKEN: your-zone-token
|
||||
resolvers:
|
||||
- 1.1.1.1:53
|
||||
```
|
||||
|
||||
## ACME Integration
|
||||
|
||||
### Account Registration
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Load or Generate ACME Key] --> B[Init LEGO Client]
|
||||
B --> C[Resolve Account by Key]
|
||||
C --> D{Account Exists?}
|
||||
D -->|Yes| E[Continue with existing]
|
||||
D -->|No| F{Has EAB?}
|
||||
F -->|Yes| G[Register with EAB]
|
||||
F -->|No| H[Register with TOS Agreement]
|
||||
G --> I[Save Registration]
|
||||
H --> I
|
||||
```
|
||||
|
||||
### DNS-01 Challenge
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as ACME CA
|
||||
participant P as GoDoxy
|
||||
participant D as DNS Provider
|
||||
|
||||
P->>C: Request certificate for domain
|
||||
C->>P: Present DNS-01 challenge
|
||||
P->>D: Create TXT record _acme-challenge.domain
|
||||
D-->>P: Record created
|
||||
P->>C: Challenge ready
|
||||
C->>D: Verify DNS TXT record
|
||||
D-->>C: Verification success
|
||||
C->>P: Issue certificate
|
||||
P->>D: Clean up TXT record
|
||||
```
|
||||
|
||||
## Multi-Certificate Support
|
||||
|
||||
The package supports multiple certificates through the `extra` configuration:
|
||||
|
||||
```yaml
|
||||
autocert:
|
||||
provider: cloudflare
|
||||
email: admin@example.com
|
||||
domains:
|
||||
- example.com
|
||||
- "*.example.com"
|
||||
cert_path: certs/example.com.crt
|
||||
key_path: certs/example.com.key
|
||||
extra:
|
||||
- domains:
|
||||
- api.example.com
|
||||
- "*.api.example.com"
|
||||
cert_path: certs/api.example.com.crt
|
||||
key_path: certs/api.example.com.key
|
||||
provider: cloudflare
|
||||
email: admin@api.example.com
|
||||
```
|
||||
|
||||
### Extra Provider Setup
|
||||
|
||||
Extra providers are initialized atomically within `NewProvider()`:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[NewProvider] --> B{Merge Config with Extra}
|
||||
B --> C[Create Provider per Extra]
|
||||
C --> D[Build SNI Matcher]
|
||||
D --> E[Register in SNI Tree]
|
||||
|
||||
style B fill:#87CEEB
|
||||
style C fill:#FFD700
|
||||
```
|
||||
|
||||
## Renewal Scheduling
|
||||
|
||||
### Renewal Timing
|
||||
|
||||
- **Initial Check**: Certificate expiry is checked at startup
|
||||
- **Renewal Window**: Renewal scheduled for 1 month before expiry
|
||||
- **Cooldown on Failure**: 1-hour cooldown after failed renewal
|
||||
- **Request Cooldown**: 15-second cooldown after startup (prevents rate limiting)
|
||||
- **Force Renewal**: `forceRenewalCh` channel allows triggering immediate renewal
|
||||
|
||||
### Force Renewal
|
||||
|
||||
The `forceRenewalCh` channel (buffered size 1) enables immediate certificate renewal on demand:
|
||||
|
||||
```go
|
||||
// Trigger forced renewal for main and all extra providers
|
||||
provider.ForceExpiryAll()
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Start] --> B[Calculate Renewal Time]
|
||||
B --> C[expiry - 30 days]
|
||||
C --> D[Start Timer]
|
||||
|
||||
D --> E{Event?}
|
||||
E -->|forceRenewalCh| F[Force Renewal]
|
||||
E -->|Timer| G[Check Failure Cooldown]
|
||||
E -->|Context Done| H[Exit]
|
||||
|
||||
G --> H1{Recently Failed?}
|
||||
H1 -->|Yes| I[Skip, Wait Next Event]
|
||||
H1 -->|No| J[Attempt Renewal]
|
||||
|
||||
J --> K{Renewal Success?}
|
||||
K -->|Yes| L[Reset Failure, Notify Success]
|
||||
K -->|No| M[Update Failure Time, Notify Failure]
|
||||
|
||||
L --> N[Reset Timer]
|
||||
I --> N
|
||||
M --> D
|
||||
|
||||
N --> D
|
||||
|
||||
style F fill:#FFD700
|
||||
style J fill:#FFD700
|
||||
style K fill:#90EE90
|
||||
style M fill:#FFA07A
|
||||
```
|
||||
|
||||
**Notifications:** Renewal success/failure triggers system notifications with provider name.
|
||||
|
||||
### CertState
|
||||
|
||||
Certificate state tracking:
|
||||
|
||||
```go
|
||||
const (
|
||||
CertStateValid // Certificate is valid and up-to-date
|
||||
CertStateExpired // Certificate has expired or needs renewal
|
||||
CertStateMismatch // Certificate domains don't match config
|
||||
)
|
||||
```
|
||||
|
||||
### RenewMode
|
||||
|
||||
Controls renewal behavior:
|
||||
|
||||
```go
|
||||
const (
|
||||
renewModeForce // Force renewal, bypass cooldown and state check
|
||||
renewModeIfNeeded // Renew only if expired or domain mismatch
|
||||
)
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
internal/autocert/
|
||||
├── README.md # This file
|
||||
├── config.go # Config struct and validation
|
||||
├── provider.go # Provider implementation
|
||||
├── setup.go # Extra provider setup
|
||||
├── sni_matcher.go # SNI matching logic
|
||||
├── providers.go # DNS provider registration
|
||||
├── state.go # Certificate state enum
|
||||
├── user.go # ACME user/account
|
||||
├── paths.go # Default paths
|
||||
└── types/
|
||||
└── provider.go # Provider interface
|
||||
```
|
||||
|
||||
## Default Paths
|
||||
|
||||
| Constant | Default Value | Description |
|
||||
| -------------------- | ---------------- | ------------------------ |
|
||||
| `CertFileDefault` | `certs/cert.crt` | Default certificate path |
|
||||
| `KeyFileDefault` | `certs/priv.key` | Default private key path |
|
||||
| `ACMEKeyFileDefault` | `certs/acme.key` | Default ACME account key |
|
||||
|
||||
Failure tracking file is generated per-certificate: `<cert_dir>/.last_failure-<hash>`
|
||||
|
||||
## Error Handling
|
||||
|
||||
The package uses structured error handling with `gperr`:
|
||||
|
||||
- **ErrMissingField** - Required configuration field missing
|
||||
- **ErrDuplicatedPath** - Duplicate certificate/key paths in extras
|
||||
- **ErrInvalidDomain** - Invalid domain format
|
||||
- **ErrUnknownProvider** - Unknown DNS provider
|
||||
- **ErrGetCertFailure** - Certificate retrieval failed
|
||||
|
||||
**Error Context:** All errors are prefixed with provider name ("main" or "extra[N]") via `fmtError()` for clear attribution.
|
||||
|
||||
### Failure Tracking
|
||||
|
||||
Last failure is persisted per-certificate to prevent rate limiting:
|
||||
|
||||
```go
|
||||
// File: <cert_dir>/.last_failure-<hash> where hash is SHA256(certPath|keyPath)[:6]
|
||||
```
|
||||
|
||||
**Cooldown Checks:** Last failure is checked in `obtainCertIfNotExists()` (15-second startup cooldown) and `renew()` (1-hour failure cooldown). The `renewModeForce` bypasses cooldown checks entirely.
|
||||
|
||||
## Integration with GoDoxy
|
||||
|
||||
The autocert package integrates with GoDoxy's configuration system:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Config
|
||||
direction TB
|
||||
A[config.yml] --> B[Parse Config]
|
||||
B --> C[AutoCert Config]
|
||||
end
|
||||
|
||||
subgraph State
|
||||
C --> D[NewProvider]
|
||||
D --> E[Schedule Renewal]
|
||||
E --> F[Set Active Provider]
|
||||
end
|
||||
|
||||
subgraph Server
|
||||
F --> G[TLS Handshake]
|
||||
G --> H[GetCert via SNI]
|
||||
H --> I[Return Certificate]
|
||||
end
|
||||
```
|
||||
|
||||
### REST API
|
||||
|
||||
Force certificate renewal via WebSocket endpoint:
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| -------------------- | ------ | ----------------------------------------- |
|
||||
| `/api/v1/cert/renew` | GET | Triggers `ForceExpiryAll()` via WebSocket |
|
||||
|
||||
The endpoint streams live logs during the renewal process.
|
||||
|
||||
## Usage Example
|
||||
|
||||
```yaml
|
||||
# config/config.yml
|
||||
autocert:
|
||||
provider: cloudflare
|
||||
email: admin@example.com
|
||||
domains:
|
||||
- example.com
|
||||
- "*.example.com"
|
||||
options:
|
||||
CF_API_TOKEN: ${CF_API_TOKEN}
|
||||
resolvers:
|
||||
- 1.1.1.1:53
|
||||
- 8.8.8.8:53
|
||||
```
|
||||
|
||||
```go
|
||||
// In config initialization
|
||||
autocertCfg := state.AutoCert
|
||||
user, legoCfg, err := autocertCfg.GetLegoConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
provider, err := autocert.NewProvider(autocertCfg, user, legoCfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("autocert error: %w", err)
|
||||
}
|
||||
|
||||
if err := provider.ObtainCertIfNotExistsAll(); err != nil {
|
||||
return fmt.Errorf("failed to obtain certificates: %w", err)
|
||||
}
|
||||
|
||||
provider.ScheduleRenewalAll(state.Task())
|
||||
provider.PrintCertExpiriesAll()
|
||||
```
|
||||
@@ -41,6 +41,7 @@ allowlist = [
|
||||
"hostinger",
|
||||
"httpreq",
|
||||
"ionos",
|
||||
"inwx",
|
||||
"linode",
|
||||
"namecheap",
|
||||
"netcup",
|
||||
@@ -49,6 +50,7 @@ allowlist = [
|
||||
"ovh",
|
||||
"porkbun",
|
||||
"rfc2136",
|
||||
# "route53",
|
||||
"scaleway",
|
||||
"spaceship",
|
||||
"vercel",
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/yusing/godoxy => ../..
|
||||
|
||||
require (
|
||||
github.com/go-acme/lego/v4 v4.30.1
|
||||
github.com/yusing/godoxy v0.21.3
|
||||
github.com/yusing/godoxy v0.23.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -22,6 +22,7 @@ require (
|
||||
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/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||
@@ -29,6 +30,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
@@ -39,6 +41,7 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/go-resty/resty/v2 v2.17.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/goccy/go-yaml v1.19.1 // indirect
|
||||
github.com/gofrs/flock v0.13.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
@@ -47,10 +50,11 @@ require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.16.0 // indirect
|
||||
github.com/gotify/server/v2 v2.7.3 // indirect
|
||||
github.com/gotify/server/v2 v2.8.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/linode/linodego v1.63.0 // indirect
|
||||
@@ -60,12 +64,14 @@ require (
|
||||
github.com/miekg/dns v1.1.69 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/nrdcg/goacmedns v0.2.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.12.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2 // indirect
|
||||
github.com/nrdcg/porkbun v0.4.0 // indirect
|
||||
github.com/ovh/go-ovh v1.9.0 // 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/pquerna/otp v1.5.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
|
||||
|
||||
@@ -34,6 +34,9 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
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/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
|
||||
@@ -53,6 +56,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||
@@ -78,6 +83,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-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4=
|
||||
github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE=
|
||||
github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@@ -100,8 +107,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAV
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=
|
||||
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
|
||||
github.com/gotify/server/v2 v2.7.3 h1:nro/ZnxdlZFvxFcw9LREGA8zdk6CK744azwhuhX/A4g=
|
||||
github.com/gotify/server/v2 v2.7.3/go.mod h1:VAtE1RIc/2j886PYs9WPQbMjqbFsoyQ0G8IdFtnAxU0=
|
||||
github.com/gotify/server/v2 v2.8.0 h1:E3UDDn/3rFZi1sjZfbuhXNnxJP3ACZhdcw/iySegPRA=
|
||||
github.com/gotify/server/v2 v2.8.0/go.mod h1:6ci5adxcE2hf1v+2oowKiQmixOxXV8vU+CRLKP6sqZA=
|
||||
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-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
@@ -114,6 +121,8 @@ github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRt
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -139,6 +148,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
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/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
|
||||
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2 h1:l0tH15ACQADZAzC+LZ+mo2tIX4H6uZu0ulrVmG5Tqz0=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2 h1:gzB4c6ztb38C/jYiqEaFC+mCGcWFHDji9e6jwymY9d4=
|
||||
@@ -153,6 +164,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
@@ -226,10 +239,12 @@ 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.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
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.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/go-acme/lego/v4/providers/dns/hetzner"
|
||||
"github.com/go-acme/lego/v4/providers/dns/hostinger"
|
||||
"github.com/go-acme/lego/v4/providers/dns/httpreq"
|
||||
"github.com/go-acme/lego/v4/providers/dns/inwx"
|
||||
"github.com/go-acme/lego/v4/providers/dns/ionos"
|
||||
"github.com/go-acme/lego/v4/providers/dns/linode"
|
||||
"github.com/go-acme/lego/v4/providers/dns/namecheap"
|
||||
@@ -57,6 +58,7 @@ func InitProviders() {
|
||||
autocert.Providers["hostinger"] = autocert.DNSProvider(hostinger.NewDefaultConfig, hostinger.NewDNSProviderConfig)
|
||||
autocert.Providers["httpreq"] = autocert.DNSProvider(httpreq.NewDefaultConfig, httpreq.NewDNSProviderConfig)
|
||||
autocert.Providers["ionos"] = autocert.DNSProvider(ionos.NewDefaultConfig, ionos.NewDNSProviderConfig)
|
||||
autocert.Providers["inwx"] = autocert.DNSProvider(inwx.NewDefaultConfig, inwx.NewDNSProviderConfig)
|
||||
autocert.Providers["linode"] = autocert.DNSProvider(linode.NewDefaultConfig, linode.NewDNSProviderConfig)
|
||||
autocert.Providers["namecheap"] = autocert.DNSProvider(namecheap.NewDefaultConfig, namecheap.NewDNSProviderConfig)
|
||||
autocert.Providers["netcup"] = autocert.DNSProvider(netcup.NewDefaultConfig, netcup.NewDNSProviderConfig)
|
||||
|
||||
Submodule internal/gopsutil updated: 2dec30129b...9532b08add
355
internal/idlewatcher/README.md
Normal file
355
internal/idlewatcher/README.md
Normal file
@@ -0,0 +1,355 @@
|
||||
# Idlewatcher
|
||||
|
||||
Idlewatcher manages container lifecycle based on idle timeout. When a container is idle for a configured duration, it can be automatically stopped, paused, or killed. When a request comes in, the container is woken up automatically.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Request Flow
|
||||
HTTP[HTTP Request] -->|Intercept| W[Watcher]
|
||||
Stream[Stream Request] -->|Intercept| W
|
||||
end
|
||||
|
||||
subgraph Wake Process
|
||||
W -->|Wake| Wake[Wake Container]
|
||||
Wake -->|Check Status| State[Container State]
|
||||
Wake -->|Wait Ready| Health[Health Check]
|
||||
Wake -->|Events| SSE[SSE Events]
|
||||
end
|
||||
|
||||
subgraph Idle Management
|
||||
Timer[Idle Timer] -->|Timeout| Stop[Stop Container]
|
||||
State -->|Running| Timer
|
||||
State -->|Stopped| Timer
|
||||
end
|
||||
|
||||
subgraph Providers
|
||||
Docker[DockerProvider] --> DockerAPI[Docker API]
|
||||
Proxmox[ProxmoxProvider] --> ProxmoxAPI[Proxmox API]
|
||||
end
|
||||
|
||||
W -->|Uses| Providers
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
idlewatcher/
|
||||
├── cmd # Command execution utilities
|
||||
├── debug.go # Debug utilities for watcher inspection
|
||||
├── errors.go # Error types and conversion
|
||||
├── events.go # Wake event types and broadcasting
|
||||
├── handle_http.go # HTTP request handling and loading page
|
||||
├── handle_http_debug.go # Debug HTTP handler (dev only)
|
||||
├── handle_stream.go # Stream connection handling
|
||||
├── health.go # Health monitoring interface
|
||||
├── loading_page.go # Loading page HTML/CSS/JS templates
|
||||
├── state.go # Container state management
|
||||
├── watcher.go # Core Watcher implementation
|
||||
├── provider/ # Container provider implementations
|
||||
│ ├── docker.go # Docker container management
|
||||
│ └── proxmox.go # Proxmox LXC management
|
||||
├── types/
|
||||
│ └── provider.go # Provider interface definition
|
||||
└── html/
|
||||
├── loading_page.html # Loading page template
|
||||
├── style.css # Loading page styles
|
||||
└── loading.js # Loading page JavaScript
|
||||
```
|
||||
|
||||
## Core Components
|
||||
|
||||
### Watcher
|
||||
|
||||
The main component that manages a single container's lifecycle:
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Watcher {
|
||||
+string Key() string
|
||||
+Wake(ctx context.Context) error
|
||||
+Start(parent task.Parent) gperr.Error
|
||||
+ServeHTTP(rw ResponseWriter, r *Request)
|
||||
+ListenAndServe(ctx context.Context, predial, onRead HookFunc)
|
||||
-idleTicker: *time.Ticker
|
||||
-healthTicker: *time.Ticker
|
||||
-state: synk.Value~*containerState~
|
||||
-provider: synk.Value~Provider~
|
||||
-dependsOn: []*dependency
|
||||
}
|
||||
|
||||
class containerState {
|
||||
+status: ContainerStatus
|
||||
+ready: bool
|
||||
+err: error
|
||||
+startedAt: time.Time
|
||||
+healthTries: int
|
||||
}
|
||||
|
||||
class dependency {
|
||||
+*Watcher
|
||||
+waitHealthy: bool
|
||||
}
|
||||
|
||||
Watcher --> containerState : manages
|
||||
Watcher --> dependency : depends on
|
||||
```
|
||||
|
||||
### Provider Interface
|
||||
|
||||
Abstraction for different container backends:
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Provider {
|
||||
<<interface>>
|
||||
+ContainerPause(ctx) error
|
||||
+ContainerUnpause(ctx) error
|
||||
+ContainerStart(ctx) error
|
||||
+ContainerStop(ctx, signal, timeout) error
|
||||
+ContainerKill(ctx, signal) error
|
||||
+ContainerStatus(ctx) (ContainerStatus, error)
|
||||
+Watch(ctx) (eventCh, errCh)
|
||||
+Close()
|
||||
}
|
||||
|
||||
class DockerProvider {
|
||||
+client: *docker.SharedClient
|
||||
+watcher: watcher.DockerWatcher
|
||||
+containerID: string
|
||||
}
|
||||
|
||||
class ProxmoxProvider {
|
||||
+*proxmox.Node
|
||||
+vmid: int
|
||||
+lxcName: string
|
||||
+running: bool
|
||||
}
|
||||
|
||||
Provider <|-- DockerProvider
|
||||
Provider <|-- ProxmoxProvider
|
||||
```
|
||||
|
||||
### Container Status
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Napping: Container stopped/paused
|
||||
Napping --> Waking: Wake request
|
||||
Waking --> Running: Container started
|
||||
Running --> Starting: Container is running but not healthy
|
||||
Starting --> Ready: Health check passes
|
||||
Ready --> Napping: Idle timeout
|
||||
Ready --> Error check fails: Health
|
||||
Error --> Waking: Retry wake
|
||||
```
|
||||
|
||||
## Lifecycle Flow
|
||||
|
||||
### Wake Flow (HTTP)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Client
|
||||
participant W as Watcher
|
||||
participant P as Provider
|
||||
participant H as HealthChecker
|
||||
participant SSE as SSE Events
|
||||
|
||||
C->>W: HTTP Request
|
||||
W->>W: resetIdleTimer()
|
||||
alt Container already ready
|
||||
W->>W: return true (proceed)
|
||||
else
|
||||
alt No loading page configured
|
||||
W->>P: ContainerStart()
|
||||
W->>H: Wait for healthy
|
||||
H-->>W: Healthy
|
||||
W->>C: Continue request
|
||||
else Loading page enabled
|
||||
W->>P: ContainerStart()
|
||||
W->>SSE: Send WakeEventStarting
|
||||
W->>C: Serve loading page
|
||||
loop Health checks
|
||||
H->>H: Check health
|
||||
H-->>W: Not healthy yet
|
||||
W->>SSE: Send progress
|
||||
end
|
||||
H-->>W: Healthy
|
||||
W->>SSE: Send WakeEventReady
|
||||
C->>W: SSE connection
|
||||
W->>SSE: Events streamed
|
||||
C->>W: Poll/retry request
|
||||
W->>W: return true (proceed)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Stream Wake Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Client
|
||||
participant W as Watcher
|
||||
participant P as Provider
|
||||
participant H as HealthChecker
|
||||
|
||||
C->>W: Connect to stream
|
||||
W->>W: preDial hook
|
||||
W->>W: wakeFromStream()
|
||||
alt Container ready
|
||||
W->>W: Pass through
|
||||
else
|
||||
W->>P: ContainerStart()
|
||||
W->>W: waitStarted()
|
||||
W->>H: Wait for healthy
|
||||
H-->>W: Healthy
|
||||
W->>C: Stream connected
|
||||
end
|
||||
```
|
||||
|
||||
### Idle Timeout Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as Client
|
||||
participant T as Idle Timer
|
||||
participant W as Watcher
|
||||
participant P as Provider
|
||||
participant D as Dependencies
|
||||
|
||||
loop Every request
|
||||
Client->>W: HTTP/Stream
|
||||
W->>W: resetIdleTimer()
|
||||
end
|
||||
|
||||
T->>W: Timeout
|
||||
W->>W: stopByMethod()
|
||||
alt stop method = pause
|
||||
W->>P: ContainerPause()
|
||||
else stop method = stop
|
||||
W->>P: ContainerStop(signal, timeout)
|
||||
else kill method = kill
|
||||
W->>P: ContainerKill(signal)
|
||||
end
|
||||
P-->>W: Result
|
||||
W->>D: Stop dependencies
|
||||
D-->>W: Done
|
||||
```
|
||||
|
||||
## Dependency Management
|
||||
|
||||
Watchers can depend on other containers being started first:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[App] -->|depends on| B[Database]
|
||||
A -->|depends on| C[Redis]
|
||||
B -->|depends on| D[Cache]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant A as App Watcher
|
||||
participant B as DB Watcher
|
||||
participant P as Provider
|
||||
|
||||
A->>B: Wake()
|
||||
Note over B: SingleFlight prevents<br/>duplicate wake
|
||||
B->>P: ContainerStart()
|
||||
P-->>B: Started
|
||||
B->>B: Wait healthy
|
||||
B-->>A: Ready
|
||||
A->>P: ContainerStart()
|
||||
P-->>A: Started
|
||||
```
|
||||
|
||||
## Event System
|
||||
|
||||
Wake events are broadcast via Server-Sent Events (SSE):
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class WakeEvent {
|
||||
+Type: WakeEventType
|
||||
+Message: string
|
||||
+Timestamp: time.Time
|
||||
+Error: string
|
||||
+WriteSSE(w io.Writer) error
|
||||
}
|
||||
|
||||
class WakeEventType {
|
||||
<<enumeration>>
|
||||
WakeEventStarting
|
||||
WakeEventWakingDep
|
||||
WakeEventDepReady
|
||||
WakeEventContainerWoke
|
||||
WakeEventWaitingReady
|
||||
WakeEventReady
|
||||
WakeEventError
|
||||
}
|
||||
|
||||
WakeEvent --> WakeEventType
|
||||
```
|
||||
|
||||
## State Machine
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
note right of Napping
|
||||
Container is stopped or paused
|
||||
Idle timer stopped
|
||||
end note
|
||||
|
||||
note right of Waking
|
||||
Container is starting
|
||||
Health checking active
|
||||
Events broadcasted
|
||||
end note
|
||||
|
||||
note right of Ready
|
||||
Container healthy
|
||||
Idle timer running
|
||||
end note
|
||||
|
||||
Napping --> Waking: Wake()
|
||||
Waking --> Ready: Health check passes
|
||||
Waking --> Error: Health check fails
|
||||
Error --> Waking: Retry
|
||||
Ready --> Napping: Idle timeout
|
||||
Ready --> Napping: Manual stop
|
||||
```
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------- | ----------------------------------------------------- |
|
||||
| `watcher.go` | Core Watcher implementation with lifecycle management |
|
||||
| `handle_http.go` | HTTP interception and loading page serving |
|
||||
| `handle_stream.go` | Stream connection wake handling |
|
||||
| `provider/docker.go` | Docker container operations |
|
||||
| `provider/proxmox.go` | Proxmox LXC container operations |
|
||||
| `state.go` | Container state transitions |
|
||||
| `events.go` | Event broadcasting via SSE |
|
||||
| `health.go` | Health monitor interface implementation |
|
||||
|
||||
## Configuration
|
||||
|
||||
See `types.IdlewatcherConfig` for configuration options:
|
||||
|
||||
- `IdleTimeout`: Duration before container is put to sleep
|
||||
- `StopMethod`: pause, stop, or kill
|
||||
- `StopSignal`: Signal to send when stopping
|
||||
- `StopTimeout`: Timeout for stop operation
|
||||
- `WakeTimeout`: Timeout for wake operation
|
||||
- `DependsOn`: List of dependent containers
|
||||
- `StartEndpoint`: Optional endpoint restriction for wake requests
|
||||
- `NoLoadingPage`: Skip loading page, wait directly
|
||||
|
||||
## Thread Safety
|
||||
|
||||
- Uses `synk.Value` for atomic state updates
|
||||
- Uses `xsync.Map` for SSE subscriber management
|
||||
- Uses `sync.RWMutex` for watcher map access
|
||||
- Uses `singleflight.Group` to prevent duplicate wake calls
|
||||
263
internal/logging/README.md
Normal file
263
internal/logging/README.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# Logging Package
|
||||
|
||||
This package provides structured logging capabilities for GoDoxy, including application logging, HTTP access logging, and in-memory log streaming.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
internal/logging/
|
||||
├── logging.go # Main logger initialization using zerolog
|
||||
├── accesslog/ # HTTP access logging with rotation and filtering
|
||||
│ ├── access_logger.go # Core logging logic and buffering
|
||||
│ ├── multi_access_logger.go # Fan-out to multiple writers
|
||||
│ ├── config.go # Configuration types and defaults
|
||||
│ ├── formatter.go # Log format implementations
|
||||
│ ├── file_logger.go # File I/O with reference counting
|
||||
│ ├── rotate.go # Log rotation based on retention policy
|
||||
│ ├── writer.go # Buffered/unbuffered writer abstractions
|
||||
│ ├── back_scanner.go # Backward line scanning for rotation
|
||||
│ ├── filter.go # Request filtering by status/method/header
|
||||
│ ├── retention.go # Retention policy definitions
|
||||
│ ├── response_recorder.go # HTTP response recording middleware
|
||||
│ └── ... # Tests and utilities
|
||||
└── memlogger/ # In-memory circular buffer with WebSocket streaming
|
||||
└── mem_logger.go # Ring buffer with WebSocket event notifications
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Application Logger"
|
||||
L[logging.go] --> Z[zerolog.Logger]
|
||||
Z --> CW[ConsoleWriter]
|
||||
end
|
||||
|
||||
subgraph "Access Log Pipeline"
|
||||
R[HTTP Request] --> M[Middleware]
|
||||
M --> RR[ResponseRecorder]
|
||||
RR --> F[Formatter]
|
||||
F --> B[BufferedWriter]
|
||||
B --> W[Writer]
|
||||
W --> F1[File]
|
||||
W --> S[Stdout]
|
||||
end
|
||||
|
||||
subgraph "Log Rotation"
|
||||
B --> RT[Rotate Timer]
|
||||
RT --> BS[BackScanner]
|
||||
BS --> T[Truncate/Move]
|
||||
T --> F1
|
||||
end
|
||||
|
||||
subgraph "In-Memory Logger"
|
||||
WB[Write Buffer]
|
||||
WB --> RB[Circular Buffer<br/>16KB max]
|
||||
RB --> WS[WebSocket]
|
||||
WS --> C[Client]
|
||||
end
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### 1. Application Logger (`logging.go`)
|
||||
|
||||
Initializes a zerolog-based console logger with level-aware formatting:
|
||||
|
||||
- **Levels**: Trace → Debug → Info (determined by `common.IsTrace`/`common.IsDebug`)
|
||||
- **Time Format**: 04:05 (trace) or 01-02 15:04 (debug/info)
|
||||
- **Multi-line Handling**: Automatically indents continuation lines
|
||||
|
||||
```go
|
||||
// Auto-initialized on import
|
||||
func InitLogger(out ...io.Writer)
|
||||
|
||||
// Create logger with fixed level
|
||||
NewLoggerWithFixedLevel(level zerolog.Level, out ...io.Writer)
|
||||
```
|
||||
|
||||
### 2. Access Logging (`accesslog/`)
|
||||
|
||||
Logs HTTP requests/responses with configurable formats, filters, and destinations.
|
||||
|
||||
#### Core Interface
|
||||
|
||||
```go
|
||||
type AccessLogger interface {
|
||||
Log(req *http.Request, res *http.Response)
|
||||
LogError(req *http.Request, err error)
|
||||
LogACL(info *maxmind.IPInfo, blocked bool)
|
||||
Config() *Config
|
||||
Flush()
|
||||
Close() error
|
||||
}
|
||||
```
|
||||
|
||||
#### Log Formats
|
||||
|
||||
| Format | Description |
|
||||
| ---------- | --------------------------------- |
|
||||
| `common` | Basic Apache Common format |
|
||||
| `combined` | Common + Referer + User-Agent |
|
||||
| `json` | Structured JSON with full details |
|
||||
|
||||
#### Example Output
|
||||
|
||||
```
|
||||
common: localhost 127.0.0.1 - - [01-04 10:30:45] "GET /api HTTP/1.1" 200 1234
|
||||
combined: localhost 127.0.0.1 - - [01-04 10:30:45] "GET /api HTTP/1.1" 200 1234 "https://example.com" "Mozilla/5.0"
|
||||
json: {"time":"04/Jan/2025:10:30:45 +0000","ip":"127.0.0.1","method":"GET",...}
|
||||
```
|
||||
|
||||
#### Filters
|
||||
|
||||
Filter incoming requests before logging:
|
||||
|
||||
- **StatusCodes**: Keep/drop by HTTP status code range
|
||||
- **Method**: Keep/drop by HTTP method
|
||||
- **Headers**: Match header existence or value
|
||||
- **CIDR**: Match client IP against CIDR ranges
|
||||
|
||||
#### Multi-Destination Support
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Request] --> B[MultiAccessLogger]
|
||||
B --> C[AccessLogger 1] --> F[File]
|
||||
B --> D[AccessLogger 2] --> S[Stdout]
|
||||
```
|
||||
|
||||
### 3. File Management (`file_logger.go`)
|
||||
|
||||
- **Reference Counting**: Multiple loggers can share the same file
|
||||
- **Auto-Close**: File closes when ref count reaches zero
|
||||
- **Thread-Safe**: Shared mutex per file path
|
||||
|
||||
### 4. Log Rotation (`rotate.go`)
|
||||
|
||||
Rotates logs based on retention policy:
|
||||
|
||||
| Policy | Description |
|
||||
| ---------- | ----------------------------------- |
|
||||
| `Days` | Keep logs within last N days |
|
||||
| `Last` | Keep last N log lines |
|
||||
| `KeepSize` | Keep last N bytes (simple truncate) |
|
||||
|
||||
**Algorithm** (for Days/Last):
|
||||
|
||||
1. Scan file backward line-by-line using `BackScanner`
|
||||
2. Parse timestamps to find cutoff point
|
||||
3. Move retained lines to file front
|
||||
4. Truncate excess
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[File End] --> B[BackScanner]
|
||||
B --> C{Valid timestamp?}
|
||||
C -->|No| D[Skip line]
|
||||
C -->|Yes| E{Within retention?}
|
||||
E -->|No| F[Keep line]
|
||||
E -->|Yes| G[Stop scanning]
|
||||
F --> H[Move to front]
|
||||
G --> I[Truncate rest]
|
||||
```
|
||||
|
||||
### 5. Buffering (`access_logger.go`)
|
||||
|
||||
- **Dynamic Sizing**: Adjusts buffer size based on write throughput
|
||||
- **Initial**: 4KB → **Max**: 8MB
|
||||
- **Adjustment**: Every 5 seconds based on writes-per-second
|
||||
|
||||
### 6. In-Memory Logger (`memlogger/`)
|
||||
|
||||
Circular buffer for real-time log streaming via WebSocket:
|
||||
|
||||
- **Size**: 16KB maximum, auto-truncates old entries
|
||||
- **Streaming**: WebSocket connection receives live updates
|
||||
- **Events API**: Subscribe to log events
|
||||
|
||||
```go
|
||||
// HTTP handler for WebSocket streaming
|
||||
HandlerFunc() gin.HandlerFunc
|
||||
|
||||
// Subscribe to log events
|
||||
Events() (<-chan []byte, func())
|
||||
|
||||
// Write to buffer (implements io.Writer)
|
||||
Write(p []byte) (n int, err error)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```yaml
|
||||
access_log:
|
||||
path: /var/log/godoxy/access.log # File path (optional)
|
||||
stdout: true # Also log to stdout (optional)
|
||||
format: combined # common | combined | json
|
||||
rotate_interval: 1h # How often to check rotation
|
||||
retention:
|
||||
days: 30 # Keep last 30 days
|
||||
# OR
|
||||
last: 10000 # Keep last 10000 lines
|
||||
# OR
|
||||
keep_size: 100MB # Keep last 100MB
|
||||
filters:
|
||||
status_codes: [400-599] # Only log errors
|
||||
method: [GET, POST]
|
||||
headers:
|
||||
- name: X-Internal
|
||||
value: "true"
|
||||
cidr:
|
||||
- 10.0.0.0/8
|
||||
fields:
|
||||
headers: drop # keep | drop | redacted
|
||||
query: keep # keep | drop | redacted
|
||||
cookies: drop # keep | drop | redacted
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Client
|
||||
participant M as Middleware
|
||||
participant R as ResponseRecorder
|
||||
participant F as Formatter
|
||||
participant B as BufferedWriter
|
||||
participant W as Writer
|
||||
|
||||
C->>M: HTTP Request
|
||||
M->>R: Capture request
|
||||
R-->>M: Continue
|
||||
|
||||
M->>M: Process request
|
||||
|
||||
C->>M: HTTP Response
|
||||
M->>R: Capture response
|
||||
R->>F: Format log line
|
||||
F->>B: Write formatted line
|
||||
B->>W: Flush when needed
|
||||
|
||||
par File Writer
|
||||
W->>File: Append line
|
||||
and Stdout Writer
|
||||
W->>Stdout: Print line
|
||||
end
|
||||
|
||||
Note over B,W: Periodic rotation check
|
||||
W->>File: Rotate if needed
|
||||
```
|
||||
|
||||
## Key Design Patterns
|
||||
|
||||
1. **Interface Segregation**: Small, focused interfaces (`AccessLogger`, `Writer`, `BufferedWriter`)
|
||||
|
||||
2. **Dependency Injection**: Writers injected at creation for flexibility
|
||||
|
||||
3. **Reference Counting**: Shared file handles prevent too-many-open-files
|
||||
|
||||
4. **Dynamic Buffering**: Adapts to write throughput automatically
|
||||
|
||||
5. **Backward Scanning**: Efficient rotation without loading entire file
|
||||
|
||||
6. **Zero-Allocation Formatting**: Build log lines in pre-allocated buffers
|
||||
285
internal/metrics/README.md
Normal file
285
internal/metrics/README.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# Metrics Package
|
||||
|
||||
System monitoring and metrics collection for GoDoxy.
|
||||
|
||||
## Overview
|
||||
|
||||
This package provides a unified metrics collection system that polls system and route data at regular intervals, stores historical data across multiple time periods, and exposes both REST and WebSocket APIs for consumption.
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Core Framework"
|
||||
P[Period<T> Generic]
|
||||
E[Entries<T> Ring Buffer]
|
||||
PL[Poller<T, A> Orchestrator]
|
||||
end
|
||||
|
||||
subgraph "Data Sources"
|
||||
SI[SystemInfo Poller]
|
||||
UP[Uptime Poller]
|
||||
end
|
||||
|
||||
subgraph "Utilities"
|
||||
UT[Utils]
|
||||
end
|
||||
|
||||
P --> E
|
||||
PL --> P
|
||||
PL --> SI
|
||||
PL --> UP
|
||||
UT -.-> PL
|
||||
UT -.-> SI
|
||||
UT -.-> UP
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
internal/metrics/
|
||||
├── period/ # Core polling and storage framework
|
||||
│ ├── period.go # Period[T] - multi-timeframe container
|
||||
│ ├── entries.go # Entries[T] - ring buffer implementation
|
||||
│ ├── poller.go # Poller[T, A] - orchestration and lifecycle
|
||||
│ └── handler.go # HTTP handler for data access
|
||||
├── systeminfo/ # System metrics (CPU, memory, disk, network, sensors)
|
||||
├── uptime/ # Route health and uptime monitoring
|
||||
└── utils/ # Shared utilities (query parsing, pagination)
|
||||
```
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. Period[T] (`period/period.go`)
|
||||
|
||||
A generic container that manages multiple time periods for the same data type.
|
||||
|
||||
```go
|
||||
type Period[T any] struct {
|
||||
Entries map[Filter]*Entries[T] // 5m, 15m, 1h, 1d, 1mo
|
||||
mu sync.RWMutex
|
||||
}
|
||||
```
|
||||
|
||||
**Time Periods:**
|
||||
|
||||
| Filter | Duration | Entries | Interval |
|
||||
| ------ | -------- | ------- | -------- |
|
||||
| `5m` | 5 min | 100 | 3s |
|
||||
| `15m` | 15 min | 100 | 9s |
|
||||
| `1h` | 1 hour | 100 | 36s |
|
||||
| `1d` | 1 day | 100 | 14.4m |
|
||||
| `1mo` | 30 days | 100 | 7.2h |
|
||||
|
||||
### 2. Entries[T] (`period/entries.go`)
|
||||
|
||||
A fixed-size ring buffer (100 entries) with time-aware sampling.
|
||||
|
||||
```go
|
||||
type Entries[T any] struct {
|
||||
entries [100]T // Fixed-size array
|
||||
index int // Current position
|
||||
count int // Number of entries
|
||||
interval time.Duration // Sampling interval
|
||||
lastAdd time.Time // Last write timestamp
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
|
||||
- Circular buffer for efficient memory usage
|
||||
- Rate-limited adds (respects configured interval)
|
||||
- JSON serialization/deserialization with temporal spacing
|
||||
|
||||
### 3. Poller[T, A] (`period/poller.go`)
|
||||
|
||||
The orchestrator that ties together polling, storage, and HTTP serving.
|
||||
|
||||
```go
|
||||
type Poller[T any, A any] struct {
|
||||
name string
|
||||
poll PollFunc[T] // Data collection
|
||||
aggregate AggregateFunc[T, A] // Data aggregation
|
||||
resultFilter FilterFunc[T] // Query filtering
|
||||
period *Period[T] // Data storage
|
||||
lastResult synk.Value[T] // Latest snapshot
|
||||
}
|
||||
```
|
||||
|
||||
**Poll Cycle (1 second interval):**
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant T as Task
|
||||
participant P as Poller
|
||||
participant D as Data Source
|
||||
participant S as Storage (Period)
|
||||
participant F as File
|
||||
|
||||
T->>P: Start()
|
||||
P->>F: Load historical data
|
||||
F-->>P: Period[T] state
|
||||
|
||||
loop Every 1 second
|
||||
P->>D: Poll(ctx, lastResult)
|
||||
D-->>P: New data point
|
||||
P->>S: Add to all periods
|
||||
P->>P: Update lastResult
|
||||
|
||||
alt Every 30 seconds
|
||||
P->>P: Gather & log errors
|
||||
end
|
||||
|
||||
alt Every 5 minutes
|
||||
P->>F: Persist to JSON
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 4. HTTP Handler (`period/handler.go`)
|
||||
|
||||
Provides REST and WebSocket endpoints for data access.
|
||||
|
||||
**Endpoints:**
|
||||
|
||||
- `GET /metrics?period=5m&aggregate=cpu_average` - Historical data
|
||||
- `WS /metrics?period=5m&interval=5s` - Streaming updates
|
||||
|
||||
**Query Parameters:**
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `period` | Filter | (none) | Time range (5m, 15m, 1h, 1d, 1mo) |
|
||||
| `aggregate` | string | (varies) | Aggregation mode |
|
||||
| `interval` | duration | 1s | WebSocket update interval |
|
||||
| `limit` | int | 0 | Max results (0 = all) |
|
||||
| `offset` | int | 0 | Pagination offset |
|
||||
| `keyword` | string | "" | Fuzzy search filter |
|
||||
|
||||
## Implementations
|
||||
|
||||
### SystemInfo Poller
|
||||
|
||||
Collects system metrics using `gopsutil`:
|
||||
|
||||
```go
|
||||
type SystemInfo struct {
|
||||
Timestamp int64
|
||||
CPUAverage *float64
|
||||
Memory mem.VirtualMemoryStat
|
||||
Disks map[string]disk.UsageStat
|
||||
DisksIO map[string]*disk.IOCountersStat
|
||||
Network net.IOCountersStat
|
||||
Sensors Sensors
|
||||
}
|
||||
```
|
||||
|
||||
**Aggregation Modes:**
|
||||
|
||||
- `cpu_average` - CPU usage percentage
|
||||
- `memory_usage` - Memory used in bytes
|
||||
- `memory_usage_percent` - Memory usage percentage
|
||||
- `disks_read_speed` - Disk read speed (bytes/s)
|
||||
- `disks_write_speed` - Disk write speed (bytes/s)
|
||||
- `disks_iops` - Disk I/O operations per second
|
||||
- `disk_usage` - Disk usage in bytes
|
||||
- `network_speed` - Upload/download speed (bytes/s)
|
||||
- `network_transfer` - Total bytes transferred
|
||||
- `sensor_temperature` - Temperature sensor readings
|
||||
|
||||
### Uptime Poller
|
||||
|
||||
Monitors route health and calculates uptime statistics:
|
||||
|
||||
```go
|
||||
type RouteAggregate struct {
|
||||
Alias string
|
||||
DisplayName string
|
||||
Uptime float32 // Percentage healthy
|
||||
Downtime float32 // Percentage unhealthy
|
||||
Idle float32 // Percentage napping/starting
|
||||
AvgLatency float32 // Average latency in ms
|
||||
CurrentStatus HealthStatus
|
||||
Statuses []Status // Historical statuses
|
||||
}
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Data Source] -->|PollFunc| B[Poller]
|
||||
B -->|Add| C[Period.Entries]
|
||||
C -->|Ring Buffer| D[(Memory)]
|
||||
D -->|Every 5min| E[(data/metrics/*.json)]
|
||||
|
||||
B -->|HTTP Request| F[ServeHTTP]
|
||||
F -->|Filter| G[Get]
|
||||
G -->|Aggregate| H[Response]
|
||||
|
||||
F -->|WebSocket| I[PeriodicWrite]
|
||||
I -->|interval| J[Push Updates]
|
||||
```
|
||||
|
||||
## Persistence
|
||||
|
||||
Data is persisted to `data/metrics/` as JSON files:
|
||||
|
||||
```json
|
||||
{
|
||||
"entries": {
|
||||
"5m": {
|
||||
"entries": [...],
|
||||
"interval": "3s"
|
||||
},
|
||||
"15m": {...},
|
||||
"1h": {...},
|
||||
"1d": {...},
|
||||
"1mo": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**On Load:**
|
||||
|
||||
- Validates and fixes interval mismatches
|
||||
- Reconstructs temporal spacing for historical entries
|
||||
|
||||
## Thread Safety
|
||||
|
||||
- `Period[T]` uses `sync.RWMutex` for concurrent access
|
||||
- `Entries[T]` is append-only (safe for single writer)
|
||||
- `Poller` uses `synk.Value[T]` for atomic last result storage
|
||||
|
||||
## Creating a New Poller
|
||||
|
||||
```go
|
||||
type MyData struct {
|
||||
Value int
|
||||
}
|
||||
|
||||
type MyAggregate struct {
|
||||
Values []int
|
||||
}
|
||||
|
||||
var MyPoller = period.NewPoller(
|
||||
"my_poll_name",
|
||||
func(ctx context.Context, last *MyData) (*MyData, error) {
|
||||
// Fetch data
|
||||
return &MyData{Value: 42}, nil
|
||||
},
|
||||
func(entries []*MyData, query url.Values) (int, MyAggregate) {
|
||||
// Aggregate for API response
|
||||
return len(entries), MyAggregate{Values: [...]}
|
||||
},
|
||||
)
|
||||
|
||||
func init() {
|
||||
MyPoller.Start()
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Poll errors are aggregated over 30-second windows
|
||||
- Errors are logged with frequency counts
|
||||
- Individual sensor warnings (e.g., ENODATA) are suppressed gracefully
|
||||
303
internal/serialization/README.md
Normal file
303
internal/serialization/README.md
Normal file
@@ -0,0 +1,303 @@
|
||||
# Serialization Package
|
||||
|
||||
A Go package for flexible, type-safe serialization/deserialization with validation support. It provides robust handling of YAML/JSON input, environment variable substitution, and field-level validation with case-insensitive matching.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
theme: redux-dark-color
|
||||
---
|
||||
flowchart TB
|
||||
subgraph Input Processing
|
||||
YAML[YAML Bytes] --> EnvSub[Env Substitution]
|
||||
EnvSub --> YAMLParse[YAML Parse]
|
||||
YAMLParse --> Map[map<string,any>]
|
||||
end
|
||||
|
||||
subgraph Type Inspection
|
||||
Map --> TypeInfo[Type Info Cache]
|
||||
TypeInfo -.-> FieldLookup[Field Lookup]
|
||||
end
|
||||
|
||||
subgraph Conversion
|
||||
FieldLookup --> Convert[Convert Function]
|
||||
Convert --> StringConvert[String Conversion]
|
||||
Convert --> NumericConvert[Numeric Conversion]
|
||||
Convert --> MapConvert[Map/Struct Conversion]
|
||||
Convert --> SliceConvert[Slice Conversion]
|
||||
end
|
||||
|
||||
subgraph Validation
|
||||
Convert --> Validate[ValidateWithFieldTags]
|
||||
Convert --> CustomValidate[Custom Validator]
|
||||
CustomValidate --> CustomValidator[CustomValidator Interface]
|
||||
end
|
||||
|
||||
subgraph Output
|
||||
Validate --> Result[Typed Struct/Map]
|
||||
end
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------- | ------------------------------------------------- |
|
||||
| `serialization.go` | Core serialization/deserialization logic |
|
||||
| `validation.go` | Field tag validation and custom validator support |
|
||||
| `time.go` | Duration unit extensions (d, w, M) |
|
||||
| `serialization_test.go` | Core functionality tests |
|
||||
| `validation_*_test.go` | Validation-specific tests |
|
||||
|
||||
## Core Types
|
||||
|
||||
```go
|
||||
type SerializedObject = map[string]any
|
||||
```
|
||||
|
||||
The `SerializedObject` is the intermediate representation used throughout deserialization.
|
||||
|
||||
### Interfaces
|
||||
|
||||
```go
|
||||
// For custom map unmarshaling logic
|
||||
type MapUnmarshaller interface {
|
||||
UnmarshalMap(m map[string]any) gperr.Error
|
||||
}
|
||||
|
||||
// For custom validation logic
|
||||
type CustomValidator interface {
|
||||
Validate() gperr.Error
|
||||
}
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. Case-Insensitive Field Matching
|
||||
|
||||
Fields are matched using FNV-1a hash with case-insensitive comparison:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
AuthToken string `json:"auth_token"`
|
||||
}
|
||||
|
||||
// Matches: "auth_token", "AUTH_TOKEN", "AuthToken", "Auth_Token"
|
||||
```
|
||||
|
||||
### 2. Field Tags
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Name string `json:"name"` // JSON/deserialize field name
|
||||
Port int `validate:"required"` // Validation tag
|
||||
Secret string `json:"-"` // Exclude from deserialization
|
||||
Token string `aliases:"key,api_key"` // Aliases for matching
|
||||
}
|
||||
```
|
||||
|
||||
| Tag | Purpose |
|
||||
| ------------- | -------------------------------------------- |
|
||||
| `json` | Field name for serialization; `-` to exclude |
|
||||
| `deserialize` | Explicit deserialize name; `-` to exclude |
|
||||
| `validate` | go-playground/validator tags |
|
||||
| `aliases` | Comma-separated alternative field names |
|
||||
|
||||
### 3. Environment Variable Substitution
|
||||
|
||||
Supports `${VAR}` syntax with prefix-aware lookup:
|
||||
|
||||
```yaml
|
||||
autocert:
|
||||
auth_token: ${CLOUDFLARE_AUTH_TOKEN}
|
||||
```
|
||||
|
||||
Prefix resolution order: `GODOXY_VAR`, `GOPROXY_VAR`, `VAR`
|
||||
|
||||
### 4. String Conversions
|
||||
|
||||
Converts strings to various types:
|
||||
|
||||
```go
|
||||
// Duration: "1h30m", "2d" (d=day, w=week, M=month)
|
||||
ConvertString("2d", reflect.ValueOf(&duration))
|
||||
|
||||
// Numeric: "123", "0xFF"
|
||||
ConvertString("123", reflect.ValueOf(&intVal))
|
||||
|
||||
// Slice: "a,b,c" or YAML list format
|
||||
ConvertString("a,b,c", reflect.ValueOf(&slice))
|
||||
|
||||
// Map/Struct: YAML format
|
||||
ConvertString("key: value", reflect.ValueOf(&mapVal))
|
||||
```
|
||||
|
||||
### 5. Custom Convertor Pattern
|
||||
|
||||
Types can implement a `Parse` method for custom string conversion:
|
||||
|
||||
```go
|
||||
type Duration struct {
|
||||
Value int
|
||||
Unit string
|
||||
}
|
||||
|
||||
func (d *Duration) Parse(v string) error {
|
||||
// custom parsing logic
|
||||
}
|
||||
```
|
||||
|
||||
## Main Functions
|
||||
|
||||
### Deserialization
|
||||
|
||||
```go
|
||||
// YAML with validation
|
||||
func UnmarshalValidateYAML[T any](data []byte, target *T) gperr.Error
|
||||
|
||||
// YAML with interceptor
|
||||
func UnmarshalValidateYAMLIntercept[T any](
|
||||
data []byte,
|
||||
target *T,
|
||||
intercept func(m map[string]any) gperr.Error,
|
||||
) gperr.Error
|
||||
|
||||
// Direct map deserialization
|
||||
func MapUnmarshalValidate(src SerializedObject, dst any) gperr.Error
|
||||
|
||||
// To xsync.Map
|
||||
func UnmarshalValidateYAMLXSync[V any](data []byte) (*xsync.Map[string, V], gperr.Error)
|
||||
```
|
||||
|
||||
### Conversion
|
||||
|
||||
```go
|
||||
// Convert any value to target reflect.Value
|
||||
func Convert(src reflect.Value, dst reflect.Value, checkValidateTag bool) gperr.Error
|
||||
|
||||
// String to target type
|
||||
func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error)
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
```go
|
||||
// Validate using struct tags
|
||||
func ValidateWithFieldTags(s any) gperr.Error
|
||||
|
||||
// Register custom validator
|
||||
func MustRegisterValidation(tag string, fn validator.Func)
|
||||
|
||||
// Validate using CustomValidator interface
|
||||
func ValidateWithCustomValidator(v reflect.Value) gperr.Error
|
||||
```
|
||||
|
||||
### Default Values
|
||||
|
||||
```go
|
||||
// Register factory for default values
|
||||
func RegisterDefaultValueFactory[T any](factory func() *T)
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
Host string `json:"host" validate:"required,hostname_port"`
|
||||
Port int `json:"port" validate:"required,min=1,max=65535"`
|
||||
MaxConns int `json:"max_conns"`
|
||||
TLSEnabled bool `json:"tls_enabled"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
yamlData := []byte(`
|
||||
host: localhost
|
||||
port: 8080
|
||||
max_conns: 100
|
||||
tls_enabled: true
|
||||
`)
|
||||
|
||||
var config ServerConfig
|
||||
if err := serialization.UnmarshalValidateYAML(yamlData, &config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// config is now populated and validated
|
||||
}
|
||||
```
|
||||
|
||||
## Deserialization Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Caller
|
||||
participant U as UnmarshalValidateYAML
|
||||
participant E as Env Substitution
|
||||
participant Y as YAML Parser
|
||||
participant M as MapUnmarshalValidate
|
||||
participant T as Type Info Cache
|
||||
participant CV as Convert
|
||||
participant V as Validator
|
||||
|
||||
C->>U: YAML bytes + target struct
|
||||
U->>E: Substitute ${ENV} vars
|
||||
E-->>U: Substituted bytes
|
||||
U->>Y: Parse YAML
|
||||
Y-->>U: map[string]any
|
||||
U->>M: Map + target
|
||||
M->>T: Get type info
|
||||
loop For each field in map
|
||||
M->>T: Lookup field by name (case-insensitive)
|
||||
T-->>M: Field reflect.Value
|
||||
M->>CV: Convert value to field type
|
||||
CV-->>M: Converted value or error
|
||||
end
|
||||
M->>V: Validate struct tags
|
||||
V-->>M: Validation errors
|
||||
M-->>U: Combined errors
|
||||
U-->>C: Result
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Errors use `gperr` (goutils error package) with structured error subjects:
|
||||
|
||||
```go
|
||||
// Unknown field
|
||||
ErrUnknownField.Subject("field_name").With(gperr.DoYouMeanField("field_name", ["fieldName"]))
|
||||
|
||||
// Validation error
|
||||
ErrValidationError.Subject("Namespace").Withf("required")
|
||||
|
||||
// Unsupported conversion
|
||||
ErrUnsupportedConversion.Subjectf("string to int")
|
||||
```
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
1. **Type Info Caching**: Uses `xsync.Map` to cache field metadata per type
|
||||
2. **Hash-based Lookup**: FNV-1a hash for O(1) field matching
|
||||
3. **Lazy Pointer Init**: Pointers initialized only when first set
|
||||
4. **Presized Collections**: Initial capacity hints for maps/slices
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
go test ./internal/serialization/... -v
|
||||
```
|
||||
|
||||
Test categories:
|
||||
|
||||
- Basic deserialization
|
||||
- Anonymous struct handling
|
||||
- Pointer primitives
|
||||
- String conversions
|
||||
- Environment substitution
|
||||
- Custom validators
|
||||
Reference in New Issue
Block a user