diff --git a/client/config.md b/client/config.md index 8718489b45..cd4ef1d6fc 100644 --- a/client/config.md +++ b/client/config.md @@ -145,6 +145,26 @@ transport: secret: SS_SECRET ``` +In case the count of users is needed to have an exact estimate of the sessions, a reporting server can be used for that. This feature is currently experimental. + +```yaml +transport: + $type: tcpudp + tcp: + <<: &shared + $type: shadowsocks + endpoint: ss.example.com:4321 + cipher: chacha20-ietf-poly1305 + secret: SECRET + prefix: "POST " + udp: *shared + +reporter: + $type: http + url: https://your-callback-server.com/outline_callback + interval: 24h +``` + ## Tunnels ### TunnelConfig diff --git a/client/go/Taskfile.yml b/client/go/Taskfile.yml index 6e37ddf1ca..34713142cf 100644 --- a/client/go/Taskfile.yml +++ b/client/go/Taskfile.yml @@ -84,6 +84,9 @@ tasks: TARGET_DIR: "{{.OUT_DIR}}/android/org/getoutline/client/tun2socks/0.0.1" # ANDROID_API must match the minSdkVersion that the Android client supports. ANDROID_API: 26 + env: + # Bug workaround as per https://github.com/golang/go/issues/71827#issuecomment-2669425491 + GODEBUG: gotypesalias=0 preconditions: - sh: '[[ -d "$ANDROID_HOME" ]]' msg: "Must set ANDROID_HOME" @@ -104,6 +107,9 @@ tasks: MACOSX_DEPLOYMENT_TARGET: 12.0 # TARGET_IOS_VERSION must be at least 13.1 for macCatalyst and match the version set in the XCode project. TARGET_IOS_VERSION: 15.5 + env: + # Bug workaround as per https://github.com/golang/go/issues/71827#issuecomment-2669425491 + GODEBUG: gotypesalias=0 cmds: - rm -rf "{{.TARGET_DIR}}" && mkdir -p "{{.TARGET_DIR}}" - export MACOSX_DEPLOYMENT_TARGET={{.MACOSX_DEPLOYMENT_TARGET}}; {{.GOMOBILE_BIND_CMD}} -target=ios,iossimulator,maccatalyst -iosversion={{.TARGET_IOS_VERSION}} -bundleid org.outline.tun2socks -o '{{.TARGET_DIR}}/Tun2socks.xcframework' '{{.TASKFILE_DIR}}/outline/platerrors' '{{.TASKFILE_DIR}}/outline/tun2socks' '{{.TASKFILE_DIR}}/outline' diff --git a/client/go/outline/client.go b/client/go/outline/client.go index 5521e7c9d0..abdb08d72a 100644 --- a/client/go/outline/client.go +++ b/client/go/outline/client.go @@ -17,12 +17,16 @@ package outline import ( "context" "errors" + "fmt" "log/slog" "net" + "net/http" + "strings" "github.com/Jigsaw-Code/outline-apps/client/go/configyaml" "github.com/Jigsaw-Code/outline-apps/client/go/outline/config" "github.com/Jigsaw-Code/outline-apps/client/go/outline/platerrors" + "github.com/Jigsaw-Code/outline-apps/client/go/outline/reporting" "github.com/Jigsaw-Code/outline-sdk/transport" "github.com/goccy/go-yaml" ) @@ -36,8 +40,10 @@ import ( // to handle that. // - Refactor so that StartSession returns a Client type Client struct { - sd *config.Dialer[transport.StreamConn] - pl *config.PacketListener + sd *config.Dialer[transport.StreamConn] + pl *config.PacketListener + reporter reporting.Reporter + sessionCancel context.CancelFunc } func (c *Client) DialStream(ctx context.Context, address string) (transport.StreamConn, error) { @@ -50,17 +56,22 @@ func (c *Client) ListenPacket(ctx context.Context) (net.PacketConn, error) { func (c *Client) StartSession() error { slog.Debug("Starting session") + var sessionCtx context.Context + sessionCtx, c.sessionCancel = context.WithCancel(context.Background()) + go c.reporter.Run(sessionCtx) return nil } func (c *Client) EndSession() error { slog.Debug("Ending session") + c.sessionCancel() return nil } // ClientConfig is used to create the Client. type ClientConfig struct { Transport configyaml.ConfigNode + Reporter configyaml.ConfigNode } // NewClientResult represents the result of [NewClientAndReturnError]. @@ -124,5 +135,38 @@ func NewClientWithBaseDialers(clientConfigText string, tcpDialer transport.Strea } } - return &Client{sd: transportPair.StreamDialer, pl: transportPair.PacketListener}, nil + client := &Client{sd: transportPair.StreamDialer, pl: transportPair.PacketListener} + if clientConfig.Reporter != nil { + httpClient := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + if strings.HasPrefix(network, "tcp") { + return client.DialStream(ctx, addr) + } else { + return nil, fmt.Errorf("protocol not supported: %v", network) + } + }, + }, + } + reporter, err := NewReporterParser(httpClient).Parse(context.Background(), clientConfig.Reporter) + if err != nil { + return nil, &platerrors.PlatformError{ + Code: platerrors.InvalidConfig, + Message: "invalid reporter config", + Cause: platerrors.ToPlatformError(err), + } + } + client.reporter = reporter + } + + return client, nil +} + +func NewReporterParser(httpClient *http.Client) *configyaml.TypeParser[reporting.Reporter] { + parser := configyaml.NewTypeParser(func(ctx context.Context, input configyaml.ConfigNode) (reporting.Reporter, error) { + return nil, errors.New("parser not specified") + }) + parser.RegisterSubParser("first-supported", config.NewFirstSupportedSubParser(parser.Parse)) + parser.RegisterSubParser("http", reporting.NewHTTPReporterSubParser(httpClient)) + return parser } diff --git a/client/go/outline/client_test.go b/client/go/outline/client_test.go index 688ba59997..fa028ed4b3 100644 --- a/client/go/outline/client_test.go +++ b/client/go/outline/client_test.go @@ -15,9 +15,14 @@ package outline import ( + "context" + "net/http" "testing" + "time" + "github.com/Jigsaw-Code/outline-apps/client/go/configyaml" "github.com/Jigsaw-Code/outline-apps/client/go/outline/platerrors" + "github.com/Jigsaw-Code/outline-apps/client/go/outline/reporting" "github.com/stretchr/testify/require" ) @@ -303,3 +308,61 @@ func Test_NewClientFromJSON_Errors(t *testing.T) { }) } } + +func Test_UsageReporting(t *testing.T) { + config := ` +transport: + $type: tcpudp + tcp: + $type: shadowsocks + endpoint: example.com:80 + <<: &cipher + cipher: chacha20-ietf-poly1305 + secret: SECRET + prefix: "POST " + udp: + $type: shadowsocks + endpoint: example.com:53 + <<: *cipher +reporter: + $type: http + url: https://your-callback-server.com/outline_callback + interval: 24h` + + result := NewClient(config) + require.Nil(t, result.Error, "Got %v", result.Error) + require.Equal(t, "example.com:80", result.Client.sd.FirstHop) + require.Equal(t, "example.com:53", result.Client.pl.FirstHop) + require.NotNil(t, result.Client.reporter, "Reporter is nil") + require.Equal(t, "https://your-callback-server.com/outline_callback", result.Client.reporter.(*reporting.HTTPReporter).URL.String()) + require.Equal(t, 24*time.Hour, result.Client.reporter.(*reporting.HTTPReporter).Interval) +} + +func Test_ParseReporter(t *testing.T) { + config := ` +$type: http +url: https://your-callback-server.com/outline_callback +interval: 24h` + yamlNode, err := configyaml.ParseConfigYAML(config) + require.NoError(t, err) + httpClient := &http.Client{} + reporter, err := NewReporterParser(httpClient).Parse(context.Background(), yamlNode) + require.NoError(t, err) + require.NotNil(t, reporter) + require.Equal(t, "https://your-callback-server.com/outline_callback", reporter.(*reporting.HTTPReporter).URL.String()) + require.Equal(t, 24*time.Hour, reporter.(*reporting.HTTPReporter).Interval) + require.Equal(t, httpClient, reporter.(*reporting.HTTPReporter).HttpClient) +} + +func Test_ParseReporter_NoInterval(t *testing.T) { + config := ` +$type: http +url: https://your-callback-server.com/outline_callback` + yamlNode, err := configyaml.ParseConfigYAML(config) + require.NoError(t, err) + reporter, err := NewReporterParser(http.DefaultClient).Parse(context.Background(), yamlNode) + require.NoError(t, err) + require.NotNil(t, reporter) + require.Equal(t, "https://your-callback-server.com/outline_callback", reporter.(*reporting.HTTPReporter).URL.String()) + require.Equal(t, time.Duration(0), reporter.(*reporting.HTTPReporter).Interval) +} diff --git a/client/go/outline/config/config_websocket.go b/client/go/outline/config/config_websocket.go index cc6595cf2a..b48b36c3ee 100644 --- a/client/go/outline/config/config_websocket.go +++ b/client/go/outline/config/config_websocket.go @@ -20,9 +20,9 @@ import ( "net" "net/http" "net/url" - "runtime" "github.com/Jigsaw-Code/outline-apps/client/go/configyaml" + "github.com/Jigsaw-Code/outline-apps/client/go/outline/useragent" "github.com/Jigsaw-Code/outline-sdk/transport" "github.com/Jigsaw-Code/outline-sdk/x/websocket" ) @@ -77,7 +77,7 @@ func parseWebsocketEndpoint[ConnType any](ctx context.Context, configMap map[str } headers := http.Header(map[string][]string{ - "User-Agent": {fmt.Sprintf("Outline (%s; %s; %s)", runtime.GOOS, runtime.GOARCH, runtime.Version())}, + "User-Agent": {useragent.GetOutlineUserAgent()}, }) connect, err := newWE(url.String(), transport.FuncStreamEndpoint(se.Connect), websocket.WithHTTPHeaders(headers)) if err != nil { diff --git a/client/go/outline/reporting/config.go b/client/go/outline/reporting/config.go new file mode 100644 index 0000000000..48d9c4c736 --- /dev/null +++ b/client/go/outline/reporting/config.go @@ -0,0 +1,59 @@ +// Copyright 2025 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package reporting + +import ( + "context" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/Jigsaw-Code/outline-apps/client/go/configyaml" +) + +// HTTPReporterConfig is the format for the HTTPReporter config. +type HTTPReporterConfig struct { + URL string + Interval string +} + +func NewHTTPReporterSubParser(httpClient *http.Client) func(ctx context.Context, input map[string]any) (Reporter, error) { + return func(ctx context.Context, input map[string]any) (Reporter, error) { + var config HTTPReporterConfig + if err := configyaml.MapToAny(input, &config); err != nil { + return nil, fmt.Errorf("invalid config format: %w", err) + } + + collectorURL, err := url.Parse(config.URL) + if err != nil { + return nil, fmt.Errorf("failed to parse the report collector URL: %w", err) + } + reporter := &HTTPReporter{URL: *collectorURL, HttpClient: httpClient} + + if config.Interval != "" { + interval, err := time.ParseDuration(config.Interval) + if err != nil { + return nil, fmt.Errorf("failed to parse interval: %w", err) + } + if interval < 1*time.Hour { + return nil, fmt.Errorf("interval must be at least 1h") + } + reporter.Interval = interval + } + + return reporter, nil + } +} diff --git a/client/go/outline/reporting/reporter.go b/client/go/outline/reporting/reporter.go new file mode 100644 index 0000000000..4daeaa8f6a --- /dev/null +++ b/client/go/outline/reporting/reporter.go @@ -0,0 +1,87 @@ +// Copyright 2025 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package reporting + +import ( + "context" + "fmt" + "log/slog" + "net/http" + "net/url" + "time" + + "github.com/Jigsaw-Code/outline-apps/client/go/outline/useragent" +) + +// Reporter is used to register reports. +type Reporter interface { + // Run blocks until the session context is done. + Run(sessionCtx context.Context) +} + +type HTTPReporter struct { + URL url.URL + Interval time.Duration + HttpClient *http.Client +} + +func (r *HTTPReporter) Run(sessionCtx context.Context) { + r.reportAndLogError() + if r.Interval == 0 { + return + } + // Only run the loop if we specified an interval. + ticker := time.NewTicker(r.Interval) + defer ticker.Stop() + for { + select { + case <-sessionCtx.Done(): + return + case _, ok := <-ticker.C: + if !ok { + return + } + r.reportAndLogError() + } + } +} + +func (r *HTTPReporter) reportAndLogError() { + slog.Debug("Sending report", "url", r.URL) + err := r.Report() + if err != nil { + slog.Warn("Failed to report", "err", err) + } +} + +func (r *HTTPReporter) Report() error { + req, err := http.NewRequest("POST", r.URL.String(), nil) + if err != nil { + return fmt.Errorf("failed to create report HTTP request: %w", err) + } + req.Close = true + // TODO: Add Outline Client version. + req.Header.Add("User-Agent", useragent.GetOutlineUserAgent()) + + resp, err := r.HttpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to send report: %w", err) + } + resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("report failed with status code %d", resp.StatusCode) + } + return nil +} diff --git a/client/go/outline/reporting/reporter_test.go b/client/go/outline/reporting/reporter_test.go new file mode 100644 index 0000000000..a31e2156da --- /dev/null +++ b/client/go/outline/reporting/reporter_test.go @@ -0,0 +1,99 @@ +// Copyright 2025 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package reporting + +import ( + "context" + "net/http" + "net/http/httptest" + "net/url" + "testing" + "testing/synctest" + "time" + + "github.com/stretchr/testify/require" +) + +func TestHTTPReporter_Report(t *testing.T) { + var receivedRequest *http.Request + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedRequest = r + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + serverURL, err := url.Parse(server.URL) + require.NoError(t, err) + + reporter := &HTTPReporter{ + URL: *serverURL, + HttpClient: server.Client(), + } + + require.NoError(t, reporter.Report()) + require.NotNil(t, receivedRequest, "Server did not receive the request") + require.Equal(t, "POST", receivedRequest.Method) +} + +func TestHTTPReporter_Report404(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/report" { + w.WriteHeader(http.StatusOK) + } else { + w.WriteHeader(http.StatusNotFound) + } + })) + defer server.Close() + + serverURL, err := url.Parse(server.URL) + require.NoError(t, err) + + reporter := &HTTPReporter{ + URL: *serverURL, + HttpClient: server.Client(), + } + + require.Error(t, reporter.Report()) +} + +func TestHTTPReporter_ReportInterval(t *testing.T) { + var requestCount int + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestCount++ + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + serverURL, err := url.Parse(server.URL) + if err != nil { + t.Fatalf("Failed to parse server URL: %v", err) + } + + reporter := &HTTPReporter{ + URL: *serverURL, + HttpClient: server.Client(), + Interval: 100 * time.Millisecond, + } + + sessionCtx, cancelSession := context.WithCancel(context.Background()) + + synctest.Test(t, func(t *testing.T) { + go reporter.Run(sessionCtx) + time.Sleep(450 * time.Millisecond) + cancelSession() + }) + + require.Equal(t, 5, requestCount) +} diff --git a/client/go/outline/useragent/useragent.go b/client/go/outline/useragent/useragent.go new file mode 100644 index 0000000000..1d412a1264 --- /dev/null +++ b/client/go/outline/useragent/useragent.go @@ -0,0 +1,25 @@ +// Copyright 2025 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package useragent + +import ( + "fmt" + "runtime" +) + +func GetOutlineUserAgent() string { + // TODO: Add Outline Client version. + return fmt.Sprintf("Outline (%s; %s; %s)", runtime.GOOS, runtime.GOARCH, runtime.Version()) +} diff --git a/go.mod b/go.mod index c8d64de07f..d2e8b2d733 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,20 @@ module github.com/Jigsaw-Code/outline-apps -go 1.22.0 +go 1.25 require ( - github.com/Jigsaw-Code/outline-sdk v0.0.18 - github.com/Jigsaw-Code/outline-sdk/x v0.0.0-20250131142109-b32720fa2c3e + github.com/Jigsaw-Code/outline-sdk v0.0.20 + github.com/Jigsaw-Code/outline-sdk/x v0.0.5 github.com/Wifx/gonetworkmanager/v2 v2.1.0 github.com/eycorsican/go-tun2socks v1.16.11 github.com/go-task/task/v3 v3.36.0 - github.com/goccy/go-yaml v1.15.19 + github.com/goccy/go-yaml v1.18.0 github.com/google/addlicense v1.1.1 github.com/google/go-licenses v1.6.0 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/stretchr/testify v1.9.0 - golang.org/x/mobile v0.0.0-20241213221354-a87c1cf6cf46 - golang.org/x/sys v0.28.0 + golang.org/x/mobile v0.0.0-20250813145510-f12310a0cfd9 + golang.org/x/sys v0.35.0 ) require ( @@ -51,13 +51,13 @@ require ( github.com/xanzy/ssh-agent v0.2.1 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.opencensus.io v0.23.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.32.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/tools v0.28.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/term v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/tools v0.36.0 // indirect gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect gopkg.in/src-d/go-git.v4 v4.13.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 133ee16234..95721615fb 100644 --- a/go.sum +++ b/go.sum @@ -59,10 +59,10 @@ cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Jigsaw-Code/outline-sdk v0.0.18 h1:xGzbag/jWGVQl5wGy0szr1jb+P8nVDFMcR2mWmgQnqc= -github.com/Jigsaw-Code/outline-sdk v0.0.18/go.mod h1:CFDKyGZA4zatKE4vMLe8TyQpZCyINOeRFbMAmYHxodw= -github.com/Jigsaw-Code/outline-sdk/x v0.0.0-20250131142109-b32720fa2c3e h1:JN3TZFpi98BTw/CZuQWxovFsgqjQ79gGrbvZE1wFIIc= -github.com/Jigsaw-Code/outline-sdk/x v0.0.0-20250131142109-b32720fa2c3e/go.mod h1:aFUEz6Z/eD0NS3c3fEIX+JO2D9aIrXCmWTb1zJFlItw= +github.com/Jigsaw-Code/outline-sdk v0.0.20 h1:4ep7MK9lFmcyPIRIbn4xrP1VKdJNsqR6+iJEOHDKnNg= +github.com/Jigsaw-Code/outline-sdk v0.0.20/go.mod h1:CFDKyGZA4zatKE4vMLe8TyQpZCyINOeRFbMAmYHxodw= +github.com/Jigsaw-Code/outline-sdk/x v0.0.5 h1:ls768pRw5by/YL5NSVG7Qspq8NrbgXsAqML3sIgFPkM= +github.com/Jigsaw-Code/outline-sdk/x v0.0.5/go.mod h1:l9BHj3M+4EPJyeDKWQlrtuppEC4P8Gfl+eKs1dyJZ2g= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -132,8 +132,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/task/v3 v3.36.0 h1:XVJ5hQ5hdzTAulHpAGzbUMUuYr9MUOEQFOFazI3hUsY= github.com/go-task/task/v3 v3.36.0/go.mod h1:XBCIAzuyG/mgZVHMUm3cCznz4+IpsBQRlW1gw7OA5sA= -github.com/goccy/go-yaml v1.15.19 h1:ivDxLiW6SbmqPZwSAM9Yq+Yr68C9FLbTNyuH3ITizxQ= -github.com/goccy/go-yaml v1.15.19/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -361,8 +361,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -389,8 +389,8 @@ golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20241213221354-a87c1cf6cf46 h1:E+R1qmJL8cmWTyWXBHVtmqRxr7FdiTwntffsba1F1Tg= -golang.org/x/mobile v0.0.0-20241213221354-a87c1cf6cf46/go.mod h1:Sf9LBimL0mWKEdgAjRmJ6iu7Z34osHQTK/devqFbM2I= +golang.org/x/mobile v0.0.0-20250813145510-f12310a0cfd9 h1:tf0OY/FXi1sPkoNVKP4w+GStqIfqbFUqDoDDm4B+iCg= +golang.org/x/mobile v0.0.0-20250813145510-f12310a0cfd9/go.mod h1:wNiuiJfmmgv45sw8EHpNeVWqpxLeEwQ8bkUIhuOYUh8= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -402,8 +402,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -450,8 +450,8 @@ golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -488,8 +488,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -557,15 +557,15 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -577,8 +577,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -636,8 +636,12 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=