From 04c8da89efcbe142be39e6fb1f7a98bb1f709098 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 30 Sep 2026 12:06:41 -0400 Subject: [PATCH 1/2] Support passing in exit node id in to the up command --- cmd/up/client/client.go | 17 +++++++++++++++- cmd/up/client/exitnode.go | 32 +++++++++++++++++++++++++++++ cmd/up/client/exitnode_test.go | 37 ++++++++++++++++++++++++++++++++++ docs/pangolin_up.md | 1 + docs/pangolin_up_client.md | 1 + 5 files changed, 87 insertions(+), 1 deletion(-) diff --git a/cmd/up/client/client.go b/cmd/up/client/client.go index 6b011aa..2ce6c2f 100644 --- a/cmd/up/client/client.go +++ b/cmd/up/client/client.go @@ -58,6 +58,7 @@ type ClientUpCmdOpts struct { ExitNodeTakesPrecedence bool DisableRelay bool SubnetRouter bool + ExitNode string GatewaySiteIDs []int GatewaySiteResourceID int @@ -197,8 +198,11 @@ logs, and removes the service again when you press Ctrl+C.`, cmd.Flags().BoolVar(&opts.ExitNodeTakesPrecedence, "exit-node-takes-precedence", false, "Do not add routes or resolve aliases for individual resources, so all traffic is sent through the exit node instead of directly to resources (default false)") cmd.Flags().BoolVar(&opts.DisableRelay, "disable-relay", false, "Disable relay connections (default false)") cmd.Flags().BoolVar(&opts.SubnetRouter, "subnet-router", false, "Enable this client to act as a subnet router: traffic forwarded from the local network is NATed to this client's own tunnel IP before going out over the tunnel. Linux only, requires CAP_NET_ADMIN. (default false)") + cmd.Flags().StringVar(&opts.ExitNode, "exit-node", "", "Exit node `NICE-ID` to route all traffic through for this connection, overriding the exit node saved by 'pangolin select exit-node'. Requires being logged in") cmd.Flags().IntSliceVar(&opts.GatewaySiteIDs, "exit-node-site-ids", nil, "Site IDs to route all traffic through as an exit node (default: the exit node saved by 'pangolin select exit-node', if any). Requires --exit-node-resource-id") cmd.Flags().IntVar(&opts.GatewaySiteResourceID, "exit-node-resource-id", 0, "ID of the exit node resource the --exit-node-site-ids belong to, so changes to that resource are applied while connected") + cmd.MarkFlagsMutuallyExclusive("exit-node", "exit-node-site-ids") + cmd.MarkFlagsMutuallyExclusive("exit-node", "exit-node-resource-id") cmd.Flags().BoolVar(&opts.Attached, "attach", false, "Run in attached (foreground) mode, (default: detached (background) mode)") cmd.Flags().BoolVar(&opts.Silent, "silent", false, "Disable TUI and run silently when detached") @@ -443,7 +447,18 @@ func clientUpMain(cmd *cobra.Command, opts *ClientUpCmdOpts, extraArgs []string) savedResourceID = activeAccount.ExitNodeResourceID } } - opts.GatewaySiteResourceID, opts.GatewaySiteIDs = resolveSavedExitNode(savedResourceID, orgID, list) + if opts.ExitNode != "" { + // Overrides the saved exit node for this run only; the saved one + // is left as is. + var err error + opts.GatewaySiteResourceID, opts.GatewaySiteIDs, err = resolveExitNodeByNiceID(opts.ExitNode, orgID, list) + if err != nil { + logger.Error("Error: %v", err) + return err + } + } else { + opts.GatewaySiteResourceID, opts.GatewaySiteIDs = resolveSavedExitNode(savedResourceID, orgID, list) + } } else if len(opts.GatewaySiteIDs) > 0 && opts.GatewaySiteResourceID <= 0 { err := fmt.Errorf("--exit-node-site-ids requires --exit-node-resource-id") logger.Error("%v", err) diff --git a/cmd/up/client/exitnode.go b/cmd/up/client/exitnode.go index 3d94f9c..c1f3f32 100644 --- a/cmd/up/client/exitnode.go +++ b/cmd/up/client/exitnode.go @@ -1,10 +1,42 @@ package client import ( + "errors" + "fmt" + "github.com/fosrl/cli/internal/api" "github.com/fosrl/cli/internal/logger" ) +// resolveExitNodeByNiceID returns the resource ID and current site IDs of the +// exit node with the given niceId, as passed to --exit-node. Unlike a saved +// exit node, this one was asked for explicitly, so failing to resolve it is an +// error rather than a reason to connect without one. +// +// list is nil when there's no user session to query the server with. +func resolveExitNodeByNiceID(niceID string, orgID string, list func(orgID string) ([]api.SiteResource, error)) (int, []int, error) { + if list == nil || orgID == "" { + return 0, nil, errors.New("--exit-node needs a logged-in session to look up; pass --exit-node-site-ids and --exit-node-resource-id instead") + } + + gateways, err := list(orgID) + if err != nil { + return 0, nil, fmt.Errorf("failed to look up exit node '%s': %w", niceID, err) + } + + for _, g := range gateways { + if g.NiceID != niceID { + continue + } + if !g.Enabled || len(g.SiteIDs) == 0 { + return 0, nil, fmt.Errorf("exit node '%s' is disabled or has no sites", niceID) + } + return g.SiteResourceID, g.SiteIDs, nil + } + + return 0, nil, fmt.Errorf("exit node '%s' not found", niceID) +} + // resolveSavedExitNode returns the resource ID and current site IDs of the exit // node saved by `pangolin select exit-node` on the active account, or 0/nil if // none should be applied. Only the resource ID is saved (scoped to the diff --git a/cmd/up/client/exitnode_test.go b/cmd/up/client/exitnode_test.go index 85200cb..440eef1 100644 --- a/cmd/up/client/exitnode_test.go +++ b/cmd/up/client/exitnode_test.go @@ -8,6 +8,43 @@ import ( "github.com/fosrl/cli/internal/api" ) +func TestResolveExitNodeByNiceID(t *testing.T) { + lister := func(gws ...api.SiteResource) func(string) ([]api.SiteResource, error) { + return func(string) ([]api.SiteResource, error) { return gws, nil } + } + + tests := []struct { + name string + niceID string + org string + list func(string) ([]api.SiteResource, error) + want []int + wantID int + wantErr bool + }{ + {"matches by niceId", "gw-a", "org1", + lister(api.SiteResource{SiteResourceID: 11, NiceID: "gw-b", Enabled: true, SiteIDs: []int{1, 2}}, api.SiteResource{SiteResourceID: 12, NiceID: "gw-a", Enabled: true, SiteIDs: []int{2, 3}}), []int{2, 3}, 12, false}, + {"not found", "gw-a", "org1", lister(api.SiteResource{SiteResourceID: 11, NiceID: "gw-b", Enabled: true, SiteIDs: []int{1, 2}}), nil, 0, true}, + {"numeric id is not a niceId", "12", "org1", lister(api.SiteResource{SiteResourceID: 12, NiceID: "gw-a", Enabled: true, SiteIDs: []int{1, 2}}), nil, 0, true}, + {"resource disabled", "gw-a", "org1", lister(api.SiteResource{SiteResourceID: 12, NiceID: "gw-a", Enabled: false, SiteIDs: []int{1, 2}}), nil, 0, true}, + {"resource has no sites", "gw-a", "org1", lister(api.SiteResource{SiteResourceID: 12, NiceID: "gw-a", Enabled: true}), nil, 0, true}, + {"lookup fails", "gw-a", "org1", func(string) ([]api.SiteResource, error) { return nil, errors.New("boom") }, nil, 0, true}, + {"no session to look it up with", "gw-a", "org1", nil, nil, 0, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotID, got, err := resolveExitNodeByNiceID(tt.niceID, tt.org, tt.list) + if (err != nil) != tt.wantErr { + t.Fatalf("got err %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) || gotID != tt.wantID { + t.Fatalf("got %d %v, want %d %v", gotID, got, tt.wantID, tt.want) + } + }) + } +} + func TestResolveSavedExitNode(t *testing.T) { const savedResourceID = 12 lister := func(gws ...api.SiteResource) func(string) ([]api.SiteResource, error) { diff --git a/docs/pangolin_up.md b/docs/pangolin_up.md index 63e52c2..77c33e5 100644 --- a/docs/pangolin_up.md +++ b/docs/pangolin_up.md @@ -19,6 +19,7 @@ pangolin up [flags] --attach Run in attached (foreground) mode, (default: detached (background) mode) --disable-relay Disable relay connections (default false) --endpoint string Client endpoint (required if not logged in) + --exit-node NICE-ID Exit node NICE-ID to route all traffic through for this connection, overriding the exit node saved by 'pangolin select exit-node'. Requires being logged in --exit-node-resource-id int ID of the exit node resource the --exit-node-site-ids belong to, so changes to that resource are applied while connected --exit-node-site-ids ints Site IDs to route all traffic through as an exit node (default: the exit node saved by 'pangolin select exit-node', if any). Requires --exit-node-resource-id --exit-node-takes-precedence Do not add routes or resolve aliases for individual resources, so all traffic is sent through the exit node instead of directly to resources (default false) diff --git a/docs/pangolin_up_client.md b/docs/pangolin_up_client.md index 646632f..425b0d9 100644 --- a/docs/pangolin_up_client.md +++ b/docs/pangolin_up_client.md @@ -23,6 +23,7 @@ pangolin up client [flags] --attach Run in attached (foreground) mode, (default: detached (background) mode) --disable-relay Disable relay connections (default false) --endpoint string Client endpoint (required if not logged in) + --exit-node NICE-ID Exit node NICE-ID to route all traffic through for this connection, overriding the exit node saved by 'pangolin select exit-node'. Requires being logged in --exit-node-resource-id int ID of the exit node resource the --exit-node-site-ids belong to, so changes to that resource are applied while connected --exit-node-site-ids ints Site IDs to route all traffic through as an exit node (default: the exit node saved by 'pangolin select exit-node', if any). Requires --exit-node-resource-id --exit-node-takes-precedence Do not add routes or resolve aliases for individual resources, so all traffic is sent through the exit node instead of directly to resources (default false) From a7fd839514441f43ff0f9c22a05e4b85e1326119 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 1 Oct 2026 16:17:34 -0400 Subject: [PATCH 2/2] Update olm and newt --- go.mod | 14 ++++++-------- go.sum | 28 ++++++++++++---------------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index a739e6a..911eb46 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,8 @@ require ( github.com/charmbracelet/huh v1.0.0 github.com/charmbracelet/lipgloss v1.1.0 github.com/creack/pty v1.1.24 - github.com/fosrl/newt v1.18.0 - github.com/fosrl/olm v1.10.0 + github.com/fosrl/newt v1.18.1 + github.com/fosrl/olm v1.10.1 github.com/mattn/go-isatty v0.0.24 github.com/pelletier/go-toml/v2 v2.2.4 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c @@ -49,7 +49,7 @@ require ( github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/felixge/httpsnoop v1.1.0 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect - github.com/gaissmai/bart v0.29.0 // indirect + github.com/gaissmai/bart v0.30.0 // indirect github.com/go-crypt/crypt v0.14.15 // indirect github.com/go-crypt/x v0.4.17 // indirect github.com/go-logr/logr v1.4.4 // indirect @@ -68,7 +68,7 @@ require ( github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect github.com/mdlayher/socket v0.5.1 // indirect - github.com/miekg/dns v1.1.70 // indirect + github.com/miekg/dns v1.1.73 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/moby/api v1.56.0 // indirect @@ -109,19 +109,17 @@ require ( go.opentelemetry.io/otel/trace v1.46.0 // indirect go.opentelemetry.io/proto/otlp v1.11.0 // indirect golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect - golang.org/x/mod v0.41.0 // indirect golang.org/x/net v0.59.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/text v0.42.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.49.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 // indirect - golang.zx2c4.com/wireguard/windows v1.0.1 // indirect + golang.zx2c4.com/wireguard/windows v1.1.1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect - google.golang.org/grpc v1.83.2 // indirect + google.golang.org/grpc v1.84.0 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect diff --git a/go.sum b/go.sum index ea64edf..a59f982 100644 --- a/go.sum +++ b/go.sum @@ -74,16 +74,16 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= -github.com/fosrl/newt v1.18.0 h1:fFBksIV6BoI+BGmv6fwo87tXBk1WlxbVoruvupeD6hk= -github.com/fosrl/newt v1.18.0/go.mod h1:CwcuQtifgDQeSWSEB3yfqOgheFv9yltVBYQNgDB/DoM= -github.com/fosrl/olm v1.10.0 h1:2YS6Kirab1yg+MZSM8y9/Ja5IpwQBFY6d7ZMsPuYGM0= -github.com/fosrl/olm v1.10.0/go.mod h1:c/ECpzDjwnkcLrXxNpFDpFViE7+NC8yRuuLCOhGj7OA= +github.com/fosrl/newt v1.18.1 h1:gKWBDKSqQLSgD6z0jPzZB86U19a2xNfOTV0S1KdYSx0= +github.com/fosrl/newt v1.18.1/go.mod h1:gTr7YDmLqjVrqZJ3SQU0MR9wKfqbUZOJF8hSFIi/M+I= +github.com/fosrl/olm v1.10.1 h1:9q6sMuWpT8MzTj7UX4q5VYzPc8CJmW+9eBqZVIUkNZU= +github.com/fosrl/olm v1.10.1/go.mod h1:WqvwXlFtE5WjDFZj35VTdMYIhIRh4Nb65NDSNpmYE1U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= -github.com/gaissmai/bart v0.29.0 h1:wO6HGE8g9YE0Wm0bCpYxwRzfQ4+fbJKOhL64e5ACGCI= -github.com/gaissmai/bart v0.29.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= +github.com/gaissmai/bart v0.30.0 h1:K1oTSQ6Nf76bhrgxeTqzZa4hE0M9x4xDfL6n6UG67Sc= +github.com/gaissmai/bart v0.30.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c= github.com/go-crypt/crypt v0.14.15 h1:q1i5OMpL05r935IxWmXgpDAVF0nvi4SMoHhGXLBQUEQ= github.com/go-crypt/crypt v0.14.15/go.mod h1:0n/to1VqIZPENj2yEUa/sLLYYnmupma6cp+QMX4zfF0= github.com/go-crypt/x v0.4.17 h1:SK642R97T/DyDRvm8FGu5Cr2z3rH5/mV+Axc7ve2PL0= @@ -133,8 +133,8 @@ github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0 github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA= -github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +github.com/miekg/dns v1.1.73 h1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE= +github.com/miekg/dns v1.1.73/go.mod h1:RW2Obtfd5NZHvOFe3zYG0W8koWOQtAzyHaLo8vASBuQ= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -239,8 +239,6 @@ golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= -golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= -golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= @@ -257,24 +255,22 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= -golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= -golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8= -golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs= +golang.zx2c4.com/wireguard/windows v1.1.1 h1:8/H97U1v1PNDNcBsMZgU3KFuND9MQdTsU2NOwmCXArE= +golang.zx2c4.com/wireguard/windows v1.1.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=