From aa7171dd5dbf0d2151226a4bba4abd9016d9891e Mon Sep 17 00:00:00 2001 From: mintyleaf Date: Sat, 29 Mar 2025 06:45:00 +0400 Subject: [PATCH 1/3] Rewrite P2P cmd flags handling; add peerguard/auth edgevpn support; WIP edgevpn ledger management http API --- core/cli/api/p2p.go | 13 +-- core/cli/explorer.go | 7 +- core/cli/federated.go | 13 +-- core/cli/p2p/p2p.go | 17 ++++ core/cli/run.go | 27 ++++-- core/cli/worker/worker_p2p.go | 20 ++-- core/explorer/discovery.go | 12 ++- core/http/routes/peerguard.go | 80 ++++++++++++++++ core/p2p/federated_server.go | 12 ++- core/p2p/p2p.go | 169 +++++++++++++++++----------------- core/p2p/p2p_disabled.go | 18 ++-- go.mod | 42 ++------- go.sum | 90 ++++-------------- pkg/utils/p2p.go | 15 +++ 14 files changed, 306 insertions(+), 229 deletions(-) create mode 100644 core/cli/p2p/p2p.go create mode 100644 core/http/routes/peerguard.go create mode 100644 pkg/utils/p2p.go diff --git a/core/cli/api/p2p.go b/core/cli/api/p2p.go index a2ecfe3febd7..0dd6ecde4800 100644 --- a/core/cli/api/p2p.go +++ b/core/cli/api/p2p.go @@ -8,12 +8,13 @@ import ( "strings" "github.com/mudler/LocalAI/core/p2p" + p2pConfig "github.com/mudler/edgevpn/pkg/config" "github.com/mudler/edgevpn/pkg/node" "github.com/rs/zerolog/log" ) -func StartP2PStack(ctx context.Context, address, token, networkID string, federated bool) error { +func StartP2PStack(ctx context.Context, p2pCfg p2pConfig.Config, address, networkID string, federated bool) error { var n *node.Node // Here we are avoiding creating multiple nodes: // - if the federated mode is enabled, we create a federated node and expose a service @@ -29,12 +30,12 @@ func StartP2PStack(ctx context.Context, address, token, networkID string, federa // Here a new node is created and started // and a service is exposed by the node - node, err := p2p.ExposeService(ctx, "localhost", port, token, p2p.NetworkID(networkID, p2p.FederatedID)) + node, err := p2p.ExposeService(ctx, p2pCfg, "localhost", port, p2p.NetworkID(networkID, p2p.FederatedID)) if err != nil { return err } - if err := p2p.ServiceDiscoverer(ctx, node, token, p2p.NetworkID(networkID, p2p.FederatedID), nil, false); err != nil { + if err := p2p.ServiceDiscoverer(ctx, node, p2p.NetworkID(networkID, p2p.FederatedID), nil, false); err != nil { return err } @@ -42,10 +43,10 @@ func StartP2PStack(ctx context.Context, address, token, networkID string, federa } // If the p2p mode is enabled, we start the service discovery - if token != "" { + if p2pCfg.NetworkToken != "" { // If a node wasn't created previously, create it if n == nil { - node, err := p2p.NewNode(token) + node, err := p2p.NewNode(p2pCfg) if err != nil { return err } @@ -58,7 +59,7 @@ func StartP2PStack(ctx context.Context, address, token, networkID string, federa // Attach a ServiceDiscoverer to the p2p node log.Info().Msg("Starting P2P server discovery...") - if err := p2p.ServiceDiscoverer(ctx, n, token, p2p.NetworkID(networkID, p2p.WorkerID), func(serviceID string, node p2p.NodeData) { + if err := p2p.ServiceDiscoverer(ctx, n, p2p.NetworkID(networkID, p2p.WorkerID), func(serviceID string, node p2p.NodeData) { var tunnelAddresses []string for _, v := range p2p.GetAvailableNodes(p2p.NetworkID(networkID, p2p.WorkerID)) { if v.IsOnline() { diff --git a/core/cli/explorer.go b/core/cli/explorer.go index 67d25304165d..9e35c3b2ea88 100644 --- a/core/cli/explorer.go +++ b/core/cli/explorer.go @@ -5,11 +5,14 @@ import ( "time" cliContext "github.com/mudler/LocalAI/core/cli/context" + cliP2P "github.com/mudler/LocalAI/core/cli/p2p" "github.com/mudler/LocalAI/core/explorer" "github.com/mudler/LocalAI/core/http" ) type ExplorerCMD struct { + cliP2P.P2PCommonFlags `embed:""` + Address string `env:"LOCALAI_ADDRESS,ADDRESS" default:":8080" help:"Bind address for the API server" group:"api"` PoolDatabase string `env:"LOCALAI_POOL_DATABASE,POOL_DATABASE" default:"explorer.json" help:"Path to the pool database" group:"api"` ConnectionTimeout string `env:"LOCALAI_CONNECTION_TIMEOUT,CONNECTION_TIMEOUT" default:"2m" help:"Connection timeout for the explorer" group:"api"` @@ -33,14 +36,14 @@ func (e *ExplorerCMD) Run(ctx *cliContext.Context) error { if e.WithSync { ds := explorer.NewDiscoveryServer(db, dur, e.ConnectionErrorThreshold) - go ds.Start(context.Background(), true) + go ds.Start(context.Background(), e.P2PCommonFlags, true) } if e.OnlySync { ds := explorer.NewDiscoveryServer(db, dur, e.ConnectionErrorThreshold) ctx := context.Background() - return ds.Start(ctx, false) + return ds.Start(ctx, e.P2PCommonFlags, false) } appHTTP := http.Explorer(db) diff --git a/core/cli/federated.go b/core/cli/federated.go index b917812ce5a8..92c84c611d89 100644 --- a/core/cli/federated.go +++ b/core/cli/federated.go @@ -4,20 +4,21 @@ import ( "context" cliContext "github.com/mudler/LocalAI/core/cli/context" + cliP2P "github.com/mudler/LocalAI/core/cli/p2p" "github.com/mudler/LocalAI/core/p2p" ) type FederatedCLI struct { - Address string `env:"LOCALAI_ADDRESS,ADDRESS" default:":8080" help:"Bind address for the API server" group:"api"` - Peer2PeerToken string `env:"LOCALAI_P2P_TOKEN,P2P_TOKEN,TOKEN" name:"p2ptoken" help:"Token for P2P mode (optional)" group:"p2p"` - RandomWorker bool `env:"LOCALAI_RANDOM_WORKER,RANDOM_WORKER" default:"false" help:"Select a random worker from the pool" group:"p2p"` - Peer2PeerNetworkID string `env:"LOCALAI_P2P_NETWORK_ID,P2P_NETWORK_ID" help:"Network ID for P2P mode, can be set arbitrarly by the user for grouping a set of instances." group:"p2p"` - TargetWorker string `env:"LOCALAI_TARGET_WORKER,TARGET_WORKER" help:"Target worker to run the federated server on" group:"p2p"` + cliP2P.P2PCommonFlags `embed:""` + + Address string `env:"LOCALAI_ADDRESS,ADDRESS" default:":8080" help:"Bind address for the API server" group:"api"` + RandomWorker bool `env:"LOCALAI_RANDOM_WORKER,RANDOM_WORKER" default:"false" help:"Select a random worker from the pool" group:"p2p"` + TargetWorker string `env:"LOCALAI_TARGET_WORKER,TARGET_WORKER" help:"Target worker to run the federated server on" group:"p2p"` } func (f *FederatedCLI) Run(ctx *cliContext.Context) error { fs := p2p.NewFederatedServer(f.Address, p2p.NetworkID(f.Peer2PeerNetworkID, p2p.FederatedID), f.Peer2PeerToken, !f.RandomWorker, f.TargetWorker) - return fs.Start(context.Background()) + return fs.Start(context.Background(), f.P2PCommonFlags) } diff --git a/core/cli/p2p/p2p.go b/core/cli/p2p/p2p.go new file mode 100644 index 000000000000..74a442641ac7 --- /dev/null +++ b/core/cli/p2p/p2p.go @@ -0,0 +1,17 @@ +package cli + +type P2PCommonFlags struct { + Peer2PeerNoDHT bool `env:"LOCALAI_P2P_DISABLE_DHT,P2P_DISABLE_DHT" name:"p2p-disable-dht" help:"Disable DHT" group:"p2p"` + Peer2PeerLimit bool `env:"LOCALAI_P2P_ENABLE_LIMITS,P2P_ENABLE_LIMITS" name:"p2p-enable-limits" help:"Enable Limits" group:"p2p"` + Peer2PeerListenAddrs []string `env:"LOCALAI_P2P_LISTEN_MADDRS,P2P_LISTEN_MADDRS" name:"p2p-listen-maddrs" help:"A list of listen multiaddresses" group:"p2p"` + Peer2PeerBootAddrs []string `env:"LOCALAI_P2P_BOOTSTRAP_PEERS_MADDRS,P2P_BOOTSTRAP_PEERS_MADDRS" name:"p2p-bootstrap-peers-maddrs" help:"A list of bootstrap peers multiaddresses" group:"p2p"` + Peer2PeerDHTAnnounceAddrs []string `env:"LOCALAI_P2P_DHT_ANNOUNCE_MADDRS,P2P_DHT_ANNOUNCE_MADDRS" name:"p2p-dht-announce-maddrs" help:"A list of DHT announce maddrs" group:"p2p"` + Peer2PeerLibLoglevel string `env:"LOCALAI_P2P_LIB_LOGLEVEL,P2P_LIB_LOGLEVEL" name:"p2p-lib-loglevel" help:"libp2p specific loglevel" group:"p2p"` + Peer2PeerDHTInterval int `env:"LOCALAI_P2P_DHT_INTERVAL,P2P_DHT_INTERVAL" default:"360" name:"p2p-dht-interval" help:"Interval for DHT refresh (used during token generation)" group:"p2p"` + Peer2PeerOTPInterval int `env:"LOCALAI_P2P_OTP_INTERVAL,P2P_OTP_INTERVAL" default:"9000" name:"p2p-otp-interval" help:"Interval for OTP refresh (used during token generation)" group:"p2p"` + Peer2PeerToken string `env:"LOCALAI_P2P_TOKEN,P2P_TOKEN,TOKEN" name:"p2ptoken" help:"Token for P2P mode (optional)" group:"p2p"` + Peer2PeerPrivkey string `env:"LOCALAI_P2P_PRIVKEY,P2P_PRIVKEY" name:"p2pprivkey" help:"A base64 encoded protobuf serialized private key used for fixed ID (edgevpn can be used for generating one)" group:"p2p"` + Peer2PeerUsePeerguard bool `env:"LOCALAI_P2P_PEERGUARD,P2P_PEERGUARD" name:"p2ppeerguard" help:"Enable peerguarding through ecdsa authorization of nodes" group:"p2p"` + Peer2PeerAuthProvders string `env:"LOCALAI_P2P_PEERGATE_AUTH,P2P_PEERGATE_AUTH" name:"p2pauth" help:"JSON dict string with '{authProviderName: {providerOpt: value}}' structure, see edgevpn project" group:"p2p"` + Peer2PeerNetworkID string `env:"LOCALAI_P2P_NETWORK_ID,P2P_NETWORK_ID" help:"Network ID for P2P mode, can be set arbitrarly by the user for grouping a set of instances" group:"p2p"` +} diff --git a/core/cli/run.go b/core/cli/run.go index 3162ef1452e4..5e39db059ce1 100644 --- a/core/cli/run.go +++ b/core/cli/run.go @@ -9,6 +9,7 @@ import ( "github.com/mudler/LocalAI/core/application" cli_api "github.com/mudler/LocalAI/core/cli/api" cliContext "github.com/mudler/LocalAI/core/cli/context" + cliP2P "github.com/mudler/LocalAI/core/cli/p2p" "github.com/mudler/LocalAI/core/config" "github.com/mudler/LocalAI/core/http" "github.com/mudler/LocalAI/core/p2p" @@ -55,10 +56,7 @@ type RunCMD struct { DisableMetricsEndpoint bool `env:"LOCALAI_DISABLE_METRICS_ENDPOINT,DISABLE_METRICS_ENDPOINT" default:"false" help:"Disable the /metrics endpoint" group:"api"` HttpGetExemptedEndpoints []string `env:"LOCALAI_HTTP_GET_EXEMPTED_ENDPOINTS" default:"^/$,^/browse/?$,^/talk/?$,^/p2p/?$,^/chat/?$,^/text2image/?$,^/tts/?$,^/static/.*$,^/swagger.*$" help:"If LOCALAI_DISABLE_API_KEY_REQUIREMENT_FOR_HTTP_GET is overriden to true, this is the list of endpoints to exempt. Only adjust this in case of a security incident or as a result of a personal security posture review" group:"hardening"` Peer2Peer bool `env:"LOCALAI_P2P,P2P" name:"p2p" default:"false" help:"Enable P2P mode" group:"p2p"` - Peer2PeerDHTInterval int `env:"LOCALAI_P2P_DHT_INTERVAL,P2P_DHT_INTERVAL" default:"360" name:"p2p-dht-interval" help:"Interval for DHT refresh (used during token generation)" group:"p2p"` - Peer2PeerOTPInterval int `env:"LOCALAI_P2P_OTP_INTERVAL,P2P_OTP_INTERVAL" default:"9000" name:"p2p-otp-interval" help:"Interval for OTP refresh (used during token generation)" group:"p2p"` - Peer2PeerToken string `env:"LOCALAI_P2P_TOKEN,P2P_TOKEN,TOKEN" name:"p2ptoken" help:"Token for P2P mode (optional)" group:"p2p"` - Peer2PeerNetworkID string `env:"LOCALAI_P2P_NETWORK_ID,P2P_NETWORK_ID" help:"Network ID for P2P mode, can be set arbitrarly by the user for grouping a set of instances" group:"p2p"` + cliP2P.P2PCommonFlags `embed:""` ParallelRequests bool `env:"LOCALAI_PARALLEL_REQUESTS,PARALLEL_REQUESTS" help:"Enable backends to handle multiple requests in parallel if they support it (e.g.: llama.cpp or vllm)" group:"backends"` SingleActiveBackend bool `env:"LOCALAI_SINGLE_ACTIVE_BACKEND,SINGLE_ACTIVE_BACKEND" help:"Allow only one backend to be run at a time" group:"backends"` PreloadBackendOnly bool `env:"LOCALAI_PRELOAD_BACKEND_ONLY,PRELOAD_BACKEND_ONLY" default:"false" help:"Do not launch the API services, only the preloaded models / backends are started (useful for multi-node setups)" group:"backends"` @@ -114,14 +112,24 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error { } token := "" + p2pCfg := p2p.NewP2PConfig(r.P2PCommonFlags) if r.Peer2Peer || r.Peer2PeerToken != "" { + log.Info().Msg("P2P mode enabled") token = r.Peer2PeerToken if token == "" { // IF no token is provided, and p2p is enabled, // we generate one and wait for the user to pick up the token (this is for interactive) log.Info().Msg("No token provided, generating one") - token = p2p.GenerateToken(r.Peer2PeerDHTInterval, r.Peer2PeerOTPInterval) + connectionData, err := p2p.GenerateNewConnectionData( + r.Peer2PeerDHTInterval, r.Peer2PeerOTPInterval, + r.Peer2PeerPrivkey, r.Peer2PeerUsePeerguard, + ) + if err != nil { + log.Warn().Msgf("Error generating token: %s", err.Error()) + } + token = connectionData.Base64() + log.Info().Msg("Generated Token:") fmt.Println(token) @@ -129,11 +137,18 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error { fmt.Printf("export TOKEN=\"%s\"\nlocal-ai worker p2p-llama-cpp-rpc\n", token) } opts = append(opts, config.WithP2PToken(token)) + + if r.Federated { + p2pCfg.PeerGuard.Autocleanup = true + p2pCfg.PeerGuard.PeerGate = true + } + + p2pCfg.NetworkToken = token } backgroundCtx := context.Background() - if err := cli_api.StartP2PStack(backgroundCtx, r.Address, token, r.Peer2PeerNetworkID, r.Federated); err != nil { + if err := cli_api.StartP2PStack(backgroundCtx, p2pCfg, r.Address, r.Peer2PeerNetworkID, r.Federated); err != nil { return err } diff --git a/core/cli/worker/worker_p2p.go b/core/cli/worker/worker_p2p.go index aa7a8f1abb9e..de551b852092 100644 --- a/core/cli/worker/worker_p2p.go +++ b/core/cli/worker/worker_p2p.go @@ -12,6 +12,7 @@ import ( "time" cliContext "github.com/mudler/LocalAI/core/cli/context" + cliP2P "github.com/mudler/LocalAI/core/cli/p2p" "github.com/mudler/LocalAI/core/p2p" "github.com/mudler/LocalAI/pkg/assets" "github.com/mudler/LocalAI/pkg/library" @@ -20,12 +21,13 @@ import ( ) type P2P struct { - WorkerFlags `embed:""` - Token string `env:"LOCALAI_TOKEN,LOCALAI_P2P_TOKEN,TOKEN" help:"P2P token to use"` - NoRunner bool `env:"LOCALAI_NO_RUNNER,NO_RUNNER" help:"Do not start the llama-cpp-rpc-server"` - RunnerAddress string `env:"LOCALAI_RUNNER_ADDRESS,RUNNER_ADDRESS" help:"Address of the llama-cpp-rpc-server"` - RunnerPort string `env:"LOCALAI_RUNNER_PORT,RUNNER_PORT" help:"Port of the llama-cpp-rpc-server"` - Peer2PeerNetworkID string `env:"LOCALAI_P2P_NETWORK_ID,P2P_NETWORK_ID" help:"Network ID for P2P mode, can be set arbitrarly by the user for grouping a set of instances" group:"p2p"` + WorkerFlags `embed:""` + cliP2P.P2PCommonFlags `embed:""` + + Token string `env:"LOCALAI_TOKEN,LOCALAI_P2P_TOKEN,TOKEN" help:"P2P token to use"` + NoRunner bool `env:"LOCALAI_NO_RUNNER,NO_RUNNER" help:"Do not start the llama-cpp-rpc-server"` + RunnerAddress string `env:"LOCALAI_RUNNER_ADDRESS,RUNNER_ADDRESS" help:"Address of the llama-cpp-rpc-server"` + RunnerPort string `env:"LOCALAI_RUNNER_PORT,RUNNER_PORT" help:"Port of the llama-cpp-rpc-server"` } func (r *P2P) Run(ctx *cliContext.Context) error { @@ -41,6 +43,8 @@ func (r *P2P) Run(ctx *cliContext.Context) error { if r.Token == "" { return fmt.Errorf("Token is required") } + p2pCfg := p2p.NewP2PConfig(r.P2PCommonFlags) + p2pCfg.NetworkToken = r.Token port, err := freeport.GetFreePort() if err != nil { @@ -60,7 +64,7 @@ func (r *P2P) Run(ctx *cliContext.Context) error { p = r.RunnerPort } - _, err = p2p.ExposeService(context.Background(), address, p, r.Token, p2p.NetworkID(r.Peer2PeerNetworkID, p2p.WorkerID)) + _, err = p2p.ExposeService(context.Background(), p2pCfg, address, p, p2p.NetworkID(r.Peer2PeerNetworkID, p2p.WorkerID)) if err != nil { return err } @@ -103,7 +107,7 @@ func (r *P2P) Run(ctx *cliContext.Context) error { } }() - _, err = p2p.ExposeService(context.Background(), address, fmt.Sprint(port), r.Token, p2p.NetworkID(r.Peer2PeerNetworkID, p2p.WorkerID)) + _, err = p2p.ExposeService(context.Background(), p2pCfg, address, fmt.Sprint(port), p2p.NetworkID(r.Peer2PeerNetworkID, p2p.WorkerID)) if err != nil { return err } diff --git a/core/explorer/discovery.go b/core/explorer/discovery.go index fe6470cb825d..296c92658e37 100644 --- a/core/explorer/discovery.go +++ b/core/explorer/discovery.go @@ -9,6 +9,7 @@ import ( "github.com/rs/zerolog/log" + cliP2P "github.com/mudler/LocalAI/core/cli/p2p" "github.com/mudler/LocalAI/core/p2p" "github.com/mudler/edgevpn/pkg/blockchain" ) @@ -40,7 +41,7 @@ type Network struct { Clusters []ClusterData } -func (s *DiscoveryServer) runBackground() { +func (s *DiscoveryServer) runBackground(p2pCommonFlags cliP2P.P2PCommonFlags) { if len(s.database.TokenList()) == 0 { time.Sleep(5 * time.Second) // avoid busy loop return @@ -50,11 +51,14 @@ func (s *DiscoveryServer) runBackground() { c, cancel := context.WithTimeout(context.Background(), s.connectionTime) defer cancel() + p2pCfg := p2p.NewP2PConfig(p2pCommonFlags) + p2pCfg.NetworkToken = token + // Connect to the network // Get the number of nodes // save it in the current state (mutex) // do not do in parallel - n, err := p2p.NewNode(token) + n, err := p2p.NewNode(p2pCfg) if err != nil { log.Err(err).Msg("Failed to create node") s.failedToken(token) @@ -197,14 +201,14 @@ func (s *DiscoveryServer) retrieveNetworkData(c context.Context, ledger *blockch } // Start the discovery server. This is meant to be run in to a goroutine. -func (s *DiscoveryServer) Start(ctx context.Context, keepRunning bool) error { +func (s *DiscoveryServer) Start(ctx context.Context, p2pCommonFlags cliP2P.P2PCommonFlags, keepRunning bool) error { for { select { case <-ctx.Done(): return fmt.Errorf("context cancelled") default: // Collect data - s.runBackground() + s.runBackground(p2pCommonFlags) if !keepRunning { return nil } diff --git a/core/http/routes/peerguard.go b/core/http/routes/peerguard.go new file mode 100644 index 000000000000..dd576656eabe --- /dev/null +++ b/core/http/routes/peerguard.go @@ -0,0 +1,80 @@ +package routes + +import ( + "context" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/mudler/edgevpn/pkg/node" +) + +const DefaultInterval = 5 * time.Second +const Timeout = 20 * time.Second + +// TODO connect routes and write a middleware for authorization based on p2p auth providers private keys +func RegisterPeerguardAuthRoutes(app *fiber.App, e *node.Node) { + app.Get("ledger/:bucket/:key", func(c *fiber.Ctx) error { + bucket := c.Params("bucket") + key := c.Params("key") + + ledger, err := e.Ledger() + if err != nil { + return err + } + + return c.JSON(ledger.CurrentData()[bucket][key]) + }) + + app.Get("ledger/:bucket", func(c *fiber.Ctx) error { + bucket := c.Params("bucket") + + ledger, err := e.Ledger() + if err != nil { + return err + } + + return c.JSON(ledger.CurrentData()[bucket]) + }) + + announcing := struct{ State string }{"Announcing"} + + // Store arbitrary data + app.Get("ledger/:bucket/:key/:value", func(c *fiber.Ctx) error { + bucket := c.Params("bucket") + key := c.Params("key") + value := c.Params("value") + + ledger, err := e.Ledger() + if err != nil { + return err + } + + ledger.Persist(context.Background(), DefaultInterval, Timeout, bucket, key, value) + return c.JSON(announcing) + }) + // Delete data from ledger + app.Get("ledger/:bucket", func(c *fiber.Ctx) error { + bucket := c.Params("bucket") + + ledger, err := e.Ledger() + if err != nil { + return err + } + + ledger.AnnounceDeleteBucket(context.Background(), DefaultInterval, Timeout, bucket) + return c.JSON(announcing) + }) + + app.Get("ledger/:bucket/:key", func(c *fiber.Ctx) error { + bucket := c.Params("bucket") + key := c.Params("key") + + ledger, err := e.Ledger() + if err != nil { + return err + } + + ledger.AnnounceDeleteBucketKey(context.Background(), DefaultInterval, Timeout, bucket, key) + return c.JSON(announcing) + }) +} diff --git a/core/p2p/federated_server.go b/core/p2p/federated_server.go index d80af082c94f..de696abb791c 100644 --- a/core/p2p/federated_server.go +++ b/core/p2p/federated_server.go @@ -10,12 +10,18 @@ import ( "io" "net" + cliP2P "github.com/mudler/LocalAI/core/cli/p2p" "github.com/mudler/edgevpn/pkg/node" "github.com/rs/zerolog/log" ) -func (f *FederatedServer) Start(ctx context.Context) error { - n, err := NewNode(f.p2ptoken) +func (f *FederatedServer) Start(ctx context.Context, p2pCommonFlags cliP2P.P2PCommonFlags) error { + p2pCfg := NewP2PConfig(p2pCommonFlags) + p2pCfg.NetworkToken = f.p2ptoken + p2pCfg.PeerGuard.Autocleanup = true + p2pCfg.PeerGuard.PeerGate = true + + n, err := NewNode(p2pCfg) if err != nil { return fmt.Errorf("creating a new node: %w", err) } @@ -24,7 +30,7 @@ func (f *FederatedServer) Start(ctx context.Context) error { return fmt.Errorf("creating a new node: %w", err) } - if err := ServiceDiscoverer(ctx, n, f.p2ptoken, f.service, func(servicesID string, tunnel NodeData) { + if err := ServiceDiscoverer(ctx, n, f.service, func(servicesID string, tunnel NodeData) { log.Debug().Msgf("Discovered node: %s", tunnel.ID) }, false); err != nil { return err diff --git a/core/p2p/p2p.go b/core/p2p/p2p.go index ce0dcc07ad43..d2d47aea20b7 100644 --- a/core/p2p/p2p.go +++ b/core/p2p/p2p.go @@ -5,32 +5,35 @@ package p2p import ( "context" + "encoding/json" "errors" "fmt" "io" "net" "os" - "strings" "sync" "time" "github.com/ipfs/go-log" + "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/peer" + cliP2P "github.com/mudler/LocalAI/core/cli/p2p" "github.com/mudler/LocalAI/pkg/utils" - "github.com/mudler/edgevpn/pkg/config" + p2pConfig "github.com/mudler/edgevpn/pkg/config" "github.com/mudler/edgevpn/pkg/node" "github.com/mudler/edgevpn/pkg/protocol" "github.com/mudler/edgevpn/pkg/services" "github.com/mudler/edgevpn/pkg/types" eutils "github.com/mudler/edgevpn/pkg/utils" - "github.com/multiformats/go-multiaddr" "github.com/phayes/freeport" zlog "github.com/rs/zerolog/log" "github.com/mudler/edgevpn/pkg/logger" ) -func generateNewConnectionData(DHTInterval, OTPInterval int) *node.YAMLConnectionConfig { +const DefaultInterval = 10 * time.Second + +func GenerateNewConnectionData(DHTInterval, OTPInterval int, privkey string, peerguardMode bool) (*node.YAMLConnectionConfig, error) { maxMessSize := 20 << 20 // 20MB keyLength := 43 if DHTInterval == 0 { @@ -40,7 +43,7 @@ func generateNewConnectionData(DHTInterval, OTPInterval int) *node.YAMLConnectio OTPInterval = 9000 } - return &node.YAMLConnectionConfig{ + connectionConfig := node.YAMLConnectionConfig{ MaxMessageSize: maxMessSize, RoomName: eutils.RandStringRunes(keyLength), Rendezvous: eutils.RandStringRunes(keyLength), @@ -58,11 +61,27 @@ func generateNewConnectionData(DHTInterval, OTPInterval int) *node.YAMLConnectio }, }, } -} -func GenerateToken(DHTInterval, OTPInterval int) string { - // Generates a new config and exit - return generateNewConnectionData(DHTInterval, OTPInterval).Base64() + if peerguardMode { + key, err := crypto.UnmarshalPrivateKey([]byte(privkey)) + if err != nil { + return &connectionConfig, err + } + pid, err := peer.IDFromPublicKey(key.GetPublic()) + if err != nil { + return &connectionConfig, err + } + + connectionConfig.TrustedPeerIDS = []string{ + pid.String(), + } + connectionConfig.ProtectedStoreKeys = []string{ + "trustzone", + "trustzoneAuth", + } + + } + return &connectionConfig, nil } func IsP2PEnabled() bool { @@ -176,11 +195,11 @@ func allocateLocalService(ctx context.Context, node *node.Node, listenAddr, serv // This is the main of the server (which keeps the env variable updated) // This starts a goroutine that keeps LLAMACPP_GRPC_SERVERS updated with the discovered services -func ServiceDiscoverer(ctx context.Context, n *node.Node, token, servicesID string, discoveryFunc func(serviceID string, node NodeData), allocate bool) error { +func ServiceDiscoverer(ctx context.Context, n *node.Node, servicesID string, discoveryFunc func(serviceID string, node NodeData), allocate bool) error { if servicesID == "" { servicesID = defaultServicesID } - tunnels, err := discoveryTunnels(ctx, n, token, servicesID, allocate) + tunnels, err := discoveryTunnels(ctx, n, servicesID, allocate) if err != nil { return err } @@ -207,7 +226,7 @@ func ServiceDiscoverer(ctx context.Context, n *node.Node, token, servicesID stri return nil } -func discoveryTunnels(ctx context.Context, n *node.Node, token, servicesID string, allocate bool) (chan NodeData, error) { +func discoveryTunnels(ctx context.Context, n *node.Node, servicesID string, allocate bool) (chan NodeData, error) { tunnels := make(chan NodeData) ledger, err := n.Ledger() @@ -316,16 +335,16 @@ func ensureService(ctx context.Context, n *node.Node, nd *NodeData, sserv string } // This is the P2P worker main -func ExposeService(ctx context.Context, host, port, token, servicesID string) (*node.Node, error) { - if servicesID == "" { - servicesID = defaultServicesID - } +func ExposeService(ctx context.Context, p2pCfg p2pConfig.Config, host, port, servicesID string) (*node.Node, error) { llger := logger.New(log.LevelFatal) - - nodeOpts, err := newNodeOpts(token) + nodeOpts, _, err := p2pCfg.ToOpts(llger) if err != nil { - return nil, err + return nil, fmt.Errorf("parsing config for new node: %w", err) } + nodeOpts = append(nodeOpts, node.FromBase64(true, p2pCfg.Discovery.DHT, p2pCfg.NetworkToken, nil, nil)) + + nodeOpts = append(nodeOpts, services.Alive(30*time.Second, 900*time.Second, 15*time.Minute)...) + // generate a random string for the name name := utils.RandString(10) @@ -347,6 +366,9 @@ func ExposeService(ctx context.Context, host, port, token, servicesID string) (* return n, fmt.Errorf("creating a new node: %w", err) } + if servicesID == "" { + servicesID = defaultServicesID + } ledger.Announce( ctx, 20*time.Second, @@ -364,11 +386,15 @@ func ExposeService(ctx context.Context, host, port, token, servicesID string) (* return n, err } -func NewNode(token string) (*node.Node, error) { - nodeOpts, err := newNodeOpts(token) +func NewNode(p2pCfg p2pConfig.Config) (*node.Node, error) { + llger := logger.New(log.LevelFatal) + nodeOpts, _, err := p2pCfg.ToOpts(llger) if err != nil { - return nil, err + return nil, fmt.Errorf("parsing config for new node: %w", err) } + nodeOpts = append(nodeOpts, node.FromBase64(true, p2pCfg.Discovery.DHT, p2pCfg.NetworkToken, nil, nil)) + + nodeOpts = append(nodeOpts, services.Alive(30*time.Second, 900*time.Second, 15*time.Minute)...) n, err := node.New(nodeOpts...) if err != nil { @@ -378,89 +404,64 @@ func NewNode(token string) (*node.Node, error) { return n, nil } -func newNodeOpts(token string) ([]node.Option, error) { - llger := logger.New(log.LevelFatal) - defaultInterval := 10 * time.Second - - // TODO: move this up, expose more config options when creating a node - noDHT := os.Getenv("LOCALAI_P2P_DISABLE_DHT") == "true" - noLimits := os.Getenv("LOCALAI_P2P_ENABLE_LIMITS") == "true" - - var listenMaddrs []string - var bootstrapPeers []string - - laddrs := os.Getenv("LOCALAI_P2P_LISTEN_MADDRS") - if laddrs != "" { - listenMaddrs = strings.Split(laddrs, ",") - } - - bootmaddr := os.Getenv("LOCALAI_P2P_BOOTSTRAP_PEERS_MADDRS") - if bootmaddr != "" { - bootstrapPeers = strings.Split(bootmaddr, ",") - } - - dhtAnnounceMaddrs := stringsToMultiAddr(strings.Split(os.Getenv("LOCALAI_P2P_DHT_ANNOUNCE_MADDRS"), ",")) +func NewP2PConfig(p2pCommonFlags cliP2P.P2PCommonFlags) p2pConfig.Config { + pa := p2pCommonFlags.Peer2PeerAuthProvders + d := map[string]map[string]interface{}{} + json.Unmarshal([]byte(pa), &d) - libp2ploglevel := os.Getenv("LOCALAI_P2P_LIB_LOGLEVEL") - if libp2ploglevel == "" { - libp2ploglevel = "fatal" - } - c := config.Config{ - ListenMaddrs: listenMaddrs, - DHTAnnounceMaddrs: dhtAnnounceMaddrs, - Limit: config.ResourceLimit{ - Enable: noLimits, + c := p2pConfig.Config{ + ListenMaddrs: p2pCommonFlags.Peer2PeerListenAddrs, + DHTAnnounceMaddrs: utils.StringsToMultiAddr(p2pCommonFlags.Peer2PeerDHTAnnounceAddrs), + Limit: p2pConfig.ResourceLimit{ + Enable: p2pCommonFlags.Peer2PeerLimit, MaxConns: 100, }, - NetworkToken: token, - LowProfile: false, - LogLevel: logLevel, - Libp2pLogLevel: libp2ploglevel, - Ledger: config.Ledger{ - SyncInterval: defaultInterval, - AnnounceInterval: defaultInterval, + LowProfile: false, + LogLevel: logLevel, + Ledger: p2pConfig.Ledger{ + SyncInterval: DefaultInterval, + AnnounceInterval: DefaultInterval, }, - NAT: config.NAT{ + NAT: p2pConfig.NAT{ Service: true, Map: true, RateLimit: true, RateLimitGlobal: 100, RateLimitPeer: 100, - RateLimitInterval: defaultInterval, + RateLimitInterval: DefaultInterval, }, - Discovery: config.Discovery{ - DHT: !noDHT, + Discovery: p2pConfig.Discovery{ + DHT: !p2pCommonFlags.Peer2PeerNoDHT, MDNS: true, - Interval: 10 * time.Second, - BootstrapPeers: bootstrapPeers, + Interval: DefaultInterval, + BootstrapPeers: p2pCommonFlags.Peer2PeerBootAddrs, }, - Connection: config.Connection{ + Connection: p2pConfig.Connection{ HolePunch: true, AutoRelay: true, MaxConnections: 1000, }, - } + PeerGuard: p2pConfig.PeerGuard{ + Enable: p2pCommonFlags.Peer2PeerUsePeerguard, - nodeOpts, _, err := c.ToOpts(llger) - if err != nil { - return nil, fmt.Errorf("parsing options: %w", err) + // Default from edgevpn + SyncInterval: 120 * time.Second, + AuthProviders: d, + }, } - nodeOpts = append(nodeOpts, services.Alive(30*time.Second, 900*time.Second, 15*time.Minute)...) - - return nodeOpts, nil -} + privkey := p2pCommonFlags.Peer2PeerPrivkey + if privkey != "" { + c.Privkey = []byte(privkey) + } -func stringsToMultiAddr(peers []string) []multiaddr.Multiaddr { - res := []multiaddr.Multiaddr{} - for _, p := range peers { - addr, err := multiaddr.NewMultiaddr(p) - if err != nil { - continue - } - res = append(res, addr) + libp2ploglevel := p2pCommonFlags.Peer2PeerLibLoglevel + if libp2ploglevel == "" { + libp2ploglevel = "fatal" } - return res + c.Libp2pLogLevel = libp2ploglevel + + return c } func copyStream(closer chan struct{}, dst io.Writer, src io.Reader) { diff --git a/core/p2p/p2p_disabled.go b/core/p2p/p2p_disabled.go index c5ba98fdab59..f27b13860e7d 100644 --- a/core/p2p/p2p_disabled.go +++ b/core/p2p/p2p_disabled.go @@ -7,22 +7,24 @@ import ( "context" "fmt" + cliP2P "github.com/mudler/LocalAI/core/cli/p2p" + p2pConfig "github.com/mudler/edgevpn/pkg/config" "github.com/mudler/edgevpn/pkg/node" ) -func GenerateToken(DHTInterval, OTPInterval int) string { - return "not implemented" +func GenerateNewConnectionData(DHTInterval, OTPInterval int, privkey string, peerguardMode bool) (*node.YAMLConnectionConfig, error) { + return nil, fmt.Errorf("not implemented") } -func (f *FederatedServer) Start(ctx context.Context) error { +func (f *FederatedServer) Start(ctx context.Context, p2pCommonFlags cliP2P.P2PCommonFlags) error { return fmt.Errorf("not implemented") } -func ServiceDiscoverer(ctx context.Context, node *node.Node, token, servicesID string, fn func(string, NodeData), allocate bool) error { +func ServiceDiscoverer(ctx context.Context, n *node.Node, servicesID string, discoveryFunc func(serviceID string, node NodeData), allocate bool) error { return fmt.Errorf("not implemented") } -func ExposeService(ctx context.Context, host, port, token, servicesID string) (*node.Node, error) { +func ExposeService(ctx context.Context, p2pCfg p2pConfig.Config, host, port, servicesID string) (*node.Node, error) { return nil, fmt.Errorf("not implemented") } @@ -30,6 +32,10 @@ func IsP2PEnabled() bool { return false } -func NewNode(token string) (*node.Node, error) { +func NewNode(p2pCfg p2pConfig.Config) (*node.Node, error) { return nil, fmt.Errorf("not implemented") } + +func NewP2PConfig(p2pCommonFlags cliP2P.P2PCommonFlags) p2pConfig.Config { + return p2pConfig.Config{} +} diff --git a/go.mod b/go.mod index 856d41f55aed..45385a49fe02 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,8 @@ require ( dario.cat/mergo v1.0.1 github.com/Masterminds/sprig/v3 v3.3.0 github.com/alecthomas/kong v0.9.0 - github.com/census-instrumentation/opencensus-proto v0.4.1 github.com/charmbracelet/glamour v0.7.0 github.com/chasefleming/elem-go v0.26.0 - github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 github.com/containerd/containerd v1.7.19 github.com/dave-gray101/v2keyauth v0.0.0-20240624150259-c45d584d25e2 github.com/elliotchance/orderedmap/v2 v2.2.0 @@ -22,18 +20,15 @@ require ( github.com/gofiber/fiber/v2 v2.52.5 github.com/gofiber/swagger v1.0.0 github.com/gofiber/template/html/v2 v2.1.2 - github.com/gofiber/websocket/v2 v2.2.1 github.com/gofrs/flock v0.12.1 - github.com/golang/protobuf v1.5.4 github.com/google/go-containerregistry v0.19.2 github.com/google/uuid v1.6.0 - github.com/grpc-ecosystem/grpc-gateway v1.5.0 github.com/hpcloud/tail v1.0.0 github.com/ipfs/go-log v1.0.5 github.com/jaypipes/ghw v0.12.0 github.com/joho/godotenv v1.5.1 github.com/klauspost/cpuid/v2 v2.2.9 - github.com/libp2p/go-libp2p v0.39.1 + github.com/libp2p/go-libp2p v0.40.0 github.com/mholt/archiver/v3 v3.5.1 github.com/microcosm-cc/bluemonday v1.0.26 github.com/mudler/edgevpn v0.30.1 @@ -41,11 +36,10 @@ require ( github.com/nikolalohinski/gonja/v2 v2.3.2 github.com/onsi/ginkgo/v2 v2.22.2 github.com/onsi/gomega v1.36.2 - github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e github.com/ory/dockertest/v3 v3.10.0 github.com/otiai10/openaigo v1.7.0 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 - github.com/prometheus/client_golang v1.20.5 + github.com/prometheus/client_golang v1.21.0 github.com/rs/zerolog v1.33.0 github.com/russross/blackfriday v1.6.0 github.com/sashabaranov/go-openai v1.26.2 @@ -61,7 +55,6 @@ require ( go.opentelemetry.io/otel/exporters/prometheus v0.50.0 go.opentelemetry.io/otel/metric v1.34.0 go.opentelemetry.io/otel/sdk/metric v1.28.0 - google.golang.org/api v0.180.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.36.5 gopkg.in/yaml.v2 v2.4.0 @@ -70,20 +63,11 @@ require ( ) require ( - cel.dev/expr v0.16.0 // indirect - cloud.google.com/go/auth v0.4.1 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect - cloud.google.com/go/compute/metadata v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect - github.com/fasthttp/websocket v1.5.3 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.0.0 // indirect - github.com/google/s2a-go v0.1.7 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.4 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/libp2p/go-yamux/v5 v5.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -91,11 +75,9 @@ require ( github.com/pion/datachannel v1.5.10 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect github.com/pion/dtls/v3 v3.0.4 // indirect - github.com/pion/ice/v2 v2.3.37 // indirect github.com/pion/ice/v4 v4.0.6 // indirect github.com/pion/interceptor v0.1.37 // indirect github.com/pion/logging v0.2.3 // indirect - github.com/pion/mdns v0.0.12 // indirect github.com/pion/mdns/v2 v2.0.7 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.15 // indirect @@ -107,18 +89,13 @@ require ( github.com/pion/stun/v3 v3.0.0 // indirect github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/transport/v3 v3.0.7 // indirect - github.com/pion/turn/v2 v2.1.6 // indirect github.com/pion/turn/v4 v4.0.0 // indirect github.com/pion/webrtc/v4 v4.0.9 // indirect - github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/shirou/gopsutil/v4 v4.24.7 // indirect github.com/wlynxg/anet v0.0.5 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect go.uber.org/mock v0.5.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/time v0.8.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect ) require ( @@ -148,7 +125,7 @@ require ( github.com/creachadair/otp v0.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect github.com/docker/cli v27.0.3+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect @@ -161,7 +138,7 @@ require ( github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-audio/audio v1.0.0 + github.com/go-audio/audio v1.0.0 // indirect github.com/go-audio/riff v1.0.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -193,7 +170,7 @@ require ( github.com/huin/goupnp v1.3.0 // indirect github.com/ipfs/boxo v0.27.4 // indirect github.com/ipfs/go-cid v0.5.0 // indirect - github.com/ipfs/go-datastore v0.6.0 // indirect + github.com/ipfs/go-datastore v0.7.0 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect github.com/ipld/go-ipld-prime v0.21.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect @@ -208,7 +185,7 @@ require ( github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.2.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect - github.com/libp2p/go-libp2p-kad-dht v0.29.0 // indirect + github.com/libp2p/go-libp2p-kad-dht v0.29.1 // indirect github.com/libp2p/go-libp2p-kbucket v0.6.5 // indirect github.com/libp2p/go-libp2p-pubsub v0.13.0 // indirect github.com/libp2p/go-libp2p-record v0.3.1 // indirect @@ -217,7 +194,6 @@ require ( github.com/libp2p/go-nat v0.2.0 // indirect github.com/libp2p/go-netroute v0.2.2 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect - github.com/libp2p/go-yamux/v4 v4.0.2 // indirect github.com/libp2p/zeroconf/v2 v2.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7 // indirect @@ -308,7 +284,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.33.0 // indirect - golang.org/x/exp v0.0.0-20250215185904-eff6e970281f // indirect + golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sync v0.11.0 // indirect @@ -326,3 +302,5 @@ require ( howett.net/plist v1.0.0 // indirect lukechampine.com/blake3 v1.3.0 // indirect ) + +replace github.com/mudler/edgevpn => github.com/swarmind/edgevpn v0.0.0-20250329011455-c0a96483b1ff diff --git a/go.sum b/go.sum index 06e0238f0835..6e8bbb5eee6a 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,7 @@ -cel.dev/expr v0.16.0 h1:yloc84fytn4zmJX2GU3TkXGsaieaV7dQ057Qs4sIG2Y= -cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= -cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= -cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= -cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= -cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= -cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= -cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= @@ -69,8 +61,6 @@ github.com/c-robinson/iplib v1.0.8/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szN 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng= @@ -80,8 +70,6 @@ github.com/chasefleming/elem-go v0.26.0/go.mod h1:hz73qILBIKnTgOujnSMtEj20/epI+f github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg= -github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= @@ -115,10 +103,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= -github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= -github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/cli v27.0.3+incompatible h1:usGs0/BoBW8MWxGeEtqPMkzOY56jZ6kYlSN5BLDioCQ= @@ -149,12 +137,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= -github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= -github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek= -github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= @@ -219,8 +201,6 @@ github.com/gofiber/template/html/v2 v2.1.2 h1:wkK/mYJ3nIhongTkG3t0QgV4ADdgOYJYVS github.com/gofiber/template/html/v2 v2.1.2/go.mod h1:E98Z/FzvpaSib06aWEgYk6GXNf3ctoyaJH8yW5ay5ak= github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= -github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w= -github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -244,8 +224,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -274,20 +252,13 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc= github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= -github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= -github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -296,7 +267,6 @@ github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.5.0 h1:WcmKMm43DR7RdtlkEXQJyo5ws8iTp98CyhCCbOHMvNI= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= @@ -323,8 +293,8 @@ github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNi github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= -github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= -github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= +github.com/ipfs/go-datastore v0.7.0 h1:a6JMuRFKYhw6XXmIVoTthF8ZFm4QQXvLDXFhXRVv8Go= +github.com/ipfs/go-datastore v0.7.0/go.mod h1:ucOWMfbOPI6ZEyaIB1q/+78RPLBPERfuUVYX1EPnNpQ= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= @@ -395,12 +365,12 @@ github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38y github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw= github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc= -github.com/libp2p/go-libp2p v0.39.1 h1:1Ur6rPCf3GR+g8jkrnaQaM0ha2IGespsnNlCqJLLALE= -github.com/libp2p/go-libp2p v0.39.1/go.mod h1:3zicI8Lp7Isun+Afo/JOACUbbJqqR2owK6RQWFsVAbI= +github.com/libp2p/go-libp2p v0.40.0 h1:1LOMO3gigxeXFs50HGEc1U79OINewUQB7o4gTKGPC3U= +github.com/libp2p/go-libp2p v0.40.0/go.mod h1:hOzj2EAIYsXpVpBnyA1pRHzpUJGF9nbWiDLjgasnbF0= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= -github.com/libp2p/go-libp2p-kad-dht v0.29.0 h1:045eW21lGlMSD9aKSZZGH4fnBMIInPwQLxIQ35P962I= -github.com/libp2p/go-libp2p-kad-dht v0.29.0/go.mod h1:mIci3rHSwDsxQWcCjfmxD8vMTgh5xLuvwb1D5WP8ZNk= +github.com/libp2p/go-libp2p-kad-dht v0.29.1 h1:RyD1RnnkXOh1gwBCrMQ6ZVfTJECY5yDOY6qxt9VNqE4= +github.com/libp2p/go-libp2p-kad-dht v0.29.1/go.mod h1:tZEFTKWCsY0xngypKyAIwNDNZOBiikSUIgd/BjTF5Ms= github.com/libp2p/go-libp2p-kbucket v0.6.5 h1:Fsl1YvZcMwqrR4DYrTO02yo9PGYs2HBQIT3lGXFMTxg= github.com/libp2p/go-libp2p-kbucket v0.6.5/go.mod h1:U6WOd0BvnSp03IQSrjgM54tg7zh1UUNsXLJqAQzClTA= github.com/libp2p/go-libp2p-pubsub v0.13.0 h1:RmFQ2XAy3zQtbt2iNPy7Tt0/3fwTnHpCQSSnmGnt1Ps= @@ -419,8 +389,8 @@ github.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFP github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= -github.com/libp2p/go-yamux/v4 v4.0.2 h1:nrLh89LN/LEiqcFiqdKDRHjGstN300C1269K/EX0CPU= -github.com/libp2p/go-yamux/v4 v4.0.2/go.mod h1:C808cCRgOs1iBwY4S71T5oxgMxgLmqUw56qh4AeBW2o= +github.com/libp2p/go-yamux/v5 v5.0.0 h1:2djUh96d3Jiac/JpGkKs4TO49YhsfLopAoryfPmf+Po= +github.com/libp2p/go-yamux/v5 v5.0.0/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU= github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q= github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -489,8 +459,6 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mudler/edgevpn v0.30.1 h1:4yyhNFJX62NpRp50sxiyZE5E/sdAqEZX+aE5Mv7QS60= -github.com/mudler/edgevpn v0.30.1/go.mod h1:IAJkkJ0oH3rwsSGOGTFT4UBYFqYuD/QyaKzTLB3P/eU= github.com/mudler/go-piper v0.0.0-20241023091659-2494246fd9fc h1:RxwneJl1VgvikiX28EkpdAyL4yQVnJMrbquKospjHyA= github.com/mudler/go-piper v0.0.0-20241023091659-2494246fd9fc/go.mod h1:O7SwdSWMilAWhBZMK9N9Y/oBDyMMzshE3ju8Xkexwig= github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82 h1:FVT07EI8njvsD4tC2Hw8Xhactp5AWhsQWD4oTeQuSAU= @@ -553,8 +521,6 @@ github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGdY0Y6qfTeUKhDawdHDpK9RGBdx80qN4Ttw= -github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0= github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/otiai10/mint v1.6.1 h1:kgbTJmOpp/0ce7hk3H8jiSuR0MXmpwWRfqUdKww17qg= @@ -578,8 +544,6 @@ github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U= github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg= -github.com/pion/ice/v2 v2.3.37 h1:ObIdaNDu1rCo7hObhs34YSBcO7fjslJMZV0ux+uZWh0= -github.com/pion/ice/v2 v2.3.37/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ= github.com/pion/ice/v4 v4.0.6 h1:jmM9HwI9lfetQV/39uD0nY4y++XZNPhvzIPCb8EwxUM= github.com/pion/ice/v4 v4.0.6/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI= @@ -587,8 +551,6 @@ github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVm github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= -github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8= -github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk= github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= @@ -611,12 +573,8 @@ github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1A github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= -github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= -github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= -github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc= -github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= github.com/pion/webrtc/v4 v4.0.9 h1:PyOYMRKJgfy0dzPcYtFD/4oW9zaw3Ze3oZzzbj2LV9E= @@ -633,8 +591,8 @@ github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= +github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= @@ -670,8 +628,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sashabaranov/go-openai v1.26.2 h1:cVlQa3gn3eYqNXRW03pPlpy6zLG52EU4g0FrWXc0EFI= github.com/sashabaranov/go-openai v1.26.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= -github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= -github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/schollz/progressbar/v3 v3.14.4 h1:W9ZrDSJk7eqmQhd3uxFNNcTr0QL+xuGNI9dEMrw0r74= github.com/schollz/progressbar/v3 v3.14.4/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -747,6 +703,8 @@ github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/swarmind/edgevpn v0.0.0-20250329011455-c0a96483b1ff h1:tAZL+yhXDkwSkkjmPWMk+iBJ/t1MP43ntJiQA4Q5+kw= +github.com/swarmind/edgevpn v0.0.0-20250329011455-c0a96483b1ff/go.mod h1:bGUdGQzwLOuMs3SII1N6SazoI1qQ1ekxdxNatOCS5ZM= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/thxcode/gguf-parser-go v0.1.0 h1:J4QruXyEQGjrAKeKZFlsD2na9l4XF5+bjR194d+wJS4= github.com/thxcode/gguf-parser-go v0.1.0/go.mod h1:Tn1PsO/YDEtLIxm1+QDCjIIH9L/9Sr7+KpxZKm0sEuE= @@ -811,8 +769,6 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/exporters/prometheus v0.50.0 h1:2Ewsda6hejmbhGFyUvWZjUThC98Cf8Zy6g0zkIimOng= @@ -863,8 +819,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20250215185904-eff6e970281f h1:oFMYAjX0867ZD2jcNiLBrI9BdpmEkvPyi5YrBGXbamg= -golang.org/x/exp v0.0.0-20250215185904-eff6e970281f/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -912,8 +868,6 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -961,7 +915,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1033,8 +986,6 @@ gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= -google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= -google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1046,10 +997,6 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= -google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -1075,7 +1022,6 @@ google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwl google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= diff --git a/pkg/utils/p2p.go b/pkg/utils/p2p.go new file mode 100644 index 000000000000..f988f364f974 --- /dev/null +++ b/pkg/utils/p2p.go @@ -0,0 +1,15 @@ +package utils + +import "github.com/multiformats/go-multiaddr" + +func StringsToMultiAddr(peers []string) []multiaddr.Multiaddr { + res := []multiaddr.Multiaddr{} + for _, p := range peers { + addr, err := multiaddr.NewMultiaddr(p) + if err != nil { + continue + } + res = append(res, addr) + } + return res +} From 5c35029ae7e3312f8c4e2a3ac700b71c8908058c Mon Sep 17 00:00:00 2001 From: mintyleaf Date: Sat, 29 Mar 2025 16:54:29 +0400 Subject: [PATCH 2/3] Add token generation for federated server; remove additional P2P setup for federated instance mode --- core/cli/federated.go | 20 ++++++++++++++++++++ core/cli/run.go | 5 ----- core/p2p/federated_server.go | 8 ++++---- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/core/cli/federated.go b/core/cli/federated.go index 92c84c611d89..8e5cfdccba52 100644 --- a/core/cli/federated.go +++ b/core/cli/federated.go @@ -2,10 +2,12 @@ package cli import ( "context" + "fmt" cliContext "github.com/mudler/LocalAI/core/cli/context" cliP2P "github.com/mudler/LocalAI/core/cli/p2p" "github.com/mudler/LocalAI/core/p2p" + "github.com/rs/zerolog/log" ) type FederatedCLI struct { @@ -18,6 +20,24 @@ type FederatedCLI struct { func (f *FederatedCLI) Run(ctx *cliContext.Context) error { + if f.Peer2PeerToken == "" { + log.Info().Msg("No token provided, generating one") + connectionData, err := p2p.GenerateNewConnectionData( + f.Peer2PeerDHTInterval, f.Peer2PeerOTPInterval, + f.Peer2PeerPrivkey, f.Peer2PeerUsePeerguard, + ) + if err != nil { + log.Warn().Msgf("Error generating token: %s", err.Error()) + } + f.Peer2PeerToken = connectionData.Base64() + + log.Info().Msg("Generated Token:") + fmt.Println(f.Peer2PeerToken) + + log.Info().Msg("To use the token, you can run the following command in another node or terminal:") + fmt.Printf("export TOKEN=\"%s\"\nlocal-ai worker p2p-llama-cpp-rpc\n", f.Peer2PeerToken) + } + fs := p2p.NewFederatedServer(f.Address, p2p.NetworkID(f.Peer2PeerNetworkID, p2p.FederatedID), f.Peer2PeerToken, !f.RandomWorker, f.TargetWorker) return fs.Start(context.Background(), f.P2PCommonFlags) diff --git a/core/cli/run.go b/core/cli/run.go index 5e39db059ce1..d058f013c78f 100644 --- a/core/cli/run.go +++ b/core/cli/run.go @@ -138,11 +138,6 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error { } opts = append(opts, config.WithP2PToken(token)) - if r.Federated { - p2pCfg.PeerGuard.Autocleanup = true - p2pCfg.PeerGuard.PeerGate = true - } - p2pCfg.NetworkToken = token } diff --git a/core/p2p/federated_server.go b/core/p2p/federated_server.go index de696abb791c..081bd13488a2 100644 --- a/core/p2p/federated_server.go +++ b/core/p2p/federated_server.go @@ -15,9 +15,9 @@ import ( "github.com/rs/zerolog/log" ) -func (f *FederatedServer) Start(ctx context.Context, p2pCommonFlags cliP2P.P2PCommonFlags) error { +func (fs *FederatedServer) Start(ctx context.Context, p2pCommonFlags cliP2P.P2PCommonFlags) error { p2pCfg := NewP2PConfig(p2pCommonFlags) - p2pCfg.NetworkToken = f.p2ptoken + p2pCfg.NetworkToken = fs.p2ptoken p2pCfg.PeerGuard.Autocleanup = true p2pCfg.PeerGuard.PeerGate = true @@ -30,13 +30,13 @@ func (f *FederatedServer) Start(ctx context.Context, p2pCommonFlags cliP2P.P2PCo return fmt.Errorf("creating a new node: %w", err) } - if err := ServiceDiscoverer(ctx, n, f.service, func(servicesID string, tunnel NodeData) { + if err := ServiceDiscoverer(ctx, n, fs.service, func(servicesID string, tunnel NodeData) { log.Debug().Msgf("Discovered node: %s", tunnel.ID) }, false); err != nil { return err } - return f.proxy(ctx, n) + return fs.proxy(ctx, n) } func (fs *FederatedServer) proxy(ctx context.Context, node *node.Node) error { From 2ee3c8311ae9a3b3bf5cc070c7c7fa03592ade5e Mon Sep 17 00:00:00 2001 From: mintyleaf Date: Thu, 3 Apr 2025 03:08:57 +0400 Subject: [PATCH 3/3] add net.Conn http request checker --- core/http/routes/peerguard.go | 80 --------- core/p2p/federated_server.go | 327 ++++++++++++++++++++++++++++------ go.mod | 2 +- go.sum | 8 +- 4 files changed, 277 insertions(+), 140 deletions(-) delete mode 100644 core/http/routes/peerguard.go diff --git a/core/http/routes/peerguard.go b/core/http/routes/peerguard.go deleted file mode 100644 index dd576656eabe..000000000000 --- a/core/http/routes/peerguard.go +++ /dev/null @@ -1,80 +0,0 @@ -package routes - -import ( - "context" - "time" - - "github.com/gofiber/fiber/v2" - "github.com/mudler/edgevpn/pkg/node" -) - -const DefaultInterval = 5 * time.Second -const Timeout = 20 * time.Second - -// TODO connect routes and write a middleware for authorization based on p2p auth providers private keys -func RegisterPeerguardAuthRoutes(app *fiber.App, e *node.Node) { - app.Get("ledger/:bucket/:key", func(c *fiber.Ctx) error { - bucket := c.Params("bucket") - key := c.Params("key") - - ledger, err := e.Ledger() - if err != nil { - return err - } - - return c.JSON(ledger.CurrentData()[bucket][key]) - }) - - app.Get("ledger/:bucket", func(c *fiber.Ctx) error { - bucket := c.Params("bucket") - - ledger, err := e.Ledger() - if err != nil { - return err - } - - return c.JSON(ledger.CurrentData()[bucket]) - }) - - announcing := struct{ State string }{"Announcing"} - - // Store arbitrary data - app.Get("ledger/:bucket/:key/:value", func(c *fiber.Ctx) error { - bucket := c.Params("bucket") - key := c.Params("key") - value := c.Params("value") - - ledger, err := e.Ledger() - if err != nil { - return err - } - - ledger.Persist(context.Background(), DefaultInterval, Timeout, bucket, key, value) - return c.JSON(announcing) - }) - // Delete data from ledger - app.Get("ledger/:bucket", func(c *fiber.Ctx) error { - bucket := c.Params("bucket") - - ledger, err := e.Ledger() - if err != nil { - return err - } - - ledger.AnnounceDeleteBucket(context.Background(), DefaultInterval, Timeout, bucket) - return c.JSON(announcing) - }) - - app.Get("ledger/:bucket/:key", func(c *fiber.Ctx) error { - bucket := c.Params("bucket") - key := c.Params("key") - - ledger, err := e.Ledger() - if err != nil { - return err - } - - ledger.AnnounceDeleteBucketKey(context.Background(), DefaultInterval, Timeout, bucket, key) - return c.JSON(announcing) - }) -} diff --git a/core/p2p/federated_server.go b/core/p2p/federated_server.go index 081bd13488a2..81fccaa3cc33 100644 --- a/core/p2p/federated_server.go +++ b/core/p2p/federated_server.go @@ -4,17 +4,36 @@ package p2p import ( + "bufio" "context" + "encoding/json" "errors" "fmt" "io" "net" + "net/http" + "slices" + "strings" + "time" + logP2P "github.com/ipfs/go-log/v2" cliP2P "github.com/mudler/LocalAI/core/cli/p2p" + edgevpnConfig "github.com/mudler/edgevpn/pkg/config" + "github.com/mudler/edgevpn/pkg/logger" "github.com/mudler/edgevpn/pkg/node" + "github.com/mudler/edgevpn/pkg/trustzone" "github.com/rs/zerolog/log" ) +const Timeout = 20 * time.Second + +const ( + peekBufferSize = 512 + authHeader = "X-Auth-Token" + headerEnd = "\r\n\r\n" + lineEnd = "\r\n" +) + func (fs *FederatedServer) Start(ctx context.Context, p2pCommonFlags cliP2P.P2PCommonFlags) error { p2pCfg := NewP2PConfig(p2pCommonFlags) p2pCfg.NetworkToken = fs.p2ptoken @@ -36,11 +55,26 @@ func (fs *FederatedServer) Start(ctx context.Context, p2pCommonFlags cliP2P.P2PC return err } - return fs.proxy(ctx, n) -} + lvl, err := logP2P.LevelFromString(p2pCfg.LogLevel) + if err != nil { + lvl = logP2P.LevelError + } + llger := logger.New(lvl) + + aps := []trustzone.AuthProvider{} + for ap, providerOpts := range p2pCfg.PeerGuard.AuthProviders { + a, err := edgevpnConfig.AuthProvider(llger, ap, providerOpts) + if err != nil { + log.Warn().Msgf("invalid authprovider: %v", err) + continue + } + aps = append(aps, a) + } -func (fs *FederatedServer) proxy(ctx context.Context, node *node.Node) error { + return fs.listener(ctx, n, aps) +} +func (fs *FederatedServer) listener(ctx context.Context, node *node.Node, aps []trustzone.AuthProvider) error { log.Info().Msgf("Allocating service '%s' on: %s", fs.service, fs.listenAddr) // Open local port for listening l, err := net.Listen("tcp", fs.listenAddr) @@ -57,6 +91,7 @@ func (fs *FederatedServer) proxy(ctx context.Context, node *node.Node) error { nodeAnnounce(ctx, node) defer l.Close() + for { select { case <-ctx.Done(): @@ -70,62 +105,243 @@ func (fs *FederatedServer) proxy(ctx context.Context, node *node.Node) error { continue } - // Handle connections in a new goroutine, forwarding to the p2p service go func() { - workerID := "" - if fs.workerTarget != "" { - workerID = fs.workerTarget - } else if fs.loadBalanced { - log.Debug().Msgf("Load balancing request") - - workerID = fs.SelectLeastUsedServer() - if workerID == "" { - log.Debug().Msgf("Least used server not found, selecting random") - workerID = fs.RandomServer() + if len(aps) > 0 { + if fs.handleHTTP(conn, node, aps) { + return } - } else { - workerID = fs.RandomServer() } + fs.proxy(ctx, node, conn) + }() + } + } +} - if workerID == "" { - log.Error().Msg("No available nodes yet") - fs.sendHTMLResponse(conn, 503, "Sorry, waiting for nodes to connect") - return - } +func (fs *FederatedServer) handleHTTP(conn net.Conn, node *node.Node, aps []trustzone.AuthProvider) bool { + defer func() { + if r := recover(); r != nil { + log.Debug().Msgf("Recovered from panic: %v", r) + conn.Close() + } + }() - log.Debug().Msgf("Selected node %s", workerID) - nodeData, exists := GetNode(fs.service, workerID) - if !exists { - log.Error().Msgf("Node %s not found", workerID) - fs.sendHTMLResponse(conn, 404, "Node not found") - return - } + r, err := testForHTTPRequest(conn) + if err != nil { + return false + } + defer r.Body.Close() + pathParts := strings.Split(strings.TrimPrefix(r.URL.Path, "/ledger/"), "/") + announcing := struct{ State string }{"Announcing"} + + // TODO deal with AuthProviders + // pubKey := r.Header.Get(authHeader) + + switch r.Method { + case http.MethodGet: + switch len(pathParts) { + case 2: // /ledger/:bucket/:key + bucket := pathParts[0] + key := pathParts[1] + + ledger, err := node.Ledger() + if err != nil { + fs.sendRawResponse(conn, http.StatusInternalServerError, "text/plain", []byte(err.Error())) + return true + } + + fs.sendJSONResponse(conn, http.StatusOK, ledger.CurrentData()[bucket][key]) + + case 1: // /ledger/:bucket + bucket := pathParts[0] + + ledger, err := node.Ledger() + if err != nil { + fs.sendRawResponse(conn, http.StatusInternalServerError, "text/plain", []byte(err.Error())) + return true + } + + fs.sendJSONResponse(conn, http.StatusOK, ledger.CurrentData()[bucket]) + + default: + fs.sendRawResponse(conn, http.StatusNotFound, "text/plain", []byte("not found")) + + } + + case http.MethodPut: + if len(pathParts) == 3 { // /ledger/:bucket/:key/:value + bucket := pathParts[0] + key := pathParts[1] + value := pathParts[2] + + ledger, err := node.Ledger() + if err != nil { + fs.sendRawResponse(conn, http.StatusInternalServerError, "text/plain", []byte(err.Error())) + return true + } + + ledger.Persist(context.Background(), DefaultInterval, Timeout, bucket, key, value) + fs.sendJSONResponse(conn, http.StatusOK, announcing) + + } else { + fs.sendRawResponse(conn, http.StatusNotFound, "text/plain", []byte("not found")) + } + + case http.MethodDelete: + switch len(pathParts) { + case 1: // /ledger/:bucket + bucket := pathParts[0] + + ledger, err := node.Ledger() + if err != nil { + fs.sendRawResponse(conn, http.StatusInternalServerError, "text/plain", []byte(err.Error())) + return true + } + + ledger.AnnounceDeleteBucket(context.Background(), DefaultInterval, Timeout, bucket) + fs.sendJSONResponse(conn, http.StatusOK, announcing) + + case 2: // /ledger/:bucket/:key + bucket := pathParts[0] + key := pathParts[1] + + ledger, err := node.Ledger() + if err != nil { + fs.sendRawResponse(conn, http.StatusInternalServerError, "text/plain", []byte(err.Error())) + return true + } + + ledger.AnnounceDeleteBucketKey(context.Background(), DefaultInterval, Timeout, bucket, key) + fs.sendJSONResponse(conn, http.StatusOK, announcing) + + default: + fs.sendRawResponse(conn, http.StatusNotFound, "text/plain", []byte("not found")) - proxyP2PConnection(ctx, node, nodeData.ServiceID, conn) - if fs.loadBalanced { - fs.RecordRequest(workerID) - } - }() } } + + return true } -// sendHTMLResponse sends a basic HTML response with a status code and a message. -// This is extracted to make the HTML content maintainable. -func (fs *FederatedServer) sendHTMLResponse(conn net.Conn, statusCode int, message string) { - defer conn.Close() +// testForHTTPRequest peeking the first N bytes from the accepted conn, and trying to match it +// against the supported http methods, then against the supported route, then if there is auth header +func testForHTTPRequest(conn net.Conn) (*http.Request, error) { + reader := bufio.NewReader(conn) - // Define the HTML content separately for easier maintenance. - htmlContent := fmt.Sprintf("

%s

\r\n", message) + peekedData, err := reader.Peek(peekBufferSize) + if err != nil && err != bufio.ErrBufferFull { + log.Debug().Msgf("Error peeking data: %v", err) + return nil, err + } + peekedString := string(peekedData) + + // 1. Parse Request Line + firstLineEnd := strings.Index(peekedString, lineEnd) + if firstLineEnd == -1 { + log.Debug().Msg("Could not find request line end") + return nil, err + } + requestLine := peekedString[:firstLineEnd] + parts := strings.Split(requestLine, " ") + if len(parts) != 3 { + log.Debug().Msg("Invalid request line format") + return nil, err + } + method := parts[0] + uri := parts[1] + + if !slices.Contains([]string{ + http.MethodGet, + http.MethodPut, + http.MethodDelete, + }, method) { + log.Debug().Msg("Unsupported HTTP method") + return nil, err + } + if !strings.HasPrefix(uri, "/ledger") { + log.Debug().Msg("Unsupported HTTP route") + return nil, err + } + + headersPart := peekedString[firstLineEnd+len(lineEnd):] + headerEndIndex := strings.Index(headersPart, headerEnd) + if headerEndIndex == -1 { + log.Debug().Msg("Could not find end of headers within peek buffer") + return nil, err + } + headersString := headersPart[:headerEndIndex] + headers := strings.Split(headersString, lineEnd) + + foundAuth := false + for _, header := range headers { + if strings.HasPrefix(header, authHeader+":") { + parts := strings.SplitN(header, ":", 2) + if len(parts) == 2 { + foundAuth = true + break + } + } + } + + if !foundAuth { + log.Debug().Msgf("Required header '%s' not found.", authHeader) + return nil, err + } + + req, err := http.ReadRequest(reader) + if err != nil { + log.Debug().Msgf("Error reading full request: %v", err) + return nil, err + } + return req, nil +} + +func (fs *FederatedServer) proxy(ctx context.Context, node *node.Node, conn net.Conn) { + workerID := "" + if fs.workerTarget != "" { + workerID = fs.workerTarget + } else if fs.loadBalanced { + log.Debug().Msgf("Load balancing request") + + workerID = fs.SelectLeastUsedServer() + if workerID == "" { + log.Debug().Msgf("Least used server not found, selecting random") + workerID = fs.RandomServer() + } + } else { + workerID = fs.RandomServer() + } + + if workerID == "" { + log.Error().Msg("No available nodes yet") + fs.sendHTMLResponse(conn, 503, "Sorry, waiting for nodes to connect") + return + } + + log.Debug().Msgf("Selected node %s", workerID) + nodeData, exists := GetNode(fs.service, workerID) + if !exists { + log.Error().Msgf("Node %s not found", workerID) + fs.sendHTMLResponse(conn, 404, "Node not found") + return + } + + proxyP2PConnection(ctx, node, nodeData.ServiceID, conn) + if fs.loadBalanced { + fs.RecordRequest(workerID) + } +} + +// sendRawResponse sends whatever provided byte data with provided content type header +func (fs *FederatedServer) sendRawResponse(conn net.Conn, statusCode int, contentType string, data []byte) { + defer conn.Close() - // Create the HTTP response with dynamic status code and content. response := fmt.Sprintf( "HTTP/1.1 %d %s\r\n"+ - "Content-Type: text/html\r\n"+ + "Content-Type: %s\r\n"+ "Connection: close\r\n"+ "\r\n"+ "%s", - statusCode, getHTTPStatusText(statusCode), htmlContent, + statusCode, http.StatusText(statusCode), contentType, data, ) // Write the response to the client connection. @@ -135,16 +351,21 @@ func (fs *FederatedServer) sendHTMLResponse(conn net.Conn, statusCode int, messa } } -// getHTTPStatusText returns a textual representation of HTTP status codes. -func getHTTPStatusText(statusCode int) string { - switch statusCode { - case 503: - return "Service Unavailable" - case 404: - return "Not Found" - case 200: - return "OK" - default: - return "Unknown Status" +// sendJSONResponse marshals provided data to JSON and sends it +func (fs *FederatedServer) sendJSONResponse(conn net.Conn, statusCode int, v any) { + data, err := json.Marshal(v) + if err != nil { + log.Error().Err(err).Msg("Error JSON marshaling") } + + fs.sendRawResponse(conn, statusCode, "application/json", data) +} + +// sendHTMLResponse sends a basic HTML response with a status code and a message. +// This is extracted to make the HTML content maintainable. +func (fs *FederatedServer) sendHTMLResponse(conn net.Conn, statusCode int, message string) { + // Define the HTML content separately for easier maintenance. + htmlContent := fmt.Sprintf("

%s

\r\n", message) + + fs.sendRawResponse(conn, statusCode, "text/html", []byte(htmlContent)) } diff --git a/go.mod b/go.mod index 45385a49fe02..c1d88669ea8b 100644 --- a/go.mod +++ b/go.mod @@ -303,4 +303,4 @@ require ( lukechampine.com/blake3 v1.3.0 // indirect ) -replace github.com/mudler/edgevpn => github.com/swarmind/edgevpn v0.0.0-20250329011455-c0a96483b1ff +replace github.com/mudler/edgevpn => github.com/swarmind/edgevpn v0.0.0-20250331231759-326a9e7360b0 diff --git a/go.sum b/go.sum index 6e8bbb5eee6a..51154aa1e811 100644 --- a/go.sum +++ b/go.sum @@ -147,8 +147,6 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/ggerganov/whisper.cpp/bindings/go v0.0.0-20240626202019-c118733a29ad h1:dQ93Vd6i25o+zH9vvnZ8mu7jtJQ6jT3D+zE3V8Q49n0= -github.com/ggerganov/whisper.cpp/bindings/go v0.0.0-20240626202019-c118733a29ad/go.mod h1:QIjZ9OktHFG7p+/m3sMvrAJKKdWrr1fZIK0rM6HZlyo= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= @@ -459,8 +457,6 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mudler/go-piper v0.0.0-20241023091659-2494246fd9fc h1:RxwneJl1VgvikiX28EkpdAyL4yQVnJMrbquKospjHyA= -github.com/mudler/go-piper v0.0.0-20241023091659-2494246fd9fc/go.mod h1:O7SwdSWMilAWhBZMK9N9Y/oBDyMMzshE3ju8Xkexwig= github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82 h1:FVT07EI8njvsD4tC2Hw8Xhactp5AWhsQWD4oTeQuSAU= github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82/go.mod h1:Urp7LG5jylKoDq0663qeBh0pINGcRl35nXdKx82PSoU= github.com/mudler/water v0.0.0-20221010214108-8c7313014ce0 h1:Qh6ghkMgTu6siFbTf7L3IszJmshMhXxNL4V+t7IIA6w= @@ -703,8 +699,8 @@ github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= -github.com/swarmind/edgevpn v0.0.0-20250329011455-c0a96483b1ff h1:tAZL+yhXDkwSkkjmPWMk+iBJ/t1MP43ntJiQA4Q5+kw= -github.com/swarmind/edgevpn v0.0.0-20250329011455-c0a96483b1ff/go.mod h1:bGUdGQzwLOuMs3SII1N6SazoI1qQ1ekxdxNatOCS5ZM= +github.com/swarmind/edgevpn v0.0.0-20250331231759-326a9e7360b0 h1:1IzdOtFh9IubHt/7kkSO56chnwHF3Yp7DsWSpXTaMgE= +github.com/swarmind/edgevpn v0.0.0-20250331231759-326a9e7360b0/go.mod h1:bGUdGQzwLOuMs3SII1N6SazoI1qQ1ekxdxNatOCS5ZM= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/thxcode/gguf-parser-go v0.1.0 h1:J4QruXyEQGjrAKeKZFlsD2na9l4XF5+bjR194d+wJS4= github.com/thxcode/gguf-parser-go v0.1.0/go.mod h1:Tn1PsO/YDEtLIxm1+QDCjIIH9L/9Sr7+KpxZKm0sEuE=