detached 27b394b

feat: Implement ACME certificate management

- Refactored token initialization to remove unnecessary logging.
- Updated database creation function to support transactions.
- Added a comprehensive ACME certificate management system, including user registration, certificate obtaining, and renewal.
- Enhanced database schema to accommodate new fields for ACME configuration in the Domain struct.
- Introduced a new file for ACME-related operations and integrated it with existing proxy logic.
- Implemented database update logic to ensure schema consistency with struct definitions.
- Added detailed logging for better traceability of ACME operations and database interactions.
commit
27b394babd6739e740c8929cbaecffdaa8c95a0f
author
bamen · · 2025-03-29 08:05 UTC
parent
b4fbd78
signature
unsigned
26 files · +3530 -3073
M go.mod +164 -4
@@ -13,6 +13,7 @@ require (
1313 github.com/docker/docker v28.0.1+incompatible
1414 github.com/docker/go-connections v0.5.0
1515 github.com/fatih/color v1.18.0
16+ github.com/go-acme/lego/v4 v4.22.2
1617 github.com/gorilla/sessions v1.4.0
1718 github.com/joho/godotenv v1.5.1
1819 github.com/labstack/echo-contrib v0.17.2
@@ -25,21 +26,180 @@ require (
2526 )
2627
2728 require (
29+ cloud.google.com/go/auth v0.13.0 // indirect
30+ cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
31+ cloud.google.com/go/compute/metadata v0.6.0 // indirect
32+ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
33+ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
34+ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect
35+ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect
36+ github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
37+ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
38+ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
39+ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
40+ github.com/Azure/go-autorest v14.2.0+incompatible // indirect
41+ github.com/Azure/go-autorest/autorest v0.11.29 // indirect
42+ github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
43+ github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect
44+ github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
45+ github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
46+ github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
47+ github.com/Azure/go-autorest/logger v0.2.1 // indirect
48+ github.com/Azure/go-autorest/tracing v0.6.0 // indirect
49+ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
50+ github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
2851 github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
52+ github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
53+ github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 // indirect
2954 github.com/andybalholm/brotli v1.1.0 // indirect
55+ github.com/aws/aws-sdk-go-v2 v1.32.7 // indirect
56+ github.com/aws/aws-sdk-go-v2/config v1.28.7 // indirect
57+ github.com/aws/aws-sdk-go-v2/credentials v1.17.48 // indirect
58+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect
59+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect
60+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect
61+ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
62+ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
63+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect
64+ github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 // indirect
65+ github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 // indirect
66+ github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect
67+ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect
68+ github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect
69+ github.com/aws/smithy-go v1.22.1 // indirect
70+ github.com/benbjohnson/clock v1.3.0 // indirect
71+ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
3072 github.com/cenkalti/backoff/v4 v4.3.0 // indirect
3173 github.com/cespare/xxhash/v2 v2.3.0 // indirect
74+ github.com/civo/civogo v0.3.11 // indirect
3275 github.com/cli/browser v1.3.0 // indirect
33- github.com/fsnotify/fsnotify v1.7.0 // indirect
76+ github.com/cloudflare/cloudflare-go v0.112.0 // indirect
77+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
78+ github.com/dimchansky/utfbom v1.1.1 // indirect
79+ github.com/dnsimple/dnsimple-go v1.7.0 // indirect
80+ github.com/exoscale/egoscale/v3 v3.1.7 // indirect
81+ github.com/fatih/structs v1.1.0 // indirect
82+ github.com/fsnotify/fsnotify v1.8.0 // indirect
83+ github.com/gabriel-vasile/mimetype v1.4.2 // indirect
84+ github.com/ghodss/yaml v1.0.0 // indirect
85+ github.com/go-errors/errors v1.0.1 // indirect
86+ github.com/go-jose/go-jose/v4 v4.0.4 // indirect
87+ github.com/go-playground/locales v0.14.1 // indirect
88+ github.com/go-playground/universal-translator v0.18.1 // indirect
89+ github.com/go-playground/validator/v10 v10.16.0 // indirect
90+ github.com/go-resty/resty/v2 v2.16.2 // indirect
91+ github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
92+ github.com/goccy/go-json v0.10.4 // indirect
93+ github.com/gofrs/flock v0.12.1 // indirect
94+ github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
95+ github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
96+ github.com/google/go-querystring v1.1.0 // indirect
97+ github.com/google/s2a-go v0.1.8 // indirect
98+ github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
99+ github.com/googleapis/gax-go/v2 v2.14.0 // indirect
100+ github.com/gophercloud/gophercloud v1.14.1 // indirect
101+ github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect
102+ github.com/hashicorp/errwrap v1.0.0 // indirect
103+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
104+ github.com/hashicorp/go-multierror v1.1.1 // indirect
105+ github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
106+ github.com/hashicorp/go-uuid v1.0.3 // indirect
107+ github.com/hashicorp/hcl v1.0.0 // indirect
108+ github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // indirect
109+ github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
110+ github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
111+ github.com/jmespath/go-jmespath v0.4.0 // indirect
112+ github.com/json-iterator/go v1.1.12 // indirect
113+ github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
34114 github.com/klauspost/compress v1.18.0 // indirect
35115 github.com/klauspost/cpuid/v2 v2.2.10 // indirect
116+ github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
117+ github.com/kylelemons/godebug v1.1.0 // indirect
118+ github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
119+ github.com/labbsr0x/goh v1.0.1 // indirect
120+ github.com/leodido/go-urn v1.2.4 // indirect
121+ github.com/linode/linodego v1.44.0 // indirect
122+ github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
123+ github.com/liquidweb/liquidweb-go v1.6.4 // indirect
124+ github.com/magiconair/properties v1.8.7 // indirect
125+ github.com/miekg/dns v1.1.62 // indirect
126+ github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
127+ github.com/mitchellh/go-homedir v1.1.0 // indirect
128+ github.com/mitchellh/mapstructure v1.5.0 // indirect
129+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
130+ github.com/modern-go/reflect2 v1.0.2 // indirect
131+ github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
36132 github.com/natefinch/atomic v1.0.1 // indirect
133+ github.com/nrdcg/auroradns v1.1.0 // indirect
134+ github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
135+ github.com/nrdcg/desec v0.10.0 // indirect
136+ github.com/nrdcg/dnspod-go v0.4.0 // indirect
137+ github.com/nrdcg/freemyip v0.3.0 // indirect
138+ github.com/nrdcg/goacmedns v0.2.0 // indirect
139+ github.com/nrdcg/goinwx v0.10.0 // indirect
140+ github.com/nrdcg/mailinabox v0.2.0 // indirect
141+ github.com/nrdcg/namesilo v0.2.1 // indirect
142+ github.com/nrdcg/nodion v0.1.0 // indirect
143+ github.com/nrdcg/porkbun v0.4.0 // indirect
144+ github.com/nzdjb/go-metaname v1.0.0 // indirect
145+ github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
146+ github.com/oracle/oci-go-sdk/v65 v65.81.1 // indirect
147+ github.com/ovh/go-ovh v1.6.0 // indirect
148+ github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
149+ github.com/pelletier/go-toml/v2 v2.1.0 // indirect
150+ github.com/peterhellberg/link v1.2.0 // indirect
151+ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
152+ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
153+ github.com/pquerna/otp v1.4.0 // indirect
154+ github.com/regfish/regfish-dnsapi-go v0.1.1 // indirect
155+ github.com/sacloud/api-client-go v0.2.10 // indirect
156+ github.com/sacloud/go-http v0.1.8 // indirect
157+ github.com/sacloud/iaas-api-go v1.14.0 // indirect
158+ github.com/sacloud/packages-go v0.0.10 // indirect
159+ github.com/sagikazarmark/locafero v0.4.0 // indirect
160+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
161+ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect
162+ github.com/selectel/domains-go v1.1.0 // indirect
163+ github.com/selectel/go-selvpcclient/v3 v3.2.1 // indirect
164+ github.com/shopspring/decimal v1.3.1 // indirect
165+ github.com/sirupsen/logrus v1.9.3 // indirect
166+ github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
167+ github.com/softlayer/softlayer-go v1.1.7 // indirect
168+ github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
169+ github.com/sony/gobreaker v0.5.0 // indirect
170+ github.com/sourcegraph/conc v0.3.0 // indirect
171+ github.com/spf13/afero v1.11.0 // indirect
172+ github.com/spf13/cast v1.6.0 // indirect
173+ github.com/spf13/viper v1.18.2 // indirect
174+ github.com/stretchr/testify v1.10.0 // indirect
175+ github.com/subosito/gotenv v1.6.0 // indirect
176+ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 // indirect
177+ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 // indirect
178+ github.com/tjfoc/gmsm v1.4.1 // indirect
179+ github.com/transip/gotransip/v6 v6.26.0 // indirect
180+ github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect
181+ github.com/vinyldns/go-vinyldns v0.9.16 // indirect
182+ github.com/volcengine/volc-sdk-golang v1.0.189 // indirect
183+ github.com/vultr/govultr/v3 v3.9.1 // indirect
184+ github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
185+ github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect
37186 github.com/zeebo/xxh3 v1.0.2 // indirect
38187 go.mongodb.org/mongo-driver v1.17.3 // indirect
39188 go.opentelemetry.io/auto/sdk v1.1.0 // indirect
189+ go.uber.org/atomic v1.11.0 // indirect
190+ go.uber.org/multierr v1.9.0 // indirect
191+ go.uber.org/ratelimit v0.3.0 // indirect
40192 golang.org/x/mod v0.24.0 // indirect
193+ golang.org/x/oauth2 v0.24.0 // indirect
41194 golang.org/x/tools v0.31.0 // indirect
42- google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e // indirect
195+ google.golang.org/api v0.214.0 // indirect
196+ google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect
197+ google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
198+ google.golang.org/grpc v1.69.2 // indirect
199+ google.golang.org/protobuf v1.36.1 // indirect
200+ gopkg.in/ini.v1 v1.67.0 // indirect
201+ gopkg.in/ns1/ns1-go.v2 v2.13.0 // indirect
202+ gopkg.in/yaml.v2 v2.4.0 // indirect
43203 )
44204
45205 require (
@@ -78,7 +238,7 @@ require (
78238 golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // direct
79239 golang.org/x/sync v0.12.0 // indirect
80240 golang.org/x/term v0.30.0 // indirect
81- google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
241+ google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect
82242 modernc.org/libc v1.61.13 // indirect
83243 modernc.org/mathutil v1.7.1 // indirect
84244 modernc.org/memory v1.8.2 // indirect
@@ -104,7 +264,7 @@ require (
104264 github.com/pkg/errors v0.9.1 // indirect
105265 github.com/valyala/bytebufferpool v1.0.0 // indirect
106266 github.com/valyala/fasttemplate v1.2.2 // indirect
107- golang.org/x/crypto v0.36.0
267+ golang.org/x/crypto v0.36.0 // indirect
108268 golang.org/x/net v0.37.0 // indirect
109269 golang.org/x/sys v0.31.0 // indirect
110270 golang.org/x/text v0.23.0 // indirect
M go.sum +1236 -8
@@ -1,29 +1,203 @@
1+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
9+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
10+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
11+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
12+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
13+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
14+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
15+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
16+cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs=
17+cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q=
18+cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU=
19+cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
20+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
21+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
22+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
23+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
24+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
25+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
26+cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
27+cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
28+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
29+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
30+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
31+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
32+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
33+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
34+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
35+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
36+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
37+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
38+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
39+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
40+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
41+github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE=
42+github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo=
143 github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
244 github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
45+github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
46+github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
47+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
48+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
49+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
50+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
51+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
52+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
53+github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
54+github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
55+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
56+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
57+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
58+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0/go.mod h1:AW8VEadnhw9xox+VaVd9sP7NjzOAnaZBLRH6Tq3cJ38=
59+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 h1:yzrctSl9GMIQ5lHu7jc8olOsGjWDCsBpJhWqfGa/YIM=
60+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0/go.mod h1:GE4m0rnnfwLGX0Y9A9A25Zx5N/90jneT5ABevqzhuFQ=
61+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 h1:zLzoX5+W2l95UJoVwiyNS4dX8vHyQ6x2xRLoBBL9wMk=
62+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk=
63+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
64+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
365 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
466 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
67+github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
68+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
69+github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
70+github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw=
71+github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs=
72+github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
73+github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc=
74+github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=
75+github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 h1:Ov8avRZi2vmrE2JcXw+tu5K/yB41r7xK9GZDiBF7NdM=
76+github.com/Azure/go-autorest/autorest/azure/auth v0.5.13/go.mod h1:5BAVfWLWXihP47vYrPuBKKf4cS0bXI+KM9Qx6ETDJYo=
77+github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc=
78+github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0=
79+github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
80+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
81+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
82+github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
83+github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
84+github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
85+github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
86+github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
87+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
88+github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
89+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
90+github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
91+github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
92+github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
93+github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
94+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
95+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
96+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
97+github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
98+github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
99+github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
100+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
5101 github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
6102 github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
7103 github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
8104 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
9105 github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
10106 github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
107+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
108+github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
109+github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
11110 github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
12111 github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
112+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
113+github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw=
114+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
115+github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0=
116+github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
13117 github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo=
14118 github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ=
15119 github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU=
16120 github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk=
121+github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
122+github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
123+github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY=
124+github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY=
125+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
126+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
127+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
128+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
129+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
130+github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 h1:HvFZUzEbNvfe8F2Mg0wBGv90bPhWDxgVtDHR5zoBOU0=
131+github.com/aliyun/alibaba-cloud-sdk-go v1.63.72/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
17132 github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
18133 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
19134 github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
20135 github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
136+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
137+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
138+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
139+github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
140+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
141+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
142+github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
143+github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
144+github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
145+github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw=
146+github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
147+github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE=
148+github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M=
149+github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ=
150+github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs=
151+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8=
152+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M=
153+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI=
154+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA=
155+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM=
156+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8=
157+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
158+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
159+github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o=
160+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
161+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
162+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M=
163+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0=
164+github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 h1:+lmJoqxuUoPlSfGk5JYQQivd9YFjUvRZR6RPY+Wcx48=
165+github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8/go.mod h1:Gg8/myP4+rgRi4+j9gQdbOEnMtwMAUUIeXo+nKCFVj8=
166+github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 h1:0jMtawybbfpFEIMy4wvfyW2Z4YLr7mnuzT0fhR67Nrc=
167+github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4/go.mod h1:xlMODgumb0Pp8bzfpojqelDrf8SL9rb5ovwmwKJl+oU=
168+github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g=
169+github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg=
170+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E=
171+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k=
172+github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY=
173+github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc=
174+github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
175+github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
176+github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
21177 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
22178 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
23179 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
24180 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
181+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
182+github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
183+github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
184+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
185+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
186+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
187+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
188+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
189+github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
190+github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
191+github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
192+github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
193+github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
194+github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
25195 github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
26196 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
197+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
198+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
199+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
200+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
27201 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
28202 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
29203 github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
@@ -38,35 +212,120 @@ github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2ll
38212 github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
39213 github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
40214 github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
215+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
216+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
217+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
218+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
219+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
220+github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM=
221+github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g=
222+github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
41223 github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
42224 github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
225+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
226+github.com/cloudflare/cloudflare-go v0.112.0 h1:caFwqXdGJCl3rjVMgbPEn8iCYAg9JsRYV3dIVQE5d7g=
227+github.com/cloudflare/cloudflare-go v0.112.0/go.mod h1:QB55kuJ5ZTeLNFcLJePfMuBilhu/LDKpLBmKFQIoSZ0=
228+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
229+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
230+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
43231 github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
44232 github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
233+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
234+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
235+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
236+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
237+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
238+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
239+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
240+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
45241 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
242+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
46243 github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
47244 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
48245 github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
49246 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
50-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
51247 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
248+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
249+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
250+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
251+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
252+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
253+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
254+github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
255+github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
52256 github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
53257 github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
258+github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY=
259+github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8=
54260 github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
55261 github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
56262 github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
57263 github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
58264 github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
59265 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
266+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
60267 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
61268 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
269+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
270+github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
271+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
272+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
273+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
274+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
275+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
276+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
277+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
278+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
279+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
280+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
62281 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
63282 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
283+github.com/exoscale/egoscale/v3 v3.1.7 h1:Q6p9tOVY0IiOW0fUpaPQWY7ggGEuSPZLAGxFgDd2sCE=
284+github.com/exoscale/egoscale/v3 v3.1.7/go.mod h1:GHKucK/J26v8PGWztGdhxWNMjrjG9PbelxKCJ4YI11Q=
285+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
286+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
287+github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
64288 github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
65289 github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
290+github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
291+github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
66292 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
67293 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
68-github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
69-github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
294+github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
295+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
296+github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo=
297+github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
298+github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
299+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
300+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
301+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
302+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
303+github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
304+github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
305+github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
306+github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
307+github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
308+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
309+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
310+github.com/go-acme/lego/v4 v4.22.2 h1:ck+HllWrV/rZGeYohsKQ5iKNnU/WAZxwOdiu6cxky+0=
311+github.com/go-acme/lego/v4 v4.22.2/go.mod h1:E2FndyI3Ekv0usNJt46mFb9LVpV/XBYT+4E3tz02Tzo=
312+github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
313+github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
314+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
315+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
316+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
317+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
318+github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
319+github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
320+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
321+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
322+github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs=
323+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
324+github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
325+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
326+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
327+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
328+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
70329 github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
71330 github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
72331 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -74,72 +333,379 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
74333 github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
75334 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
76335 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
336+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
337+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
338+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
339+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
340+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
341+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
342+github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
343+github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
344+github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
345+github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
346+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
347+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
348+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
349+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
350+github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
351+github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
352+github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
353+github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
354+github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
355+github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
356+github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
357+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
358+github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
359+github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
360+github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
361+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
362+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
77363 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
78364 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
365+github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
366+github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
367+github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
368+github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
369+github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
370+github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
371+github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
372+github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
373+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
374+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
375+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
376+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
377+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
378+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
379+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
380+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
381+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
382+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
383+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
384+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
385+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
386+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
387+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
388+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
389+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
390+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
391+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
392+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
393+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
394+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
395+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
396+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
397+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
398+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
399+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
400+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
401+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
402+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
403+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
404+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
405+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
406+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
407+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
408+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
409+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
410+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
411+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
412+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
413+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
414+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
415+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
416+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
417+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
418+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
419+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
420+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
421+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
422+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
423+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
79424 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
80425 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
81426 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
427+github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
428+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
429+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
430+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
431+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
82432 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
83433 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
434+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
435+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
436+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
437+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
438+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
439+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
440+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
441+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
442+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
443+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
84444 github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
85445 github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
446+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
447+github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
448+github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
449+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
450+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
451+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
86452 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
87453 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
454+github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
455+github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
456+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
457+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
458+github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o=
459+github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk=
460+github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
461+github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw=
462+github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
463+github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 h1:sH7xkTfYzxIEgzq1tDHIMKRh1vThOEOGNsettdEeLbE=
464+github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56/go.mod h1:VSalo4adEk+3sNkmVJLnhHoOyOYYS8sTWLG4mv5BKto=
465+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
466+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
88467 github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
89468 github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
90469 github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
91470 github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
471+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
472+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
473+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
474+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
92475 github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
93476 github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
477+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
94478 github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
95479 github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
480+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
481+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
482+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
483+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
484+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
485+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
96486 github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
97487 github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
488+github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
489+github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
490+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
491+github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
492+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
493+github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
494+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
495+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
496+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
497+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
498+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
499+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
500+github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
501+github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
502+github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
503+github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
504+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
505+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
506+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
507+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
508+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
509+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
510+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
511+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
512+github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
513+github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
514+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
515+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
516+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
517+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
518+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
519+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
520+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
521+github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
522+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
523+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
524+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
525+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
526+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
527+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
528+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
529+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
530+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
531+github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
532+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
533+github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
534+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
535+github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
98536 github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
99537 github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
538+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
539+github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 h1:kQ2Agpfy7Ze1ajn9xCQG9G6T7XIbqv+FBDS/U98W9Mk=
540+github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
541+github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
542+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
543+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
544+github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E=
545+github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
546+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
100547 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
101548 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
549+github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
550+github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU=
551+github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
552+github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
553+github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
554+github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
555+github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
556+github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
557+github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
558+github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
559+github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
560+github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
561+github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
562+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
563+github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
564+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
102565 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
103566 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
567+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
568+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
569+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
570+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
571+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
572+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
573+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
574+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
575+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
576+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
577+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
578+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
579+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
580+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
581+github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
582+github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
583+github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
104584 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
105585 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
586+github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
587+github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
588+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
106589 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
107590 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
591+github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
592+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
593+github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
108594 github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
109595 github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
110596 github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
111597 github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
598+github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
599+github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
600+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
601+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
602+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
603+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
604+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
605+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
606+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
112607 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
113608 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
609+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
610+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
114611 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
115612 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
613+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
614+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
615+github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPCK0jE6YNBAevnk=
616+github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
617+github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk=
618+github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
116619 github.com/labstack/echo-contrib v0.17.2 h1:K1zivqmtcC70X9VdBFdLomjPDEVHlrcAObqmuFj1c6w=
117620 github.com/labstack/echo-contrib v0.17.2/go.mod h1:NeDh3PX7j/u+jR4iuDt1zHmWZSCz9c/p9mxXcDpyS8E=
118621 github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
119622 github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
120623 github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
121624 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
625+github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
626+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
627+github.com/linode/linodego v1.44.0 h1:JZLLWzCAx3CmHSV9NmCoXisuqKtrmPhfY9MrgvaHMUY=
628+github.com/linode/linodego v1.44.0/go.mod h1:umdoNOmtbqAdGQbmQnPFZ2YS4US+/mU/1bA7MjoKAvg=
629+github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
630+github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM=
631+github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ=
632+github.com/liquidweb/liquidweb-go v1.6.4 h1:6S0m3hHSpiLqGD7AFSb7lH/W/qr1wx+tKil9fgIbjMc=
633+github.com/liquidweb/liquidweb-go v1.6.4/go.mod h1:B934JPIIcdA+uTq2Nz5PgOtG6CuCaEvQKe/Ge/5GgZ4=
122634 github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
123635 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
636+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
637+github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
638+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
639+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
640+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
124641 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
642+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
643+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
644+github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
645+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
125646 github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
126647 github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
648+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
127649 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
650+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
651+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
652+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
653+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
128654 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
129655 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
130656 github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
131657 github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
658+github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
659+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
132660 github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
133661 github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
662+github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
663+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
664+github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
665+github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
134666 github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
135667 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
136668 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
137669 github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
138670 github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
671+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
672+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
673+github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
674+github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
675+github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
676+github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
677+github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34=
678+github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M=
679+github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
680+github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
681+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
682+github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
683+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
684+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
685+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
686+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
687+github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
688+github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
689+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
690+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
691+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
692+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
693+github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
694+github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
695+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
696+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
139697 github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
140698 github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
141699 github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
142700 github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
701+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
702+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
703+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
704+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
705+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
706+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
707+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
708+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
143709 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
144710 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
145711 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
@@ -148,51 +714,318 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
148714 github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
149715 github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
150716 github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
717+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
718+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
719+github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
720+github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
151721 github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
152722 github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
723+github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
724+github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY=
725+github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g=
726+github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
727+github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
728+github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
729+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
730+github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
153731 github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
154732 github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
733+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
734+github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo=
735+github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk=
736+github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qugYbmpbmW8hn85Ig6buSMBRlz3KI=
737+github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8=
738+github.com/nrdcg/desec v0.10.0 h1:qrEDiqnsvNU9QE7lXIXi/tIHAfyaFXKxF2/8/52O8uM=
739+github.com/nrdcg/desec v0.10.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs=
740+github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U=
741+github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
742+github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc=
743+github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM=
744+github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0=
745+github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
746+github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM=
747+github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4=
748+github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk=
749+github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc=
750+github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
751+github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
752+github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw=
753+github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms=
754+github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
755+github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
756+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
757+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
758+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
759+github.com/nzdjb/go-metaname v1.0.0 h1:sNASlZC1RM3nSudtBTE1a3ZVTDyTpjqI5WXRPrdZ9Hg=
760+github.com/nzdjb/go-metaname v1.0.0/go.mod h1:0GR0LshZax1Lz4VrOrfNSE4dGvTp7HGjiemdczXT2H4=
761+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
762+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
763+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
764+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
765+github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
766+github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
767+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
768+github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
769+github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
770+github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
771+github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
772+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
773+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
774+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
775+github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
776+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
777+github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
778+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
779+github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
780+github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
781+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
155782 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
156783 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
157784 github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
158785 github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
786+github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
787+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
788+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
789+github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
790+github.com/oracle/oci-go-sdk/v65 v65.81.1 h1:JYc47bk8n/MUchA2KHu1ggsCQzlJZQLJ+tTKfOho00E=
791+github.com/oracle/oci-go-sdk/v65 v65.81.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
792+github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI=
793+github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
794+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
795+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
796+github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
797+github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
798+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
799+github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
800+github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
801+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
802+github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM=
803+github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
804+github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc=
805+github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
806+github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
807+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
808+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
809+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
810+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
159811 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
160812 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
161-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
813+github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
814+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
815+github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
162816 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
817+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
818+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
819+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
820+github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
821+github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
822+github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
823+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
824+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
825+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
826+github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
827+github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
828+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
829+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
830+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
831+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
832+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
833+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
834+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
835+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
836+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
837+github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
838+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
839+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
840+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
841+github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
842+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
843+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
844+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
845+github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
846+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
847+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
848+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
849+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
850+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
851+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
852+github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
853+github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
854+github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
855+github.com/regfish/regfish-dnsapi-go v0.1.1 h1:TJFtbePHkd47q5GZwYl1h3DIYXmoxdLjW/SBsPtB5IE=
856+github.com/regfish/regfish-dnsapi-go v0.1.1/go.mod h1:ubIgXSfqarSnl3XHSn8hIFwFF3h0yrq0ZiWD93Y2VjY=
163857 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
164858 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
165859 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
166860 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
167861 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
862+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
863+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
864+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
168865 github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
169866 github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
867+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
170868 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
869+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
870+github.com/sacloud/api-client-go v0.2.10 h1:+rv3jDohD+pkdYwOTBiB+jZsM0xK3AxadXRzhp3q66c=
871+github.com/sacloud/api-client-go v0.2.10/go.mod h1:Jj3CTy2+O4bcMedVDXlbHuqqche85HEPuVXoQFhLaRc=
872+github.com/sacloud/go-http v0.1.8 h1:ynreWA/vnM8G2ksbMlmefBHsXURKPz49qlPRqQ9IQdw=
873+github.com/sacloud/go-http v0.1.8/go.mod h1:7TL7TN1fnPKHsMifIqURDkGujnKViCgEz5Ei/LQdFK8=
874+github.com/sacloud/iaas-api-go v1.14.0 h1:xjkFWqdo4ilTrKPNNYBNWR/CZ/kVRsJrdAHAad6J/AQ=
875+github.com/sacloud/iaas-api-go v1.14.0/go.mod h1:C8os2Mnj0TOmMdSllwhaDWKMVG2ysFnpe69kyA4M3V0=
876+github.com/sacloud/packages-go v0.0.10 h1:UiQGjy8LretewkRhsuna1TBM9Vz/l9FoYpQx+D+AOck=
877+github.com/sacloud/packages-go v0.0.10/go.mod h1:f8QITBh9z4IZc4yE9j21Q8b0sXEMwRlRmhhjWeDVTYs=
878+github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
879+github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
880+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
881+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
882+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 h1:yoKAVkEVwAqbGbR8n87rHQ1dulL25rKloGadb3vm770=
883+github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30/go.mod h1:sH0u6fq6x4R5M7WxkoQFY/o7UaiItec0o1LinLCJNq8=
884+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
885+github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo=
886+github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA=
887+github.com/selectel/go-selvpcclient/v3 v3.2.1 h1:ny6WIAMiHzKxOgOEnwcWE79wIQij1AHHylzPA41MXCw=
888+github.com/selectel/go-selvpcclient/v3 v3.2.1/go.mod h1:3EfSf8aEWyhspOGbvZ6mvnFg7JN5uckxNyBFPGWsXNQ=
889+github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
890+github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
891+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
892+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
893+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
894+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
895+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
171896 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
172897 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
898+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
899+github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
900+github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
901+github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q=
902+github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
903+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
904+github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA=
905+github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
906+github.com/softlayer/softlayer-go v1.1.7 h1:SgTL+pQZt1h+5QkAhVmHORM/7N9c1X0sljJhuOIHxWE=
907+github.com/softlayer/softlayer-go v1.1.7/go.mod h1:WeJrBLoTJcaT8nO1azeyHyNpo/fDLtbpbvh+pzts+Qw=
908+github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ=
909+github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums=
910+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
911+github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
912+github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
913+github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
914+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
915+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
916+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
917+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
918+github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
919+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
920+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
921+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
922+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
923+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
924+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
925+github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
173926 github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
174927 github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
928+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
929+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
930+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
931+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
175932 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
176933 github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
934+github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
935+github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
936+github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
937+github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
177938 github.com/starskey-io/starskey v0.1.8 h1:zTufRQle6SJQY0NwMcFWuAcJN3c4QId1OmFCVkvuYh0=
178939 github.com/starskey-io/starskey v0.1.8/go.mod h1:qly4ec2C/4Y45jhpL+q4m+Uxzg3mjj0t7RjpJslB3ao=
940+github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
941+github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
942+github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
179943 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
944+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
945+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
946+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
947+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
948+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
949+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
950+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
951+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
952+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
180953 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
954+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
955+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
956+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
957+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
958+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
181959 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
182960 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
961+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
962+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
963+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
964+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 h1:krcqtAmexnHHBm/4ge4tr2b1cn/a7JGBESVGoZYXQAE=
965+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
966+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 h1:aEFtLD1ceyeljQXB1S2BjN0zjTkf0X3XmpuxFIiC29w=
967+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065/go.mod h1:HWvwy09hFSMXrj9SMvVRWV4U7rZO3l+WuogyNuxiT3M=
968+github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
969+github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
970+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
971+github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550=
972+github.com/transip/gotransip/v6 v6.26.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s=
973+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
974+github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
975+github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
976+github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
977+github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
978+github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI=
979+github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss=
980+github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
183981 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
184982 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
185983 github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
186984 github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
985+github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ=
986+github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q=
987+github.com/volcengine/volc-sdk-golang v1.0.189 h1:VMDTHWYXakXJtZqPYn0As/h4eB0c4imvyru6mIp+o60=
988+github.com/volcengine/volc-sdk-golang v1.0.189/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE=
989+github.com/vultr/govultr/v3 v3.9.1 h1:uxSIb8Miel7tqTs3ee+z3t+JelZikwqBBsZzCOPBy/8=
990+github.com/vultr/govultr/v3 v3.9.1/go.mod h1:Rd8ebpXm7jxH3MDmhnEs+zrlYW212ouhx+HeUMfHm2o=
991+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
992+github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
993+github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
994+github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
995+github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
996+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
997+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
998+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
999+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
1000+github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c h1:Rnr+lDYXVkP+3eT8/d68iq4G/UeIhyCQk+HKa8toTvg=
1001+github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo=
1002+github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 h1:qmpz0Kvr9GAng8LAhRcKIpY71CEAcL3EBkftVlsP5Cw=
1003+github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134/go.mod h1:KgZCJrxdhdw/sKhTQ/M3S9WOLri2PCnBlc4C3s+PfKY=
1004+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
1005+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
1871006 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
1007+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
1881008 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
1009+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
1891010 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
1901011 github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
1911012 github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
1921013 github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
1931014 github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
1015+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
1016+go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
1017+go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
1018+go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
1019+go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
1020+go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
1941021 go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
1951022 go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
1023+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
1024+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
1025+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
1026+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
1027+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
1028+go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
1961029 go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
1971030 go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
1981031 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
@@ -211,22 +1044,93 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J
2111044 go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
2121045 go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
2131046 go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
1047+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
2141048 go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
2151049 go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
1050+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
1051+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
1052+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
1053+go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
1054+go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
1055+go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
1056+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
1057+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
1058+go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
1059+go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
1060+go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
1061+go.uber.org/ratelimit v0.3.0 h1:IdZd9wqvFXnvLvSEBo0KPcGfkoBGNkpTHlrE3Rcjkjw=
1062+go.uber.org/ratelimit v0.3.0/go.mod h1:So5LG7CV1zWpY1sHe+DXTJqQvOx+FFPFaAs2SnoyBaI=
1063+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
1064+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
1065+go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
1066+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
1067+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
2161068 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
1069+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
1070+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
1071+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
1072+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
2171073 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
1074+golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
2181075 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
1076+golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
1077+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
1078+golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
1079+golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
1080+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
1081+golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
1082+golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
2191083 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
1084+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
1085+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
1086+golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
1087+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
1088+golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
2201089 golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
1090+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
2211091 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
2221092 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
2231093 golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
2241094 golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
2251095 golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
1096+golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
1097+golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
1098+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
1099+golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
1100+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
1101+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
1102+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
1103+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
1104+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
1105+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
1106+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
1107+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
1108+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
2261109 golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
2271110 golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
1111+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
1112+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
1113+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
1114+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
1115+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
1116+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
1117+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
1118+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
1119+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
1120+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
1121+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
1122+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
1123+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
1124+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
1125+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
1126+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
1127+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
1128+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
1129+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
1130+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
2281131 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
2291132 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
1133+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
2301134 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
2311135 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
2321136 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -234,23 +1138,81 @@ golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
2341138 golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
2351139 golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
2361140 golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
1141+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1142+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1143+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1144+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1145+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1146+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1147+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1148+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1149+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1150+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
2371151 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
1152+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
1153+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
1154+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
1155+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
2381156 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1157+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1158+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1159+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1160+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1161+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1162+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1163+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
2391164 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1165+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1166+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
1167+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
1168+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
1169+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
1170+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
1171+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
1172+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
1173+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
1174+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
1175+golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
2401176 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
1177+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
2411178 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
1179+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
1180+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
1181+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
1182+golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
1183+golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
1184+golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
1185+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
1186+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
2421187 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
2431188 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
2441189 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
1190+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
2451191 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
2461192 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
2471193 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
2481194 golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
2491195 golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
2501196 golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
1197+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
1198+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
1199+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
1200+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
1201+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
1202+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
1203+golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
1204+golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
1205+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1206+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1207+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1208+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
2511209 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
2521210 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1211+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1212+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
2531213 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1214+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1215+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
2541216 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
2551217 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
2561218 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
@@ -259,19 +1221,83 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
2591221 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
2601222 golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
2611223 golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
1224+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1225+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1226+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1227+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1228+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1229+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1230+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1231+golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
2621232 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
2631233 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1234+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
2641235 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1236+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1237+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1238+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1239+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1240+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1241+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1242+golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1243+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1244+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1245+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1246+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1247+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1248+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1249+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1250+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1251+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1252+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1253+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1254+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1255+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1256+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1257+golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1258+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1259+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1260+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1261+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1262+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1263+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1264+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1265+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1266+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1267+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1268+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1269+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1270+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1271+golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1272+golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
2651273 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1274+golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
2661275 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1276+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1277+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1278+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1279+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1280+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1281+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1282+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1283+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2671284 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1285+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2681286 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1287+golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1288+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1289+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2691290 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1291+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2701292 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1293+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1294+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2711295 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2721296 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2731297 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1298+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2741299 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1300+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2751301 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2761302 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2771303 golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
@@ -282,30 +1308,94 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
2821308 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
2831309 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
2841310 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
1311+golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
2851312 golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
1313+golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
2861314 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
2871315 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
2881316 golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
2891317 golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
2901318 golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
1319+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
2911320 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1321+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1322+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
2921323 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
1324+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
1325+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
1326+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
2931327 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
1328+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
2941329 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
2951330 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
2961331 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
1332+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
2971333 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
2981334 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
2991335 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
3001336 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
3011337 golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
3021338 golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
1339+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
1340+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
1341+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
1342+golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
1343+golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
1344+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
3031345 golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
3041346 golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
1347+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1348+golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
3051349 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1350+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1351+golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1352+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
1353+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
1354+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
1355+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
1356+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
1357+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
1358+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
1359+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
1360+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
1361+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
1362+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
1363+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1364+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1365+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1366+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1367+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1368+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1369+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
3061370 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1371+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1372+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1373+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
1374+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
1375+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
1376+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
1377+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
1378+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
1379+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
1380+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
1381+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
1382+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
1383+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
1384+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
1385+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
1386+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
1387+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
1388+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
1389+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
3071390 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
1391+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
1392+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
1393+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
1394+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
3081395 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
1396+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
1397+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
1398+golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
3091399 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
3101400 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
3111401 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
@@ -316,22 +1406,155 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
3161406 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
3171407 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
3181408 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
319-google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
320-google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
321-google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e h1:Elxv5MwEkCI9f5SkoL6afed6NTdxaGoAo39eANBwHL8=
322-google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
1409+gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
1410+gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
1411+gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
1412+gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
1413+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
1414+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
1415+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
1416+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
1417+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
1418+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
1419+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
1420+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
1421+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
1422+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
1423+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
1424+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
1425+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
1426+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
1427+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
1428+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
1429+google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA=
1430+google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE=
1431+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
1432+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
1433+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
1434+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
1435+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
1436+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
1437+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
1438+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
1439+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
1440+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
1441+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
1442+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
1443+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
1444+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
1445+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
1446+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
1447+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
1448+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
1449+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
1450+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
1451+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
1452+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
1453+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
1454+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
1455+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
1456+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
1457+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
1458+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
1459+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
1460+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
1461+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
1462+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
1463+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
1464+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
1465+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
1466+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
1467+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
1468+google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
1469+google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU=
1470+google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4=
1471+google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ=
1472+google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88=
1473+google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY=
1474+google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
1475+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
1476+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
1477+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
1478+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
1479+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
1480+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
1481+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
1482+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
1483+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
1484+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
1485+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
1486+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
1487+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
1488+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
1489+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
1490+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
1491+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
3231492 google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
3241493 google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
1494+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
1495+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
1496+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
1497+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
1498+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
1499+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
1500+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
1501+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
1502+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
1503+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
1504+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
1505+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
1506+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
1507+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
3251508 google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
3261509 google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
1510+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
3271511 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1512+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1513+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1514+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3281515 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
3291516 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
1517+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
1518+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
1519+gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
1520+gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
1521+gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
1522+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
1523+gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
1524+gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
1525+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
1526+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
1527+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
1528+gopkg.in/ns1/ns1-go.v2 v2.13.0 h1:I5NNqI9Bi1SGK92TVkOvLTwux5LNrix/99H2datVh48=
1529+gopkg.in/ns1/ns1-go.v2 v2.13.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc=
1530+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
1531+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
1532+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
1533+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
1534+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
1535+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1536+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1537+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1538+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1539+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1540+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1541+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1542+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
1543+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
3301544 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1545+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1546+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
3311547 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
3321548 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
3331549 gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
3341550 gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
1551+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
1552+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
1553+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
1554+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
1555+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
1556+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
1557+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
3351558 modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
3361559 modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
3371560 modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
@@ -356,3 +1579,8 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
3561579 modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
3571580 modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
3581581 modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
1582+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
1583+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
1584+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
1585+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
1586+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
M internal/cli/cmd/proxy.go +6 -7
@@ -134,11 +134,8 @@ func NewProxyCommand(a *server.App) *cobra.Command {
134134 }
135135
136136 // List routes from the database
137- rows, err := a.DB.Query(`
138- SELECT d.name, pr.container_id, pr.container_ip, pr.container_port, pr.active
139- FROM proxy_route pr
140- JOIN domain d ON pr.domain_id = d.id
141- `)
137+ // Use the query from the instantiated proxy queries
138+ rows, err := a.DB.Query(p.Queries.GetAllRoutes)
142139 if err != nil {
143140 log.Fatal("Failed to query routes:", err)
144141 }
@@ -150,9 +147,11 @@ func NewProxyCommand(a *server.App) *cobra.Command {
150147
151148 var count int
152149 for rows.Next() {
153- var domain, containerID, containerIP, containerPort string
150+ // Adjust variables to match the columns selected by GetAllRoutes
151+ var id, domain, containerID, containerIP, containerPort, protocol, path string
154152 var active bool
155- if err := rows.Scan(&domain, &containerID, &containerIP, &containerPort, &active); err != nil {
153+ // Adjust scan targets
154+ if err := rows.Scan(&id, &domain, &containerID, &containerIP, &containerPort, &protocol, &path, &active); err != nil {
156155 log.Fatal("Failed to scan row:", err)
157156 }
158157
M internal/common/init_config.go +18 -14
@@ -74,20 +74,24 @@ type ContainerEngineConfig struct {
7474 }
7575
7676 type ReverseProxyConfig struct {
77- Enabled bool `yaml:"enabled"` // Whether the reverse proxy is enabled
78- Port string `yaml:"port"` // Port for the reverse proxy to listen on
79- HttpPort string `yaml:"httpPort"` // HTTP port (usually 80) for redirecting to HTTPS
80- CertDir string `yaml:"certDir"` // Directory to store Let's Encrypt certificates
81- AutoRenew bool `yaml:"autoRenew"` // Whether to automatically renew certificates
82- RenewBefore int `yaml:"renewBefore"` // Days before expiry to renew certificates
83- LetsEncryptMode string `yaml:"letsEncryptMode"` // "staging" or "production"
84- Email string `yaml:"email"` // Email for Let's Encrypt
85- CacheSize int `yaml:"cacheSize"` // Size of the certificate cache
86- GracePeriod int `yaml:"gracePeriod"` // Shutdown grace period in seconds
87- EnableHttpLogs bool `yaml:"enableHttpLogs"` // Whether to enable HTTP request logging (default: true)
88- EnableRateLimit bool `yaml:"enableRateLimit"` // Whether to enable rate limiting middleware (default: false)
89- DetectUpstreamProxy bool `yaml:"detectUpstreamProxy"` // Whether to detect and handle upstream TLS termination proxies (default: false)
90- SkipCertificates bool `yaml:"skipCertificates"` // Skip Let's Encrypt certificate acquisition when behind a TLS terminating proxy (default: false)
77+ Enabled bool `yaml:"enabled"` // Whether the reverse proxy is enabled
78+ Port string `yaml:"port"` // Port for the reverse proxy to listen on
79+ HttpPort string `yaml:"httpPort"` // HTTP port (usually 80) for redirecting to HTTPS
80+ CertDir string `yaml:"certDir"` // Directory to store Let's Encrypt certificates
81+ Email string `yaml:"email"` // Email for Let's Encrypt
82+ LetsEncryptMode string `yaml:"letsEncryptMode"` // staging or production
83+ SkipCertificates bool `yaml:"skipCertificates"` // Skip Let's Encrypt certificate acquisition when behind a TLS terminating proxy
84+ GracePeriod int `yaml:"gracePeriod"` // Shutdown grace period in seconds
85+ AutoRenew bool `yaml:"autoRenew"` // Whether to automatically renew certificates
86+ RenewBefore int `yaml:"renewBefore"` // Days before expiry to renew certificates
87+ CacheSize int `yaml:"cacheSize"` // Size of the certificate cache
88+ EnableHttpLogs bool `yaml:"enableHttpLogs"` // Whether to enable HTTP request logging
89+ EnableRateLimit bool `yaml:"enableRateLimit"` // Whether to enable rate limiting middleware
90+ DetectUpstreamProxy bool `yaml:"detectUpstreamProxy"` // Whether to detect and handle upstream TLS termination proxies
91+ DefaultChallengeType string `yaml:"defaultChallengeType"` // http-01 or dns-01
92+ DefaultHttpChallengePort string `yaml:"defaultHttpChallengePort"`
93+ DefaultDnsChallengePropagationTimeout int `yaml:"defaultDnsChallengePropagationTimeout"`
94+ DefaultDnsChallengePollingInterval int `yaml:"defaultDnsChallengePollingInterval"`
9195 }
9296
9397 // Default values
M internal/db/db_struct.go +22 -5
@@ -1,5 +1,9 @@
11 package db
22
3+import (
4+ "database/sql"
5+)
6+
37 // User struct model
48 type User struct {
59 ID string `sql:"id, primary_key"`
@@ -46,11 +50,15 @@ type Clients struct {
4650
4751 // Domain represents a domain configuration for the proxy
4852 type Domain struct {
49- ID string `sql:"id, primary_key"`
50- Name string `sql:"name"` // e.g. example.com
51- AccountID string `sql:"account_id, foreign_key=account.id"`
52- CreatedAt string `sql:"created_at"`
53- UpdatedAt string `sql:"updated_at"`
53+ ID string `sql:"id, primary_key"`
54+ Name string `sql:"name"` // e.g. example.com
55+ AccountID string `sql:"account_id, foreign_key=account.id"`
56+ CreatedAt string `sql:"created_at"`
57+ UpdatedAt string `sql:"updated_at"`
58+ AcmeEnabled bool `sql:"acme_enabled"` // Whether ACME is enabled for this domain
59+ AcmeChallengeType sql.NullString `sql:"acme_challenge_type"` // e.g. "http-01", "dns-01"
60+ AcmeDnsProvider sql.NullString `sql:"acme_dns_provider"` // DNS provider name if using DNS-01
61+ AcmeDnsCredentialsRef sql.NullString `sql:"acme_dns_credentials_ref"` // Reference to credentials (not raw credentials)
5462 }
5563
5664 // Certificate represents a TLS certificate for a domain
@@ -79,6 +87,15 @@ type ProxyRoute struct {
7987 UpdatedAt string `sql:"updated_at"`
8088 }
8189
90+// AcmeAccount represents the stored ACME account details
91+type AcmeAccount struct {
92+ Email string `sql:"email, primary_key"`
93+ PrivateKey string `sql:"private_key"` // PEM encoded private key
94+ RegistrationInfo sql.NullString `sql:"registration_info"` // JSON marshaled registration resource
95+ CreatedAt string `sql:"created_at"`
96+ UpdatedAt string `sql:"updated_at"`
97+}
98+
8299 // GithubUserInfo holds the essential information for a Github user
83100 type GithubUserInfo struct {
84101 Login string `json:"login"`
M internal/db/queries/account.go +14 -12
@@ -2,29 +2,31 @@ package queries
22
33 import (
44 "database/sql"
5+
56 "github.com/bnema/gordon/internal/db"
7+ "github.com/google/uuid"
68 )
79
810 // AccountQueries contains all SQL queries for account operations
911 type AccountQueries struct {
10- InsertAccount string
12+ InsertAccount string
1113 CheckAccountExists string
12- GetAccountByID string
14+ GetAccountByID string
1315 }
1416
1517 // NewAccountQueries returns a new instance of AccountQueries
1618 func NewAccountQueries() *AccountQueries {
1719 return &AccountQueries{
18- InsertAccount: "INSERT INTO account (id, user_id) VALUES (?, ?)",
20+ InsertAccount: "INSERT INTO account (id, user_id) VALUES (?, ?)",
1921 CheckAccountExists: "SELECT EXISTS(SELECT 1 FROM account WHERE id = ?)",
20- GetAccountByID: "SELECT id, user_id FROM account WHERE id = ?",
22+ GetAccountByID: "SELECT id, user_id FROM account WHERE id = ?",
2123 }
2224 }
2325
2426 // CreateDBAccount creates a new account in the database
2527 func CreateDBAccount(database *sql.DB, userID string) (*db.Account, error) {
2628 account := &db.Account{
27- ID: generateUUID(),
29+ ID: uuid.NewString(),
2830 UserID: userID,
2931 }
3032
@@ -32,23 +34,23 @@ func CreateDBAccount(database *sql.DB, userID string) (*db.Account, error) {
3234 NewAccountQueries().InsertAccount,
3335 account.ID, userID,
3436 )
35-
37+
3638 return account, err
3739 }
3840
3941 // CheckDBAccountExists checks if an account exists in the database
4042 func CheckDBAccountExists(database *sql.DB, accountID string) (bool, error) {
4143 var exists bool
42-
44+
4345 err := database.QueryRow(
4446 NewAccountQueries().CheckAccountExists,
4547 accountID,
4648 ).Scan(&exists)
47-
49+
4850 if err != nil {
4951 return false, err
5052 }
51-
53+
5254 return exists, nil
5355 }
5456
@@ -57,15 +59,15 @@ func GetDBAccountByID(database *sql.DB, accountID string) (*db.Account, error) {
5759 account := &db.Account{
5860 ID: accountID,
5961 }
60-
62+
6163 err := database.QueryRow(
6264 NewAccountQueries().GetAccountByID,
6365 accountID,
6466 ).Scan(&account.ID, &account.UserID)
65-
67+
6668 if err != nil {
6769 return nil, err
6870 }
69-
71+
7072 return account, nil
7173 }
M internal/db/queries/provider.go +94 -17
@@ -2,15 +2,20 @@ package queries
22
33 import (
44 "database/sql"
5+
56 "github.com/bnema/gordon/internal/db"
7+ "github.com/google/uuid"
68 )
79
810 // ProviderQueries contains all SQL queries for provider operations
911 type ProviderQueries struct {
10- InsertProvider string
11- PopulateProviderFromDB string
12- CheckProviderExists string
13- GetProviderByAccountLogin string
12+ InsertProvider string
13+ PopulateProviderFromDB string
14+ CheckProviderExists string
15+ GetProviderByAccountLogin string
16+ CountProvidersQuery string
17+ InsertProviderForAccountQuery string
18+ GetUserByProviderLoginQuery string
1419 }
1520
1621 // NewProviderQueries returns a new instance of ProviderQueries
@@ -20,13 +25,82 @@ func NewProviderQueries() *ProviderQueries {
2025 PopulateProviderFromDB: "SELECT id, account_id, name, login, avatar_url, profile_url, email FROM provider WHERE account_id = ?",
2126 CheckProviderExists: "SELECT EXISTS(SELECT 1 FROM provider WHERE login = ? AND email = ?)",
2227 GetProviderByAccountLogin: "SELECT id, account_id, name, login, avatar_url, profile_url, email FROM provider WHERE login = ? AND email = ?",
28+ CountProvidersQuery: "SELECT COUNT(*) FROM provider",
29+ InsertProviderForAccountQuery: `
30+ INSERT INTO provider (id, account_id, name, login, avatar_url, profile_url, email)
31+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
32+ GetUserByProviderLoginQuery: `
33+ SELECT u.id, u.name, u.email, p.account_id
34+ FROM provider p
35+ JOIN account a ON p.account_id = a.id
36+ JOIN user u ON a.user_id = u.id
37+ WHERE p.name = ? AND p.login = ?`,
38+ }
39+}
40+
41+// CountProviders returns the total number of provider records in the database.
42+func CountProviders(database *sql.DB) (int, error) {
43+ var count int
44+ err := database.QueryRow(NewProviderQueries().CountProvidersQuery).Scan(&count)
45+ if err != nil {
46+ return 0, err
47+ }
48+ return count, nil
49+}
50+
51+// Inserts a new provider record linked to an existing account.
52+func InsertProviderForAccount(database *sql.DB, accountID string, providerName string, userInfo *db.GithubUserInfo) error {
53+ providerID := uuid.NewString()
54+ primaryEmail := ""
55+ if len(userInfo.Emails) > 0 {
56+ primaryEmail = userInfo.Emails[0]
57+ }
58+
59+ _, err := database.Exec(
60+ NewProviderQueries().InsertProviderForAccountQuery,
61+ providerID,
62+ accountID,
63+ providerName,
64+ userInfo.Login,
65+ userInfo.AvatarURL,
66+ userInfo.ProfileURL,
67+ primaryEmail,
68+ )
69+ return err
70+}
71+
72+// Retrieves the User and Account ID associated with a specific provider login.
73+func GetUserByProviderLogin(database *sql.DB, providerName string, providerLogin string) (*db.User, string, error) {
74+ user := &db.User{}
75+ var accountID string
76+
77+ err := database.QueryRow(
78+ NewProviderQueries().GetUserByProviderLoginQuery,
79+ providerName,
80+ providerLogin,
81+ ).Scan(
82+ &user.ID,
83+ &user.Name,
84+ &user.Email,
85+ &accountID,
86+ )
87+
88+ if err != nil {
89+ return nil, "", err
2390 }
91+
92+ return user, accountID, nil
2493 }
2594
2695 // CreateDBGitHubProvider creates a new GitHub provider in the database.
2796 func CreateDBGitHubProvider(database *sql.DB, accountID string, userInfo *db.GithubUserInfo) error {
28- providerID := generateUUID()
29-
97+ providerID := uuid.NewString()
98+
99+ primaryEmail := ""
100+ if len(userInfo.Emails) > 0 {
101+ primaryEmail = userInfo.Emails[0]
102+ }
103+
30104 _, err := database.Exec(
31105 NewProviderQueries().InsertProvider,
32106 providerID,
@@ -35,16 +109,16 @@ func CreateDBGitHubProvider(database *sql.DB, accountID string, userInfo *db.Git
35109 userInfo.Login,
36110 userInfo.AvatarURL,
37111 userInfo.ProfileURL,
38- userInfo.Emails[0],
112+ primaryEmail,
39113 )
40-
114+
41115 return err
42116 }
43117
44118 // PopulateDBProviderFromDB populates provider information from the database.
45119 func PopulateDBProviderFromDB(database *sql.DB, accountID string) (*db.Provider, error) {
46120 provider := &db.Provider{}
47-
121+
48122 err := database.QueryRow(
49123 NewProviderQueries().PopulateProviderFromDB,
50124 accountID,
@@ -57,35 +131,38 @@ func PopulateDBProviderFromDB(database *sql.DB, accountID string) (*db.Provider,
57131 &provider.ProfileURL,
58132 &provider.Email,
59133 )
60-
134+
61135 if err != nil {
62136 return nil, err
63137 }
64-
138+
65139 return provider, nil
66140 }
67141
68142 // CheckDBProviderExists checks if a provider exists in the database.
69143 func CheckDBProviderExists(database *sql.DB, login string, email string) (bool, error) {
70144 var exists bool
71-
145+
72146 err := database.QueryRow(
73147 NewProviderQueries().CheckProviderExists,
74148 login,
75149 email,
76150 ).Scan(&exists)
77-
151+
78152 if err != nil {
153+ if err == sql.ErrNoRows {
154+ return false, nil
155+ }
79156 return false, err
80157 }
81-
158+
82159 return exists, nil
83160 }
84161
85162 // GetProviderByLoginAndEmail returns a provider from the database by login and email.
86163 func GetProviderByLoginAndEmail(database *sql.DB, login string, email string) (*db.Provider, error) {
87164 provider := &db.Provider{}
88-
165+
89166 err := database.QueryRow(
90167 NewProviderQueries().GetProviderByAccountLogin,
91168 login,
@@ -99,10 +176,10 @@ func GetProviderByLoginAndEmail(database *sql.DB, login string, email string) (*
99176 &provider.ProfileURL,
100177 &provider.Email,
101178 )
102-
179+
103180 if err != nil {
104181 return nil, err
105182 }
106-
183+
107184 return provider, nil
108185 }
M internal/db/queries/proxy.go +103 -15
@@ -3,18 +3,36 @@ package queries
33 // ProxyQueries contains all SQL queries used by the proxy package
44 type ProxyQueries struct {
55 // Route queries
6- GetActiveRoutes string
7- GetRouteByDomain string
8- InsertDomain string
9- GetDomainByName string
10- GetFirstAccount string
11- UpdateRoute string
12- InsertRoute string
13- DeleteRouteByDomainID string
14- DeleteDomainByID string
15- UpdateRouteIP string
16- GetAllRoutes string
17- MarkRouteInactive string
6+ GetActiveRoutes string
7+ GetRouteByDomain string
8+ GetRouteIDAndContainerIDByDomainName string
9+ GetRouteIPByID string
10+ InsertDomain string
11+ GetDomainByName string
12+ GetFirstAccount string
13+ UpdateRoute string
14+ UpdateRecreatedRoute string
15+ InsertRoute string
16+ DeleteRouteByDomainID string
17+ DeleteDomainByID string
18+ UpdateRouteIP string
19+ GetAllRoutes string
20+ MarkRouteInactive string
21+
22+ // ACME queries
23+ GetDomainAcmeConfig string
24+ UpdateDomainAcmeConfig string
25+ GetCertificateByDomain string
26+ UpdateCertificate string
27+
28+ // Admin domain queries
29+ CheckDomainExists string
30+ CreateAdminDomain string
31+ UpdateAdminDomainAcme string
32+
33+ // ACME Account queries (NEW)
34+ GetAcmeAccountByEmail string
35+ InsertOrReplaceAcmeAccount string
1836 }
1937
2038 // NewProxyQueries returns a new instance of ProxyQueries
@@ -29,9 +47,20 @@ func NewProxyQueries() *ProxyQueries {
2947 `,
3048 GetRouteByDomain: `
3149 SELECT id, container_id, container_port
32- FROM proxy_route
50+ FROM proxy_route
3351 WHERE domain_id = ?
3452 `,
53+ GetRouteIDAndContainerIDByDomainName: `
54+ SELECT pr.id, pr.container_id
55+ FROM proxy_route pr
56+ JOIN domain d ON pr.domain_id = d.id
57+ WHERE d.name = ?
58+ `,
59+ GetRouteIPByID: `
60+ SELECT container_ip
61+ FROM proxy_route
62+ WHERE id = ?
63+ `,
3564 InsertDomain: `
3665 INSERT INTO domain (id, name, account_id, created_at, updated_at)
3766 VALUES (?, ?, ?, ?, ?)
@@ -53,6 +82,11 @@ func NewProxyQueries() *ProxyQueries {
5382 updated_at = ?
5483 WHERE id = ?
5584 `,
85+ UpdateRecreatedRoute: `
86+ UPDATE proxy_route
87+ SET container_id = ?, container_ip = ?, container_port = ?, updated_at = ?
88+ WHERE id = ?
89+ `,
5690 InsertRoute: `
5791 INSERT INTO proxy_route (
5892 id, domain_id, container_id, container_ip, container_port,
@@ -70,13 +104,67 @@ func NewProxyQueries() *ProxyQueries {
70104 WHERE domain_id = ? AND container_id = ?
71105 `,
72106 GetAllRoutes: `
73- SELECT d.name, pr.container_id, pr.container_ip, pr.container_port, pr.protocol, pr.path
107+ SELECT pr.id, d.name, pr.container_id, pr.container_ip, pr.container_port, pr.protocol, pr.path, pr.active
74108 FROM proxy_route pr
75109 JOIN domain d ON pr.domain_id = d.id
76- WHERE pr.active = 1
77110 `,
78111 MarkRouteInactive: `
79112 UPDATE proxy_route SET active = 0, updated_at = ? WHERE domain_id = ?
80113 `,
114+
115+ // ACME queries
116+ GetDomainAcmeConfig: `
117+ SELECT acme_enabled, acme_challenge_type, acme_dns_provider, acme_dns_credentials_ref
118+ FROM domain WHERE name = ?
119+ `,
120+ UpdateDomainAcmeConfig: `
121+ UPDATE domain SET
122+ acme_enabled = ?,
123+ acme_challenge_type = ?,
124+ acme_dns_provider = ?,
125+ acme_dns_credentials_ref = ?,
126+ updated_at = ?
127+ WHERE name = ?
128+ `,
129+ GetCertificateByDomain: `
130+ SELECT c.cert_file, c.key_file, c.issued_at, c.expires_at, c.issuer, c.status
131+ FROM certificate c
132+ JOIN domain d ON c.domain_id = d.id
133+ WHERE d.name = ?
134+ `,
135+ UpdateCertificate: `
136+ INSERT OR REPLACE INTO certificate (
137+ id, domain_id, cert_file, key_file, issued_at, expires_at, issuer, status
138+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
139+ `,
140+
141+ // Admin domain queries
142+ CheckDomainExists: `
143+ SELECT EXISTS(SELECT 1 FROM domain WHERE name = ?)
144+ `,
145+ CreateAdminDomain: `
146+ INSERT INTO domain (
147+ id, name, account_id, created_at, updated_at, acme_enabled, acme_challenge_type
148+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
149+ `,
150+ UpdateAdminDomainAcme: `
151+ UPDATE domain SET
152+ acme_enabled = 1,
153+ acme_challenge_type = 'http-01',
154+ updated_at = ?
155+ WHERE name = ?
156+ `,
157+
158+ // ACME Account queries (NEW)
159+ GetAcmeAccountByEmail: `
160+ SELECT private_key, registration_info
161+ FROM acme_account
162+ WHERE email = ?
163+ `,
164+ InsertOrReplaceAcmeAccount: `
165+ INSERT OR REPLACE INTO acme_account (
166+ email, private_key, registration_info, created_at, updated_at
167+ ) VALUES (?, ?, ?, ?, ?)
168+ `,
81169 }
82170 }
M internal/db/queries/sessions.go +80 -32
@@ -7,6 +7,7 @@ import (
77
88 "github.com/bnema/gordon/internal/db"
99 "github.com/bnema/gordon/pkg/logger"
10+ "github.com/google/uuid"
1011 )
1112
1213 // SessionQueries contains all SQL queries for session operations
@@ -38,18 +39,18 @@ func NewSessionQueries() *SessionQueries {
3839 // CreateDBSession creates a session for the user.
3940 func CreateDBSession(database *sql.DB, browserInfo string, accessToken string, accountID string) (*db.Sessions, error) {
4041 expireTime := time.Now().Add(time.Hour * 24).Format(time.RFC3339)
41- sessionID := generateUUID()
42-
42+ sessionID := uuid.NewString()
43+
4344 _, err := database.Exec(
4445 NewSessionQueries().InsertSession,
4546 sessionID, accountID, accessToken, browserInfo, expireTime, true,
4647 )
47-
48+
4849 if err != nil {
4950 logger.Error("Failed to create session in database", "error", err, "session_id", sessionID, "account_id", accountID)
5051 return nil, err
5152 }
52-
53+
5354 // Create a Sessions struct to return
5455 session := &db.Sessions{
5556 ID: sessionID,
@@ -59,7 +60,7 @@ func CreateDBSession(database *sql.DB, browserInfo string, accessToken string, a
5960 Expires: expireTime,
6061 IsOnline: true,
6162 }
62-
63+
6364 logger.Debug("Session created in database", "session_id", sessionID, "account_id", accountID, "expires", expireTime)
6465 return session, nil
6566 }
@@ -97,51 +98,98 @@ func GetDBUserSession(database *sql.DB, accessToken string, browserInfo string)
9798 // UpdateDBSession updates an existing session with a new access token
9899 func UpdateDBSession(database *sql.DB, sessionID string, accessToken string) error {
99100 expireTime := time.Now().Add(time.Hour * 24).Format(time.RFC3339)
100-
101+
101102 _, err := database.Exec(
102103 NewSessionQueries().UpdateSessionToken,
103104 accessToken,
104105 expireTime,
105106 sessionID,
106107 )
107-
108+
108109 if err != nil {
109110 logger.Error("Failed to update session in database", "error", err, "session_id", sessionID)
110111 return err
111112 }
112-
113+
113114 logger.Debug("Session updated in database", "sessionID", sessionID, "expires", expireTime)
114115 return nil
115116 }
116117
117-// CreateOrUpdateDBSession creates a new session or updates an existing one
118-func CreateOrUpdateDBSession(database *sql.DB, accessToken string, browserInfo string, accountID string) error {
119- // Check if the session exists using the access token and browser info
120- session, err := GetDBUserSession(database, accessToken, browserInfo)
118+// CreateOrUpdateSession creates a new session or updates an existing one
119+func CreateOrUpdateSession(database *sql.DB, accountID string, accessToken string, browserInfo string) (*db.Sessions, error) {
120+ // Check if a session exists for this account with the same browser info
121+ // (Token might change, so check by account/browser)
122+ existingSession, err := GetSessionByAccountAndBrowser(database, accountID, browserInfo)
121123 if err != nil && err != sql.ErrNoRows {
122- logger.Warn("Failed to check for session", "error", err)
123- return fmt.Errorf("error checking for session: %w", err)
124+ logger.Error("Failed to check for existing session", "error", err, "accountID", accountID)
125+ return nil, fmt.Errorf("error checking for existing session: %w", err)
124126 }
125127
126- // If the session exists, update the session
127- if session != nil {
128- err := UpdateDBSession(database, session.ID, accessToken)
128+ expireTime := time.Now().Add(time.Hour * 24).Format(time.RFC3339)
129+
130+ // If the session exists, update its token and expiry
131+ if existingSession != nil {
132+ logger.Debug("Existing session found, updating token and expiry", "sessionID", existingSession.ID, "accountID", accountID)
133+ _, err := database.Exec(
134+ NewSessionQueries().UpdateSessionToken,
135+ accessToken,
136+ expireTime,
137+ existingSession.ID,
138+ )
129139 if err != nil {
130- logger.Warn("Failed to update session", "error", err)
131- return fmt.Errorf("error updating session: %w", err)
140+ logger.Error("Failed to update existing session", "error", err, "session_id", existingSession.ID)
141+ return nil, fmt.Errorf("error updating session: %w", err)
132142 }
133- logger.Debug("Session updated in database", "accountID", accountID, "sessionID", session.ID)
143+ // Update the struct we have in memory
144+ existingSession.AccessToken = accessToken
145+ existingSession.Expires = expireTime
146+ existingSession.IsOnline = true // Ensure it's marked online
147+ logger.Info("Session updated successfully", "sessionID", existingSession.ID)
148+ return existingSession, nil
134149 } else {
135- // If the session does not exist, create the session
136- _, err := CreateDBSession(database, browserInfo, accessToken, accountID)
150+ // If the session does not exist, create a new one
151+ logger.Debug("No existing session found, creating new session", "accountID", accountID)
152+ sessionID := uuid.NewString() // Use uuid directly
153+ _, err := database.Exec(
154+ NewSessionQueries().InsertSession,
155+ sessionID, accountID, accessToken, browserInfo, expireTime, true,
156+ )
137157 if err != nil {
138- logger.Warn("Failed to create session", "error", err)
139- return fmt.Errorf("error creating session: %w", err)
158+ logger.Error("Failed to create new session", "error", err, "accountID", accountID)
159+ return nil, fmt.Errorf("error creating session: %w", err)
160+ }
161+ // Create the session struct to return
162+ newSession := &db.Sessions{
163+ ID: sessionID,
164+ AccountID: accountID,
165+ AccessToken: accessToken,
166+ BrowserInfo: browserInfo,
167+ Expires: expireTime,
168+ IsOnline: true,
140169 }
141- logger.Debug("New session created in database", "accountID", accountID)
170+ logger.Info("New session created successfully", "sessionID", newSession.ID)
171+ return newSession, nil
142172 }
173+}
143174
144- return nil
175+// GetSessionByAccountAndBrowser finds a session by account ID and browser info
176+func GetSessionByAccountAndBrowser(database *sql.DB, accountID string, browserInfo string) (*db.Sessions, error) {
177+ session := &db.Sessions{}
178+ // Define the query inline or add to SessionQueries struct
179+ query := "SELECT id, account_id, access_token, browser_info, expires, is_online FROM sessions WHERE account_id = ? AND browser_info = ?"
180+ err := database.QueryRow(query, accountID, browserInfo).Scan(
181+ &session.ID,
182+ &session.AccountID,
183+ &session.AccessToken,
184+ &session.BrowserInfo,
185+ &session.Expires,
186+ &session.IsOnline,
187+ )
188+ if err != nil {
189+ // Return sql.ErrNoRows if not found
190+ return nil, err
191+ }
192+ return session, nil
145193 }
146194
147195 // DeleteDBSession deletes the session from the database.
@@ -151,12 +199,12 @@ func DeleteDBSession(database *sql.DB, accountID string, sessionID string) error
151199 accountID,
152200 sessionID,
153201 )
154-
202+
155203 if err != nil {
156204 logger.Error("Failed to delete session from database", "error", err, "account_id", accountID, "session_id", sessionID)
157205 return err
158206 }
159-
207+
160208 logger.Debug("Session deleted from database", "accountID", accountID, "sessionID", sessionID)
161209 return nil
162210 }
@@ -194,12 +242,12 @@ func ExtendSessionExpiration(database *sql.DB, accountID string, sessionID strin
194242 accountID,
195243 sessionID,
196244 )
197-
245+
198246 if err != nil {
199247 logger.Error("Failed to extend session expiration", "error", err, "account_id", accountID, "session_id", sessionID)
200248 return err
201249 }
202-
250+
203251 logger.Debug("Session expiration time extended", "accountID", accountID, "sessionID", sessionID, "newExpirationTime", newExpirationTime)
204252 return nil
205253 }
@@ -227,12 +275,12 @@ func InvalidateDBSession(database *sql.DB, sessionID string) error {
227275 false,
228276 sessionID,
229277 )
230-
278+
231279 if err != nil {
232280 logger.Error("Failed to invalidate session", "error", err, "session_id", sessionID)
233281 return err
234282 }
235-
283+
236284 logger.Debug("Session invalidated", "sessionID", sessionID)
237285 return nil
238286 }
M internal/db/queries/user.go +66 -69
@@ -3,35 +3,61 @@ package queries
33 import (
44 "database/sql"
55 "fmt"
6- "time"
76
87 "github.com/bnema/gordon/internal/db"
98 "github.com/bnema/gordon/pkg/logger"
109 "github.com/google/uuid"
1110 )
1211
13-func generateUUID() string {
14- return uuid.New().String()
15-}
16-
1712 // UserQueries contains all SQL queries for user operations
1813 type UserQueries struct {
19- InsertUser string
20- CheckUserExists string
21- GetUserByProvider string
22- GetAccountCount string
14+ InsertUser string
15+ CheckUserExists string
16+ GetUserByProvider string
17+ GetAccountCount string
18+ GetFirstUserAndAccountQuery string
19+ UpdateUserDetailsQuery string
2320 }
2421
2522 // NewUserQueries returns a new instance of UserQueries
2623 func NewUserQueries() *UserQueries {
2724 return &UserQueries{
2825 InsertUser: "INSERT INTO user (id, name, email) VALUES (?, ?, ?)",
29- CheckUserExists: "SELECT id FROM user",
26+ CheckUserExists: "SELECT EXISTS(SELECT 1 FROM user LIMIT 1)",
3027 GetUserByProvider: "SELECT user.id, user.name, user.email FROM user INNER JOIN account ON user.id = account.user_id INNER JOIN provider ON account.id = provider.account_id WHERE provider.login = ? AND provider.email = ?",
3128 GetAccountCount: "SELECT COUNT(*) FROM account",
29+ GetFirstUserAndAccountQuery: `
30+ SELECT u.id, u.name, u.email, a.id
31+ FROM user u
32+ JOIN account a ON u.id = a.user_id
33+ ORDER BY u.rowid ASC -- Rely on implicit rowid for 'first'
34+ LIMIT 1`,
35+ UpdateUserDetailsQuery: "UPDATE user SET name = ?, email = ? WHERE id = ?",
3236 }
3337 }
3438
39+// GetFirstUserAndAccount retrieves the first user and their associated account ID. Assumes this is the seeded admin.
40+func GetFirstUserAndAccount(database *sql.DB) (*db.User, string, error) {
41+ user := &db.User{}
42+ var accountID string
43+ err := database.QueryRow(NewUserQueries().GetFirstUserAndAccountQuery).Scan(
44+ &user.ID,
45+ &user.Name,
46+ &user.Email,
47+ &accountID,
48+ )
49+ if err != nil {
50+ return nil, "", err
51+ }
52+ return user, accountID, nil
53+}
54+
55+// UpdateUserDetails updates the name and email for a specific user ID.
56+func UpdateUserDetails(database *sql.DB, userID string, newName string, newEmail string) error {
57+ _, err := database.Exec(NewUserQueries().UpdateUserDetailsQuery, newName, newEmail, userID)
58+ return err
59+}
60+
3561 // CreateUser creates a new user along with the associated account, provider, and session.
3662 func CreateUser(database *sql.DB, accessToken string, browserInfo string, userInfo *db.GithubUserInfo) (*db.User, string, *db.Sessions, error) {
3763 // Check if a user already exists. If so, return an error.
@@ -58,7 +84,7 @@ func CreateUser(database *sql.DB, accessToken string, browserInfo string, userIn
5884 logger.Info("Database account created successfully", "account_id", account.ID)
5985
6086 logger.Debug("Creating GitHub provider for account", "account_id", account.ID)
61- err = CreateDBGitHubProvider(database, account.ID, userInfo)
87+ err = InsertProviderForAccount(database, account.ID, "github", userInfo)
6288 if err != nil {
6389 logger.Error("Failed to create GitHub provider", "error", err)
6490 return nil, "", nil, err
@@ -66,7 +92,7 @@ func CreateUser(database *sql.DB, accessToken string, browserInfo string, userIn
6692 logger.Info("GitHub provider created successfully")
6793
6894 logger.Debug("Creating session for account", "account_id", account.ID)
69- session, err := CreateDBSession(database, browserInfo, accessToken, account.ID)
95+ session, err := CreateOrUpdateSession(database, account.ID, accessToken, browserInfo)
7096 if err != nil {
7197 logger.Error("Failed to create session", "error", err)
7298 return nil, "", nil, err
@@ -78,10 +104,15 @@ func CreateUser(database *sql.DB, accessToken string, browserInfo string, userIn
78104
79105 // CreateDBUser creates a new user in the database
80106 func CreateDBUser(database *sql.DB, userInfo *db.GithubUserInfo) (*db.User, error) {
107+ primaryEmail := ""
108+ if len(userInfo.Emails) > 0 {
109+ primaryEmail = userInfo.Emails[0]
110+ }
111+
81112 user := &db.User{
82- ID: generateUUID(),
113+ ID: uuid.NewString(),
83114 Name: userInfo.Login,
84- Email: userInfo.Emails[0],
115+ Email: primaryEmail,
85116 }
86117
87118 _, err := database.Exec(
@@ -93,34 +124,29 @@ func CreateDBUser(database *sql.DB, userInfo *db.GithubUserInfo) (*db.User, erro
93124
94125 // CheckDBUserExists checks if any user exists in the database
95126 func CheckDBUserExists(database *sql.DB) (bool, error) {
96- var userID string
97- err := database.QueryRow(NewUserQueries().CheckUserExists).Scan(&userID)
127+ var exists bool
128+ err := database.QueryRow(NewUserQueries().CheckUserExists).Scan(&exists)
98129 if err != nil {
99- if err == sql.ErrNoRows {
100- return false, nil
101- }
102130 return false, err
103131 }
104-
105- if userID != "" {
106- return true, nil
107- }
108-
109- return false, nil
132+ return exists, nil
110133 }
111134
112135 // CheckDBUserIsGood checks if the user matches the provided GitHub info
113136 func CheckDBUserIsGood(database *sql.DB, userInfo *db.GithubUserInfo) (*db.User, bool, error) {
114137 login := userInfo.Login
115- email := userInfo.Emails[0]
138+ primaryEmail := ""
139+ if len(userInfo.Emails) > 0 {
140+ primaryEmail = userInfo.Emails[0]
141+ }
116142
117143 user := &db.User{}
118-
144+
119145 // Check if the user exists based on github login and email
120- err := database.QueryRow(NewUserQueries().GetUserByProvider, login, email).Scan(
146+ err := database.QueryRow(NewUserQueries().GetUserByProvider, login, primaryEmail).Scan(
121147 &user.ID, &user.Name, &user.Email,
122148 )
123-
149+
124150 if err != nil {
125151 if err == sql.ErrNoRows {
126152 return nil, false, nil
@@ -138,51 +164,22 @@ func CheckDBUserIsGood(database *sql.DB, userInfo *db.GithubUserInfo) (*db.User,
138164 // UpdateUser updates the user session with new access token
139165 func UpdateUser(database *sql.DB, accessToken string, browserInfo string, userInfo *db.GithubUserInfo) (*db.User, string, *db.Sessions, error) {
140166 // First, get the user info to get their account ID
141- user, isGood, err := CheckDBUserIsGood(database, userInfo)
167+ user, accountID, err := GetUserByProviderLogin(database, "github", userInfo.Login)
142168 if err != nil {
143- return nil, "", nil, fmt.Errorf("error checking user: %w", err)
144- }
145-
146- if !isGood {
147- return nil, "", nil, fmt.Errorf("user not found in database")
169+ if err == sql.ErrNoRows {
170+ return nil, "", nil, fmt.Errorf("user not found in database for provider login '%s'", userInfo.Login)
171+ }
172+ return nil, "", nil, fmt.Errorf("error checking user by provider login: %w", err)
148173 }
149-
150- // Get the account ID for this user
151- var accountID string
152- err = database.QueryRow("SELECT id FROM account WHERE user_id = ?", user.ID).Scan(&accountID)
174+
175+ // If user found, update/create their session
176+ session, err := CreateOrUpdateSession(database, accountID, accessToken, browserInfo)
153177 if err != nil {
154- return nil, "", nil, fmt.Errorf("error getting account ID: %w", err)
155- }
156-
157- // Check if the session exists using the access token and browser info
158- existingSession, err := GetDBUserSession(database, accessToken, browserInfo)
159- if err != nil && err != sql.ErrNoRows {
160- return nil, "", nil, fmt.Errorf("error checking for session: %w", err)
161- }
162-
163- var session *db.Sessions
164-
165- // If the session exists, update the session
166- if existingSession != nil {
167- expiresTime := time.Now().Add(time.Hour * 24).Format(time.RFC3339)
168- err := UpdateDBSession(database, existingSession.ID, accessToken)
169- if err != nil {
170- return nil, "", nil, fmt.Errorf("error updating session: %w", err)
171- }
172- // Update the session struct with new values
173- existingSession.AccessToken = accessToken
174- existingSession.Expires = expiresTime
175- session = existingSession
176- logger.Debug("Session updated in database", "accountID", accountID, "sessionID", session.ID)
177- } else {
178- // If the session does not exist, create the session
179- session, err = CreateDBSession(database, browserInfo, accessToken, accountID)
180- if err != nil {
181- return nil, "", nil, fmt.Errorf("could not create session: %w", err)
182- }
183- logger.Debug("New session created in database", "accountID", accountID, "sessionID", session.ID)
178+ return nil, "", nil, fmt.Errorf("error creating/updating session: %w", err)
184179 }
185180
181+ logger.Debug("Session updated/created in database", "accountID", accountID, "sessionID", session.ID)
182+
186183 return user, accountID, session, nil
187184 }
188185
M internal/httpserve/handlers/endpoint_deploy.go +1 -4
@@ -573,10 +573,7 @@ func AddProxyRoute(a *server.App, containerID, containerIP, containerPort, targe
573573
574574 // Verify the database update with a direct query
575575 var updatedContainerIP string
576- err = a.GetDB().QueryRow(`
577- SELECT container_ip
578- FROM proxy_route
579- WHERE id = ?`, existingRouteID).Scan(&updatedContainerIP)
576+ err = a.GetDB().QueryRow(p.Queries.GetRouteIPByID, existingRouteID).Scan(&updatedContainerIP)
580577
581578 if err == nil && updatedContainerIP != containerIP {
582579 logger.Warn("Database verification failed: container IP mismatch",
M internal/httpserve/handlers/login.go +87 -55
@@ -1,6 +1,7 @@
11 package handlers
22
33 import (
4+ "database/sql"
45 "encoding/base64"
56 "encoding/json"
67 "fmt"
@@ -257,88 +258,119 @@ func getSession(c echo.Context) (*sessions.Session, error) {
257258
258259 // handleUser handles the user creation or update
259260 func handleUser(c echo.Context, a *server.App, accessToken, browserInfo string, userInfo *db.GithubUserInfo) (*db.User, string, *db.Sessions, error) {
260- logger.Debug("Starting handleUser function", "github_login", userInfo.Login, "github_email", userInfo.Emails[0])
261- userExists, err := queries.CheckDBUserExists(a.DB)
261+ logger.Debug("Starting handleUser function", "github_login", userInfo.Login)
262+
263+ // --- Check provider count ---
264+ providerCount, err := queries.CountProviders(a.DB)
262265 if err != nil {
263- logger.Error("Error checking if user exists", "error", err)
264- return nil, "", nil, fmt.Errorf("could not check if user exists: %w", err)
266+ logger.Error("Error checking provider count", "error", err)
267+ return nil, "", nil, fmt.Errorf("could not check provider count: %w", err)
265268 }
269+ logger.Debug("Provider count check result", "count", providerCount)
266270
267- logger.Debug("User exists check result", "user_exists", userExists)
271+ // --- Case 1: First ever GitHub login ---
272+ if providerCount == 0 {
273+ logger.Info("First GitHub login detected. Linking to seeded admin account.")
268274
269- if !userExists {
270- // if it is a new user creation we compare the gordon token
271- logger.Debug("No user exists, attempting to create new user")
275+ // Compare the Gordon token (still need admin token for the *very first* setup)
272276 err := CompareGordonToken(c, a)
273277 if err != nil {
274- logger.Error("Token comparison failed", "error", err)
275- return nil, "", nil, echo.NewHTTPError(http.StatusUnauthorized, "Token is empty or not valid")
278+ logger.Error("Admin token comparison failed for first login", "error", err)
279+ return nil, "", nil, echo.NewHTTPError(http.StatusUnauthorized, "Admin token is empty or not valid for initial setup")
276280 }
277- logger.Debug("Token comparison successful, proceeding with user creation")
281+ logger.Debug("Admin token comparison successful for first login.")
278282
279- user, accountID, session, err := createUser(a, accessToken, browserInfo, userInfo)
283+ // --- Link to Seeded Admin ---
284+ seededUser, seededAccountID, session, err := linkProviderToSeededAdmin(a, accessToken, browserInfo, userInfo)
280285 if err != nil {
281- logger.Error("Failed to create user", "error", err)
286+ logger.Error("Failed to link provider to seeded admin", "error", err)
282287 return nil, "", nil, err
283288 }
284- logger.Info("Successfully created new user", "user_id", user.ID, "user_name", user.Name, "account_id", accountID)
285- return user, accountID, session, nil
289+ logger.Info("Successfully linked GitHub provider to seeded admin account", "user_id", seededUser.ID, "account_id", seededAccountID)
290+ return seededUser, seededAccountID, session, nil
286291 }
287292
288- if userExists {
289- // if it is an existing user we compare the login and email to see if it is the same user
290- logger.Debug("User exists, checking if GitHub info matches existing user")
291- _, isGoodUser, err := queries.CheckDBUserIsGood(a.DB, userInfo)
292- if err != nil {
293- logger.Error("Error checking if user is good", "error", err)
294- return nil, "", nil, fmt.Errorf("could not check if user is good: %w", err)
295- }
293+ // --- Case 2: Subsequent GitHub logins ---
294+ if providerCount > 0 {
295+ logger.Debug("Existing provider(s) found. Checking if incoming GitHub login matches.")
296296
297- logger.Debug("User check result", "is_good_user", isGoodUser)
298-
299- if !isGoodUser {
300- logger.Warn("Unauthorized user", "github_login", userInfo.Login)
301- return nil, "", nil, echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized user")
297+ // --- Check if incoming GitHub user matches an existing provider ---
298+ existingUser, existingAccountID, err := queries.GetUserByProviderLogin(a.DB, "github", userInfo.Login)
299+ if err != nil {
300+ // Handle sql.ErrNoRows specifically: means this GitHub user is not linked yet.
301+ if err == sql.ErrNoRows {
302+ logger.Warn("Unauthorized GitHub user trying to log in (no matching provider found).", "github_login", userInfo.Login)
303+ // For now, deny access. Future: could allow multiple users if desired.
304+ return nil, "", nil, echo.NewHTTPError(http.StatusUnauthorized, "This GitHub account is not authorized.")
305+ }
306+ // Handle other potential database errors
307+ logger.Error("Error checking for existing provider by login", "error", err)
308+ return nil, "", nil, fmt.Errorf("could not check existing provider: %w", err)
302309 }
310+ logger.Debug("Incoming GitHub login matches existing user.", "github_login", userInfo.Login, "user_id", existingUser.ID, "account_id", existingAccountID)
303311
304- if isGoodUser {
305- // Update the user
306- logger.Debug("User is authorized, updating session")
307- updatedUser, accountID, session, err := updateUser(a, accessToken, browserInfo, userInfo)
308- if err != nil {
309- logger.Error("Could not update user", "error", err)
310- return nil, "", nil, fmt.Errorf("could not update user: %w", err)
311- }
312- logger.Info("Successfully updated user session", "user_id", updatedUser.ID, "account_id", accountID)
313- return updatedUser, accountID, session, nil
312+ // --- Update Session for Existing User ---
313+ // The user is valid and matches the existing provider, just update their session.
314+ session, err := queries.CreateOrUpdateSession(a.DB, existingAccountID, accessToken, browserInfo)
315+ if err != nil {
316+ logger.Error("Could not create/update session for existing user", "error", err)
317+ return nil, "", nil, fmt.Errorf("could not update session: %w", err)
314318 }
319+ logger.Info("Successfully created/updated session for existing user", "user_id", existingUser.ID, "account_id", existingAccountID)
320+ return existingUser, existingAccountID, session, nil
315321 }
316322
317- logger.Error("Could not handle user - reached end of function without returning")
323+ // Should not be reached if logic is correct
324+ logger.Error("Could not handle user - reached end of function unexpectedly", "provider_count", providerCount)
318325 return nil, "", nil, fmt.Errorf("could not handle user")
319326 }
320327
321-// createUser creates a new user in the database
322-func createUser(a *server.App, accessToken, browserInfo string, userInfo *db.GithubUserInfo) (*db.User, string, *db.Sessions, error) {
323- logger.Debug("Starting createUser function", "github_login", userInfo.Login)
324- user, accountID, session, err := queries.CreateUser(a.DB, accessToken, browserInfo, userInfo)
328+// --- NEW FUNCTION: linkProviderToSeededAdmin ---
329+// Finds the seeded admin, updates its details, creates the provider and session.
330+func linkProviderToSeededAdmin(a *server.App, accessToken, browserInfo string, userInfo *db.GithubUserInfo) (*db.User, string, *db.Sessions, error) {
331+ logger.Debug("Starting linkProviderToSeededAdmin", "github_login", userInfo.Login)
332+
333+ // 1. Get the seeded user and account ID (assuming it's the only one)
334+ seededUser, seededAccountID, err := queries.GetFirstUserAndAccount(a.DB)
325335 if err != nil {
326- logger.Error("Failed to create user in database", "error", err)
327- return nil, "", nil, fmt.Errorf("could not create user: %w", err)
336+ logger.Error("Failed to get seeded user/account", "error", err)
337+ return nil, "", nil, fmt.Errorf("critical: could not find seeded admin account: %w", err)
328338 }
329- logger.Info("User created successfully", "user_id", user.ID, "name", user.Name, "account_id", accountID)
330- return user, accountID, session, nil
331-}
339+ logger.Debug("Found seeded admin", "user_id", seededUser.ID, "account_id", seededAccountID, "original_email", seededUser.Email)
332340
333-// updateUser updates an existing user in the database
334-func updateUser(a *server.App, accessToken, browserInfo string, userInfo *db.GithubUserInfo) (*db.User, string, *db.Sessions, error) {
335- user, accountID, session, err := queries.UpdateUser(a.DB, accessToken, browserInfo, userInfo)
341+ // 2. Update the seeded user's details with GitHub info
342+ newEmail := seededUser.Email // Keep original unless GitHub provides one
343+ if len(userInfo.Emails) > 0 && userInfo.Emails[0] != "" {
344+ newEmail = userInfo.Emails[0]
345+ }
346+ newName := userInfo.Login
347+
348+ err = queries.UpdateUserDetails(a.DB, seededUser.ID, newName, newEmail)
349+ if err != nil {
350+ logger.Error("Failed to update seeded user details", "user_id", seededUser.ID, "error", err)
351+ return nil, "", nil, fmt.Errorf("could not update seeded user details: %w", err)
352+ }
353+ logger.Info("Updated seeded user details", "user_id", seededUser.ID, "new_name", newName, "new_email", newEmail)
354+ seededUser.Name = newName
355+ seededUser.Email = newEmail
356+
357+ // 3. Create the Provider record linked to the seeded account
358+ err = queries.InsertProviderForAccount(a.DB, seededAccountID, "github", userInfo)
336359 if err != nil {
337- logger.Error("Could not update user", "error", err)
338- return nil, "", nil, fmt.Errorf("could not update user: %w", err)
360+ logger.Error("Failed to insert provider record for seeded account", "account_id", seededAccountID, "error", err)
361+ return nil, "", nil, fmt.Errorf("could not insert provider record: %w", err)
339362 }
340- logger.Info("User updated successfully", "user_id", user.ID, "account_id", accountID)
341- return user, accountID, session, nil
363+ logger.Info("Inserted provider record", "account_id", seededAccountID, "provider", "github", "login", userInfo.Login)
364+
365+ // 4. Create/Update the Session record linked to the seeded account
366+ session, err := queries.CreateOrUpdateSession(a.DB, seededAccountID, accessToken, browserInfo)
367+ if err != nil {
368+ logger.Error("Failed to create/update session for seeded account", "account_id", seededAccountID, "error", err)
369+ return nil, "", nil, fmt.Errorf("could not create/update session: %w", err)
370+ }
371+ logger.Info("Created/Updated session record", "account_id", seededAccountID, "session_id", session.ID)
372+
373+ return seededUser, seededAccountID, session, nil
342374 }
343375
344376 // setSessionValues sets the session values for the user
M internal/httpserve/handlers/session_db.go +65 -27
@@ -1,6 +1,7 @@
11 package handlers
22
33 import (
4+ "database/sql"
45 "fmt"
56 "time"
67
@@ -16,67 +17,104 @@ func ValidateSessionAndUser(c echo.Context, a *server.App) error {
1617
1718 sess, err := session.Get("session", c)
1819 if err != nil {
19- logger.Warn("Failed to get session", "error", err)
20+ logger.Warn("Failed to get session from cookie store", "error", err)
2021 return fmt.Errorf("failed to get session: %w", err)
2122 }
2223
2324 accountID, ok := sess.Values["accountID"].(string)
24- if !ok {
25- logger.Warn("Account ID not found or invalid type")
26- return fmt.Errorf("accountID not found or invalid type")
25+ if !ok || accountID == "" {
26+ logger.Warn("Account ID not found in session or is empty")
27+ return fmt.Errorf("accountID not found or invalid")
2728 }
2829
2930 sessionID, ok := sess.Values["sessionID"].(string)
30- if !ok {
31- logger.Warn("Session ID not found or invalid type")
32- return fmt.Errorf("sessionID not found or invalid type")
31+ if !ok || sessionID == "" {
32+ logger.Warn("Session ID not found in session or is empty")
33+ return fmt.Errorf("sessionID not found or invalid")
34+ }
35+
36+ authenticated, ok := sess.Values["authenticated"].(bool)
37+ if !ok || !authenticated {
38+ logger.Warn("User not authenticated according to session cookie", "ok", ok, "authenticated_val", sess.Values["authenticated"])
39+ return fmt.Errorf("user not authenticated")
3340 }
3441
3542 accountExists, err := IsAccountIDInDB(a, accountID)
36- if err != nil || !accountExists {
37- logger.Warn("Account does not exist")
43+ if err != nil {
44+ logger.Error("Failed to check account existence", "error", err, "accountID", accountID)
45+ return fmt.Errorf("failed checking account existence: %w", err)
46+ }
47+ if !accountExists {
48+ logger.Warn("Account associated with session not found in DB", "accountID", accountID)
3849 return fmt.Errorf("account does not exist")
3950 }
4051
41- sessionExpired, err := IsSessionExpiredInDB(a, accountID, sessionID)
42- if err != nil || sessionExpired {
43- logger.Warn("Session is expired")
44- return fmt.Errorf("session is expired")
52+ isValidInDB, err := IsSessionValidInDB(a, sessionID)
53+ if err != nil {
54+ logger.Error("Failed to check session validity in DB", "error", err, "sessionID", sessionID)
55+ return fmt.Errorf("failed checking session validity in DB: %w", err)
56+ }
57+ if !isValidInDB {
58+ logger.Warn("Session is invalid in DB (expired or not online)", "sessionID", sessionID)
59+ return fmt.Errorf("session invalid or expired")
4560 }
4661
47- // Extend session expiration conditionally, e.g., if it's close to expiring
4862 err = queries.ExtendSessionExpiration(a.DB, accountID, sessionID, time.Now().Add(sessionExpiryTime))
4963 if err != nil {
50- logger.Warn("Failed to extend session", "error", err)
51- return fmt.Errorf("failed to extend session: %v", err)
64+ logger.Error("Failed to extend session in DB", "error", err, "accountID", accountID, "sessionID", sessionID)
65+ return fmt.Errorf("failed to extend session: %w", err)
66+ }
67+ sess.Values["expires"] = time.Now().Add(sessionExpiryTime).Format(time.RFC3339)
68+ if err := sess.Save(c.Request(), c.Response()); err != nil {
69+ logger.Error("Failed to save updated expiry to cookie session", "error", err, "sessionID", sessionID)
5270 }
5371
54- logger.Debug("Session extended successfully", "accountID", accountID, "sessionID", sessionID)
55-
72+ logger.Debug("Session validated and extended successfully", "accountID", accountID, "sessionID", sessionID)
5673 return nil
5774 }
5875
59-func IsSessionExpiredInDB(a *server.App, accountID string, sessionID string) (bool, error) {
60- currentTime := time.Now()
61- expirationTime, err := queries.GetSessionExpiration(a.DB, accountID, sessionID, currentTime)
76+func IsSessionValidInDB(a *server.App, sessionID string) (bool, error) {
77+ var dbExpiresStr string
78+ var isOnline bool
79+
80+ query := "SELECT expires, is_online FROM sessions WHERE id = ?"
81+ err := a.DB.QueryRow(query, sessionID).Scan(&dbExpiresStr, &isOnline)
82+
6283 if err != nil {
63- logger.Warn("Failed to get session expiration time", "error", err)
84+ if err == sql.ErrNoRows {
85+ logger.Warn("Session not found in DB", "sessionID", sessionID)
86+ return false, nil
87+ }
88+ logger.Error("Failed to query session validity from DB", "error", err, "sessionID", sessionID)
6489 return false, err
6590 }
6691
67- logger.Debug("Session expiration time for account", "accountID", accountID, "sessionID", sessionID, "expirationTime", expirationTime)
68- return currentTime.After(expirationTime), nil
92+ if !isOnline {
93+ logger.Warn("Session found in DB but marked as not online", "sessionID", sessionID)
94+ return false, nil
95+ }
96+
97+ expirationTime, err := time.Parse(time.RFC3339, dbExpiresStr)
98+ if err != nil {
99+ logger.Error("Failed to parse session expiration time from DB", "error", err, "sessionID", sessionID, "expires_str", dbExpiresStr)
100+ return false, fmt.Errorf("invalid expiry format in DB: %w", err)
101+ }
102+
103+ if time.Now().After(expirationTime) {
104+ logger.Warn("Session found in DB but expired", "sessionID", sessionID, "expires_at", expirationTime)
105+ return false, nil
106+ }
107+
108+ return true, nil
69109 }
70110
71111 func IsAccountIDInDB(a *server.App, accountID string) (bool, error) {
72112 accountExists, err := queries.CheckDBAccountExists(a.DB, accountID)
73113 if err != nil {
74- logger.Warn("Failed to check account existence", "error", err)
114+ logger.Error("Failed to check account existence", "error", err, "accountID", accountID)
75115 return false, err
76116 }
77-
78117 logger.Debug("Account existence check", "accountID", accountID, "exists", accountExists)
79-
80118 return accountExists, nil
81119 }
82120
A internal/proxy/acme.go +546 -0
@@ -0,0 +1,546 @@
1+package proxy
2+
3+import (
4+ "crypto"
5+ "crypto/tls"
6+ "database/sql"
7+ "encoding/json"
8+ "fmt"
9+ "os"
10+ "strings"
11+ "time"
12+
13+ "github.com/bnema/gordon/internal/db/queries"
14+ "github.com/bnema/gordon/internal/interfaces"
15+ "github.com/bnema/gordon/pkg/logger"
16+ "github.com/go-acme/lego/v4/certcrypto"
17+ "github.com/go-acme/lego/v4/certificate"
18+ "github.com/go-acme/lego/v4/challenge/http01"
19+ "github.com/go-acme/lego/v4/lego"
20+ "github.com/go-acme/lego/v4/providers/dns"
21+ "github.com/go-acme/lego/v4/registration"
22+)
23+
24+// CertManagerConfig holds configuration for the certificate manager
25+type CertManagerConfig struct {
26+ CertDir string
27+ Email string
28+ Mode string
29+ SkipCertificates bool
30+ BehindTLSProxy bool
31+ AdminDomain string
32+ RootDomain string
33+ HttpPort string
34+}
35+
36+// CertificateManager handles ACME certificate operations
37+type CertificateManager struct {
38+ config CertManagerConfig
39+ client *lego.Client
40+ user *AcmeUser
41+ db *sql.DB
42+ queries *queries.ProxyQueries
43+}
44+
45+// AcmeUser implements the registration.User interface
46+type AcmeUser struct {
47+ Email string `json:"email"`
48+ Registration *registration.Resource `json:"registration"`
49+ key crypto.PrivateKey `json:"-"` // Exclude key from JSON marshal
50+}
51+
52+// GetEmail returns the user's email
53+func (u *AcmeUser) GetEmail() string {
54+ return u.Email
55+}
56+
57+// GetRegistration returns the user's registration
58+func (u *AcmeUser) GetRegistration() *registration.Resource {
59+ return u.Registration
60+}
61+
62+// GetPrivateKey returns the user's private key
63+func (u *AcmeUser) GetPrivateKey() crypto.PrivateKey {
64+ return u.key
65+}
66+
67+// NewCertificateManager creates a new certificate manager
68+func NewCertificateManager(config CertManagerConfig, app interfaces.AppInterface, queries *queries.ProxyQueries) (*CertificateManager, error) {
69+ logger.Debug("Initializing certificate manager", "config", config)
70+
71+ db := app.GetDB() // Get DB connection
72+
73+ // --- User Loading/Creation ---
74+ user, err := loadAcmeUserFromDB(config.Email, db, queries)
75+
76+ if err != nil {
77+ // Check if the error is "user not found"
78+ if err == sql.ErrNoRows {
79+ logger.Info("No existing ACME user found in DB, creating new one.", "email", config.Email)
80+
81+ // Create a new key
82+ privateKey, keyErr := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
83+ if keyErr != nil {
84+ return nil, fmt.Errorf("failed to generate private key: %w", keyErr)
85+ }
86+
87+ user = &AcmeUser{
88+ Email: config.Email,
89+ key: privateKey,
90+ // Registration will be nil initially
91+ }
92+
93+ // Save the new user (key only for now, registration later)
94+ if saveErr := saveAcmeUserToDB(user, db, queries); saveErr != nil {
95+ // Log the error but proceed, registration might fix it or fail later
96+ logger.Error("Failed to save newly generated ACME user key to DB", "error", saveErr)
97+ // Depending on policy, might want to return error here:
98+ // return nil, fmt.Errorf("failed to save initial ACME user: %w", saveErr)
99+ }
100+ } else {
101+ // Handle other DB errors during load
102+ return nil, fmt.Errorf("failed to load ACME user from DB: %w", err)
103+ }
104+ } else {
105+ logger.Debug("Loaded existing ACME user from DB", "email", user.Email)
106+ }
107+ // --- End User Loading/Creation ---
108+
109+ // Create the lego config using the loaded/created user
110+ legoConfig := lego.NewConfig(user)
111+ legoConfig.Certificate.KeyType = certcrypto.RSA2048 // Match key type used
112+
113+ // Set the server based on mode
114+ if config.Mode == "staging" {
115+ logger.Info("Using Let's Encrypt staging environment")
116+ legoConfig.CADirURL = lego.LEDirectoryStaging
117+ } else {
118+ logger.Info("Using Let's Encrypt production environment")
119+ legoConfig.CADirURL = lego.LEDirectoryProduction
120+ }
121+
122+ // Create the lego client
123+ client, err := lego.NewClient(legoConfig)
124+ if err != nil {
125+ return nil, fmt.Errorf("failed to create lego client: %w", err)
126+ }
127+
128+ // --- ACME User Registration ---
129+ if user.Registration == nil {
130+ logger.Info("ACME user registration not found, attempting to register", "email", user.Email)
131+ reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
132+ if err != nil {
133+ // Check if it's an "already registered" error, which is fine
134+ // Note: Lego might handle this implicitly, but explicit check is safer.
135+ // The exact error type/message might vary depending on lego version and ACME server.
136+ // We might need to refine this error check based on observed behavior.
137+ if strings.Contains(err.Error(), "urn:ietf:params:acme:error:accountDoesNotExist") {
138+ // This might indicate a mismatch between loaded key and LE's record.
139+ // We might need to re-create the key and re-register, or query the account by key.
140+ // For now, just log and return error.
141+ logger.Error("ACME account registration failed: Account does not exist (key mismatch?)", "error", err)
142+ return nil, fmt.Errorf("failed to register ACME user (account not found): %w", err)
143+ } else if strings.Contains(err.Error(), "user already exists") { // Example, adjust as needed
144+ logger.Info("ACME user is already registered with the server.")
145+ // Try to resolve the account details if possible
146+ // reg, err = client.Registration.ResolveAccountByKey() // Requires lego client supporting this
147+ // if err != nil {
148+ // logger.Error("Failed to resolve existing ACME account by key", "error", err)
149+ // return nil, fmt.Errorf("failed to resolve existing ACME account: %w", err)
150+ // }
151+ // For now, let's assume if registration failed with "exists", we might need manual intervention
152+ // or a more sophisticated recovery mechanism. Returning error for safety.
153+ // If Obtain works despite this, we can reconsider.
154+ logger.Error("ACME user registration failed, but server indicates user exists. Manual check might be needed.", "error", err)
155+ return nil, fmt.Errorf("failed to register ACME user (already exists conflict): %w", err)
156+ } else {
157+ // Handle other registration errors
158+ logger.Error("ACME user registration failed", "error", err)
159+ return nil, fmt.Errorf("failed to register ACME user: %w", err)
160+ }
161+ } else {
162+ logger.Info("ACME user registered successfully", "uri", reg.URI)
163+ user.Registration = reg // Store the registration resource
164+
165+ // Save the user again, now with registration info
166+ if saveErr := saveAcmeUserToDB(user, db, queries); saveErr != nil {
167+ logger.Error("Failed to save ACME user registration info to DB", "error", saveErr)
168+ // Continue, as registration itself succeeded, but log the failure
169+ } else {
170+ logger.Debug("Saved ACME user registration info to DB")
171+ }
172+ }
173+ } else {
174+ logger.Debug("Existing ACME user registration loaded from DB", "uri", user.Registration.URI)
175+ // Optional: Could add a check here to ensure the registration is still valid
176+ }
177+ // --- End ACME User Registration ---
178+
179+ // Create the certificate manager struct
180+ cm := &CertificateManager{
181+ config: config,
182+ user: user,
183+ client: client,
184+ db: db,
185+ queries: queries,
186+ }
187+
188+ return cm, nil
189+}
190+
191+// loadAcmeUserFromDB loads the user's private key and registration info from the DB
192+func loadAcmeUserFromDB(email string, db *sql.DB, queries *queries.ProxyQueries) (*AcmeUser, error) {
193+ var privateKeyPEM string
194+ var registrationJSON sql.NullString
195+
196+ err := db.QueryRow(queries.GetAcmeAccountByEmail, email).Scan(&privateKeyPEM, &registrationJSON)
197+ if err != nil {
198+ if err == sql.ErrNoRows {
199+ logger.Debug("ACME user not found in DB", "email", email)
200+ return nil, sql.ErrNoRows // Return specific error
201+ }
202+ return nil, fmt.Errorf("failed to query ACME user: %w", err)
203+ }
204+
205+ // Parse the private key
206+ privateKey, err := certcrypto.ParsePEMPrivateKey([]byte(privateKeyPEM))
207+ if err != nil {
208+ return nil, fmt.Errorf("failed to parse private key from DB: %w", err)
209+ }
210+
211+ user := &AcmeUser{
212+ Email: email,
213+ key: privateKey,
214+ }
215+
216+ // Load the registration info if it exists
217+ if registrationJSON.Valid && registrationJSON.String != "" {
218+ // Need to unmarshal into a registration.Resource pointer
219+ reg := &registration.Resource{}
220+ err = json.Unmarshal([]byte(registrationJSON.String), reg)
221+ if err != nil {
222+ // Log the error but potentially continue without registration? Or return error?
223+ // Returning error is safer.
224+ logger.Error("Failed to unmarshal ACME registration info from DB", "email", email, "error", err)
225+ return nil, fmt.Errorf("failed to unmarshal registration info: %w", err)
226+ }
227+ user.Registration = reg // Assign the pointer
228+ logger.Debug("Loaded ACME user registration info from DB", "email", email, "uri", user.Registration.URI)
229+ } else {
230+ logger.Debug("ACME user registration info not found or null in DB", "email", email)
231+ }
232+
233+ return user, nil
234+}
235+
236+// saveAcmeUserToDB saves the user's private key and registration info to the DB
237+func saveAcmeUserToDB(user *AcmeUser, db *sql.DB, queries *queries.ProxyQueries) error {
238+ if user.key == nil {
239+ return fmt.Errorf("cannot save user without a private key")
240+ }
241+
242+ // Encode private key to PEM
243+ privateKeyPEM := certcrypto.PEMEncode(user.key)
244+
245+ // Marshal registration info to JSON (handle nil)
246+ var registrationJSON sql.NullString
247+ if user.Registration != nil {
248+ regBytes, err := json.Marshal(user.Registration)
249+ if err != nil {
250+ return fmt.Errorf("failed to marshal registration info: %w", err)
251+ }
252+ registrationJSON = sql.NullString{String: string(regBytes), Valid: true}
253+ }
254+
255+ now := time.Now().Format(time.RFC3339)
256+
257+ // Use InsertOrReplace query
258+ _, err := db.Exec(queries.InsertOrReplaceAcmeAccount,
259+ user.Email,
260+ string(privateKeyPEM),
261+ registrationJSON, // Pass the NullString directly
262+ now, // created_at (will be updated on replace)
263+ now, // updated_at
264+ )
265+
266+ if err != nil {
267+ return fmt.Errorf("failed to save ACME user to DB: %w", err)
268+ }
269+
270+ logger.Debug("Saved ACME user details to DB", "email", user.Email)
271+ return nil
272+}
273+
274+// SetupChallengeProvider sets up the appropriate challenge provider for a domain
275+func (cm *CertificateManager) SetupChallengeProvider(domainName string, proxy *Proxy) error {
276+ // Get domain configuration from database
277+ var domain struct {
278+ AcmeEnabled bool
279+ AcmeChallengeType sql.NullString
280+ AcmeDnsProvider sql.NullString
281+ AcmeDnsCredentialsRef sql.NullString
282+ }
283+
284+ err := proxy.app.GetDB().QueryRow(`
285+ SELECT acme_enabled, acme_challenge_type, acme_dns_provider, acme_dns_credentials_ref
286+ FROM domain WHERE name = ?
287+ `, domainName).Scan(&domain.AcmeEnabled, &domain.AcmeChallengeType, &domain.AcmeDnsProvider, &domain.AcmeDnsCredentialsRef)
288+
289+ if err != nil {
290+ return fmt.Errorf("failed to get domain configuration: %w", err)
291+ }
292+
293+ // Determine challenge type
294+ challengeType := domain.AcmeChallengeType.String
295+ if !domain.AcmeChallengeType.Valid {
296+ challengeType = proxy.config.DefaultChallengeType
297+ }
298+
299+ // Set up the appropriate challenge provider
300+ switch challengeType {
301+ case "http-01":
302+ // Use the configured HTTP challenge port
303+ port := proxy.config.DefaultHttpChallengePort
304+ if port == "" {
305+ port = "80"
306+ }
307+ provider := http01.NewProviderServer("", port)
308+ if err := cm.client.Challenge.SetHTTP01Provider(provider); err != nil {
309+ return fmt.Errorf("failed to set HTTP-01 provider: %w", err)
310+ }
311+
312+ case "dns-01":
313+ if domain.AcmeDnsProvider.String == "" {
314+ return fmt.Errorf("DNS provider not configured for domain %s", domainName)
315+ }
316+
317+ // Get DNS provider credentials from environment
318+ creds := cm.getDNSProviderCredentials(domain.AcmeDnsCredentialsRef.String)
319+ if creds == nil {
320+ return fmt.Errorf("DNS provider credentials not found for domain %s", domainName)
321+ }
322+
323+ // Create and configure the DNS provider
324+ provider, err := dns.NewDNSChallengeProviderByName(domain.AcmeDnsProvider.String)
325+ if err != nil {
326+ return fmt.Errorf("failed to create DNS provider: %w", err)
327+ }
328+
329+ // Configure the provider with credentials
330+ if err := provider.Present(domainName, "", ""); err != nil {
331+ return fmt.Errorf("failed to configure DNS provider: %w", err)
332+ }
333+
334+ // Set DNS timeouts
335+ if dnsProvider, ok := provider.(interface {
336+ SetPropagationTimeout(time.Duration)
337+ SetPollingInterval(time.Duration)
338+ }); ok {
339+ dnsProvider.SetPropagationTimeout(
340+ time.Duration(proxy.config.DefaultDnsChallengePropagationTimeout) * time.Second,
341+ )
342+ dnsProvider.SetPollingInterval(
343+ time.Duration(proxy.config.DefaultDnsChallengePollingInterval) * time.Second,
344+ )
345+ }
346+
347+ if err := cm.client.Challenge.SetDNS01Provider(provider); err != nil {
348+ return fmt.Errorf("failed to set DNS-01 provider: %w", err)
349+ }
350+
351+ default:
352+ return fmt.Errorf("unsupported challenge type: %s", challengeType)
353+ }
354+
355+ return nil
356+}
357+
358+// GetKeyAuthorization returns the key authorization for a token
359+func (cm *CertificateManager) GetKeyAuthorization(token string) (string, error) {
360+ // Create a new HTTP-01 provider server
361+ provider := http01.NewProviderServer("", cm.config.HttpPort)
362+
363+ // Present the challenge
364+ err := provider.Present("", token, "")
365+ if err != nil {
366+ return "", fmt.Errorf("failed to present challenge: %w", err)
367+ }
368+
369+ // Get the key authorization from the provider
370+ path := http01.ChallengePath(token)
371+ return path, nil
372+}
373+
374+// ObtainCertificate obtains a new certificate for a domain
375+func (cm *CertificateManager) ObtainCertificate(domainName string, proxy *Proxy) (*certificate.Resource, error) {
376+ logger.Debug("Obtaining certificate for domain", "domain", domainName)
377+
378+ // Set up the challenge provider
379+ if err := cm.SetupChallengeProvider(domainName, proxy); err != nil {
380+ return nil, fmt.Errorf("failed to set up challenge provider: %w", err)
381+ }
382+
383+ // Request the certificate
384+ request := certificate.ObtainRequest{
385+ Domains: []string{domainName},
386+ Bundle: true,
387+ }
388+
389+ cert, err := cm.client.Certificate.Obtain(request)
390+ if err != nil {
391+ return nil, fmt.Errorf("failed to obtain certificate: %w", err)
392+ }
393+
394+ // Update the certificate in the database
395+ if err := cm.updateCertificateInDB(domainName, cert, proxy); err != nil {
396+ logger.Error("Failed to update certificate in database", "error", err)
397+ }
398+
399+ return cert, nil
400+}
401+
402+// updateCertificateInDB updates the certificate information in the database
403+func (cm *CertificateManager) updateCertificateInDB(domainName string, cert *certificate.Resource, proxy *Proxy) error {
404+ // Get the domain ID
405+ var domainID string
406+ err := proxy.app.GetDB().QueryRow("SELECT id FROM domain WHERE name = ?", domainName).Scan(&domainID)
407+ if err != nil {
408+ return fmt.Errorf("failed to get domain ID: %w", err)
409+ }
410+
411+ // Parse the certificate to get expiry date
412+ x509Cert, err := certcrypto.ParsePEMCertificate(cert.Certificate)
413+ if err != nil {
414+ return fmt.Errorf("failed to parse certificate: %w", err)
415+ }
416+
417+ // Update or insert the certificate
418+ _, err = proxy.app.GetDB().Exec(`
419+ INSERT OR REPLACE INTO certificate (
420+ id, domain_id, cert_file, key_file, issued_at, expires_at, issuer, status
421+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
422+ `,
423+ cert.Domain,
424+ domainID,
425+ string(cert.Certificate),
426+ string(cert.PrivateKey),
427+ time.Now().Format(time.RFC3339),
428+ x509Cert.NotAfter.Format(time.RFC3339),
429+ "Let's Encrypt",
430+ "valid",
431+ )
432+
433+ if err != nil {
434+ return fmt.Errorf("failed to update certificate in database: %w", err)
435+ }
436+
437+ return nil
438+}
439+
440+// RenewCertificate renews an existing certificate
441+func (cm *CertificateManager) RenewCertificate(domainName string, proxy *Proxy) (*certificate.Resource, error) {
442+ logger.Debug("Renewing certificate for domain", "domain", domainName)
443+
444+ // Set up the challenge provider
445+ if err := cm.SetupChallengeProvider(domainName, proxy); err != nil {
446+ return nil, fmt.Errorf("failed to set up challenge provider: %w", err)
447+ }
448+
449+ // Get the existing certificate from the database
450+ var certFile, keyFile string
451+ err := proxy.app.GetDB().QueryRow(`
452+ SELECT cert_file, key_file FROM certificate c
453+ JOIN domain d ON c.domain_id = d.id
454+ WHERE d.name = ?
455+ `, domainName).Scan(&certFile, &keyFile)
456+ if err != nil {
457+ return nil, fmt.Errorf("failed to get existing certificate: %w", err)
458+ }
459+
460+ // Create certificate resource for renewal
461+ cert := &certificate.Resource{
462+ Domain: domainName,
463+ Certificate: []byte(certFile),
464+ PrivateKey: []byte(keyFile),
465+ }
466+
467+ // Renew the certificate
468+ _, err = cm.client.Certificate.Renew(*cert, true, false, "")
469+ if err != nil {
470+ return nil, fmt.Errorf("failed to renew certificate: %w", err)
471+ }
472+
473+ // Update the certificate in the database
474+ if err := cm.updateCertificateInDB(domainName, cert, proxy); err != nil {
475+ logger.Error("Failed to update certificate in database", "error", err)
476+ }
477+
478+ return cert, nil
479+}
480+
481+// GetCertificateForTLS returns a certificate for TLS handshake
482+func (cm *CertificateManager) GetCertificateForTLS(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
483+ logger.Debug("Getting certificate for TLS handshake", "server_name", hello.ServerName)
484+
485+ // Get the certificate resource
486+ cert, err := cm.GetCertificate(hello.ServerName)
487+ if err != nil {
488+ return nil, fmt.Errorf("failed to get certificate: %w", err)
489+ }
490+
491+ // Convert to tls.Certificate
492+ tlsCert, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
493+ if err != nil {
494+ return nil, fmt.Errorf("failed to parse certificate: %w", err)
495+ }
496+
497+ return &tlsCert, nil
498+}
499+
500+// GetCertificate retrieves a certificate from the database
501+func (cm *CertificateManager) GetCertificate(domainName string) (*certificate.Resource, error) {
502+ // Try to obtain the certificate if it doesn't exist
503+ request := certificate.ObtainRequest{
504+ Domains: []string{domainName},
505+ Bundle: true,
506+ }
507+ cert, err := cm.client.Certificate.Obtain(request)
508+ if err != nil {
509+ return nil, fmt.Errorf("failed to obtain certificate: %w", err)
510+ }
511+ return cert, nil
512+}
513+
514+// GetAutocertManager returns the autocert manager
515+func (cm *CertificateManager) GetAutocertManager() *tls.Config {
516+ return &tls.Config{
517+ GetCertificate: cm.GetCertificateForTLS,
518+ MinVersion: tls.VersionTLS12,
519+ }
520+}
521+
522+// getDNSProviderCredentials retrieves DNS provider credentials from environment variables
523+func (cm *CertificateManager) getDNSProviderCredentials(ref string) map[string]string {
524+ if ref == "" {
525+ return nil
526+ }
527+
528+ creds := make(map[string]string)
529+ prefix := fmt.Sprintf("GORDON_DNS_CRED_%s_", ref)
530+
531+ for _, env := range os.Environ() {
532+ if strings.HasPrefix(env, prefix) {
533+ parts := strings.SplitN(env, "=", 2)
534+ if len(parts) == 2 {
535+ key := strings.TrimPrefix(parts[0], prefix)
536+ creds[key] = parts[1]
537+ }
538+ }
539+ }
540+
541+ if len(creds) == 0 {
542+ return nil
543+ }
544+
545+ return creds
546+}
D internal/proxy/certificates.go +0 -2280 large diff (2280 lines changed): collapsed
M internal/proxy/containers.go +20 -49
@@ -111,63 +111,34 @@ func (p *Proxy) scanContainersAndCheckCertificates() error {
111111
112112 // Check certificates for each domain
113113 for _, domain := range domains {
114- // Check certificate for the full domain
115- hasCert, certType := p.checkCertificateInCache(domain)
114+ // With the refactored CertificateManager, we don't need to manually check
115+ // the cache here. We can just attempt to ensure a certificate exists
116+ // by calling requestDomainCertificate, which delegates to GetCertificate.
117+ // GetCertificate will use autocert's cache and request a new cert only if needed.
116118
117- if certType == "staging" && p.config.LetsEncryptMode == "production" {
118- logger.Info("Found staging certificate but production mode is enabled",
119- "domain", domain,
120- "action", "will request new production certificate")
121- }
119+ logger.Debug("Ensuring certificate exists for domain",
120+ "domain", domain,
121+ "container", containerName)
122122
123- if hasCert {
124- logger.Debug("Certificate exists for domain",
125- "domain", domain,
126- "container", containerName)
127- } else {
128- logger.Info("Certificate not found in cache, requesting",
129- "domain", domain,
130- "container", containerName)
131-
132- // Request certificate (non-blocking)
133- go func(domain string) {
134- // Set flag to indicate we're processing a specific domain
135- p.processingSpecificDomain = true
136- // Try to request certificate for domain
137- p.requestDomainCertificate(domain)
138- // Reset flag when done
139- p.processingSpecificDomain = false
140- }(domain)
141- }
123+ // Request certificate (non-blocking).
124+ // GetCertificate handles checking cache/validity and requesting if necessary.
125+ go p.requestDomainCertificate(domain) // Simplified call
142126
143- // If this is a subdomain, check the parent domain too
127+ // If this is a subdomain, ensure the parent domain cert exists too
144128 parts := strings.Split(domain, ".")
145129 if len(parts) >= 3 {
146- // This is a subdomain (e.g., sub.domain.com)
147130 parentDomain := strings.Join(parts[1:], ".")
148131
149132 // Only check parent domain if it's not an ordinary TLD
150- if len(parts) > 2 && !isCommonTLD(parts[len(parts)-1]) {
151- hasCert, certType := p.checkCertificateInCache(parentDomain)
152- if certType == "staging" && p.config.LetsEncryptMode == "production" {
153- logger.Info("Found staging certificate but production mode is enabled",
154- "domain", parentDomain,
155- "action", "will request new production certificate")
156- }
157- if !hasCert {
158- logger.Info("Certificate not found for parent domain",
159- "domain", parentDomain,
160- "container", containerName)
161-
162- // Request certificate for parent domain also
163- go func(domain string) {
164- // Set flag to indicate we're processing a specific domain
165- p.processingSpecificDomain = true
166- p.requestDomainCertificate(domain)
167- // Reset flag when done
168- p.processingSpecificDomain = false
169- }(parentDomain)
170- }
133+ // and not the same as the root domain (to avoid redundant checks)
134+ if len(parts) > 2 && !isCommonTLD(parts[len(parts)-1]) && parentDomain != p.app.GetConfig().Http.Domain {
135+ logger.Debug("Ensuring certificate exists for parent domain",
136+ "parent_domain", parentDomain,
137+ "original_domain", domain,
138+ "container", containerName)
139+
140+ // Request certificate for parent domain also
141+ go p.requestDomainCertificate(parentDomain) // Simplified call
171142 }
172143 }
173144 }
M internal/proxy/core.go +228 -40
@@ -1,11 +1,16 @@
11 package proxy
22
33 import (
4+ "context"
5+ "fmt"
46 "net"
7+ "os"
8+ "strings"
59 "sync"
610 "time"
711
812 "crypto/tls"
13+ "database/sql"
914 "net/http"
1015
1116 "github.com/bnema/gordon/internal/common"
@@ -14,7 +19,6 @@ import (
1419 "github.com/bnema/gordon/pkg/docker"
1520 "github.com/bnema/gordon/pkg/logger"
1621 "github.com/labstack/echo/v4"
17- "golang.org/x/crypto/acme/autocert"
1822 )
1923
2024 // - core.go - Core proxy structure and constructor
@@ -29,39 +33,32 @@ type Proxy struct {
2933 config common.ReverseProxyConfig
3034 app interfaces.AppInterface
3135 httpsServer *echo.Echo
32- httpServer *echo.Echo
3336 routes map[string]*ProxyRouteInfo
3437 mu sync.RWMutex
35- certManager *autocert.Manager
36- fallbackCert *tls.Certificate // Fallback self-signed certificate
3738 serverStarted bool
39+ ctx context.Context // Context for storing shared data
3840
39- // Flag to track specific domain certificate operations
41+ // Flag to track specific domain certificate operations (May need review if CertManager handles this)
4042 processingSpecificDomain bool
4143
4244 // Store Gordon's own container ID at startup for reliable identification
4345 gordonContainerID string
4446
45- reverseProxyClient *http.Client
46- processingGlobalCert bool
47- dbMaxRetries int
48- dbRetryBaseDelay time.Duration
49- acmeManager *autocert.Manager
50- acmePerDomainRefreshMutex sync.Mutex
51- acmeDirCache autocert.DirCache
52- shutdown chan struct{} // Channel for signaling shutdown to background goroutines
47+ reverseProxyClient *http.Client
48+ dbMaxRetries int
49+ dbRetryBaseDelay time.Duration
50+ shutdown chan struct{} // Channel for signaling shutdown to background goroutines
5351
5452 // Track recently created containers to prevent recreation logic
5553 recentContainers map[string]time.Time
5654 recentContainersMu sync.RWMutex
5755 containerCooldownPeriod time.Duration
5856
59- // Upstream proxy detection
60- upstreamProxyDetected bool
61- upstreamProxyMu sync.RWMutex
57+ // Certificate Management
58+ certificateManager *CertificateManager // New refactored manager
6259
63- // SQL queries
64- queries *queries.ProxyQueries
60+ // SQL Queries
61+ Queries *queries.ProxyQueries
6562 }
6663
6764 // NewProxy creates a new instance of the reverse proxy
@@ -80,6 +77,7 @@ func NewProxy(app interfaces.AppInterface) (*Proxy, error) {
8077 app: app,
8178 serverStarted: false,
8279 shutdown: make(chan struct{}),
80+ ctx: context.Background(),
8381 }, nil
8482 }
8583
@@ -96,6 +94,7 @@ func NewProxy(app interfaces.AppInterface) (*Proxy, error) {
9694 app: app,
9795 serverStarted: false,
9896 shutdown: make(chan struct{}),
97+ ctx: context.Background(),
9998 }, nil
10099 }
101100
@@ -103,10 +102,6 @@ func NewProxy(app interfaces.AppInterface) (*Proxy, error) {
103102 httpsServer := echo.New()
104103 httpsServer.HideBanner = true
105104
106- // Set up the echo server for HTTP traffic (redirects to HTTPS)
107- httpServer := echo.New()
108- httpServer.HideBanner = true
109-
110105 // Initialize container storage and cooldown settings
111106 recentContainers := make(map[string]time.Time)
112107 containerCooldownPeriod := time.Duration(10) * time.Second
@@ -145,10 +140,10 @@ func NewProxy(app interfaces.AppInterface) (*Proxy, error) {
145140
146141 // Create the proxy instance
147142 p := &Proxy{
148- config: config,
149- app: app,
150- httpsServer: httpsServer,
151- httpServer: httpServer,
143+ config: config,
144+ app: app,
145+ httpsServer: httpsServer,
146+ // httpServer: httpServer, // Removed assignment
152147 routes: make(map[string]*ProxyRouteInfo),
153148 reverseProxyClient: reverseProxyClient,
154149 dbMaxRetries: dbMaxRetries,
@@ -156,20 +151,51 @@ func NewProxy(app interfaces.AppInterface) (*Proxy, error) {
156151 recentContainers: recentContainers,
157152 containerCooldownPeriod: containerCooldownPeriod,
158153 shutdown: make(chan struct{}),
159- queries: proxyQueries,
154+ Queries: proxyQueries,
155+ ctx: context.Background(),
156+ }
157+
158+ // --- Certificate Manager Setup ---
159+ // Determine cert directory
160+ certDir := config.CertDir
161+ if certDir == "" {
162+ certDir = app.GetConfig().General.StorageDir + "/certs"
160163 }
161164
162- // Setup the certificate manager for HTTPS
163- p.setupCertManager()
165+ // Initial upstream proxy detection (before creating CertManager)
166+ initialBehindTLSProxy := detectInitialUpstreamProxy(&config)
164167
165- // Verify the certificate manager was properly initialized
166- if p.certManager == nil {
167- logger.Error("Certificate manager initialization failed",
168- "solution", "Check certificate directory permissions and configuration")
168+ // Create Certificate Manager Config
169+ certManagerConfig := CertManagerConfig{
170+ CertDir: certDir,
171+ Email: config.Email,
172+ Mode: config.LetsEncryptMode, // staging or production
173+ SkipCertificates: config.SkipCertificates,
174+ BehindTLSProxy: initialBehindTLSProxy, // Pass initial detection result
175+ AdminDomain: app.GetConfig().Http.FullDomain(),
176+ RootDomain: app.GetConfig().Http.Domain,
177+ // RouteValidator: p.isHostInRoutes, // TODO: Pass a function if needed by hostPolicy
178+ }
179+
180+ // Create the Certificate Manager
181+ certMgr, err := NewCertificateManager(certManagerConfig, app, p.Queries)
182+ if err != nil {
183+ logger.Error("Failed to initialize Certificate Manager", "error", err)
184+ // Return an error to prevent starting a broken proxy
185+ return nil, fmt.Errorf("failed to initialize certificate manager: %w", err)
169186 } else {
170187 logger.Debug("Certificate manager successfully initialized")
188+ p.certificateManager = certMgr // Store the new manager
189+ }
190+
191+ // Ensure admin domain has proper ACME configuration
192+ if err := p.ensureAdminDomainConfig(); err != nil {
193+ logger.Error("Failed to ensure admin domain configuration", "error", err)
194+ return nil, fmt.Errorf("failed to ensure admin domain configuration: %w", err)
171195 }
172196
197+ // --- End Certificate Manager Setup ---
198+
173199 // Detect our container ID for later use
174200 p.detectGordonContainer()
175201
@@ -181,13 +207,6 @@ func (p *Proxy) Close() {
181207 // Signal all background goroutines to stop
182208 close(p.shutdown)
183209
184- // Close the HTTP and HTTPS servers if they exist
185- if p.httpServer != nil {
186- if err := p.httpServer.Close(); err != nil {
187- logger.Error("Error closing HTTP server", "error", err)
188- }
189- }
190-
191210 if p.httpsServer != nil {
192211 if err := p.httpsServer.Close(); err != nil {
193212 logger.Error("Error closing HTTPS server", "error", err)
@@ -196,3 +215,172 @@ func (p *Proxy) Close() {
196215
197216 logger.Info("Proxy resources cleaned up")
198217 }
218+
219+// detectInitialUpstreamProxy checks environment variables for signs of an upstream proxy
220+// This is used during initialization before the CertificateManager is created.
221+func detectInitialUpstreamProxy(config *common.ReverseProxyConfig) bool {
222+ // Check if detection is explicitly disabled
223+ if !config.DetectUpstreamProxy {
224+ logger.Debug("Upstream proxy detection disabled by config")
225+ return false
226+ }
227+
228+ // Check for common environment variables set by upstream proxies
229+ if os.Getenv("HTTPS") == "on" ||
230+ os.Getenv("HTTP_X_FORWARDED_PROTO") == "https" ||
231+ os.Getenv("HTTP_X_FORWARDED_SSL") == "on" {
232+ logger.Info("Initial upstream TLS-terminating proxy detected via environment variables",
233+ "detection_method", "environment_variables",
234+ "https", os.Getenv("HTTPS"),
235+ "x_forwarded_proto", os.Getenv("HTTP_X_FORWARDED_PROTO"),
236+ "x_forwarded_ssl", os.Getenv("HTTP_X_FORWARDED_SSL"))
237+ return true
238+ }
239+
240+ // Check Cloudflare headers
241+ if cfVisitor := os.Getenv("HTTP_CF_VISITOR"); cfVisitor != "" {
242+ if strings.Contains(cfVisitor, "\"scheme\":\"https\"") {
243+ logger.Info("Initial Cloudflare proxy detected via environment variables",
244+ "detection_method", "cloudflare_headers",
245+ "cf_visitor", cfVisitor)
246+ return true
247+ }
248+ }
249+
250+ logger.Debug("No initial upstream proxy detected via environment variables")
251+ return false
252+}
253+
254+// requestDomainCertificate requests a certificate for a domain
255+func (p *Proxy) requestDomainCertificate(domainName string) error {
256+ logger.Debug("Requesting certificate for domain", "domain", domainName)
257+
258+ // Check if we're already processing this domain
259+ if p.processingSpecificDomain {
260+ logger.Debug("Already processing a specific domain, skipping", "domain", domainName)
261+ return nil
262+ }
263+
264+ // Get domain configuration
265+ var acmeEnabled bool
266+ // Use sql.NullString for potentially nullable string columns
267+ var acmeChallengeType, acmeDnsProvider, acmeDnsCredentialsRef sql.NullString
268+ err := p.app.GetDB().QueryRow(p.Queries.GetDomainAcmeConfig, domainName).Scan(
269+ &acmeEnabled, &acmeChallengeType, &acmeDnsProvider, &acmeDnsCredentialsRef,
270+ )
271+ if err != nil {
272+ // Handle case where the domain might not exist in the acme_configs table yet
273+ if err == sql.ErrNoRows {
274+ logger.Debug("No ACME configuration found for domain, assuming ACME is disabled", "domain", domainName)
275+ return nil // Treat as ACME disabled if no config exists
276+ }
277+ logger.Error("Failed to get domain ACME configuration", "domain", domainName, "error", err)
278+ return err
279+ }
280+
281+ if !acmeEnabled {
282+ logger.Debug("ACME not enabled for domain", "domain", domainName)
283+ return nil
284+ }
285+
286+ // Check if certificate exists and is valid
287+ var certFile, keyFile string
288+ var issuedAtStr, expiresAtStr string // Read timestamps as strings first
289+ var issuer, status string
290+ var issuedAt, expiresAt time.Time // Keep these for use after parsing
291+
292+ // Scan from DB, reading timestamps as strings
293+ err = p.app.GetDB().QueryRow(p.Queries.GetCertificateByDomain, domainName).Scan(
294+ &certFile, &keyFile, &issuedAtStr, &expiresAtStr, &issuer, &status,
295+ )
296+
297+ // Handle potential sql.ErrNoRows if the certificate doesn't exist yet
298+ if err != nil && err != sql.ErrNoRows {
299+ logger.Error("Failed to check existing certificate", "domain", domainName, "error", err)
300+ return err
301+ }
302+
303+ // If a certificate was found (no error or ErrNoRows), parse the timestamps
304+ if err == nil {
305+ issuedAt, err = time.Parse(time.RFC3339, issuedAtStr)
306+ if err != nil {
307+ logger.Error("Failed to parse issued_at timestamp from DB", "domain", domainName, "value", issuedAtStr, "error", err)
308+ // Return the parse error as it indicates data corruption or an unexpected format
309+ return fmt.Errorf("failed to parse issued_at for domain %s: %w", domainName, err)
310+ }
311+ expiresAt, err = time.Parse(time.RFC3339, expiresAtStr)
312+ if err != nil {
313+ logger.Error("Failed to parse expires_at timestamp from DB", "domain", domainName, "value", expiresAtStr, "error", err)
314+ // Return the parse error
315+ return fmt.Errorf("failed to parse expires_at for domain %s: %w", domainName, err)
316+ }
317+ logger.Debug("Successfully scanned and parsed certificate timestamps", "domain", domainName, "issued", issuedAt, "expires", expiresAt)
318+ }
319+ // Note: If err was sql.ErrNoRows, issuedAt/expiresAt will remain zero time.Time, which is handled correctly later.
320+
321+ // If certificate exists and is valid
322+ if err == nil && status == "valid" {
323+ // Check if certificate needs renewal
324+ renewBefore := time.Duration(p.config.RenewBefore) * 24 * time.Hour
325+ if time.Now().Add(renewBefore).Before(expiresAt) {
326+ logger.Debug("Certificate is still valid and does not need renewal",
327+ "domain", domainName,
328+ "expires_at", expiresAt,
329+ "renew_before_days", p.config.RenewBefore)
330+ return nil // Certificate is valid, no action needed
331+ }
332+ logger.Debug("Certificate requires renewal", "domain", domainName, "expires_at", expiresAt)
333+ } else if err == sql.ErrNoRows {
334+ logger.Debug("No existing certificate found for domain", "domain", domainName)
335+ } else {
336+ logger.Debug("Existing certificate is not valid", "domain", domainName, "status", status, "scan_error", err)
337+ }
338+
339+ // --- Proceed with obtaining/renewing certificate ---
340+
341+ // Check if required ACME config fields are present now that we know ACME is enabled
342+ if !acmeChallengeType.Valid || acmeChallengeType.String == "" {
343+ err := fmt.Errorf("ACME challenge type is missing for domain %s", domainName)
344+ logger.Error(err.Error())
345+ return err
346+ }
347+ // If DNS challenge, provider and credentials ref are required
348+ if acmeChallengeType.String == "dns-01" {
349+ if !acmeDnsProvider.Valid || acmeDnsProvider.String == "" {
350+ err := fmt.Errorf("ACME DNS provider is missing for domain %s using dns-01 challenge", domainName)
351+ logger.Error(err.Error())
352+ return err
353+ }
354+ if !acmeDnsCredentialsRef.Valid || acmeDnsCredentialsRef.String == "" {
355+ err := fmt.Errorf("ACME DNS credentials reference is missing for domain %s using dns-01 challenge", domainName)
356+ logger.Error(err.Error())
357+ return err
358+ }
359+ // Log the DNS provider being used (optional, but helpful for debugging)
360+ logger.Debug("Using DNS provider for ACME challenge", "domain", domainName, "provider", acmeDnsProvider.String)
361+ }
362+
363+ // Set processing flag
364+ p.processingSpecificDomain = true
365+ defer func() {
366+ p.processingSpecificDomain = false
367+ }()
368+
369+ // Request new certificate using the CertificateManager
370+ cert, err := p.certificateManager.ObtainCertificate(domainName, p) // Pass 'p' (Proxy instance)
371+ if err != nil {
372+ logger.Error("Failed to obtain/renew certificate", "domain", domainName, "error", err)
373+ // Update certificate status to 'failed' in DB?
374+ // _, dbErr := p.app.GetDB().Exec(p.Queries.UpdateCertificateStatus, "failed", domainName)
375+ // if dbErr != nil {
376+ // logger.Error("Failed to update certificate status after obtain failure", "domain", domainName, "db_error", dbErr)
377+ // }
378+ return err // Return the obtain error
379+ }
380+
381+ logger.Info("Successfully obtained certificate",
382+ "domain", domainName,
383+ "cert_url", cert.CertURL)
384+
385+ return nil
386+}
M internal/proxy/db_helpers.go +60 -0
@@ -2,8 +2,11 @@ package proxy
22
33 import (
44 "database/sql"
5+ "fmt"
6+ "time"
57
68 "github.com/bnema/gordon/internal/db"
9+ "github.com/bnema/gordon/pkg/logger"
710 )
811
912 // dbExecWithRetry performs a database execution with retry logic for handling locked database
@@ -25,3 +28,60 @@ func (p *Proxy) dbBeginWithRetry() (*sql.Tx, error) {
2528 func txExecWithRetry(tx *sql.Tx, query string, args ...interface{}) (sql.Result, error) {
2629 return db.TxExecWithRetry(tx, query, args...)
2730 }
31+
32+// ensureAdminDomainConfig ensures the admin domain has proper ACME configuration in the database
33+func (p *Proxy) ensureAdminDomainConfig() error {
34+ adminDomain := p.app.GetConfig().Http.FullDomain()
35+ adminAccountID := "" // Variable to store the admin account ID
36+
37+ // Check if domain exists
38+ var exists bool
39+ err := p.app.GetDB().QueryRow(p.Queries.CheckDomainExists, adminDomain).Scan(&exists)
40+ if err != nil {
41+ return fmt.Errorf("failed to check admin domain existence: %w", err)
42+ }
43+
44+ // Get the first account ID (assumed to be the admin)
45+ err = p.app.GetDB().QueryRow(p.Queries.GetFirstAccount).Scan(&adminAccountID)
46+ if err != nil {
47+ if err == sql.ErrNoRows {
48+ // This case should ideally not happen if account setup is done first
49+ logger.Error("No accounts found in the database. Cannot associate admin domain.")
50+ return fmt.Errorf("no accounts found in database")
51+ }
52+ return fmt.Errorf("failed to get admin account ID: %w", err)
53+ }
54+ if adminAccountID == "" {
55+ // Sanity check
56+ logger.Error("Admin account ID is empty. Cannot associate admin domain.")
57+ return fmt.Errorf("admin account ID is empty")
58+ }
59+
60+ if !exists {
61+ // Create the admin domain with proper ACME config and account ID
62+ now := time.Now().Format(time.RFC3339)
63+ _, err = p.app.GetDB().Exec(p.Queries.CreateAdminDomain,
64+ adminDomain, // ID (assuming domain name is unique ID here?)
65+ adminDomain, // Name
66+ adminAccountID, // Account ID (NEW)
67+ now, // Created At
68+ now, // Updated At
69+ true, // ACME Enabled
70+ "http-01", // ACME Challenge Type
71+ )
72+ if err != nil {
73+ return fmt.Errorf("failed to create admin domain: %w", err)
74+ }
75+ logger.Info("Created admin domain with ACME configuration", "domain", adminDomain, "account_id", adminAccountID)
76+ } else {
77+ // Update existing domain to ensure ACME is enabled (no need to update account_id here, assume it's set correctly)
78+ now := time.Now().Format(time.RFC3339)
79+ _, err = p.app.GetDB().Exec(p.Queries.UpdateAdminDomainAcme, now, adminDomain)
80+ if err != nil {
81+ return fmt.Errorf("failed to update admin domain ACME config: %w", err)
82+ }
83+ logger.Info("Updated admin domain ACME configuration", "domain", adminDomain)
84+ }
85+
86+ return nil
87+}
M internal/proxy/middleware.go +18 -14
@@ -21,22 +21,20 @@ const RequestIDKey = "request_id"
2121
2222 // setupMiddleware configures middleware for both HTTP and HTTPS servers
2323 func (p *Proxy) setupMiddleware() {
24- // Configure Echo to trust X-Forwarded-* headers
25- // Since we are the reverse proxy, we need to make sure we identify client IPs correctly
26- p.httpsServer.IPExtractor = echo.ExtractIPFromXFFHeader()
27- p.httpServer.IPExtractor = echo.ExtractIPFromXFFHeader()
24+ // Configure Echo IP extraction. Since the proxy is edge-facing (directly exposed),
25+ // ignore X-Forwarded-For and use the direct remote address.
26+ p.httpsServer.IPExtractor = echo.ExtractIPDirect()
2827
2928 // Add UUID generator middleware for both HTTP and HTTPS servers
3029 p.httpsServer.Use(p.createRequestIDMiddleware())
31- p.httpServer.Use(p.createRequestIDMiddleware())
3230
3331 // Check if rate limiting is disabled via configuration
3432 if p.config.EnableRateLimit {
3533 // Create a new Starskey rate limiter store for HTTPS server
3634 httpsRateLimiter, err := kv.NewStarskeyRateLimiterStore(
3735 "./data/ratelimiter/https", // Path to store rate limit data
38- 10, // Rate: 10 requests per second (lowered from 10)
39- 30, // Burst: 30 requests (lowered from 30)
36+ 10, // Rate: 10 requests per second
37+ 30, // Burst: 30 requests
4038 3*time.Minute, // Expires in 3 minutes
4139 )
4240 if err != nil {
@@ -46,12 +44,14 @@ func (p *Proxy) setupMiddleware() {
4644 p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
4745 Store: middleware.NewRateLimiterMemoryStoreWithConfig(
4846 middleware.RateLimiterMemoryStoreConfig{
49- Rate: rate.Limit(10), // 10 requests per second (lowered from 10)
50- Burst: 30, // Burst of 30 requests (lowered from 30)
47+ Rate: rate.Limit(10), // 10 requests per second
48+ Burst: 30, // Burst of 30 requests
5149 ExpiresIn: 3 * time.Minute, // Store expiration
5250 },
5351 ),
5452 DenyHandler: func(context echo.Context, identifier string, err error) error {
53+ logger.Info("[RateLimit Debug] DenyHandler invoked for Memory Fallback", "identifier", identifier, "error", err.Error())
54+
5555 logger.Warn("Rate limit exceeded",
5656 "ip", identifier,
5757 "path", context.Request().URL.Path,
@@ -68,6 +68,8 @@ func (p *Proxy) setupMiddleware() {
6868 Store: httpsRateLimiter,
6969 IdentifierExtractor: func(ctx echo.Context) (string, error) {
7070 id := ctx.RealIP()
71+ logger.Info("[RateLimit Debug] IdentifierExtractor called", "ip_identified", id, "remote_addr", ctx.Request().RemoteAddr)
72+
7173 logger.Debug("Rate limiting HTTPS request",
7274 "ip", id,
7375 "remote_addr", ctx.Request().RemoteAddr)
@@ -78,6 +80,8 @@ func (p *Proxy) setupMiddleware() {
7880 return id, nil
7981 },
8082 DenyHandler: func(context echo.Context, identifier string, err error) error {
83+ logger.Info("[RateLimit Debug] DenyHandler invoked for Starskey", "identifier", identifier, "error", err.Error())
84+
8185 logger.Warn("HTTPS rate limit exceeded",
8286 "ip", identifier,
8387 "path", context.Request().URL.Path,
@@ -124,7 +128,7 @@ func (p *Proxy) setupMiddleware() {
124128 logger.Error("Failed to create HTTP rate limiter store, falling back to memory store", "error", err)
125129
126130 // Fallback to memory store if Starskey store creation fails
127- p.httpServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
131+ p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
128132 Store: middleware.NewRateLimiterMemoryStoreWithConfig(
129133 middleware.RateLimiterMemoryStoreConfig{
130134 Rate: rate.Limit(2), // 2 requests per second (lowered from 10)
@@ -145,7 +149,7 @@ func (p *Proxy) setupMiddleware() {
145149 logger.Debug("Using Starskey rate limiter for HTTP server")
146150
147151 // Add custom Starskey rate limiter middleware
148- p.httpServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
152+ p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
149153 Store: httpRateLimiter,
150154 IdentifierExtractor: func(ctx echo.Context) (string, error) {
151155 id := ctx.RealIP()
@@ -180,16 +184,16 @@ func (p *Proxy) setupMiddleware() {
180184
181185 // Add custom logger middleware with request ID for HTTP server if enabled in config
182186 if p.config.EnableHttpLogs {
183- p.httpServer.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
187+ p.httpsServer.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
184188 Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}","status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}","bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
185189 CustomTimeFormat: "2006-01-02T15:04:05.00000Z07:00",
186190 Skipper: func(c echo.Context) bool {
187191 return !p.config.EnableHttpLogs
188192 },
189193 }))
190- logger.Debug("HTTP request logging enabled for HTTP server")
194+ logger.Debug("HTTP request logging enabled for HTTPS server")
191195 } else {
192- logger.Debug("HTTP request logging disabled for HTTP server")
196+ logger.Debug("HTTP request logging disabled for HTTPS server")
193197 }
194198 }
195199
M internal/proxy/routes.go +53 -43
@@ -52,7 +52,7 @@ func (p *Proxy) loadRoutes() error {
5252 }
5353
5454 // Query the database for active proxy routes using retry mechanism
55- rows, err := p.dbQueryWithRetry(p.queries.GetActiveRoutes)
55+ rows, err := p.dbQueryWithRetry(p.Queries.GetActiveRoutes)
5656 if err != nil {
5757 if err == sql.ErrNoRows {
5858 logger.Info("No active proxy routes found")
@@ -163,11 +163,6 @@ func (p *Proxy) configureRoutes() {
163163 if p.httpsServer != nil {
164164 p.httpsServer.Any("/*", p.proxyRequest)
165165 }
166-
167- // Also add the handler to the HTTP server for HTTP-01 challenges
168- if p.httpServer != nil {
169- p.httpServer.Any("/*", p.proxyRequest)
170- }
171166 }
172167
173168 // AddRoute adds a new route to the database and reloads the proxy
@@ -230,7 +225,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr
230225
231226 // Get the account ID - default to the first account if available
232227 var accountID string
233- err = tx.QueryRow(p.queries.GetFirstAccount).Scan(&accountID)
228+ err = tx.QueryRow(p.Queries.GetFirstAccount).Scan(&accountID)
234229 if err != nil {
235230 if err == sql.ErrNoRows {
236231 logger.Warn("No account found when adding route, domains will have NULL account_id")
@@ -242,14 +237,14 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr
242237
243238 // Check if the domain exists
244239 var domainID string
245- err = tx.QueryRow(p.queries.GetDomainByName, hostname).Scan(&domainID)
240+ err = tx.QueryRow(p.Queries.GetDomainByName, hostname).Scan(&domainID)
246241 if err != nil {
247242 if err == sql.ErrNoRows {
248243 // Domain doesn't exist, create it
249244 domainID = uuid.New().String()
250245 now := time.Now().Format(time.RFC3339)
251246 _, err = txExecWithRetry(tx,
252- p.queries.InsertDomain,
247+ p.Queries.InsertDomain,
253248 domainID, hostname, accountID, now, now,
254249 )
255250 if err != nil {
@@ -278,7 +273,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr
278273 // Domain part doesn't exist, create it
279274 domainPartID = uuid.New().String()
280275 _, err = txExecWithRetry(tx,
281- p.queries.InsertDomain,
276+ p.Queries.InsertDomain,
282277 domainPartID, domainPart, accountID, now, now,
283278 )
284279 if err != nil {
@@ -305,7 +300,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr
305300 var existingRouteID string
306301 var existingContainerID string
307302 var existingContainerPort string
308- err = tx.QueryRow(p.queries.GetRouteByDomain, domainID).Scan(&existingRouteID, &existingContainerID, &existingContainerPort)
303+ err = tx.QueryRow(p.Queries.GetRouteByDomain, domainID).Scan(&existingRouteID, &existingContainerID, &existingContainerPort)
309304
310305 if err == nil {
311306 // Route exists, update it
@@ -326,16 +321,9 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr
326321 "new_port", containerPort)
327322 }
328323
324+ // Use the UpdateRoute query from the queries package
329325 _, err = txExecWithRetry(tx,
330- `UPDATE proxy_route SET
331- container_id = ?,
332- container_ip = ?,
333- container_port = ?,
334- protocol = ?,
335- path = ?,
336- active = ?,
337- updated_at = ?
338- WHERE id = ?`,
326+ p.Queries.UpdateRoute,
339327 containerID, containerIP, containerPort, protocol, path, true, now, existingRouteID,
340328 )
341329 if err != nil {
@@ -349,7 +337,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr
349337 routeID := uuid.New().String()
350338 now := time.Now().Format(time.RFC3339)
351339 _, err = txExecWithRetry(tx,
352- p.queries.InsertRoute,
340+ p.Queries.InsertRoute,
353341 routeID, domainID, containerID, containerIP, containerPort,
354342 protocol, path, true, now, now,
355343 )
@@ -414,7 +402,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr
414402 // Set the flag to indicate we're processing a specific domain
415403 p.processingSpecificDomain = true
416404 // Try to request certificate for domain
417- p.requestDomainCertificate(domain)
405+ p.requestCertificateForDomain(domain)
418406 // Reset the flag after we're done
419407 p.processingSpecificDomain = false
420408 }(hostname)
@@ -442,7 +430,7 @@ func (p *Proxy) RemoveRoute(domainName string) error {
442430
443431 // Get the domain ID
444432 var domainID string
445- err = tx.QueryRow(p.queries.GetDomainByName, domainName).Scan(&domainID)
433+ err = tx.QueryRow(p.Queries.GetDomainByName, domainName).Scan(&domainID)
446434 if err != nil {
447435 if err == sql.ErrNoRows {
448436 return fmt.Errorf("domain not found: %s", domainName)
@@ -451,13 +439,13 @@ func (p *Proxy) RemoveRoute(domainName string) error {
451439 }
452440
453441 // Delete the route with retry
454- _, err = txExecWithRetry(tx, p.queries.DeleteRouteByDomainID, domainID)
442+ _, err = txExecWithRetry(tx, p.Queries.DeleteRouteByDomainID, domainID)
455443 if err != nil {
456444 return fmt.Errorf("failed to delete route: %w", err)
457445 }
458446
459447 // Delete the domain with retry
460- _, err = txExecWithRetry(tx, p.queries.DeleteDomainByID, domainID)
448+ _, err = txExecWithRetry(tx, p.Queries.DeleteDomainByID, domainID)
461449 if err != nil {
462450 return fmt.Errorf("failed to delete domain: %w", err)
463451 }
@@ -521,7 +509,7 @@ func (p *Proxy) Reload() error {
521509 newRoutes := make(map[string]*ProxyRouteInfo)
522510
523511 // Load routes from the database into the new map
524- rows, err := p.app.GetDB().Query(p.queries.GetActiveRoutes)
512+ rows, err := p.app.GetDB().Query(p.Queries.GetActiveRoutes)
525513 if err != nil {
526514 if err == sql.ErrNoRows {
527515 logger.Info("No active proxy routes found during reload")
@@ -644,12 +632,6 @@ func (p *Proxy) Reload() error {
644632 // Rebuild route configuration
645633 p.configureRoutes()
646634
647- // Force recreation of the route handlers if servers are initialized
648- if p.httpServer != nil {
649- logger.Debug("Refreshing HTTP routes")
650- p.httpServer.Routes()
651- }
652-
653635 if p.httpsServer != nil {
654636 logger.Debug("Refreshing HTTPS routes")
655637 p.httpsServer.Routes()
@@ -726,7 +708,7 @@ func (p *Proxy) ForceUpdateRouteIP(domain string, newIP string) error {
726708 // Next, update the database
727709 // Get the domain ID with retry
728710 var domainID string
729- rows, err := p.app.GetDB().Query(p.queries.GetDomainByName, domain)
711+ rows, err := p.app.GetDB().Query(p.Queries.GetDomainByName, domain)
730712 if err != nil {
731713 logger.Error("Failed to query domain for IP update", "domain", domain, "error", err)
732714 return fmt.Errorf("failed to query domain: %w", err)
@@ -747,7 +729,7 @@ func (p *Proxy) ForceUpdateRouteIP(domain string, newIP string) error {
747729
748730 // Update the proxy_route record with retry and ensure it's marked as active
749731 now := time.Now().Format(time.RFC3339)
750- result, err := p.app.GetDB().Exec(p.queries.UpdateRouteIP, newIP, now, domainID, containerID)
732+ result, err := p.app.GetDB().Exec(p.Queries.UpdateRouteIP, newIP, now, domainID, containerID)
751733
752734 if err != nil {
753735 logger.Error("Failed to update IP in database", "domain", domain, "error", err)
@@ -817,7 +799,7 @@ func (p *Proxy) FindRoutesByOldName(containerName string) map[string]*ProxyRoute
817799 result := make(map[string]*ProxyRouteInfo)
818800
819801 // Query the database with retry to find if any route might be associated with this container name
820- rows, err := p.app.GetDB().Query(p.queries.GetAllRoutes)
802+ rows, err := p.app.GetDB().Query(p.Queries.GetAllRoutes)
821803 if err != nil {
822804 logger.Error("Failed to query database for routes by old name", "error", err)
823805 return result
@@ -1182,7 +1164,7 @@ func (p *Proxy) StartContainerEventListener() error {
11821164
11831165 // Update the IP in the database directly
11841166 now := time.Now().Format(time.RFC3339)
1185- _, err = p.dbExecWithRetry(p.queries.UpdateRouteIP,
1167+ _, err = p.dbExecWithRetry(p.Queries.UpdateRouteIP,
11861168 containerIP, now, domainID, containerID)
11871169
11881170 if err != nil {
@@ -1374,7 +1356,7 @@ func (p *Proxy) markRoutesInactive(domains []string) {
13741356 for _, domain := range domains {
13751357 // First, get the domain ID
13761358 var domainID string
1377- err := tx.QueryRow(p.queries.GetDomainByName, domain).Scan(&domainID)
1359+ err := tx.QueryRow(p.Queries.GetDomainByName, domain).Scan(&domainID)
13781360 if err != nil {
13791361 logger.Error("Failed to get domain ID for inactive route", "domain", domain, "error", err)
13801362 continue
@@ -1382,7 +1364,7 @@ func (p *Proxy) markRoutesInactive(domains []string) {
13821364
13831365 // Update the route to mark it as inactive
13841366 now := time.Now().Format(time.RFC3339)
1385- _, err = tx.Exec(p.queries.MarkRouteInactive, now, domainID)
1367+ _, err = tx.Exec(p.Queries.MarkRouteInactive, now, domainID)
13861368 if err != nil {
13871369 logger.Error("Failed to mark route as inactive in database", "domain", domain, "error", err)
13881370 continue
@@ -1785,11 +1767,11 @@ func (p *Proxy) proxyRequest(c echo.Context) error {
17851767 host = strings.TrimSuffix(strings.ToLower(host), ".")
17861768
17871769 // Debug log the incoming request
1788- logger.Debug("Proxy request received",
1789- "host", host,
1790- "method", c.Request().Method,
1791- "path", c.Request().URL.Path,
1792- "client_ip", c.RealIP())
1770+ // logger.Debug("Proxy request received",
1771+ // "host", host,
1772+ // "method", c.Request().Method,
1773+ // "path", c.Request().URL.Path,
1774+ // "client_ip", c.RealIP())
17931775
17941776 // Get the route for this host
17951777 p.mu.RLock()
@@ -2086,3 +2068,31 @@ func (p *Proxy) IsContainerInCooldownPeriod(containerID string) bool {
20862068 // Check if the container is still in its cooldown period
20872069 return time.Since(timestamp) < p.containerCooldownPeriod
20882070 }
2071+
2072+// Add specific domain handling to use our new certificate manager
2073+
2074+// requestCertificateForDomain requests a certificate for a specific domain using our new refactored system
2075+func (p *Proxy) requestCertificateForDomain(domain string) {
2076+ // Use the certificate manager stored on the Proxy struct
2077+ if p.certificateManager != nil {
2078+ // Request the certificate using the new manager
2079+ _, err := p.certificateManager.GetCertificate(domain)
2080+ if err != nil {
2081+ logger.Error("Failed to get certificate with new manager",
2082+ "domain", domain,
2083+ "error", err)
2084+ } else {
2085+ logger.Info("Successfully secured certificate for domain",
2086+ "domain", domain)
2087+ }
2088+ return
2089+ }
2090+
2091+ // Fall back to old method if new manager not available
2092+ // logger.Debug("Using legacy certificate manager for domain",
2093+ // "domain", domain)
2094+ // p.requestDomainCertificateRefactored(domain) // Removed fallback to non-existent method
2095+
2096+ // If manager is nil, log an error (should have been caught earlier)
2097+ logger.Error("requestCertificateForDomain called but certificateManager is nil", "domain", domain)
2098+}
M internal/proxy/server.go +123 -207
@@ -11,8 +11,6 @@ import (
1111
1212 "github.com/bnema/gordon/pkg/docker"
1313 "github.com/bnema/gordon/pkg/logger"
14- "github.com/labstack/echo/v4"
15- "golang.org/x/crypto/acme"
1614 )
1715
1816 // This file contains server operations functions for the proxy.
@@ -153,205 +151,12 @@ func (p *Proxy) Start() error {
153151 // Configure routes
154152 p.configureRoutes()
155153
156- // Configure HTTP server to handle Let's Encrypt HTTP-01 challenges
157- // and redirect everything else to HTTPS
158- p.httpServer.Any("/.well-known/acme-challenge/*", echo.WrapHandler(p.certManager.HTTPHandler(nil)))
159-
160- // Create a custom HTTPS server with proper TLS config
161- httpsServer := &http.Server{
162- Addr: fmt.Sprintf(":%s", p.config.Port),
163- Handler: p.httpsServer,
164- TLSConfig: &tls.Config{
165- GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
166- // Log SNI information in debug mode
167- if hello.ServerName != "" {
168- logger.Debug("TLS handshake with SNI",
169- "server_name", hello.ServerName)
170- }
171-
172- // Verify certificate manager is properly initialized
173- if p.certManager == nil {
174- logger.Error("TLS certificate manager is nil during handshake - certificate initialization failed",
175- "server_name", hello.ServerName,
176- "solution", "Check certificate configuration and logs for setup errors")
177-
178- // Use fallback certificate if we have one
179- if p.fallbackCert != nil {
180- logger.Warn("Using fallback self-signed certificate due to missing certificate manager",
181- "server_name", hello.ServerName)
182- return p.fallbackCert, nil
183- }
184-
185- return nil, fmt.Errorf("certificate manager not initialized")
186- }
187-
188- // Try to get the certificate from the autocert manager
189- logger.Debug("Attempting to get certificate from manager",
190- "server_name", hello.ServerName,
191- "has_fallback", p.fallbackCert != nil)
192-
193- // First try checking if we have a valid certificate in cache
194- cacheKey := "cert-" + hello.ServerName
195- certCache := p.certManager.Cache
196- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
197- defer cancel()
198-
199- if certCache != nil {
200- certData, err := certCache.Get(ctx, cacheKey)
201- if err == nil && len(certData) > 0 {
202- logger.Debug("Found certificate in cache", "domain", hello.ServerName)
203- // Use directly if found in cache - skip the potentially slower autocert.Manager GetCertificate
204- keyKey := "key-" + hello.ServerName
205- keyData, keyErr := certCache.Get(ctx, keyKey)
206- if keyErr == nil && len(keyData) > 0 {
207- cert, err := tls.X509KeyPair(certData, keyData)
208- if err == nil {
209- logger.Debug("Successfully loaded certificate from cache",
210- "domain", hello.ServerName)
211- return &cert, nil
212- }
213- logger.Error("Failed to create X509KeyPair from cache",
214- "domain", hello.ServerName,
215- "error", err)
216- } else {
217- logger.Error("Certificate found in cache but key is missing or invalid",
218- "domain", hello.ServerName,
219- "key_error", keyErr)
220- }
221- } else if err != nil {
222- logger.Debug("Certificate not found in cache during TLS handshake",
223- "domain", hello.ServerName,
224- "error", err)
225-
226- // Check if we have it on disk but not in cache (unlikely after preloading)
227- hasOnDisk, certType := p.checkCertificateInFilesystem(hello.ServerName)
228- if hasOnDisk {
229- logger.Warn("Certificate found on disk but not in cache - this should not happen after preloading",
230- "domain", hello.ServerName,
231- "type", certType,
232- "solution", "Check preloadCertificates implementation")
233- }
234- }
235- }
236-
237- // If not in cache, use the standard GetCertificate method
238- // Let's Encrypt may be contacted if no certificate exists
239- cert, err := p.certManager.GetCertificate(hello)
240-
241- if err != nil {
242- logger.Error("Error getting certificate from manager",
243- "server_name", hello.ServerName,
244- "error", err)
245-
246- // Check for rate limit errors
247- if strings.Contains(err.Error(), "too many certificates") ||
248- strings.Contains(err.Error(), "rateLimited") {
249- logger.Error("Let's Encrypt rate limit hit during TLS handshake",
250- "domain", hello.ServerName,
251- "solution", "Wait for rate limit to expire or manually add certificate")
252- }
253-
254- // Try manually loading certificate files directly as a fallback before using fallback cert
255- if manualCert, manualErr := p.manuallyLoadCertificate(hello.ServerName); manualErr == nil {
256- logger.Info("Successfully loaded certificate manually as fallback",
257- "domain", hello.ServerName)
258- return manualCert, nil
259- } else {
260- logger.Debug("Manual certificate loading also failed",
261- "domain", hello.ServerName,
262- "error", manualErr)
263- }
264-
265- // If we can't get a certificate and we have a fallback, use it for the admin domain
266- if hello.ServerName == p.app.GetConfig().Http.FullDomain() && p.fallbackCert != nil {
267- logger.Debug("Using fallback certificate for admin domain",
268- "domain", hello.ServerName,
269- "error", err)
270- return p.fallbackCert, nil
271- }
272- } else if cert == nil {
273- logger.Error("Certificate manager returned nil certificate without error",
274- "server_name", hello.ServerName)
275-
276- // Try manually loading certificate files directly as a fallback
277- if manualCert, manualErr := p.manuallyLoadCertificate(hello.ServerName); manualErr == nil {
278- logger.Info("Successfully loaded certificate manually when autocert returned nil",
279- "domain", hello.ServerName)
280- return manualCert, nil
281- }
282-
283- // If we can't get a certificate and we have a fallback, use it for the admin domain
284- if hello.ServerName == p.app.GetConfig().Http.FullDomain() && p.fallbackCert != nil {
285- logger.Debug("Using fallback certificate for admin domain",
286- "domain", hello.ServerName)
287- return p.fallbackCert, nil
288- }
289- } else {
290- logger.Debug("Successfully obtained certificate from manager",
291- "server_name", hello.ServerName)
292- }
293-
294- return cert, err
295- },
296- MinVersion: tls.VersionTLS12,
297- // Add server name to use when client doesn't send SNI
298- ServerName: p.app.GetConfig().Http.FullDomain(),
299- // Add support for TLS-ALPN-01 challenges
300- NextProtos: []string{acme.ALPNProto},
301- },
302- // Add increased timeouts to prevent hanging connections
303- ReadTimeout: 60 * time.Second,
304- WriteTimeout: 60 * time.Second,
305- IdleTimeout: 120 * time.Second,
306- ReadHeaderTimeout: 30 * time.Second, // Add header timeout
154+ // Configure HTTPS and HTTP servers using our refactored method
155+ if err := p.StartHTTPSServer(); err != nil {
156+ logger.Error("Failed to start HTTPS server", "error", err)
157+ return err
307158 }
308159
309- // Store the HTTPS server for later use in Stop()
310- p.httpsServer.Server = httpsServer
311-
312- // Create a custom HTTP server
313- httpServer := &http.Server{
314- Addr: fmt.Sprintf(":%s", p.config.HttpPort),
315- Handler: p.httpServer,
316- ReadTimeout: 30 * time.Second,
317- WriteTimeout: 30 * time.Second,
318- IdleTimeout: 120 * time.Second,
319- }
320-
321- p.serverStarted = true
322-
323- // Start HTTPS server with more error details
324- go func() {
325- logger.Info("Starting HTTPS server", "address", httpsServer.Addr)
326- // Using empty strings for cert and key files since we're using GetCertificate
327- if err := httpsServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
328- logger.Error("HTTPS server failed", "error", err, "address", httpsServer.Addr)
329-
330- // Print more diagnostic information
331- if strings.Contains(err.Error(), "bind: address already in use") {
332- logger.Error("Port 443 is already in use by another process. Check if another web server is running.",
333- "solution", "Stop other services using port 443 or configure Gordon to use a different HTTPS port")
334- } else if strings.Contains(err.Error(), "permission denied") {
335- logger.Error("Permission denied when binding to port 443. This often happens when running without root privileges.",
336- "solution", "Use setcap to allow binding to privileged ports or run in a container with proper port mapping")
337- } else if strings.Contains(err.Error(), "certificate") {
338- logger.Error("Certificate-related error when starting HTTPS server",
339- "solution", "Check certificate files and permissions in the certificate directory")
340- }
341- }
342- }()
343-
344- // Start HTTP server
345- go func() {
346- logger.Info("Starting HTTP server", "address", httpServer.Addr)
347- if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
348- logger.Error("HTTP server failed", "error", err)
349- }
350- }()
351-
352- // Test admin connection after server starts
353- go p.TestAdminConnectionLater()
354-
355160 return nil
356161 }
357162
@@ -381,12 +186,12 @@ func (p *Proxy) Stop() error {
381186 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
382187 defer cancel()
383188
384- // Shutdown HTTP server if it's running
385- if p.httpServer != nil {
386- if err := p.httpServer.Shutdown(ctx); err != nil {
387- logger.Error("Failed to gracefully shutdown HTTP server", "error", err)
388- }
389- }
189+ // Removed HTTP server shutdown logic
190+ // if p.httpServer != nil {
191+ // if err := p.httpServer.Shutdown(ctx); err != nil {
192+ // logger.Error("Failed to gracefully shutdown HTTP server", "error", err)
193+ // }
194+ // }
390195
391196 // Shutdown HTTPS server if it's running
392197 if p.httpsServer != nil {
@@ -536,9 +341,9 @@ func (p *Proxy) checkExternalPortAccess() {
536341 }
537342
538343 // Verify TLS settings are consistent
539- if p.certManager == nil {
344+ if p.certificateManager == nil { // Updated check
540345 logger.Error("Certificate manager is nil - TLS will not work correctly",
541- "solution", "Check setupCertManager implementation")
346+ "solution", "Check NewProxy implementation and logs")
542347 }
543348
544349 // First try a basic TCP connection to port 443 to test if it's open
@@ -654,3 +459,114 @@ func (p *Proxy) checkExternalPortAccess() {
654459 // logger.Debug("Could not auto-detect Gordon container, using default name", "container_name", "gordon")
655460 // return "gordon"
656461 // }
462+
463+// StartHTTPSServer starts the HTTPS server
464+func (p *Proxy) StartHTTPSServer() error {
465+ logger.Info("Starting HTTPS server",
466+ "port", p.config.Port,
467+ "mode", p.config.LetsEncryptMode)
468+
469+ // Verify the certificate manager is initialized (it's done in NewProxy now)
470+ if p.certificateManager == nil {
471+ // This should ideally not happen if NewProxy succeeded
472+ logger.Error("Certificate manager is nil in StartHTTPSServer, cannot start HTTPS")
473+ return fmt.Errorf("certificate manager not initialized")
474+ }
475+ logger.Info("Using new certificate management system")
476+
477+ // Setup TLS configuration
478+ tlsConfig := &tls.Config{
479+ // Use the helper function that gets the GetCertificate func from the manager
480+ GetCertificate: p.getTLSCertificateFunc(),
481+ // Ensure we use TLS 1.2 or higher
482+ MinVersion: tls.VersionTLS12,
483+ // Optimize cipher suites for security and performance
484+ CipherSuites: []uint16{
485+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
486+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
487+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
488+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
489+ tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
490+ tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
491+ },
492+ // Use server's preferred ciphersuite
493+ PreferServerCipherSuites: true,
494+ // Enable HTTP/2
495+ NextProtos: []string{"h2", "http/1.1"},
496+ // Add server name to use when client doesn't send SNI
497+ ServerName: p.app.GetConfig().Http.FullDomain(),
498+ }
499+
500+ // Removed setup for ACME challenge handler on the persistent HTTP server,
501+ // as it's no longer running. Lego's temporary server will handle challenges.
502+ // p.httpServer.Any("/.well-known/acme-challenge/*", echo.WrapHandler(p.getACMEHTTPHandler()))
503+
504+ // Create a custom HTTPS server with proper TLS config
505+ httpsServer := &http.Server{
506+ Addr: fmt.Sprintf(":%s", p.config.Port),
507+ Handler: p.httpsServer,
508+ TLSConfig: tlsConfig,
509+ // Add increased timeouts to prevent hanging connections
510+ ReadTimeout: 60 * time.Second,
511+ WriteTimeout: 60 * time.Second,
512+ IdleTimeout: 120 * time.Second,
513+ ReadHeaderTimeout: 30 * time.Second, // Add header timeout
514+ }
515+
516+ // Store the HTTPS server for later use in Stop()
517+ p.httpsServer.Server = httpsServer
518+
519+ // // Create a custom HTTP server
520+ // httpServer := &http.Server{
521+ // Addr: fmt.Sprintf(":%s", p.config.HttpPort),
522+ // Handler: p.httpServer,
523+ // ReadTimeout: 30 * time.Second,
524+ // WriteTimeout: 30 * time.Second,
525+ // IdleTimeout: 120 * time.Second,
526+ // }
527+
528+ p.serverStarted = true
529+
530+ // httpServer is no longer needed as ACME HTTP-01 uses its own temporary server
531+ // httpServer := &http.Server{
532+ // Addr: fmt.Sprintf(":%s", p.config.HttpPort),
533+ // Handler: p.httpServer,
534+ // ReadTimeout: 30 * time.Second,
535+ // WriteTimeout: 30 * time.Second,
536+ // IdleTimeout: 120 * time.Second,
537+ // }
538+
539+ // Start HTTPS server with more error details
540+ go func() {
541+ logger.Info("Starting HTTPS server", "address", httpsServer.Addr)
542+ // Using empty strings for cert and key files since we're using GetCertificate
543+ if err := httpsServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
544+ logger.Error("HTTPS server failed", "error", err, "address", httpsServer.Addr)
545+
546+ // Print more diagnostic information
547+ if strings.Contains(err.Error(), "bind: address already in use") {
548+ logger.Error("Port 443 is already in use by another process. Check if another web server is running.",
549+ "solution", "Stop other services using port 443 or configure Gordon to use a different HTTPS port")
550+ } else if strings.Contains(err.Error(), "permission denied") {
551+ logger.Error("Permission denied when binding to port 443. This often happens when running without root privileges.",
552+ "solution", "Use setcap to allow binding to privileged ports or run in a container with proper port mapping")
553+ } else if strings.Contains(err.Error(), "certificate") {
554+ logger.Error("Certificate-related error when starting HTTPS server",
555+ "solution", "Check certificate files and permissions in the certificate directory")
556+ }
557+ }
558+ }()
559+
560+ // Test admin connection after server starts
561+ go p.TestAdminConnectionLater()
562+
563+ return nil
564+}
565+
566+// getTLSCertificateFunc returns a function that gets certificates for TLS handshakes
567+func (p *Proxy) getTLSCertificateFunc() func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
568+ return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
569+ logger.Debug("Getting certificate for TLS handshake", "server_name", hello.ServerName)
570+ return p.certificateManager.GetCertificateForTLS(hello)
571+ }
572+}
M internal/server/app_server.go +1 -0
@@ -38,6 +38,7 @@ type DBTables struct {
3838 Domain db.Domain `sql:"domain"`
3939 Certificate db.Certificate `sql:"certificate"`
4040 ProxyRoute db.ProxyRoute `sql:"proxy_route"`
41+ AcmeAccount db.AcmeAccount `sql:"acme_account"`
4142 }
4243
4344 // GetConfig returns the configuration
M internal/server/sqlite_init.go +193 -163
@@ -6,10 +6,14 @@ import (
66 "fmt"
77 "os"
88 "path/filepath"
9+ "reflect"
10+ "strings"
911 "time"
1012
1113 "github.com/bnema/gordon/internal/db"
14+ pkgsqlite "github.com/bnema/gordon/pkg/sqlite"
1215 "github.com/charmbracelet/log"
16+ "github.com/google/uuid"
1317 _ "modernc.org/sqlite"
1418 )
1519
@@ -37,113 +41,228 @@ func (a *App) ensureDBDir() error {
3741 return nil
3842 }
3943
40-// checkDBFile checks if the database file exists.
41-func (a *App) checkDBFile(dbPath string) (bool, error) {
42- if _, err := os.Stat(dbPath); os.IsNotExist(err) {
43- log.Debug("Database file does not exist", "path", dbPath)
44- return false, nil
45- } else if err != nil {
46- return false, err
44+// InitializeDB initializes the database in memory and loads data from disk if available
45+func InitializeDB(a *App) (*sql.DB, error) {
46+ log.Debug("Initializing database")
47+ if err := a.ensureDBDir(); err != nil {
48+ return nil, fmt.Errorf("failed to ensure DB directory: %w", err)
4749 }
4850
49- // File exists, but let's also check if the tables exist
50- db, err := sql.Open("sqlite", dbPath)
51- if err != nil {
52- return false, fmt.Errorf("failed to open DB to check tables: %w", err)
51+ diskDBPath := a.getDiskDBFilePath()
52+ a.DBPath = diskDBPath
53+
54+ // --- Start Refactored Schema Handling ---
55+ log.Debug("Ensuring database schema is up-to-date on disk", "path", diskDBPath)
56+ // UpdateDatabase will create the file and tables if they don't exist,
57+ // or update the schema if they do. It also handles backups.
58+ if err := pkgsqlite.UpdateDatabase(diskDBPath, a.DBTables); err != nil {
59+ return nil, fmt.Errorf("failed to create/update database schema on disk: %w", err)
5360 }
54- defer db.Close()
61+ log.Debug("Database schema on disk is up-to-date.")
62+ // --- End Refactored Schema Handling ---
5563
56- // Check if domain table exists
57- var tableCount int
58- err = db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='domain'").Scan(&tableCount)
64+ // --- BEGIN Seed Admin Account if Necessary ---
65+ log.Debug("Checking if admin account seeding is necessary", "path", diskDBPath)
66+ // Need to temporarily open the disk DB to check and potentially write
67+ seedDB, err := sql.Open("sqlite", diskDBPath)
5968 if err != nil {
60- return false, fmt.Errorf("failed to check for domain table: %w", err)
69+ return nil, fmt.Errorf("failed to open disk DB for seeding check: %w", err)
6170 }
6271
63- if tableCount == 0 {
64- log.Debug("Database file exists but domain table is missing, will reinitialize")
65- // The file exists but doesn't have the required tables, treat as non-existent
66- if err := os.Remove(dbPath); err != nil {
67- return false, fmt.Errorf("failed to remove corrupted DB file: %w", err)
72+ var accountCount int
73+ err = seedDB.QueryRow("SELECT COUNT(*) FROM account").Scan(&accountCount)
74+ if err != nil {
75+ seedDB.Close()
76+ // If the table doesn't exist yet (shouldn't happen after UpdateDatabase), log it
77+ if pkgsqlite.IsNoSuchTableError(err) {
78+ log.Warn("Account table not found during seeding check, skipping seed.", "error", err)
79+ // Proceed without seeding, assuming UpdateDatabase handled creation correctly
80+ } else {
81+ return nil, fmt.Errorf("failed to query account count from disk DB: %w", err)
82+ }
83+ } else if accountCount == 0 {
84+ log.Info("No accounts found, seeding default admin user and account.")
85+ // Begin transaction for seeding
86+ seedTx, txErr := seedDB.Begin()
87+ if txErr != nil {
88+ seedDB.Close()
89+ return nil, fmt.Errorf("failed to begin transaction for seeding: %w", txErr)
6890 }
69- return false, nil
70- }
7191
72- log.Debug("Database file exists and contains required tables", "path", dbPath)
73- return true, nil
74-}
92+ adminUserID := uuid.NewString()
93+ adminAccountID := uuid.NewString()
94+ adminEmail := "admin@local.host" // Or some other placeholder
95+
96+ // Insert User
97+ _, txErr = seedTx.Exec("INSERT INTO user (id, name, email) VALUES (?, ?, ?)",
98+ adminUserID, "Admin User", adminEmail)
99+ if txErr != nil {
100+ seedTx.Rollback()
101+ seedDB.Close()
102+ return nil, fmt.Errorf("failed to insert default admin user: %w", txErr)
103+ }
75104
76-// InitializeDB initializes the database in memory and loads data from disk if available
77-func InitializeDB(a *App) (*sql.DB, error) {
78- log.Debug("Initializing in-memory database")
79- if err := a.ensureDBDir(); err != nil {
80- return nil, fmt.Errorf("failed to ensure DB directory: %w", err)
105+ // Insert Account
106+ _, txErr = seedTx.Exec("INSERT INTO account (id, user_id) VALUES (?, ?)",
107+ adminAccountID, adminUserID)
108+ if txErr != nil {
109+ seedTx.Rollback()
110+ seedDB.Close()
111+ return nil, fmt.Errorf("failed to insert default admin account: %w", txErr)
112+ }
113+
114+ // Commit transaction
115+ txErr = seedTx.Commit()
116+ if txErr != nil {
117+ seedDB.Close()
118+ return nil, fmt.Errorf("failed to commit seeding transaction: %w", txErr)
119+ }
120+ log.Info("Default admin user and account seeded successfully", "user_id", adminUserID, "account_id", adminAccountID)
121+ } else {
122+ log.Debug("Accounts already exist, skipping admin seeding.", "count", accountCount)
81123 }
82124
83- diskDBPath := a.getDiskDBFilePath()
84- a.DBPath = diskDBPath
125+ // Close the temporary connection used for seeding
126+ if err := seedDB.Close(); err != nil {
127+ // Log error but don't necessarily fail initialization at this point
128+ log.Error("Error closing temporary seed DB connection", "error", err)
129+ }
130+ // --- END Seed Admin Account if Necessary ---
85131
86- // Create in-memory database connection
132+ // Create in-memory database connection (remains the same)
87133 log.Debug("Opening in-memory SQLite database")
88134 memDB, err := sql.Open("sqlite", "file::memory:?cache=shared")
89135 if err != nil {
90136 return nil, fmt.Errorf("failed to open in-memory DB: %w", err)
91137 }
92138
93- // Test that the in-memory database is working
139+ // Test that the in-memory database is working (remains the same)
94140 log.Debug("Testing in-memory database connection")
95141 if err := memDB.Ping(); err != nil {
142+ memDB.Close() // Close memDB if ping fails
96143 return nil, fmt.Errorf("failed to ping in-memory database: %w", err)
97144 }
98145
99- // Check if disk database exists
100- dbExists, err := a.checkDBFile(diskDBPath)
146+ // --- Load from Disk to Memory ---
147+ // Now that the disk schema is guaranteed to be correct, load it into memory.
148+ // We need to check if the file actually contains data before loading.
149+ // A simple way is to check if the file size is > 0 after UpdateDatabase.
150+ fileInfo, err := os.Stat(diskDBPath)
151+ loadNeeded := true
101152 if err != nil {
102- return nil, fmt.Errorf("failed to check DB file: %w", err)
153+ if os.IsNotExist(err) {
154+ log.Debug("Database file still doesn't exist after schema update (likely first run with no tables?), skipping load from disk.")
155+ loadNeeded = false // Nothing to load
156+ } else {
157+ memDB.Close()
158+ return nil, fmt.Errorf("failed to stat disk DB file after schema update: %w", err)
159+ }
160+ } else if fileInfo.Size() == 0 {
161+ log.Debug("Database file is empty after schema update, skipping load from disk.")
162+ loadNeeded = false // Nothing to load
103163 }
104164
105- if !dbExists {
106- log.Debug("Database needs bootstrapping", "path", diskDBPath)
107- // Create tables in memory
108- log.Debug("Creating tables in memory")
109- if err := bootstrapDB(memDB); err != nil {
110- return nil, fmt.Errorf("failed to bootstrap in-memory DB: %w", err)
165+ if loadNeeded {
166+ log.Debug("Loading database from disk into memory", "path", diskDBPath)
167+ if err := loadDiskDBToMemory(diskDBPath, memDB); err != nil {
168+ memDB.Close()
169+ return nil, fmt.Errorf("failed to load disk DB into memory: %w", err)
170+ }
171+ log.Debug("Successfully loaded disk database into memory")
172+ } else {
173+ // If we didn't load, the memory DB is empty but has the correct schema
174+ // implicitly because UpdateDatabase already ran on the (possibly new) disk file.
175+ // However, loadDiskDBToMemory *also* creates the schema in memory based on the disk file.
176+ // If the disk file was *just* created by UpdateDatabase and is empty,
177+ // loadDiskDBToMemory won't run, and memDB won't have the tables yet.
178+ // We need to ensure the schema exists in memory even if the disk file was initially empty.
179+ // Easiest way: re-apply the schema creation logic to memDB.
180+ log.Debug("Applying schema to empty in-memory database")
181+ memTx, err := memDB.Begin()
182+ if err != nil {
183+ memDB.Close()
184+ return nil, fmt.Errorf("failed to begin transaction on memory DB for schema creation: %w", err)
185+ }
186+ schemaCreated := false
187+ // Iterate through DBTables fields using reflection to get correct table names
188+ dbTablesType := reflect.TypeOf(a.DBTables)
189+ dbTablesValue := reflect.ValueOf(a.DBTables)
190+ if dbTablesType.Kind() == reflect.Ptr {
191+ dbTablesType = dbTablesType.Elem()
192+ dbTablesValue = dbTablesValue.Elem()
111193 }
112194
113- // Save the initialized in-memory database to disk
114- log.Debug("Saving initialized in-memory database to disk")
115- if err := saveMemDBToDisk(memDB, diskDBPath); err != nil {
116- return nil, fmt.Errorf("failed to save initial in-memory DB to disk: %w", err)
195+ if dbTablesType.Kind() == reflect.Struct {
196+ log.Debug("Reflecting DBTables struct for in-memory schema creation", "type", dbTablesType.Name())
197+ for i := 0; i < dbTablesType.NumField(); i++ {
198+ field := dbTablesType.Field(i)
199+ fieldValue := dbTablesValue.Field(i).Interface() // e.g., db.ProxyRoute instance
200+ sqlTag := field.Tag.Get("sql")
201+
202+ if sqlTag == "" || sqlTag == "-" {
203+ log.Debug("Skipping field without sql tag in memory schema creation", "field", field.Name)
204+ continue // Skip fields without a sql tag or marked to be ignored
205+ }
206+
207+ tableName := sqlTag // Use the tag directly as the table name
208+ log.Debug("Attempting to create table in memory", "tableName", tableName, "structType", reflect.TypeOf(fieldValue).Name())
209+
210+ if err := pkgsqlite.CreateTableInTx(memTx, fieldValue, tableName); err != nil {
211+ // Ignore "table already exists" errors if somehow they occur
212+ if !strings.Contains(strings.ToLower(err.Error()), "already exists") {
213+ log.Error("Failed to create table in memory", "table", tableName, "error", err)
214+ memTx.Rollback()
215+ memDB.Close()
216+ return nil, fmt.Errorf("failed to create table %s in memory: %w", tableName, err)
217+ } else {
218+ log.Debug("Table already exists in memory, skipping creation", "table", tableName)
219+ }
220+ } else {
221+ log.Debug("Successfully created table in memory", "table", tableName)
222+ schemaCreated = true
223+ }
224+ }
225+ } else {
226+ log.Error("a.DBTables is not a struct, cannot create schema in memory")
227+ memDB.Close()
228+ return nil, fmt.Errorf("internal error: DBTables is not a struct type (%T)", a.DBTables)
117229 }
118230
119- log.Debug("Initialized new in-memory database and saved to disk")
120- } else {
121- // Load existing database from disk into memory
122- log.Debug("Loading existing database from disk into memory", "path", diskDBPath)
123- if err := loadDiskDBToMemory(diskDBPath, memDB); err != nil {
124- return nil, fmt.Errorf("failed to load disk DB into memory: %w", err)
231+ if err := memTx.Commit(); err != nil {
232+ memDB.Close()
233+ return nil, fmt.Errorf("failed to commit schema creation transaction to memory DB: %w", err)
234+ }
235+ if schemaCreated {
236+ log.Debug("Successfully applied schema to in-memory database")
237+ } else {
238+ log.Debug("In-memory schema already existed or no tables defined.")
125239 }
126240
127- log.Debug("Successfully loaded disk database into memory")
128241 }
242+ // --- End Load from Disk to Memory ---
129243
130244 a.DB = memDB
131245
132- if dbExists {
133- // Populate the struct tables from the database
134- log.Debug("Populating struct tables from existing database")
246+ // Populate struct tables if data was loaded
247+ if loadNeeded {
248+ log.Debug("Populating struct tables from loaded database")
135249 if err := PopulateStructWithTables(a); err != nil {
136- return nil, err
250+ // Don't fail initialization for this, maybe just log an error
251+ log.Error("Failed to populate struct tables after loading DB", "error", err)
252+ // Depending on severity, you might want to return nil, err here
137253 }
254+ } else {
255+ log.Debug("Skipping struct table population as no data was loaded from disk.")
138256 }
139257
140- // Create a context for the auto-save routine that will be canceled when the app is shutting down
258+ // Create context for auto-save (remains the same)
141259 a.DBSaveCtx, a.DBSaveCancel = context.WithCancel(context.Background())
142260
143- // Start auto-save routine
144- log.Debug("Starting auto-save routine with interval of", "minutes", AutoSaveInterval)
261+ // Start auto-save routine (remains the same)
262+ log.Debug("Starting auto-save routine with interval", "minutes", AutoSaveInterval)
145263 go autoSaveDB(a.DBSaveCtx, a, AutoSaveInterval)
146264
265+ log.Info("Database initialization completed successfully")
147266 return memDB, nil
148267 }
149268
@@ -446,105 +565,6 @@ func joinStrings(strings []string, separator string) string {
446565 return result
447566 }
448567
449-// bootstrapDB creates the initial database schema
450-func bootstrapDB(db *sql.DB) error {
451- log.Debug("Bootstrapping database with initial schema")
452-
453- // Begin a transaction
454- tx, err := db.Begin()
455- if err != nil {
456- return fmt.Errorf("error beginning transaction: %w", err)
457- }
458-
459- // Create tables
460- log.Debug("Creating database tables")
461- _, err = tx.Exec(`
462- CREATE TABLE IF NOT EXISTS user (
463- id TEXT PRIMARY KEY,
464- name TEXT,
465- email TEXT
466- );
467- CREATE TABLE IF NOT EXISTS account (
468- id TEXT PRIMARY KEY,
469- user_id TEXT,
470- FOREIGN KEY (user_id) REFERENCES user(id)
471- );
472- CREATE TABLE IF NOT EXISTS sessions (
473- id TEXT PRIMARY KEY,
474- account_id TEXT,
475- browser_info TEXT,
476- access_token TEXT,
477- expires TEXT,
478- is_online INTEGER,
479- FOREIGN KEY (account_id) REFERENCES account(id)
480- );
481- CREATE TABLE IF NOT EXISTS provider (
482- id TEXT PRIMARY KEY,
483- account_id TEXT,
484- name TEXT,
485- login TEXT,
486- avatar_url TEXT,
487- profile_url TEXT,
488- email TEXT,
489- FOREIGN KEY (account_id) REFERENCES account(id)
490- );
491- CREATE TABLE IF NOT EXISTS clients (
492- id TEXT PRIMARY KEY,
493- account_id TEXT,
494- os TEXT,
495- ip TEXT,
496- hostname TEXT,
497- expires TEXT,
498- FOREIGN KEY (account_id) REFERENCES account(id)
499- );
500- CREATE TABLE IF NOT EXISTS domain (
501- id TEXT PRIMARY KEY,
502- name TEXT,
503- account_id TEXT,
504- created_at TEXT,
505- updated_at TEXT,
506- FOREIGN KEY (account_id) REFERENCES account(id)
507- );
508- CREATE TABLE IF NOT EXISTS certificate (
509- id TEXT PRIMARY KEY,
510- domain_id TEXT,
511- cert_file TEXT,
512- key_file TEXT,
513- issued_at TEXT,
514- expires_at TEXT,
515- issuer TEXT,
516- status TEXT,
517- FOREIGN KEY (domain_id) REFERENCES domain(id)
518- );
519- CREATE TABLE IF NOT EXISTS proxy_route (
520- id TEXT PRIMARY KEY,
521- domain_id TEXT,
522- container_id TEXT,
523- container_ip TEXT,
524- container_port TEXT,
525- protocol TEXT,
526- path TEXT,
527- active INTEGER,
528- created_at TEXT,
529- updated_at TEXT,
530- FOREIGN KEY (domain_id) REFERENCES domain(id)
531- );
532- `)
533- if err != nil {
534- tx.Rollback()
535- return fmt.Errorf("error creating tables: %w", err)
536- }
537-
538- // Commit the transaction
539- log.Debug("Committing transaction with initial schema")
540- if err := tx.Commit(); err != nil {
541- return fmt.Errorf("error committing transaction: %w", err)
542- }
543-
544- log.Debug("Database bootstrap completed successfully")
545- return nil
546-}
547-
548568 type DBTable interface {
549569 ScanFromRows(rows *sql.Rows) error
550570 }
@@ -618,7 +638,17 @@ func (c *ClientsWrapper) ScanFromRows(rows *sql.Rows) error {
618638 }
619639
620640 func (d *DomainWrapper) ScanFromRows(rows *sql.Rows) error {
621- return rows.Scan(&d.ID, &d.Name, &d.AccountID, &d.CreatedAt, &d.UpdatedAt)
641+ return rows.Scan(
642+ &d.ID,
643+ &d.Name,
644+ &d.AccountID,
645+ &d.CreatedAt,
646+ &d.UpdatedAt,
647+ &d.AcmeEnabled,
648+ &d.AcmeChallengeType,
649+ &d.AcmeDnsProvider,
650+ &d.AcmeDnsCredentialsRef,
651+ )
622652 }
623653
624654 func (c *CertificateWrapper) ScanFromRows(rows *sql.Rows) error {
@@ -663,7 +693,7 @@ func PopulateStructWithTables(a *App) error {
663693
664694 // Populate the Domain table
665695 domainWrapper := &DomainWrapper{Domain: &a.DBTables.Domain}
666- if err := populateTableFromDB(a, "SELECT id, name, account_id, created_at, updated_at FROM domain", domainWrapper); err != nil {
696+ if err := populateTableFromDB(a, "SELECT id, name, account_id, created_at, updated_at, acme_enabled, acme_challenge_type, acme_dns_provider, acme_dns_credentials_ref FROM domain", domainWrapper); err != nil {
667697 return err
668698 }
669699
M internal/server/token_init.go +1 -5
@@ -25,11 +25,7 @@ func HandleNewTokenInitialization(a *App) (string, error) {
2525 token, err := generateRandomToken(16) // 16 bytes
2626 if err != nil {
2727 return "", fmt.Errorf("failed to generate token: %v", err)
28- }
29-
30- // url = config.Http.SubDomain + config.Http.TopDomain + config.Http.Port + config.Admin.Path
31-
32- fmt.Printf("Login with the new token: %s\n", token)
28+
3329
3430 // Store the token in the config file
3531 a.Config.General.Token = token
M pkg/sqlite/db_create.go +3 -3
@@ -7,8 +7,8 @@ import (
77 "strings"
88 )
99
10-// CreateTable creates a table in the database based on the given struct.
11-func CreateTable(db *sql.DB, table interface{}, tableName string) error {
10+// CreateTableInTx creates a table within a transaction based on the given struct.
11+func CreateTableInTx(tx *sql.Tx, table interface{}, tableName string) error {
1212 t := reflect.TypeOf(table)
1313
1414 var fields []string
@@ -53,7 +53,7 @@ func CreateTable(db *sql.DB, table interface{}, tableName string) error {
5353 }
5454 createTableSQL += ");"
5555
56- _, err := db.Exec(createTableSQL)
56+ _, err := tx.Exec(createTableSQL)
5757 if err != nil {
5858 return fmt.Errorf("failed to create table %s: %v", tableName, err)
5959 }
A pkg/sqlite/db_update.go +328 -0
@@ -0,0 +1,328 @@
1+package pkgsqlite
2+
3+import (
4+ "database/sql"
5+ "fmt"
6+ "os"
7+ "path/filepath"
8+ "reflect"
9+ "strings"
10+ "time"
11+
12+ "github.com/charmbracelet/log"
13+)
14+
15+// UpdateDatabase compares struct definitions with the actual database schema on disk and updates it accordingly
16+// Accepts the DBTables struct (or similar) containing the table definitions as fields with `sql` tags.
17+func UpdateDatabase(dbPath string, dbTablesStruct interface{}) error {
18+ log.Debug("Starting database schema update check on disk", "path", dbPath)
19+
20+ // Check if the database file exists BEFORE attempting backup
21+ _, statErr := os.Stat(dbPath)
22+ if statErr == nil {
23+ // File exists, proceed with backup
24+ log.Debug("Database file exists, attempting backup.", "path", dbPath)
25+ if err := backupDatabase(dbPath); err != nil {
26+ // If backup fails on an existing file, that's a real problem
27+ return fmt.Errorf("failed to backup existing database: %w", err)
28+ }
29+ } else if os.IsNotExist(statErr) {
30+ // File does not exist, skip backup
31+ log.Debug("Database file doesn't exist, skipping backup.", "path", dbPath)
32+ } else {
33+ // Other error during stat (e.g., permissions), return it
34+ return fmt.Errorf("failed to check database file status before backup: %w", statErr)
35+ }
36+
37+ // Open connection to the disk database
38+ // sql.Open will create the file if it doesn't exist when a connection is made/queried
39+ diskDB, err := sql.Open("sqlite", dbPath)
40+ if err != nil {
41+ return fmt.Errorf("failed to open disk DB for schema update: %w", err)
42+ }
43+ defer diskDB.Close()
44+
45+ // Optional: Ping to ensure the connection is valid and file is created if needed
46+ if err := diskDB.Ping(); err != nil {
47+ // It's possible ping fails if the directory doesn't exist yet, ensure dir first.
48+ // Let's rely on the transaction to create the file implicitly if needed.
49+ // Commenting out ping as it might add complexity here.
50+ // return fmt.Errorf("failed to ping disk DB after opening: %w", err)
51+ }
52+
53+ // Begin transaction on disk DB
54+ tx, err := diskDB.Begin()
55+ if err != nil {
56+ return fmt.Errorf("failed to begin transaction on disk DB: %w", err)
57+ }
58+ // Using defer with a named return error allows checking commit error
59+ var txErr error
60+ defer func() {
61+ if r := recover(); r != nil {
62+ _ = tx.Rollback() // Attempt rollback on panic
63+ // Re-panic or handle as needed
64+ panic(r)
65+ } else if txErr != nil {
66+ log.Debug("Rolling back transaction due to error", "error", txErr)
67+ if rbErr := tx.Rollback(); rbErr != nil {
68+ log.Error("Failed to rollback transaction", "rollback_error", rbErr)
69+ }
70+ }
71+ }()
72+
73+ // Update each table schema using reflection on the provided struct
74+ updated := false
75+ dbTablesType := reflect.TypeOf(dbTablesStruct)
76+ dbTablesValue := reflect.ValueOf(dbTablesStruct)
77+ if dbTablesType.Kind() == reflect.Ptr {
78+ dbTablesType = dbTablesType.Elem()
79+ dbTablesValue = dbTablesValue.Elem()
80+ }
81+
82+ if dbTablesType.Kind() != reflect.Struct {
83+ _ = tx.Rollback() // Attempt rollback before returning error
84+ return fmt.Errorf("UpdateDatabase expects a struct, got %T", dbTablesStruct)
85+ }
86+ log.Debug("Reflecting DBTables struct for disk schema update", "type", dbTablesType.Name(), "numFields", dbTablesType.NumField())
87+
88+ for i := 0; i < dbTablesType.NumField(); i++ {
89+ field := dbTablesType.Field(i)
90+ fieldValue := dbTablesValue.Field(i).Interface() // e.g., db.ProxyRoute instance
91+ sqlTag := field.Tag.Get("sql")
92+
93+ if sqlTag == "" || sqlTag == "-" {
94+ log.Debug("Skipping field without sql tag in schema update", "field", field.Name)
95+ continue // Skip fields without a sql tag or marked to be ignored
96+ }
97+
98+ tableName := sqlTag // Use the tag directly as the table name
99+ log.Debug("Processing field for schema update", "field", field.Name, "tableName", tableName, "structType", reflect.TypeOf(fieldValue).Name())
100+
101+ var tableUpdated bool
102+ // Pass the correct tableName from the tag and the actual struct instance (fieldValue)
103+ tableUpdated, txErr = updateTableSchema(tx, tableName, fieldValue)
104+ if txErr != nil {
105+ // updateTableSchema handles creation if table is missing
106+ // Return the error wrapped, defer will handle rollback
107+ return fmt.Errorf("failed to update/create table '%s' (from field '%s'): %w", tableName, field.Name, txErr)
108+ }
109+ if tableUpdated {
110+ updated = true
111+ }
112+ }
113+
114+ // Commit transaction - Assign error to txErr for defer check
115+ txErr = tx.Commit()
116+ if txErr != nil {
117+ return fmt.Errorf("failed to commit transaction on disk DB: %w", txErr)
118+ }
119+
120+ if updated {
121+ log.Info("Successfully updated/created database schema on disk")
122+ } else {
123+ log.Debug("Database schema on disk is up-to-date")
124+ }
125+ return nil
126+}
127+
128+// updateTableSchema updates a single table's schema based on struct definition
129+// Returns true if the schema was updated or created, false otherwise.
130+func updateTableSchema(tx *sql.Tx, tableName string, structType interface{}) (bool, error) {
131+ log.Debug("Starting schema update check for table", "table", tableName)
132+ updated := false
133+ // Get existing columns
134+ existingColumns, err := getTableColumns(tx, tableName)
135+ if err != nil {
136+ // If the error is "no such table", try creating it
137+ if IsNoSuchTableError(err) {
138+ log.Info("Table does not exist (getTableColumns returned error), attempting to create", "table", tableName)
139+ if createErr := CreateTableInTx(tx, structType, tableName); createErr != nil {
140+ return false, fmt.Errorf("failed to create missing table %s: %w", tableName, createErr)
141+ }
142+ log.Info("Successfully created table", "table", tableName)
143+ return true, nil // Table was created (updated=true)
144+ }
145+ // Otherwise, it's a different error when getting columns
146+ return false, fmt.Errorf("failed to get existing columns for table %s: %w", tableName, err)
147+ }
148+
149+ // Check if getTableColumns succeeded but found no columns.
150+ // Treat this as if the table needs creation.
151+ if len(existingColumns) == 0 {
152+ log.Info("Table exists according to PRAGMA but has no columns, treating as missing/invalid, attempting to create", "table", tableName)
153+ // Attempt to drop the potentially problematic empty/invalid table first (optional but safer)
154+ // We can ignore errors here, as it might not actually exist for DROP.
155+ _, _ = tx.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName))
156+
157+ // Now create it properly
158+ if createErr := CreateTableInTx(tx, structType, tableName); createErr != nil {
159+ return false, fmt.Errorf("failed to create missing/invalid table %s: %w", tableName, createErr)
160+ }
161+ log.Info("Successfully recreated table", "table", tableName)
162+ return true, nil // Table was created/recreated (updated=true)
163+ }
164+
165+ log.Debug("Existing columns found", "table", tableName, "columns", existingColumns)
166+
167+ // Get struct fields
168+ structFields := getStructFields(structType)
169+ log.Debug("Struct fields found", "table", tableName, "fields", structFields)
170+
171+ // Compare and add missing columns
172+ for fieldName, fieldType := range structFields {
173+ if _, exists := existingColumns[fieldName]; !exists {
174+ sqlType := convertGoTypeToSQLType(fieldType) // Use the helper function
175+ query := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s %s", tableName, fieldName, sqlType)
176+
177+ log.Debug("Attempting to add missing column", "table", tableName, "column", fieldName, "type", sqlType, "query", query)
178+ if _, err := tx.Exec(query); err != nil {
179+ if isDuplicateColumnError(err) {
180+ log.Debug("Column already exists (detected via duplicate error), skipping", "table", tableName, "column", fieldName)
181+ } else {
182+ log.Error("Failed to execute ALTER TABLE", "table", tableName, "column", fieldName, "query", query, "error", err)
183+ return false, fmt.Errorf("failed to add column %s to table %s: %w", fieldName, tableName, err)
184+ }
185+ } else {
186+ log.Info("Successfully added column", "table", tableName, "column", fieldName)
187+ updated = true // Column was successfully added
188+ }
189+ }
190+ }
191+
192+ log.Debug("Finished schema update check for table", "table", tableName, "schema_was_modified", updated)
193+ return updated, nil
194+}
195+
196+// getTableColumns returns a map of existing column names in the table
197+func getTableColumns(tx *sql.Tx, tableName string) (map[string]bool, error) {
198+ columns := make(map[string]bool)
199+
200+ query := fmt.Sprintf("PRAGMA table_info(%s)", tableName)
201+ rows, err := tx.Query(query)
202+ if err != nil {
203+ // Specifically check if the error indicates "no such table" from the PRAGMA itself
204+ if IsNoSuchTableError(err) {
205+ // Return the specific error type so the caller can handle it
206+ return nil, err
207+ }
208+ // Otherwise, wrap the generic error
209+ return nil, fmt.Errorf("failed to query table info for %s: %w", tableName, err)
210+ }
211+ defer rows.Close()
212+
213+ scanFailed := false // Flag to track scan errors
214+ for rows.Next() {
215+ var (
216+ cid int
217+ name string
218+ colType string
219+ notnull int
220+ dflt sql.NullString
221+ pk int
222+ )
223+ if err := rows.Scan(&cid, &name, &colType, &notnull, &dflt, &pk); err != nil {
224+ log.Error("Failed to scan column info row", "table", tableName, "error", err)
225+ scanFailed = true // Set flag on scan error
226+ break // Stop processing rows on error
227+ }
228+ columns[name] = true
229+ }
230+
231+ // Check for errors after the loop (including scan errors)
232+ if err := rows.Err(); err != nil {
233+ // Prioritize returning the specific scan error if it happened
234+ if scanFailed {
235+ // We already logged the scan error, return a generic iteration error
236+ return nil, fmt.Errorf("error scanning table info for %s: %w", tableName, err)
237+ }
238+ // Otherwise, return the iteration error from rows.Err()
239+ return nil, fmt.Errorf("error iterating table info for %s: %w", tableName, err)
240+ }
241+
242+ // If scan failed, even if rows.Err() is nil, return an error
243+ if scanFailed {
244+ return nil, fmt.Errorf("failed to scan one or more column info rows for table %s", tableName)
245+ }
246+
247+ // If we get here, PRAGMA succeeded and iteration/scanning completed without error.
248+ // It's safe to return the map (which might be empty) and nil error.
249+ return columns, nil
250+}
251+
252+// getStructFields returns a map of field names to their types from a struct
253+func getStructFields(structType interface{}) map[string]string {
254+ fields := make(map[string]string)
255+ t := reflect.TypeOf(structType)
256+
257+ // If it's a pointer, get the element type
258+ if t.Kind() == reflect.Ptr {
259+ t = t.Elem()
260+ }
261+
262+ // Ensure it's actually a struct before proceeding
263+ if t.Kind() != reflect.Struct {
264+ log.Warn("getStructFields called with non-struct type", "type", t.Name())
265+ return fields // Return empty map
266+ }
267+
268+ for i := 0; i < t.NumField(); i++ {
269+ field := t.Field(i)
270+ sqlTag := field.Tag.Get("sql")
271+ if sqlTag == "" {
272+ continue
273+ }
274+
275+ // Get the SQL column name from the tag
276+ parts := strings.Split(sqlTag, ",")
277+ sqlName := parts[0]
278+
279+ // Store the field type
280+ fields[sqlName] = field.Type.Name()
281+ }
282+
283+ return fields
284+}
285+
286+// backupDatabase creates a backup of the database file
287+// Assumes the dbPath already exists when this is called.
288+func backupDatabase(dbPath string) error {
289+ // Create backup directory if it doesn't exist
290+ backupDir := filepath.Join(filepath.Dir(dbPath), "backups")
291+ if err := os.MkdirAll(backupDir, 0755); err != nil {
292+ return fmt.Errorf("failed to create backup directory: %w", err)
293+ }
294+
295+ // Generate backup filename with timestamp
296+ timestamp := time.Now().Format("20060102_150405")
297+ backupFile := filepath.Join(backupDir, fmt.Sprintf("%s_%s.db", filepath.Base(dbPath), timestamp))
298+
299+ // Read the database file
300+ data, err := os.ReadFile(dbPath)
301+ if err != nil {
302+ // This shouldn't happen now based on the check in UpdateDatabase, but check again.
303+ if os.IsNotExist(err) {
304+ log.Warn("backupDatabase called but file vanished", "path", dbPath)
305+ return nil // File disappeared between check and read? Treat as success.
306+ }
307+ return fmt.Errorf("failed to read database file for backup: %w", err)
308+ }
309+
310+ // Write to backup file
311+ if err := os.WriteFile(backupFile, data, 0644); err != nil {
312+ return fmt.Errorf("failed to write backup file: %w", err)
313+ }
314+
315+ log.Info("Created database backup", "backup_file", backupFile)
316+ return nil
317+}
318+
319+// isDuplicateColumnError checks if the error is due to a column already existing (SQLite specific)
320+func isDuplicateColumnError(err error) bool {
321+ return err != nil && strings.Contains(strings.ToLower(err.Error()), "duplicate column name")
322+}
323+
324+// IsNoSuchTableError checks if the error is due to a table not existing (Exported: changed i to I)
325+func IsNoSuchTableError(err error) bool {
326+ // Check for the specific SQLite error message
327+ return err != nil && strings.Contains(strings.ToLower(err.Error()), "no such table")
328+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close