├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── bearer.yml │ ├── codeql.yml │ ├── go.yml │ └── goreleaser.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── _example ├── example1 │ └── example1.go ├── example2 │ └── example2.go └── example3 │ └── main.go ├── autocertcache.go ├── autotls.go ├── doc.go ├── go.mod └── go.sum /.editorconfig: -------------------------------------------------------------------------------- 1 | # unifying the coding style for different editors and IDEs => editorconfig.org 2 | 3 | ; indicate this is the root of the project 4 | root = true 5 | 6 | ########################################################### 7 | ; common 8 | ########################################################### 9 | 10 | [*] 11 | charset = utf-8 12 | 13 | end_of_line = LF 14 | insert_final_newline = true 15 | trim_trailing_whitespace = true 16 | 17 | indent_style = space 18 | indent_size = 2 19 | 20 | ########################################################### 21 | ; make 22 | ########################################################### 23 | 24 | [Makefile] 25 | indent_style = tab 26 | 27 | [makefile] 28 | indent_style = tab 29 | 30 | ########################################################### 31 | ; markdown 32 | ########################################################### 33 | 34 | [*.md] 35 | trim_trailing_whitespace = false 36 | 37 | ########################################################### 38 | ; golang 39 | ########################################################### 40 | 41 | [*.go] 42 | indent_style = tab 43 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: gomod 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/bearer.yml: -------------------------------------------------------------------------------- 1 | name: Bearer PR Check 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | 11 | jobs: 12 | rule_check: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - uses: reviewdog/action-setup@v1 19 | with: 20 | reviewdog_version: latest 21 | 22 | - name: Run Report 23 | id: report 24 | uses: bearer/bearer-action@v2 25 | with: 26 | format: rdjson 27 | output: rd.json 28 | diff: true 29 | 30 | - name: Run reviewdog 31 | if: always() 32 | env: 33 | REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | run: | 35 | cat rd.json | reviewdog -f=rdjson -reporter=github-pr-review 36 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [master] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [master] 20 | schedule: 21 | - cron: "41 23 * * 6" 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ["go"] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | - name: Perform CodeQL Analysis 54 | uses: github/codeql-action/analyze@v3 55 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version-file: go.mod 21 | check-latest: true 22 | 23 | - name: Setup golangci-lint 24 | uses: golangci/golangci-lint-action@v8 25 | with: 26 | version: v2.1 27 | test: 28 | strategy: 29 | matrix: 30 | os: [ubuntu-latest] 31 | go: [1.23, 1.24] 32 | include: 33 | - os: ubuntu-latest 34 | go-build: ~/.cache/go-build 35 | name: ${{ matrix.os }} @ Go ${{ matrix.go }} 36 | runs-on: ${{ matrix.os }} 37 | env: 38 | GO111MODULE: on 39 | GOPROXY: https://proxy.golang.org 40 | steps: 41 | - name: Set up Go ${{ matrix.go }} 42 | uses: actions/setup-go@v5 43 | with: 44 | go-version: ${{ matrix.go }} 45 | 46 | - name: Checkout Code 47 | uses: actions/checkout@v4 48 | with: 49 | ref: ${{ github.ref }} 50 | 51 | - uses: actions/cache@v4 52 | with: 53 | path: | 54 | ${{ matrix.go-build }} 55 | ~/go/pkg/mod 56 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 57 | restore-keys: | 58 | ${{ runner.os }}-go- 59 | - name: Run Tests 60 | run: | 61 | go test -v -covermode=atomic -coverprofile=coverage.out 62 | 63 | - name: Upload coverage to Codecov 64 | uses: codecov/codecov-action@v5 65 | with: 66 | flags: ${{ matrix.os }},go-${{ matrix.go }} 67 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: Goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Setup go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version-file: go.mod 24 | check-latest: true 25 | - name: Run GoReleaser 26 | uses: goreleaser/goreleaser-action@v6 27 | with: 28 | # either 'goreleaser' (default) or 'goreleaser-pro' 29 | distribution: goreleaser 30 | version: latest 31 | args: release --clean 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | vendor/* 27 | !vendor/vendor.json 28 | .idea 29 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - bodyclose 6 | - dogsled 7 | - dupl 8 | - errcheck 9 | - exhaustive 10 | - gochecknoinits 11 | - goconst 12 | - gocritic 13 | - gocyclo 14 | - goprintffuncname 15 | - gosec 16 | - govet 17 | - ineffassign 18 | - lll 19 | - misspell 20 | - nakedret 21 | - noctx 22 | - nolintlint 23 | - rowserrcheck 24 | - staticcheck 25 | - unconvert 26 | - unparam 27 | - unused 28 | - whitespace 29 | exclusions: 30 | generated: lax 31 | presets: 32 | - comments 33 | - common-false-positives 34 | - legacy 35 | - std-error-handling 36 | paths: 37 | - third_party$ 38 | - builtin$ 39 | - examples$ 40 | formatters: 41 | enable: 42 | - gofmt 43 | - gofumpt 44 | - goimports 45 | exclusions: 46 | generated: lax 47 | paths: 48 | - third_party$ 49 | - builtin$ 50 | - examples$ 51 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | builds: 2 | - # If true, skip the build. 3 | # Useful for library projects. 4 | # Default is false 5 | skip: true 6 | 7 | changelog: 8 | use: github 9 | groups: 10 | - title: Features 11 | regexp: "^.*feat[(\\w)]*:+.*$" 12 | order: 0 13 | - title: "Bug fixes" 14 | regexp: "^.*fix[(\\w)]*:+.*$" 15 | order: 1 16 | - title: "Enhancements" 17 | regexp: "^.*chore[(\\w)]*:+.*$" 18 | order: 2 19 | - title: "Refactor" 20 | regexp: "^.*refactor[(\\w)]*:+.*$" 21 | order: 3 22 | - title: "Build process updates" 23 | regexp: ^.*?(build|ci)(\(.+\))??!?:.+$ 24 | order: 4 25 | - title: "Documentation updates" 26 | regexp: ^.*?docs?(\(.+\))??!?:.+$ 27 | order: 4 28 | - title: Others 29 | order: 999 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Gin-Gonic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autotls 2 | 3 | [![Run Tests](https://github.com/gin-gonic/autotls/actions/workflows/go.yml/badge.svg)](https://github.com/gin-gonic/autotls/actions/workflows/go.yml) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/autotls)](https://goreportcard.com/report/github.com/gin-gonic/autotls) 5 | [![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/autotls?status.svg)](https://pkg.go.dev/github.com/gin-gonic/autotls) 6 | 7 | Support Let's Encrypt for a Go server application. 8 | 9 | ## example 10 | 11 | example for 1-line LetsEncrypt HTTPS servers. 12 | 13 | ```go 14 | package main 15 | 16 | import ( 17 | "log" 18 | "net/http" 19 | 20 | "github.com/gin-gonic/autotls" 21 | "github.com/gin-gonic/gin" 22 | ) 23 | 24 | func main() { 25 | r := gin.Default() 26 | 27 | // Ping handler 28 | r.GET("/ping", func(c *gin.Context) { 29 | c.String(http.StatusOK, "pong") 30 | }) 31 | 32 | log.Fatal(autotls.Run(r, "example1.com", "example2.com")) 33 | } 34 | ``` 35 | 36 | example for custom autocert manager. 37 | 38 | ```go 39 | package main 40 | 41 | import ( 42 | "log" 43 | "net/http" 44 | 45 | "github.com/gin-gonic/autotls" 46 | "github.com/gin-gonic/gin" 47 | "golang.org/x/crypto/acme/autocert" 48 | ) 49 | 50 | func main() { 51 | r := gin.Default() 52 | 53 | // Ping handler 54 | r.GET("/ping", func(c *gin.Context) { 55 | c.String(http.StatusOK, "pong") 56 | }) 57 | 58 | m := autocert.Manager{ 59 | Prompt: autocert.AcceptTOS, 60 | HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), 61 | Cache: autocert.DirCache("/var/www/.cache"), 62 | } 63 | 64 | log.Fatal(autotls.RunWithManager(r, &m)) 65 | } 66 | ``` 67 | 68 | example usage for graceful shutdown with custom context. 69 | 70 | ```go 71 | package main 72 | 73 | import ( 74 | "context" 75 | "log" 76 | "net/http" 77 | "os/signal" 78 | "syscall" 79 | 80 | "github.com/gin-gonic/autotls" 81 | "github.com/gin-gonic/gin" 82 | ) 83 | 84 | func main() { 85 | // Create context that listens for the interrupt signal from the OS. 86 | ctx, stop := signal.NotifyContext( 87 | context.Background(), 88 | syscall.SIGINT, 89 | syscall.SIGTERM, 90 | ) 91 | defer stop() 92 | 93 | r := gin.Default() 94 | 95 | // Ping handler 96 | r.GET("/ping", func(c *gin.Context) { 97 | c.String(http.StatusOK, "pong") 98 | }) 99 | 100 | log.Fatal(autotls.RunWithContext(ctx, r, "example1.com", "example2.com")) 101 | } 102 | ``` 103 | -------------------------------------------------------------------------------- /_example/example1/example1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/autotls" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func main() { 12 | r := gin.Default() 13 | 14 | // Ping handler 15 | r.GET("/ping", func(c *gin.Context) { 16 | c.String(http.StatusOK, "pong") 17 | }) 18 | 19 | log.Fatal(autotls.Run(r, "example1.com", "example2.com")) 20 | } 21 | -------------------------------------------------------------------------------- /_example/example2/example2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/autotls" 8 | "github.com/gin-gonic/gin" 9 | "golang.org/x/crypto/acme/autocert" 10 | ) 11 | 12 | func main() { 13 | r := gin.Default() 14 | 15 | // Ping handler 16 | r.GET("/ping", func(c *gin.Context) { 17 | c.String(http.StatusOK, "pong") 18 | }) 19 | 20 | m := autocert.Manager{ 21 | Prompt: autocert.AcceptTOS, 22 | HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), 23 | Cache: autocert.DirCache("/var/www/.cache"), 24 | } 25 | 26 | log.Fatal(autotls.RunWithManager(r, &m)) 27 | } 28 | -------------------------------------------------------------------------------- /_example/example3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/gin-gonic/autotls" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func main() { 15 | // Create context that listens for the interrupt signal from the OS. 16 | ctx, stop := signal.NotifyContext( 17 | context.Background(), 18 | syscall.SIGINT, 19 | syscall.SIGTERM, 20 | ) 21 | defer stop() 22 | 23 | r := gin.Default() 24 | 25 | // Ping handler 26 | r.GET("/ping", func(c *gin.Context) { 27 | c.String(http.StatusOK, "pong") 28 | }) 29 | 30 | log.Fatal(autotls.RunWithContext(ctx, r, "example1.com", "example2.com")) 31 | } 32 | -------------------------------------------------------------------------------- /autocertcache.go: -------------------------------------------------------------------------------- 1 | package autotls 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | 9 | "golang.org/x/crypto/acme/autocert" 10 | ) 11 | 12 | func getCacheDir() (autocert.DirCache, error) { 13 | dir := cacheDir() 14 | if err := os.MkdirAll(dir, 0o700); err != nil { 15 | return "", errors.New("warning: autocert.NewListener not using a cache: " + err.Error()) 16 | } 17 | return autocert.DirCache(dir), nil 18 | } 19 | 20 | func cacheDir() string { 21 | const base = "golang-autocert" 22 | switch runtime.GOOS { 23 | case "darwin": 24 | return filepath.Join(homeDir(), "Library", "Caches", base) 25 | case "windows": 26 | for _, ev := range []string{"APPDATA", "CSIDL_APPDATA", "TEMP", "TMP"} { 27 | if v := os.Getenv(ev); v != "" { 28 | return filepath.Join(v, base) 29 | } 30 | } 31 | // Worst case: 32 | return filepath.Join(homeDir(), base) 33 | } 34 | if xdg := os.Getenv("XDG_CACHE_HOME"); xdg != "" { 35 | return filepath.Join(xdg, base) 36 | } 37 | return filepath.Join(homeDir(), ".cache", base) 38 | } 39 | 40 | func homeDir() string { 41 | if runtime.GOOS == "windows" { 42 | return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") 43 | } 44 | if h := os.Getenv("HOME"); h != "" { 45 | return h 46 | } 47 | return "/" 48 | } 49 | -------------------------------------------------------------------------------- /autotls.go: -------------------------------------------------------------------------------- 1 | package autotls 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "net/http" 7 | "time" 8 | 9 | "golang.org/x/crypto/acme/autocert" 10 | "golang.org/x/sync/errgroup" 11 | ) 12 | 13 | type tlsContextKey string 14 | 15 | var ( 16 | ctxKey = tlsContextKey("autls") 17 | todoCtx = context.WithValue(context.Background(), ctxKey, "done") 18 | ReadHeaderTimeout = 3 * time.Second 19 | ) 20 | 21 | func run(ctx context.Context, r http.Handler, domain ...string) error { 22 | var g errgroup.Group 23 | 24 | s1 := &http.Server{ 25 | Addr: ":http", 26 | Handler: http.HandlerFunc(redirect), 27 | ReadHeaderTimeout: ReadHeaderTimeout, 28 | } 29 | s2 := &http.Server{ 30 | Handler: r, 31 | ReadHeaderTimeout: ReadHeaderTimeout, 32 | } 33 | 34 | g.Go(func() error { 35 | return s1.ListenAndServe() 36 | }) 37 | g.Go(func() error { 38 | return s2.Serve(autocert.NewListener(domain...)) 39 | }) 40 | 41 | g.Go(func() error { 42 | if v := ctx.Value(ctxKey); v != nil { 43 | return nil 44 | } 45 | 46 | <-ctx.Done() 47 | 48 | var gShutdown errgroup.Group 49 | gShutdown.Go(func() error { 50 | return s1.Shutdown(context.Background()) 51 | }) 52 | gShutdown.Go(func() error { 53 | return s2.Shutdown(context.Background()) 54 | }) 55 | 56 | return gShutdown.Wait() 57 | }) 58 | return g.Wait() 59 | } 60 | 61 | // Run support 1-line LetsEncrypt HTTPS servers with graceful shutdown 62 | func RunWithContext(ctx context.Context, r http.Handler, domain ...string) error { 63 | return run(ctx, r, domain...) 64 | } 65 | 66 | // Run support 1-line LetsEncrypt HTTPS servers 67 | func Run(r http.Handler, domain ...string) error { 68 | return run(todoCtx, r, domain...) 69 | } 70 | 71 | // RunWithManager support custom autocert manager 72 | func RunWithManager(r http.Handler, m *autocert.Manager) error { 73 | return RunWithManagerAndTLSConfig(r, m, m.TLSConfig()) 74 | } 75 | 76 | // RunWithManagerAndTLSConfig support custom autocert manager and tls.Config 77 | func RunWithManagerAndTLSConfig(r http.Handler, m *autocert.Manager, tlsc *tls.Config) error { 78 | var g errgroup.Group 79 | 80 | if m.Cache == nil { 81 | cache, err := getCacheDir() 82 | if err != nil { 83 | return err 84 | } 85 | m.Cache = cache 86 | } 87 | 88 | defaultTLSConfig := m.TLSConfig() 89 | tlsc.GetCertificate = defaultTLSConfig.GetCertificate 90 | tlsc.NextProtos = defaultTLSConfig.NextProtos 91 | 92 | s := &http.Server{ 93 | Addr: ":https", 94 | TLSConfig: tlsc, 95 | Handler: r, 96 | ReadHeaderTimeout: ReadHeaderTimeout, 97 | } 98 | 99 | g.Go(func() error { 100 | s := &http.Server{ 101 | Addr: ":http", 102 | Handler: m.HTTPHandler(http.HandlerFunc(redirect)), 103 | ReadHeaderTimeout: ReadHeaderTimeout, 104 | } 105 | return s.ListenAndServe() 106 | }) 107 | 108 | g.Go(func() error { 109 | return s.ListenAndServeTLS("", "") 110 | }) 111 | 112 | return g.Wait() 113 | } 114 | 115 | func redirect(w http.ResponseWriter, req *http.Request) { 116 | target := "https://" + req.Host + req.RequestURI 117 | http.Redirect(w, req, target, http.StatusMovedPermanently) 118 | } 119 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package autotls support Let's Encrypt for a Go server application. 2 | // 3 | // package main 4 | // 5 | // import ( 6 | // "log" 7 | // 8 | // "github.com/gin-gonic/autotls" 9 | // "github.com/gin-gonic/gin" 10 | // ) 11 | // 12 | // func main() { 13 | // r := gin.Default() 14 | // 15 | // // Ping handler 16 | // r.GET("/ping", func(c *gin.Context) { 17 | // c.String(200, "pong") 18 | // }) 19 | // 20 | // log.Fatal(autotls.Run(r, "example1.com", "example2.com")) 21 | // } 22 | package autotls 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gin-gonic/autotls 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | golang.org/x/crypto v0.38.0 7 | golang.org/x/sync v0.14.0 8 | ) 9 | 10 | require ( 11 | golang.org/x/net v0.40.0 // indirect 12 | golang.org/x/text v0.25.0 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 2 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 3 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 4 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 5 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 6 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 7 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 8 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 9 | --------------------------------------------------------------------------------