mirror of
https://github.com/1f349/orchid.git
synced 2025-02-05 14:06:40 +00:00
Simplify serving and setup in main binary
This commit is contained in:
parent
0722b67969
commit
bb7c4bcedc
@ -1,20 +1,117 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"github.com/google/subcommands"
|
"github.com/1f349/mjwt"
|
||||||
|
"github.com/1f349/orchid"
|
||||||
|
httpAcme "github.com/1f349/orchid/http-acme"
|
||||||
|
"github.com/1f349/orchid/logger"
|
||||||
|
"github.com/1f349/orchid/renewal"
|
||||||
|
"github.com/1f349/orchid/servers"
|
||||||
|
"github.com/1f349/violet/utils"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
exitReload "github.com/mrmelon54/exit-reload"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
var configPath string
|
||||||
subcommands.Register(subcommands.HelpCommand(), "")
|
|
||||||
subcommands.Register(subcommands.FlagsCommand(), "")
|
|
||||||
subcommands.Register(subcommands.CommandsCommand(), "")
|
|
||||||
subcommands.Register(&serveCmd{}, "")
|
|
||||||
subcommands.Register(&setupCmd{}, "")
|
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.StringVar(&configPath, "conf", "", "/path/to/config.json : path to the config file")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
ctx := context.Background()
|
|
||||||
os.Exit(int(subcommands.Execute(ctx)))
|
logger.Logger.Info("Starting...")
|
||||||
|
|
||||||
|
if configPath == "" {
|
||||||
|
logger.Logger.Fatal("Config flag is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
wd, err := getWD(configPath)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Fatal("Failed to find config directory: ", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to open the config file
|
||||||
|
openConf, err := os.Open(configPath)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
break
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
// handle potential errors during setup
|
||||||
|
err = trySetup(wd)
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, errExitSetup):
|
||||||
|
// exit setup without questions
|
||||||
|
return
|
||||||
|
case err == nil:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
logger.Logger.Fatal("Failed to run setup", "err", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
logger.Logger.Fatal("Open config file: ", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// config file opened with no errors
|
||||||
|
|
||||||
|
defer openConf.Close()
|
||||||
|
|
||||||
|
var config startUpConfig
|
||||||
|
err = yaml.NewDecoder(openConf).Decode(&config)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Fatal("Invalid config file: ", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
runDaemon(wd, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDaemon(wd string, conf startUpConfig) {
|
||||||
|
// load the MJWT RSA public key from a pem encoded file
|
||||||
|
mJwtVerify, err := mjwt.NewKeyStoreFromPath(filepath.Join(wd, "keys"))
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Fatal("Failed to load MJWT verifier public key from file", "path", filepath.Join(wd, "keys"), "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// open sqlite database
|
||||||
|
db, err := orchid.InitDB(filepath.Join(wd, "orchid.db.sqlite"))
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Fatal("Failed to open database", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certDir := filepath.Join(wd, "renewal-certs")
|
||||||
|
keyDir := filepath.Join(wd, "renewal-keys")
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
acmeProv, err := httpAcme.NewHttpAcmeProvider(filepath.Join(wd, "tokens.yml"), conf.Acme.PresentUrl, conf.Acme.CleanUpUrl, conf.Acme.RefreshUrl)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Fatal("HTTP Acme Error", "err", err)
|
||||||
|
}
|
||||||
|
renewalService, err := renewal.NewService(wg, db, acmeProv, conf.LE, certDir, keyDir)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger.Fatal("Service Error", "err", err)
|
||||||
|
}
|
||||||
|
srv := servers.NewApiServer(conf.Listen, db, mJwtVerify, conf.Domains)
|
||||||
|
logger.Logger.Info("Starting API server", "listen", srv.Addr)
|
||||||
|
go utils.RunBackgroundHttp(logger.Logger, srv)
|
||||||
|
|
||||||
|
exitReload.ExitReload("Violet", func() {}, func() {
|
||||||
|
// stop renewal service and api server
|
||||||
|
renewalService.Shutdown()
|
||||||
|
srv.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWD(configPath string) (string, error) {
|
||||||
|
if configPath == "" {
|
||||||
|
return os.Getwd()
|
||||||
|
}
|
||||||
|
wdAbs, err := filepath.Abs(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Dir(wdAbs), nil
|
||||||
}
|
}
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"github.com/1f349/mjwt"
|
|
||||||
"github.com/1f349/orchid"
|
|
||||||
httpAcme "github.com/1f349/orchid/http-acme"
|
|
||||||
"github.com/1f349/orchid/logger"
|
|
||||||
"github.com/1f349/orchid/renewal"
|
|
||||||
"github.com/1f349/orchid/servers"
|
|
||||||
"github.com/1f349/violet/utils"
|
|
||||||
"github.com/google/subcommands"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"github.com/mrmelon54/exit-reload"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type serveCmd struct{ configPath string }
|
|
||||||
|
|
||||||
func (s *serveCmd) Name() string { return "serve" }
|
|
||||||
func (s *serveCmd) Synopsis() string { return "Serve certificate renewal service" }
|
|
||||||
func (s *serveCmd) SetFlags(f *flag.FlagSet) {
|
|
||||||
f.StringVar(&s.configPath, "conf", "", "/path/to/config.json : path to the config file")
|
|
||||||
}
|
|
||||||
func (s *serveCmd) Usage() string {
|
|
||||||
return `serve [-conf <config file>]
|
|
||||||
Serve certificate renewal service using information from config file
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *serveCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
|
|
||||||
logger.Logger.Info("Starting...")
|
|
||||||
|
|
||||||
if s.configPath == "" {
|
|
||||||
logger.Logger.Error("Config flag is missing")
|
|
||||||
return subcommands.ExitUsageError
|
|
||||||
}
|
|
||||||
|
|
||||||
openConf, err := os.Open(s.configPath)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
logger.Logger.Error("Missing config file")
|
|
||||||
} else {
|
|
||||||
logger.Logger.Error("Open config file: ", "err", err)
|
|
||||||
}
|
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
|
||||||
|
|
||||||
var conf startUpConfig
|
|
||||||
err = yaml.NewDecoder(openConf).Decode(&conf)
|
|
||||||
if err != nil {
|
|
||||||
logger.Logger.Error("Invalid config file: ", "err", err)
|
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
|
||||||
|
|
||||||
wd := filepath.Dir(s.configPath)
|
|
||||||
normalLoad(conf, wd)
|
|
||||||
return subcommands.ExitSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalLoad(conf startUpConfig, wd string) {
|
|
||||||
// load the MJWT RSA public key from a pem encoded file
|
|
||||||
mJwtVerify, err := mjwt.NewKeyStoreFromPath(filepath.Join(wd, "keys"))
|
|
||||||
if err != nil {
|
|
||||||
logger.Logger.Fatal("Failed to load MJWT verifier public key from file", "path", filepath.Join(wd, "keys"), "err", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// open sqlite database
|
|
||||||
db, err := orchid.InitDB(filepath.Join(wd, "orchid.db.sqlite"))
|
|
||||||
if err != nil {
|
|
||||||
logger.Logger.Fatal("Failed to open database", "err", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certDir := filepath.Join(wd, "renewal-certs")
|
|
||||||
keyDir := filepath.Join(wd, "renewal-keys")
|
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
acmeProv, err := httpAcme.NewHttpAcmeProvider(filepath.Join(wd, "tokens.yml"), conf.Acme.PresentUrl, conf.Acme.CleanUpUrl, conf.Acme.RefreshUrl)
|
|
||||||
if err != nil {
|
|
||||||
logger.Logger.Fatal("HTTP Acme Error", "err", err)
|
|
||||||
}
|
|
||||||
renewalService, err := renewal.NewService(wg, db, acmeProv, conf.LE, certDir, keyDir)
|
|
||||||
if err != nil {
|
|
||||||
logger.Logger.Fatal("Service Error", "err", err)
|
|
||||||
}
|
|
||||||
srv := servers.NewApiServer(conf.Listen, db, mJwtVerify, conf.Domains)
|
|
||||||
logger.Logger.Info("Starting API server", "listen", srv.Addr)
|
|
||||||
go utils.RunBackgroundHttp(logger.Logger, srv)
|
|
||||||
|
|
||||||
exit_reload.ExitReload("Violet", func() {}, func() {
|
|
||||||
// stop renewal service and api server
|
|
||||||
renewalService.Shutdown()
|
|
||||||
srv.Close()
|
|
||||||
})
|
|
||||||
}
|
|
@ -2,16 +2,15 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"flag"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
httpAcme "github.com/1f349/orchid/http-acme"
|
httpAcme "github.com/1f349/orchid/http-acme"
|
||||||
|
"github.com/1f349/orchid/logger"
|
||||||
"github.com/1f349/orchid/renewal"
|
"github.com/1f349/orchid/renewal"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/google/subcommands"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
@ -22,37 +21,18 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type setupCmd struct{ wdPath string }
|
var errExitSetup = errors.New("exit setup")
|
||||||
|
|
||||||
func (s *setupCmd) Name() string { return "setup" }
|
|
||||||
func (s *setupCmd) Synopsis() string { return "Setup certificate renewal service" }
|
|
||||||
func (s *setupCmd) SetFlags(f *flag.FlagSet) {
|
|
||||||
f.StringVar(&s.wdPath, "wd", ".", "Path to the directory to create config files in (defaults to the working directory)")
|
|
||||||
}
|
|
||||||
func (s *setupCmd) Usage() string {
|
|
||||||
return `setup [-wd <directory>]
|
|
||||||
Setup Orchid automatically by answering questions.
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *setupCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
|
|
||||||
// get absolute path to specify files
|
|
||||||
wdAbs, err := filepath.Abs(s.wdPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("[Orchid] Failed to get full directory path: ", err)
|
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func trySetup(wd string) error {
|
||||||
// ask about running the setup steps
|
// ask about running the setup steps
|
||||||
createFile := false
|
createFile := false
|
||||||
err = survey.AskOne(&survey.Confirm{Message: fmt.Sprintf("Create Orchid config files in this directory: '%s'?", wdAbs)}, &createFile)
|
err := survey.AskOne(&survey.Confirm{Message: fmt.Sprintf("Create Orchid config files in this directory: '%s'?", wd)}, &createFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("[Orchid] Error: ", err)
|
return err
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
}
|
||||||
if !createFile {
|
if !createFile {
|
||||||
fmt.Println("[Orchid] Goodbye")
|
logger.Logger.Info("Goodbye")
|
||||||
return subcommands.ExitSuccess
|
return errExitSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
var answers struct {
|
var answers struct {
|
||||||
@ -64,7 +44,6 @@ func (s *setupCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
|
|||||||
AcmeRefreshUrl string
|
AcmeRefreshUrl string
|
||||||
LEEmail string
|
LEEmail string
|
||||||
}
|
}
|
||||||
_ = answers
|
|
||||||
|
|
||||||
// ask main questions
|
// ask main questions
|
||||||
err = survey.Ask([]*survey.Question{
|
err = survey.Ask([]*survey.Question{
|
||||||
@ -88,8 +67,7 @@ func (s *setupCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
|
|||||||
},
|
},
|
||||||
}, &answers)
|
}, &answers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("[Orchid] Error: ", err)
|
return err
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if answers.AcmeRefresh != "" {
|
if answers.AcmeRefresh != "" {
|
||||||
@ -111,35 +89,31 @@ func (s *setupCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
|
|||||||
},
|
},
|
||||||
}, &answers)
|
}, &answers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("[Orchid] Error: ", err)
|
return err
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := rsa.GenerateKey(rand.New(rand.NewSource(time.Now().UnixNano())), 4096)
|
key, err := rsa.GenerateKey(rand.New(rand.NewSource(time.Now().UnixNano())), 4096)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("[Orchid] Error: ", err)
|
return fmt.Errorf("failed to generate private key: %w", err)
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
}
|
||||||
keyBytes := x509.MarshalPKCS1PrivateKey(key)
|
keyBytes := x509.MarshalPKCS1PrivateKey(key)
|
||||||
keyBuf := new(bytes.Buffer)
|
keyBuf := new(bytes.Buffer)
|
||||||
err = pem.Encode(keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
|
err = pem.Encode(keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("[Orchid] Error: ", err)
|
return fmt.Errorf("failed to PEM encode private key: %w", err)
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// write config file
|
// write config file
|
||||||
confFile := filepath.Join(wdAbs, "config.yml")
|
confFile := filepath.Join(wd, "config.yml")
|
||||||
createConf, err := os.Create(confFile)
|
createConf, err := os.Create(confFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("[Orchid] Failed to create config file: ", err)
|
return fmt.Errorf("failed to create config file: %w", err)
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
}
|
||||||
|
defer createConf.Close()
|
||||||
|
|
||||||
confEncode := yaml.NewEncoder(createConf)
|
// this is the whole config structure
|
||||||
confEncode.SetIndent(2)
|
config := startUpConfig{
|
||||||
err = confEncode.Encode(startUpConfig{
|
|
||||||
Listen: answers.ApiListen,
|
Listen: answers.ApiListen,
|
||||||
Acme: acmeConfig{
|
Acme: acmeConfig{
|
||||||
PresentUrl: answers.AcmePresentUrl,
|
PresentUrl: answers.AcmePresentUrl,
|
||||||
@ -155,18 +129,20 @@ func (s *setupCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
|
|||||||
Certificate: "default",
|
Certificate: "default",
|
||||||
},
|
},
|
||||||
Domains: strings.Split(answers.ApiDomains, ","),
|
Domains: strings.Split(answers.ApiDomains, ","),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
confEncode := yaml.NewEncoder(createConf)
|
||||||
|
confEncode.SetIndent(2)
|
||||||
|
err = confEncode.Encode(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("[Orchid] Failed to write config file: ", err)
|
return fmt.Errorf("failed to write config file: %w", err)
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// write token file
|
// write token file
|
||||||
tokenFile := filepath.Join(wdAbs, "tokens.yml")
|
tokenFile := filepath.Join(wd, "tokens.yml")
|
||||||
createTokens, err := os.Create(tokenFile)
|
createTokens, err := os.Create(tokenFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("[Orchid] Failed to create tokens file: ", err)
|
return fmt.Errorf("failed to create tokens file: %w", err)
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
}
|
||||||
|
|
||||||
confEncode = yaml.NewEncoder(createTokens)
|
confEncode = yaml.NewEncoder(createTokens)
|
||||||
@ -176,14 +152,13 @@ func (s *setupCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
|
|||||||
Refresh: answers.AcmeRefresh,
|
Refresh: answers.AcmeRefresh,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("[Orchid] Failed to write tokens file: ", err)
|
return fmt.Errorf("failed to write tokens file: %w", err)
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("[Orchid] Setup complete")
|
logger.Logger.Info("Setup complete")
|
||||||
fmt.Printf("[Orchid] Run the renewal service with `orchid serve -conf %s`\n", confFile)
|
logger.Logger.Infof("Run the renewal service with `orchid-daemon -conf %s`", confFile)
|
||||||
|
|
||||||
return subcommands.ExitSuccess
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func listenAddressValidator(ans interface{}) error {
|
func listenAddressValidator(ans interface{}) error {
|
||||||
|
1
go.mod
1
go.mod
@ -10,7 +10,6 @@ require (
|
|||||||
github.com/go-acme/lego/v4 v4.21.0
|
github.com/go-acme/lego/v4 v4.21.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1
|
github.com/golang-jwt/jwt/v4 v4.5.1
|
||||||
github.com/golang-migrate/migrate/v4 v4.18.2
|
github.com/golang-migrate/migrate/v4 v4.18.2
|
||||||
github.com/google/subcommands v1.2.0
|
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
|
2
go.sum
2
go.sum
@ -42,8 +42,6 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
|||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
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 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
|
||||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
Loading…
Reference in New Issue
Block a user