From 50576084c613773d0ba4a826685132a245f74fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Dec 2024 21:56:23 +0800 Subject: [PATCH] release: Add publish testflight --- .github/workflows/build.yml | 6 +- cmd/internal/app_store_connect/client.go | 30 --- .../app_store_connect/client_linkname.go | 140 ------------ cmd/internal/app_store_connect/main.go | 213 +++++++++++------- go.mod | 6 +- go.sum | 13 +- 6 files changed, 145 insertions(+), 263 deletions(-) delete mode 100644 cmd/internal/app_store_connect/client.go delete mode 100644 cmd/internal/app_store_connect/client_linkname.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1cdee2c..c37179ee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -393,7 +393,7 @@ jobs: - name: Setup Xcode stable if: matrix.if && github.ref == 'refs/heads/main-next' run: |- - sudo xcode-select -s /Applications/Xcode_16.1.app + sudo xcode-select -s /Applications/Xcode_16.2.app - name: Setup Xcode beta if: matrix.if && github.ref == 'refs/heads/dev-next' run: |- @@ -491,6 +491,10 @@ jobs: -authenticationKeyPath $ASC_KEY_PATH \ -authenticationKeyID $ASC_KEY_ID \ -authenticationKeyIssuerID $ASC_KEY_ISSUER_ID + - name: Publish to TestFlight + if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next' + run: |- + go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }} - name: Build image if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch' run: |- diff --git a/cmd/internal/app_store_connect/client.go b/cmd/internal/app_store_connect/client.go deleted file mode 100644 index bdc76076..00000000 --- a/cmd/internal/app_store_connect/client.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "context" - "fmt" - _ "unsafe" - - "github.com/cidertool/asc-go/asc" -) - -type Client struct { - *asc.Client -} - -func (c *Client) UpdateBuildForAppStoreVersion(ctx context.Context, id string, buildID *string) (*asc.Response, error) { - linkage := newRelationshipDeclaration(buildID, "builds") - url := fmt.Sprintf("appStoreVersions/%s/relationships/build", id) - return c.patch(ctx, url, newRequestBody(linkage), nil) -} - -func newRelationshipDeclaration(id *string, relationshipType string) *asc.RelationshipData { - if id == nil { - return nil - } - - return &asc.RelationshipData{ - ID: *id, - Type: relationshipType, - } -} diff --git a/cmd/internal/app_store_connect/client_linkname.go b/cmd/internal/app_store_connect/client_linkname.go deleted file mode 100644 index 7ccfc4fe..00000000 --- a/cmd/internal/app_store_connect/client_linkname.go +++ /dev/null @@ -1,140 +0,0 @@ -package main - -import ( - "context" - "net/http" - "net/url" - "reflect" - _ "unsafe" - - "github.com/cidertool/asc-go/asc" - "github.com/google/go-querystring/query" -) - -func (c *Client) newRequest(ctx context.Context, method string, path string, body *requestBody, options ...requestOption) (*http.Request, error) { - return clientNewRequest(c.Client, ctx, method, path, body, options...) -} - -//go:linkname clientNewRequest github.com/cidertool/asc-go/asc.(*Client).newRequest -func clientNewRequest(c *asc.Client, ctx context.Context, method string, path string, body *requestBody, options ...requestOption) (*http.Request, error) - -func (c *Client) do(ctx context.Context, req *http.Request, v interface{}) (*asc.Response, error) { - return clientDo(c.Client, ctx, req, v) -} - -//go:linkname clientDo github.com/cidertool/asc-go/asc.(*Client).do -func clientDo(c *asc.Client, ctx context.Context, req *http.Request, v interface{}) (*asc.Response, error) - -// get sends a GET request to the API as configured. -func (c *Client) get(ctx context.Context, url string, query interface{}, v interface{}, options ...requestOption) (*asc.Response, error) { - var err error - if query != nil { - url, err = appendingQueryOptions(url, query) - if err != nil { - return nil, err - } - } - - req, err := c.newRequest(ctx, "GET", url, nil, options...) - if err != nil { - return nil, err - } - - resp, err := c.do(ctx, req, v) - if err != nil { - return resp, err - } - - return resp, err -} - -// post sends a POST request to the API as configured. -func (c *Client) post(ctx context.Context, url string, body *requestBody, v interface{}) (*asc.Response, error) { - req, err := c.newRequest(ctx, "POST", url, body, withContentType("application/json")) - if err != nil { - return nil, err - } - - resp, err := c.do(ctx, req, v) - if err != nil { - return resp, err - } - - return resp, err -} - -// patch sends a PATCH request to the API as configured. -func (c *Client) patch(ctx context.Context, url string, body *requestBody, v interface{}) (*asc.Response, error) { - req, err := c.newRequest(ctx, "PATCH", url, body, withContentType("application/json")) - if err != nil { - return nil, err - } - - resp, err := c.do(ctx, req, v) - if err != nil { - return resp, err - } - - return resp, err -} - -// delete sends a DELETE request to the API as configured. -func (c *Client) delete(ctx context.Context, url string, body *requestBody) (*asc.Response, error) { - req, err := c.newRequest(ctx, "DELETE", url, body, withContentType("application/json")) - if err != nil { - return nil, err - } - - return c.do(ctx, req, nil) -} - -// request is a common structure for a request body sent to the API. -type requestBody struct { - Data interface{} `json:"data"` - Included interface{} `json:"included,omitempty"` -} - -func newRequestBody(data interface{}) *requestBody { - return newRequestBodyWithIncluded(data, nil) -} - -func newRequestBodyWithIncluded(data interface{}, included interface{}) *requestBody { - return &requestBody{Data: data, Included: included} -} - -type requestOption func(*http.Request) - -func withAccept(typ string) requestOption { - return func(req *http.Request) { - req.Header.Set("Accept", typ) - } -} - -func withContentType(typ string) requestOption { - return func(req *http.Request) { - req.Header.Set("Content-Type", typ) - } -} - -// AddOptions adds the parameters in opt as URL query parameters to s. opt -// must be a struct whose fields may contain "url" tags. -func appendingQueryOptions(s string, opt interface{}) (string, error) { - v := reflect.ValueOf(opt) - if v.Kind() == reflect.Ptr && v.IsNil() { - return s, nil - } - - u, err := url.Parse(s) - if err != nil { - return s, err - } - - qs, err := query.Values(opt) - if err != nil { - return s, err - } - - u.RawQuery = qs.Encode() - - return u.String(), nil -} diff --git a/cmd/internal/app_store_connect/main.go b/cmd/internal/app_store_connect/main.go index 75e00475..1fe34742 100644 --- a/cmd/internal/app_store_connect/main.go +++ b/cmd/internal/app_store_connect/main.go @@ -7,13 +7,12 @@ import ( "strconv" "time" + "github.com/sagernet/asc-go/asc" "github.com/sagernet/sing-box/cmd/internal/build_shared" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" - - "github.com/cidertool/asc-go/asc" ) func main() { @@ -54,20 +53,20 @@ const ( groupID = "5c5f3b78-b7a0-40c0-bcad-e6ef87bbefda" ) -func createClient() *Client { +func createClient(expireDuration time.Duration) *asc.Client { privateKey, err := os.ReadFile(os.Getenv("ASC_KEY_PATH")) if err != nil { log.Fatal(err) } - tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), time.Minute, privateKey) + tokenConfig, err := asc.NewTokenConfig(os.Getenv("ASC_KEY_ID"), os.Getenv("ASC_KEY_ISSUER_ID"), expireDuration, privateKey) if err != nil { log.Fatal(err) } - return &Client{asc.NewClient(tokenConfig.Client())} + return asc.NewClient(tokenConfig.Client()) } func fetchMacOSVersion(ctx context.Context) error { - client := createClient() + client := createClient(time.Minute) versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ FilterPlatform: []string{"MAC_OS"}, }) @@ -105,71 +104,101 @@ func publishTestflight(ctx context.Context) error { return err } tag := tagVersion.VersionString() - client := createClient() + client := createClient(10 * time.Minute) + log.Info(tag, " list build IDs") buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil) if err != nil { return err } - buildIDS := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string { + buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string { return it.ID }) - for _, platform := range []asc.Platform{ - asc.PlatformIOS, - asc.PlatformMACOS, - asc.PlatformTVOS, - } { + var platforms []asc.Platform + if len(os.Args) == 3 { + switch os.Args[2] { + case "ios": + platforms = []asc.Platform{asc.PlatformIOS} + case "macos": + platforms = []asc.Platform{asc.PlatformMACOS} + case "tvos": + platforms = []asc.Platform{asc.PlatformTVOS} + default: + return E.New("unknown platform: ", os.Args[2]) + } + } else { + platforms = []asc.Platform{ + asc.PlatformIOS, + asc.PlatformMACOS, + asc.PlatformTVOS, + } + } + for _, platform := range platforms { log.Info(string(platform), " list builds") - builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{ - FilterApp: []string{appID}, - FilterPreReleaseVersionPlatform: []string{string(platform)}, - }) - if err != nil { - return err - } - log.Info(string(platform), " ", tag, " list localizations") - localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, builds.Data[0].ID, nil) - if err != nil { - return err - } - localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool { - return *it.Attributes.Locale == "en-US" - }) - if localization.ID == "" { - log.Fatal(string(platform), " ", tag, " no en-US localization found") - } - if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" { - log.Info(string(platform), " ", tag, " update localization") - _, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr( - F.ToString("sing-box ", tag), - )) + for { + builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{ + FilterApp: []string{appID}, + FilterPreReleaseVersionPlatform: []string{string(platform)}, + }) if err != nil { return err } - } - if !common.Contains(buildIDS, builds.Data[0].ID) { + build := builds.Data[0] + if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute { + log.Info(string(platform), " ", tag, " waiting for process") + time.Sleep(15 * time.Second) + continue + } + if *build.Attributes.ProcessingState != "VALID" { + log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState) + time.Sleep(15 * time.Second) + continue + } + log.Info(string(platform), " ", tag, " list localizations") + localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil) + if err != nil { + return err + } + localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool { + return *it.Attributes.Locale == "en-US" + }) + if localization.ID == "" { + log.Fatal(string(platform), " ", tag, " no en-US localization found") + } + if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" { + log.Info(string(platform), " ", tag, " update localization") + _, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr( + F.ToString("sing-box ", tagVersion.String()), + )) + if err != nil { + return err + } + } log.Info(string(platform), " ", tag, " publish") - _, err = client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{builds.Data[0].ID}) + response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID}) + if response != nil && response.StatusCode == http.StatusUnprocessableEntity { + log.Info("waiting for process") + time.Sleep(15 * time.Second) + continue + } else if err != nil { + return err + } + log.Info(string(platform), " ", tag, " list submissions") + betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{ + FilterBuild: []string{build.ID}, + }) if err != nil { return err } - } - log.Info(string(platform), " ", tag, " list submissions") - betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{ - FilterBuild: []string{builds.Data[0].ID}, - }) - if err != nil { - return err - } - if len(betaSubmissions.Data) == 0 { - log.Info(string(platform), " ", tag, " create submission") - _, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, builds.Data[0].ID) - if err != nil { - return err + if len(betaSubmissions.Data) == 0 { + log.Info(string(platform), " ", tag, " create submission") + _, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID) + if err != nil { + return err + } } - continue + break } - } return nil } @@ -187,34 +216,40 @@ func cancelAppStore(ctx context.Context, platform string) error { if err != nil { return err } - client := createClient() - log.Info(platform, " list versions") - versions, _, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ - FilterPlatform: []string{string(platform)}, - }) - if err != nil { - return err - } - version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool { - return *it.Attributes.VersionString == tag - }) - if version.ID == "" { + client := createClient(time.Minute) + for { + log.Info(platform, " list versions") + versions, response, err := client.Apps.ListAppStoreVersionsForApp(ctx, appID, &asc.ListAppStoreVersionsQuery{ + FilterPlatform: []string{string(platform)}, + }) + if isRetryable(response) { + continue + } else if err != nil { + return err + } + version := common.Find(versions.Data, func(it asc.AppStoreVersion) bool { + return *it.Attributes.VersionString == tag + }) + if version.ID == "" { + return nil + } + log.Info(platform, " ", tag, " get submission") + submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil) + if response != nil && response.StatusCode == http.StatusNotFound { + return nil + } + if isRetryable(response) { + continue + } else if err != nil { + return err + } + log.Info(platform, " ", tag, " delete submission") + _, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID) + if err != nil { + return err + } return nil } - log.Info(string(platform), " ", tag, " get submission") - submission, response, err := client.Submission.GetAppStoreVersionSubmissionForAppStoreVersion(ctx, version.ID, nil) - if response != nil && response.StatusCode == http.StatusNotFound { - return nil - } - if err != nil { - return err - } - log.Info(platform, " ", tag, " delete submission") - _, err = client.Submission.DeleteSubmission(ctx, submission.Data.ID) - if err != nil { - return err - } - return nil } func prepareAppStore(ctx context.Context) error { @@ -222,7 +257,7 @@ func prepareAppStore(ctx context.Context) error { if err != nil { return err } - client := createClient() + client := createClient(time.Minute) for _, platform := range []asc.Platform{ asc.PlatformIOS, asc.PlatformMACOS, @@ -291,7 +326,7 @@ func prepareAppStore(ctx context.Context) error { log.Fatal(string(platform), " ", tag, " unknown state ", string(*version.Attributes.AppStoreState)) } log.Info(string(platform), " ", tag, " update build") - response, err = client.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID) + response, err = client.Apps.UpdateBuildForAppStoreVersion(ctx, version.ID, buildID) if err != nil { return err } @@ -364,7 +399,7 @@ func publishAppStore(ctx context.Context) error { if err != nil { return err } - client := createClient() + client := createClient(time.Minute) for _, platform := range []asc.Platform{ asc.PlatformIOS, asc.PlatformMACOS, @@ -398,3 +433,15 @@ func publishAppStore(ctx context.Context) error { } return nil } + +func isRetryable(response *asc.Response) bool { + if response == nil { + return false + } + switch response.StatusCode { + case http.StatusInternalServerError, http.StatusUnprocessableEntity: + return true + default: + return false + } +} diff --git a/go.mod b/go.mod index cd073875..eab4e0be 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.20 require ( berty.tech/go-libtor v1.0.385 github.com/caddyserver/certmagic v0.20.0 - github.com/cidertool/asc-go v0.5.1 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 github.com/go-chi/chi/v5 v5.1.0 @@ -20,6 +19,7 @@ require ( github.com/miekg/dns v1.1.62 github.com/ooni/go-libtor v1.1.8 github.com/oschwald/maxminddb-golang v1.12.0 + github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 github.com/sagernet/cors v1.2.1 @@ -61,7 +61,7 @@ require ( require ( github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect - github.com/cenkalti/backoff/v4 v4.1.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -71,7 +71,7 @@ require ( github.com/gobwas/pool v0.2.1 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/go-querystring v1.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index fc2c2392..60a048de 100644 --- a/go.sum +++ b/go.sum @@ -6,10 +6,8 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= -github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc= -github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cidertool/asc-go v0.5.1 h1:KYki2Y8IXJMOkOXy9y1sdr8tz6IdW2ti770K4bk7WY0= -github.com/cidertool/asc-go v0.5.1/go.mod h1:LyrZWU7DeCh8cWrFwXcpl93ixRUUL2aEZV7/0h07FxA= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -41,10 +39,11 @@ github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= @@ -102,6 +101,8 @@ github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1 github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4= +github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=