From 7ae51291351cfcc22c068fecc1a2983fd2c0824a Mon Sep 17 00:00:00 2001 From: juligasa <11684004+juligasa@users.noreply.github.com> Date: Mon, 18 Sep 2023 02:13:05 +0200 Subject: [PATCH] wip(sites): init remote site on group creation --- backend/cmd/mintter-site/sites/sites.go | 2 +- backend/cmd/monitord/server/checks.go | 36 +------- backend/daemon/api/groups/v1alpha/groups.go | 92 ++++++++++++++++++++- backend/mttnet/mttnet.go | 26 ++++++ 4 files changed, 118 insertions(+), 38 deletions(-) diff --git a/backend/cmd/mintter-site/sites/sites.go b/backend/cmd/mintter-site/sites/sites.go index 72bca7a707..f457881357 100644 --- a/backend/cmd/mintter-site/sites/sites.go +++ b/backend/cmd/mintter-site/sites/sites.go @@ -107,7 +107,7 @@ func (ws *Website) RegisterSite(ctx context.Context, hostname string) (link stri } // GetSiteInfo exposes the public information of a site. Which group is serving and how to reach the site via p2p. -func (ws Website) GetSiteInfo(ctx context.Context, _ *groups.GetSiteInfoRequest) (*groups.PublicSiteInfo, error) { +func (ws Website) GetSiteInfo(ctx context.Context, in *groups.GetSiteInfoRequest) (*groups.PublicSiteInfo, error) { n, ok := ws.Net.Get() if !ok { return nil, errNodeNotReadyYet diff --git a/backend/cmd/monitord/server/checks.go b/backend/cmd/monitord/server/checks.go index ef9992466c..7032d36bd3 100644 --- a/backend/cmd/monitord/server/checks.go +++ b/backend/cmd/monitord/server/checks.go @@ -4,16 +4,13 @@ package server import ( "context" "fmt" - "io" - groups "mintter/backend/genproto/groups/v1alpha" - "net/http" + groups "mintter/backend/daemon/api/groups/v1alpha" "time" peer "github.com/libp2p/go-libp2p/core/peer" peerstore "github.com/libp2p/go-libp2p/core/peerstore" ping "github.com/libp2p/go-libp2p/p2p/protocol/ping" "github.com/multiformats/go-multiaddr" - "google.golang.org/protobuf/encoding/protojson" ) func (s *Srv) checkP2P(ctx context.Context, peer peer.AddrInfo, numPings int) (time.Duration, error) { @@ -59,7 +56,7 @@ func (s *Srv) checkP2P(ctx context.Context, peer peer.AddrInfo, numPings int) (t } func (s *Srv) checkMintterAddrs(ctx context.Context, hostname, mustInclude string) (info peer.AddrInfo, err error) { - resp, err := s.getSiteInfoHTTP(ctx, hostname) + resp, err := groups.GetSiteInfoHTTP(ctx, hostname) if err != nil { return info, err } @@ -84,32 +81,3 @@ func (s *Srv) checkMintterAddrs(ctx context.Context, hostname, mustInclude strin return info, nil } - -func (s *Srv) getSiteInfoHTTP(ctx context.Context, SiteHostname string) (*groups.PublicSiteInfo, error) { - requestURL := fmt.Sprintf("%s/%s", SiteHostname, ".well-known/hypermedia-site") - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil) - if err != nil { - return nil, fmt.Errorf("could not create request to well-known site: %w ", err) - } - - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, fmt.Errorf("could not contact to provided site [%s]: %w ", requestURL, err) - } - defer res.Body.Close() - if res.StatusCode < 200 || res.StatusCode > 299 { - return nil, fmt.Errorf("site info url [%s] not working. Status code: %d", requestURL, res.StatusCode) - } - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("failed to read json body: %w", err) - } - - resp := &groups.PublicSiteInfo{} - if err := protojson.Unmarshal(data, resp); err != nil { - return nil, fmt.Errorf("failed to unmarshal JSON body: %w", err) - } - return resp, nil -} diff --git a/backend/daemon/api/groups/v1alpha/groups.go b/backend/daemon/api/groups/v1alpha/groups.go index f00d537de3..4276fe7cce 100644 --- a/backend/daemon/api/groups/v1alpha/groups.go +++ b/backend/daemon/api/groups/v1alpha/groups.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "fmt" + "io" "mintter/backend/core" groups "mintter/backend/genproto/groups/v1alpha" "mintter/backend/hlc" @@ -15,14 +16,17 @@ import ( "mintter/backend/pkg/errutil" "mintter/backend/pkg/future" "mintter/backend/pkg/maputil" + "net/http" "strings" "time" "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/peer" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -47,9 +51,13 @@ func (srv *Server) CreateGroup(ctx context.Context, in *groups.CreateGroupReques if in.Title == "" { return nil, errutil.MissingArgument("title") } - + var n *mttnet.Node + var ok bool if in.SiteSetupUrl != "" { - return nil, status.Errorf(codes.Unimplemented, "site setup is not implemented yet") + n, ok = srv.node.Get() + if !ok { + return nil, fmt.Errorf("Node not ready yet") + } } me, err := srv.getMe() @@ -83,6 +91,7 @@ func (srv *Server) CreateGroup(ctx context.Context, in *groups.CreateGroupReques if err != nil { return nil, err } + hb, err := e.CreateChange(ts, me.DeviceKey(), del, patch, hyper.WithAction("Create")) if err != nil { return nil, err @@ -92,6 +101,27 @@ func (srv *Server) CreateGroup(ctx context.Context, in *groups.CreateGroupReques return nil, err } + if in.SiteSetupUrl != "" { + siteURL := strings.Split(in.SiteSetupUrl, "/secret-invite/")[0] + resp, err := GetSiteInfoHTTP(ctx, siteURL) + if err != nil { + return nil, fmt.Errorf("Could not contact site at %s: %w", siteURL, err) + } + pid, err := peer.Decode(resp.PeerInfo.PeerId) + if err != nil { + return nil, fmt.Errorf("failed to decode peer ID %s: %w", resp.PeerInfo.PeerId, err) + } + c, err := n.SiteClient(ctx, pid) + if err != nil { + return nil, fmt.Errorf("Could not contact site via P2P: %w", err) + } + if _, err := c.InitializeServer(ctx, &groups.InitializeServerRequest{ + Secret: in.SiteSetupUrl, + GroupId: id, + }); err != nil { + return nil, fmt.Errorf("Could not publish group to site. P2P group, however, was created successfully: %w", err) + } + } return groupToProto(srv.blobs, e) } @@ -132,8 +162,13 @@ func (srv *Server) UpdateGroup(ctx context.Context, in *groups.UpdateGroupReques return nil, errutil.MissingArgument("id") } + var n *mttnet.Node + var ok bool if in.SiteSetupUrl != "" { - return nil, status.Errorf(codes.Unimplemented, "site setup is not implemented yet") + n, ok = srv.node.Get() + if !ok { + return nil, fmt.Errorf("Node not ready yet") + } } me, err := srv.getMe() @@ -195,6 +230,27 @@ func (srv *Server) UpdateGroup(ctx context.Context, in *groups.UpdateGroupReques return nil, err } + if in.SiteSetupUrl != "" { + siteURL := strings.Split(in.SiteSetupUrl, "/secret-invite/")[0] + resp, err := GetSiteInfoHTTP(ctx, siteURL) + if err != nil { + return nil, fmt.Errorf("Could not contact site at %s: %w", siteURL, err) + } + pid, err := peer.Decode(resp.PeerInfo.PeerId) + if err != nil { + return nil, fmt.Errorf("failed to decode peer ID %s: %w", resp.PeerInfo.PeerId, err) + } + c, err := n.SiteClient(ctx, pid) + if err != nil { + return nil, fmt.Errorf("Could not contact site via P2P: %w", err) + } + if _, err := c.InitializeServer(ctx, &groups.InitializeServerRequest{ + Secret: in.SiteSetupUrl, + GroupId: in.Id, + }); err != nil { + return nil, fmt.Errorf("Could not publish group to site. P2P group, however, was updated successfully: %w", err) + } + } return groupToProto(srv.blobs, e) } @@ -565,3 +621,33 @@ func (srv *Server) getDelegation(ctx context.Context) (cid.Cid, error) { return out, nil } + +// GetSiteInfoHTTP gets public information from a site. +func GetSiteInfoHTTP(ctx context.Context, SiteUrl string) (*groups.PublicSiteInfo, error) { + requestURL := fmt.Sprintf("%s/%s", SiteUrl, ".well-known/hypermedia-site") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil) + if err != nil { + return nil, fmt.Errorf("could not create request to well-known site: %w ", err) + } + + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("could not contact to provided site [%s]: %w ", requestURL, err) + } + defer res.Body.Close() + if res.StatusCode < 200 || res.StatusCode > 299 { + return nil, fmt.Errorf("site info url [%s] not working. Status code: %d", requestURL, res.StatusCode) + } + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("failed to read json body: %w", err) + } + + resp := &groups.PublicSiteInfo{} + if err := protojson.Unmarshal(data, resp); err != nil { + return nil, fmt.Errorf("failed to unmarshal JSON body: %w", err) + } + return resp, nil +} diff --git a/backend/mttnet/mttnet.go b/backend/mttnet/mttnet.go index 1b6c6262f9..7d876e6b47 100644 --- a/backend/mttnet/mttnet.go +++ b/backend/mttnet/mttnet.go @@ -7,6 +7,7 @@ import ( "io" "mintter/backend/config" "mintter/backend/core" + groups "mintter/backend/genproto/groups/v1alpha" p2p "mintter/backend/genproto/p2p/v1alpha" "mintter/backend/hyper" "mintter/backend/hyper/hypersql" @@ -50,6 +51,18 @@ const ( var userAgent = "mintter/" +// WebsiteClient is the bridge to talk to remote sites. +type WebsiteClient interface { + // InitializeServer instruct the website that starts serving a given group. + InitializeServer(context.Context, *groups.InitializeServerRequest, ...grpc.CallOption) (*groups.InitializeServerResponse, error) + + // GetSiteInfo gets public site information, to be also found in /.well-known/hypermedia-site + GetSiteInfo(context.Context, *groups.GetSiteInfoRequest, ...grpc.CallOption) (*groups.PublicSiteInfo, error) + + // PublishBlobs pushes given blobs to the site. + PublishBlobs(context.Context, *groups.PublishBlobsRequest, ...grpc.CallOption) (*groups.PublishBlobsResponse, error) +} + // DefaultRelays bootstrap mintter-owned relays so they can reserve slots to do holepunch. func DefaultRelays() []peer.AddrInfo { return []peer.AddrInfo{ @@ -204,6 +217,19 @@ func (n *Node) Client(ctx context.Context, pid peer.ID) (p2p.P2PClient, error) { return n.client.Dial(ctx, pid) } +// SiteClient opens a connection with a remote website. +func (n *Node) SiteClient(ctx context.Context, pid peer.ID) (WebsiteClient, error) { + if err := n.Connect(ctx, n.p2p.Peerstore().PeerInfo(pid)); err != nil { + return nil, err + } + + conn, err := n.client.dialPeer(ctx, pid) + if err != nil { + return nil, err + } + return groups.NewWebsiteClient(conn), nil +} + // ArePrivateIPsAllowed check if private IPs (local) are allowed to connect. func (n *Node) ArePrivateIPsAllowed() bool { return !n.cfg.NoPrivateIps