diff --git a/SUPPORT.md b/SUPPORT.md index 17a9381..090fa77 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -5,7 +5,6 @@ Thanks for using LIBR! Here's how to get help when you need it. ## 🚀 Quick Help ### 🐛 **Found a Bug?** -<<<<<<< HEAD [Create a Bug Report](https://github.com/libr-forum/libr/issues/new?template=bug_report.md) ### 💡 **Have an Idea?** @@ -13,15 +12,6 @@ Thanks for using LIBR! Here's how to get help when you need it. ### ❓ **Need Help?** [Ask in Q&A Discussions](https://github.com/libr-forum/libr/discussions/categories/q-a) -======= -[Create a Bug Report](https://github.com/libr-forum/libr/issues/new?template=bug_report.md) - -### 💡 **Have an Idea?** -[Share it in Discussions](https://github.com/libr-forum/libr/discussions/categories/ideas-feature-requests) - -### ❓ **Need Help?** -[Ask in Q&A Discussions](https://github.com/libr-forum/libr/discussions/categories/q-a) ->>>>>>> 9778abfea970abad1ec6f572173b51b742c8068d ## 📚 **Documentation** @@ -50,11 +40,7 @@ git checkout -b update-readme **Stuck with Git?** - **Beginner Git Guide:** [git-scm.com/book](https://git-scm.com/book) - **Interactive Git Tutorial:** [learngitbranching.js.org](https://learngitbranching.js.org/) -<<<<<<< HEAD -- **Ask for help:** [Q&A Discussions](https://github.com/libr-forum/libr/discussions/categories/q-a) -======= - **Ask for help:** [Q&A Discussions](https://github.com/libr-forum/libr/discussions/categories/q-a) ->>>>>>> 9778abfea970abad1ec6f572173b51b742c8068d ### Common Workflow Questions @@ -74,27 +60,16 @@ git rebase main **"My branch has conflicts, help!"** 1. Don't panic! 😊 -<<<<<<< HEAD 2. Ask in [Discussions](https://github.com/libr-forum/libr/discussions) -======= -2. Ask in [Discussions](https://github.com/libr-forum/libr/discussions) ->>>>>>> 9778abfea970abad1ec6f572173b51b742c8068d 3. Our maintainers will help you resolve them ## 💬 **Community Channels** ### GitHub Discussions (Primary) -<<<<<<< HEAD - **[General Discussion](https://github.com/libr-forum/libr/discussions/categories/general)** - Chat about anything LIBR-related - **[Q&A](https://github.com/libr-forum/libr/discussions/categories/q-a)** - Get help from the community - **[Ideas](https://github.com/libr-forum/libr/discussions/categories/ideas-feature-requests)** - Share feature requests and ideas - **[Show and Tell](https://github.com/libr-forum/libr/discussions/categories/show-and-tell)** - Share what you've built -======= -- **[General Discussion](https://github.com/libr-forum/libr/discussions/categories/general)** - Chat about anything LIBR-related -- **[Q&A](https://github.com/libr-forum/libr/discussions/categories/q-a)** - Get help from the community -- **[Ideas](https://github.com/libr-forum/libr/discussions/categories/ideas-feature-requests)** - Share feature requests and ideas -- **[Show and Tell](https://github.com/libr-forum/libr/discussions/categories/show-and-tell)** - Share what you've built ->>>>>>> 9778abfea970abad1ec6f572173b51b742c8068d ### Email Support For private matters or security issues: **devlup@iitj.ac.in** @@ -104,11 +79,7 @@ For private matters or security issues: **devlup@iitj.ac.in** If you're new to open source or Git/GitHub: 1. **Start here:** [New Contributor Guide](docs/BEGINNER_GUIDE.md) -<<<<<<< HEAD -2. **Look for:** Issues labeled [`good first issue`](https://github.com/libr-forum/libr/labels/good%20first%20issue) -======= 2. **Look for:** Issues labeled [`good first issue`](https://github.com/libr-forum/libr/labels/good%20first%20issue) ->>>>>>> 9778abfea970abad1ec6f572173b51b742c8068d 3. **Learn Git:** We have [branch naming guidelines](CONTRIBUTING.md#-branch-naming--workflow-guidelines) to help you! 4. **Get help:** Our [maintainers](docs/MAINTAINER_GUIDE.md) are here to help! @@ -123,11 +94,7 @@ If you're new to open source or Git/GitHub: ### Setup Issues - **Project won't build?** Run `./scripts/setup.sh` first - **Dependencies missing?** Check our [setup guide](docs/BEGINNER_GUIDE.md#first-time-setup) -<<<<<<< HEAD -- **Still stuck?** Create an issue with the [`help wanted`](https://github.com/libr-forum/libr/labels/help%20wanted) label -======= - **Still stuck?** Create an issue with the [`help wanted`](https://github.com/libr-forum/libr/labels/help%20wanted) label ->>>>>>> 9778abfea970abad1ec6f572173b51b742c8068d ### Development Questions - **Don't know which file to edit?** Ask in discussions first diff --git a/core/db/.env b/core/db/.env index 07a2d53..9d4222f 100644 --- a/core/db/.env +++ b/core/db/.env @@ -2,4 +2,5 @@ DB_PATH = data/libr.db IP = 49.36.179.166 PORT = 33122 BOOTSTRAP = - +JS_ServerURL = https://libr-server.onrender.com +JS_API_KEY = qwertyuiop diff --git a/core/db/config/config.go b/core/db/config/config.go index d6c28be..b8a30e5 100644 --- a/core/db/config/config.go +++ b/core/db/config/config.go @@ -1,6 +1,7 @@ package config import ( + "database/sql" "fmt" "log" @@ -100,3 +101,5 @@ func createTables() error { return nil } + +var DBtype = "normal" \ No newline at end of file diff --git a/core/db/config/dbConfigManager.go b/core/db/config/dbConfigManager.go new file mode 100644 index 0000000..93e0a7d --- /dev/null +++ b/core/db/config/dbConfigManager.go @@ -0,0 +1,77 @@ +package config + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + + "github.com/libr-forum/Libr/core/crypto/logger" + "github.com/libr-forum/Libr/core/db/internal/models" +) + +func GetDBConfigPath() string { + var path string + + switch runtime.GOOS { + case "windows": + appData := os.Getenv("APPDATA") + path = filepath.Join(appData, "libr", "dbconfig", "dbconfig.json") + case "darwin": + home, _ := os.UserHomeDir() + path = filepath.Join(home, "Library", "Application Support", "libr", "dbconfig", "dbconfig.json") + case "linux": + home, _ := os.UserHomeDir() + path = filepath.Join(home, ".config", "libr", "dbconfig", "dbconfig.json") + default: + path = filepath.Join("dbconfig", "dbconfig.json") + } + + return path +} + + +func ReadDBConfigFile() (models.DBConfig, error) { + path := GetDBConfigPath() + fmt.Println(path) + file, err := os.Open(path) + if err != nil { + logger.LogToFile("failed to open dbconfig.json") + return models.DBConfig{}, fmt.Errorf("failed to open dbconfig.json: %w", err) + } + defer file.Close() + + // Debug: print raw file contents + fileInfo, _ := file.Stat() + fileSize := fileInfo.Size() + rawBytes := make([]byte, fileSize) + _, err = file.ReadAt(rawBytes, 0) + if err != nil { + fmt.Println("[DEBUG] Error reading raw file bytes:", err) + } else { + fmt.Println("[DEBUG] Raw dbconfig.json contents:") + fmt.Println(string(rawBytes)) + } + + // Reset file pointer to beginning for decoding + file.Seek(0, 0) + + var configObj models.DBConfig + if err := json.NewDecoder(file).Decode(&configObj); err != nil { + logger.LogToFile("[DEBUG]Failed to decode dbconfig.json") + fmt.Println("[DEBUG] JSON decode error:", err) + return models.DBConfig{}, fmt.Errorf("failed to decode dbconfig.json: %w", err) + } + + fmt.Println("[DEBUG] Decoded configObj:", configObj) + + // Set DBtype based on API_KEY + if configObj.API_KEY == "" { + DBtype = "normal" + } else { + DBtype = "boot" + } + + return configObj, nil +} \ No newline at end of file diff --git a/core/db/config/envconfig.go b/core/db/config/envconfig.go new file mode 100644 index 0000000..437e02e --- /dev/null +++ b/core/db/config/envconfig.go @@ -0,0 +1,65 @@ +package config + +import ( + "log" + "os" + "strconv" + "embed" + "github.com/joho/godotenv" +) +//go:embed .env +var _ embed.FS // Embedded .env file (unused, but required for go:embed syntax) + + + +// Config holds all configuration for the application. +type Config struct { + DBPath string + IP string + Port int + Bootstrap string + JSServerURL string + JSAPIKey string +} + +// Cfg is a global, public variable that holds the loaded configuration. + + + +// Cfg is a global, public variable that holds the loaded configuration. +var Cfg Config +func init() { + // Load the .env file. + // It's okay if it fails, we can rely on OS environment variables as a fallback. + if err := godotenv.Load(); err != nil { + log.Println("No .env file found, using OS environment variables") + } + + // Load configuration into the Cfg struct + Cfg = Config{ + DBPath: getEnv("DB_PATH", "data/default.db"), // Provide a default + IP: getEnv("IP", "0.0.0.0"), // Default to listen on all interfaces + Port: getEnvAsInt("PORT", 33122), // Default port + Bootstrap: getEnv("BOOTSTRAP", ""), // No default needed + JSServerURL: getEnv("JS_ServerURL", ""), // No default needed + JSAPIKey: getEnv("JS_API_KEY", ""), // No default needed + } + log.Println("Configuration loaded successfully") +} + +// getEnv is a helper function to read an environment variable or return a default value. +func getEnv(key, fallback string) string { + if value, exists := os.LookupEnv(key); exists { + return value + } + return fallback +} + +// getEnvAsInt is a helper to parse an environment variable as an integer. +func getEnvAsInt(key string, fallback int) int { + valueStr := getEnv(key, "") + if value, err := strconv.Atoi(valueStr); err == nil { + return value + } + return fallback +} \ No newline at end of file diff --git a/core/db/internal/keycache/keycache.go b/core/db/internal/keycache/keycache.go index 223dab4..a7f84a0 100644 --- a/core/db/internal/keycache/keycache.go +++ b/core/db/internal/keycache/keycache.go @@ -6,6 +6,7 @@ import ( "fmt" "log" + "github.com/libp2p/go-libp2p/core/crypto" "github.com/libr-forum/Libr/core/crypto/cryptoutils" ) @@ -28,3 +29,13 @@ func LoadPubKey() string { pub, _, _ := cryptoutils.LoadKeys() return base64.StdEncoding.EncodeToString(pub) } + +func LoadPrivKey() crypto.PrivKey { + _, priv, _ := cryptoutils.LoadKeys() // priv is ed25519.PrivateKey (a []byte) + + libp2pPriv, err := crypto.UnmarshalEd25519PrivateKey(priv) + if err != nil { + panic(err) + } + return libp2pPriv +} \ No newline at end of file diff --git a/core/db/internal/models/models.go b/core/db/internal/models/models.go index d0e3106..d7bd443 100644 --- a/core/db/internal/models/models.go +++ b/core/db/internal/models/models.go @@ -40,6 +40,11 @@ type Mod struct { PublicKey string `json:"public_key"` } +type Mods struct{ + Peerid string `json:"peer_id"` + PublicKey string `json:"public_key"` +} + type ReportMsg struct { PublicKey string `json:"public_key"` Msg Msg `json:"msg"` @@ -70,4 +75,9 @@ type KBucket struct { func (kb *KBucket) String() string { data, _ := json.MarshalIndent(kb, "", " ") return string(data) +} + +type DBConfig struct { + API_KEY string `json:"x_api_key"` + JS_ServerURL string `json:"jsurl"` } \ No newline at end of file diff --git a/core/db/internal/network/bootstrap/bootstrap.go b/core/db/internal/network/bootstrap/bootstrap.go index d687b29..d6be5e3 100644 --- a/core/db/internal/network/bootstrap/bootstrap.go +++ b/core/db/internal/network/bootstrap/bootstrap.go @@ -19,8 +19,24 @@ import ( func BootstrapFromPeers(dbnodes []*models.Node, localNode *models.Node, rt *routing.RoutingTable) { fmt.Println("🌐 Bootstrapping from peers...") for _, n := range dbnodes { - fmt.Printf("PeerId: %s, NodeId: %s\n", n.PeerId, base64.StdEncoding.EncodeToString(n.NodeId[:])) + output := "" + + if n.PeerId != "" { + output += fmt.Sprintf("PeerId: %s", n.PeerId) + } + + if len(n.NodeId) > 0 { + if output != "" { + output += ", " + } + output += fmt.Sprintf("NodeId: %s", base64.StdEncoding.EncodeToString(n.NodeId[:])) + } + + if output != "" { + fmt.Println(output) + } } + var wg sync.WaitGroup seen := make(map[string]bool) var mu sync.Mutex // protect access to `seen` map diff --git a/core/db/internal/network/peers/functions.go b/core/db/internal/network/peers/functions.go index 9fd93c9..1934f35 100644 --- a/core/db/internal/network/peers/functions.go +++ b/core/db/internal/network/peers/functions.go @@ -45,9 +45,9 @@ func RegisterLocalState(n *models.Node, rt *routing.RoutingTable) { } func initDHT() { - bootstrapAddrs, _ := utils.GetDbAddr() + bootstrapAddrs, _ := utils.GetDBFromJSServer() - fmt.Print("Bootstrap addresses: ", bootstrapAddrs, "\n") + // fmt.Print("Bootstrap addresses: ", bootstrapAddrs, "\n") // 3. Init DB and routing config.InitDB() diff --git a/core/db/internal/network/peers/peer.go b/core/db/internal/network/peers/peer.go index 17e8d1d..542c0a8 100644 --- a/core/db/internal/network/peers/peer.go +++ b/core/db/internal/network/peers/peer.go @@ -8,6 +8,9 @@ import ( "crypto/x509" "log" "math/big" + "net/http" + + //"os" "sort" "context" @@ -29,6 +32,9 @@ import ( "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client" "github.com/libp2p/go-libp2p/p2p/protocol/holepunch" "github.com/libp2p/go-libp2p/p2p/protocol/identify" + "github.com/libr-forum/Libr/core/db/config" + "github.com/libr-forum/Libr/core/db/internal/keycache" + "github.com/libr-forum/Libr/core/db/internal/node" "github.com/multiformats/go-multiaddr" "github.com/libp2p/go-libp2p/p2p/transport/tcp" @@ -84,24 +90,73 @@ func NewChatPeer(relayMultiAddrList []string) (*ChatPeer, error) { // Other TLS configurations like ClientAuth, InsecureSkipVerify, etc. } + privKey := keycache.LoadPrivKey() + fmt.Println("[DEBUG] Creating libp2p Host") + h, err := libp2p.New( - libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/0/ws"), // WebSocket - libp2p.Security(libp2ptls.ID, libp2ptls.New), - libp2p.ConnectionManager(connMgr), - libp2p.EnableNATService(), - libp2p.EnableRelay(), - libp2p.Transport(tcp.NewTCPTransport), - libp2p.Transport(websocket.New, websocket.WithTLSConfig(tlsConfig)), - // libp2p.Transport(websocket.NewWithTLSConfig(tlsConfig)), - // libp2p.Transport(websocket.New), - ) + libp2p.Identity(privKey), // ✅ ensures peer ID is derived from privKey + libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/0/ws"), + libp2p.Security(libp2ptls.ID, libp2ptls.New), + libp2p.ConnectionManager(connMgr), + libp2p.EnableNATService(), + libp2p.EnableRelay(), + libp2p.Transport(tcp.NewTCPTransport), + libp2p.Transport(websocket.New, websocket.WithTLSConfig(tlsConfig)), +) if err != nil { fmt.Println("[DEBUG] Failed to create Host:", err) return nil, err } + //pubKey := keycache.LoadPubKey() + cf,err := config.ReadDBConfigFile() + if(err!=nil){ + fmt.Println("Error reading vlas from config file") + } + JS_API_key := cf.API_KEY + + JS_ServerURL := cf.JS_ServerURL + + if(config.DBtype=="boot"){ + fmt.Println("RUNNING DB AS BOOTSTRAP. APPROPRIATE CONFIG FOUND") + if JS_API_key == "" { + fmt.Println("[DEBUG] Missing JS API key or server URL") + log.Fatal("Cant get config vars at peer.go") + } + node_id:=node.GenerateNodeIDFromPublicKey() + Data := map[string]string{ + "peer_id": h.ID().String(), + "node_id" : node_id, + } + + jsonData, err := json.Marshal(Data) + if(err!=nil){ + fmt.Println("Error marchalling req json for post boot") + } + req,err := http.NewRequest("POST", JS_ServerURL+"/api/postboot", bytes.NewBuffer(jsonData)) + + if err != nil { + return nil,fmt.Errorf("failed to create request: %w",err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("x-api-key", JS_API_key) + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil,fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil,fmt.Errorf("server returned non-200 status code: %d", resp.StatusCode) + } + + fmt.Println("Inserted bootstrap node successfully") +} fmt.Println("[DEBUG] Creating identify service") idSvc, err := identify.NewIDService(h) if err != nil { @@ -149,7 +204,9 @@ func NewChatPeer(relayMultiAddrList []string) (*ChatPeer, error) { sort.Slice(distmap, func(i, j int) bool { return distmap[i].dist.Cmp(distmap[j].dist) < 0 }) - + if(len(relayMultiAddrList)==0){ + log.Fatal("No relays given, please restart after some time") + } relayIDused := distmap[0].relayID fmt.Println(relayIDused) var relayAddr string diff --git a/core/db/internal/network/rpc.go b/core/db/internal/network/rpc.go index 2de0591..7036362 100644 --- a/core/db/internal/network/rpc.go +++ b/core/db/internal/network/rpc.go @@ -189,7 +189,7 @@ func SendFindValue(key string, self *models.Node, rt *routing.RoutingTable) ([]m // } func DeleteValue(key *[20]byte, repCert *models.ReportCert, self *models.Node, rt *routing.RoutingTable) ([]*models.Node, error) { - validMods, _ := utils.GetOnlineMods() + validMods, _ := utils.GetModsFromJSServer() selfDist := node.XORBigInt(self.NodeId, *key) closest := rt.FindClosest(*key, config.K) diff --git a/core/db/internal/storage/validator.go b/core/db/internal/storage/validator.go index f10e02c..4eb9bac 100644 --- a/core/db/internal/storage/validator.go +++ b/core/db/internal/storage/validator.go @@ -77,7 +77,7 @@ func ValidateRepCertFields(repCert *models.ReportCert) error { return nil } -func ValidateRepCert(repCert *models.ReportCert, validMods []*models.Mod) error { +func ValidateRepCert(repCert *models.ReportCert, validMods []*models.Mods) error { if err := ValidateRepCertFields(repCert); err != nil { return err } @@ -107,7 +107,7 @@ func ValidateRepCert(repCert *models.ReportCert, validMods []*models.Mod) error return ValidateRepModCerts(repCert, validMods) } -func ValidateRepModCerts(repCert *models.ReportCert, validMods []*models.Mod) error { +func ValidateRepModCerts(repCert *models.ReportCert, validMods []*models.Mods) error { validMap := make(map[string]struct{}) for _, mod := range validMods { validMap[mod.PublicKey] = struct{}{} @@ -165,7 +165,7 @@ func ValidateRepModCerts(repCert *models.ReportCert, validMods []*models.Mod) er func ValidateModCert(msgCert *models.MsgCert) error { apprCount := 0 rejCount := 0 - validMods, _ := utils.GetOnlineMods() + validMods, _ := utils.GetModsFromJSServer() totalMods := len(validMods) for _, modcert := range msgCert.ModCerts { payload := msgCert.Msg.Content + strconv.FormatInt(msgCert.Msg.Ts, 10) + modcert.Status diff --git a/core/db/internal/utils/mongodb.go b/core/db/internal/utils/mongodb.go index 3e86aac..9c68b2e 100644 --- a/core/db/internal/utils/mongodb.go +++ b/core/db/internal/utils/mongodb.go @@ -1,137 +1,154 @@ package utils import ( - "context" + "encoding/json" "fmt" - "log" - "strings" + "net/http" "time" + "github.com/libr-forum/Libr/core/db/config" "github.com/libr-forum/Libr/core/db/internal/models" - "github.com/libr-forum/Libr/core/db/internal/node" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" ) var MongoClient *mongo.Client -// SetupMongo initializes the global MongoClient -func SetupMongo(uri string) error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri)) +type modResp struct { + ModLists []models.Mods `json:"modlist"` +} + + +type NodeResp struct { + NodesLists []models.Node `json:"nodeslist"` +} + + +func GetRelayAddrFromJSServer() ([]string, error) { + cf, err := config.ReadDBConfigFile() if err != nil { - return fmt.Errorf("failed to connect to MongoDB: %w", err) + fmt.Println("Error reading dbconfig.json:", err) + return nil, err } + serverURL := cf.JS_ServerURL - if err := client.Ping(ctx, nil); err != nil { - return fmt.Errorf("failed to ping MongoDB: %w", err) + req, err := http.NewRequest("GET", serverURL+"/api/getrelay", nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) } - MongoClient = client - log.Println("✅ MongoDB connected") - return nil -} + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("server returned non-200 status code: %d", resp.StatusCode) + } -// DisconnectMongo gracefully closes the MongoDB connection -func DisconnectMongo() { - if MongoClient != nil { - if err := MongoClient.Disconnect(context.Background()); err != nil { - log.Println("⚠️ Error disconnecting MongoDB:", err) - } else { - log.Println("🛑 MongoDB disconnected") - } + var payload relayResp + if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + return nil, fmt.Errorf("failed to decode JSON response: %w", err) } + fmt.Println(resp.Body) + fmt.Println(payload) + + var addresses []string +for _, relay := range payload.RelayList.Relays { + addresses = append(addresses, relay.Address) +} + fmt.Println(addresses) + return addresses, nil } -// 🚀 Uses global MongoClient and ctx -func GetDbAddr() ([]*models.Node, error) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - collection := MongoClient.Database("Addrs").Collection("nodes") // replace with actual DB & collection - cursor, err := collection.Find(ctx, bson.M{}) +type relayResp struct { + RelayList struct { + Relays []struct { + Address string `json:"address"` + } `json:"relaylist"` + } `json:"relay_list"` +} + +// ✅ Fetch mods +func GetModsFromJSServer() ([]*models.Mods, error) { + cf, err := config.ReadDBConfigFile() if err != nil { + fmt.Println("Error reading dbconfig.json:", err) return nil, err } - defer cursor.Close(ctx) - - var nodeList []*models.Node - for cursor.Next(ctx) { - var doc struct { - NodeId string `bson:"node_id"` - PeerId string `bson:"peer_id"` - } - if err := cursor.Decode(&doc); err != nil { - return nil, err - } - - nodeId, _ := node.DecodeNodeID(doc.NodeId) - node := &models.Node{ - NodeId: nodeId, - PeerId: doc.PeerId, - } - nodeList = append(nodeList, node) - } - return nodeList, nil -} + serverURL := cf.JS_ServerURL -func GetOnlineMods() ([]*models.Mod, error) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() + req, err := http.NewRequest("GET", serverURL+"/api/getmod", nil) // 🔥 corrected endpoint + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } - collection := MongoClient.Database("Addrs").Collection("mods") - cursor, err := collection.Find(ctx, bson.M{}) + var ModReturnList []*models.Mods + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("server returned non-200 status code: %d", resp.StatusCode) + } + + var payload modResp + if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + return nil, fmt.Errorf("failed to decode JSON response: %w", err) + } + + for i := range payload.ModLists { + m := payload.ModLists[i] // copy to avoid pointer bug + ModReturnList = append(ModReturnList, &m) } - defer cursor.Close(ctx) - - var mods []*models.Mod - for cursor.Next(ctx) { - var doc struct { - IP string `bson:"ip"` - Port string `bson:"port"` - PublicKey string `bson:"public_key"` - } - if err := cursor.Decode(&doc); err != nil { - return nil, err - } - mods = append(mods, &models.Mod{ - IP: doc.IP, - Port: doc.Port, - PublicKey: doc.PublicKey, - }) - } - fmt.Println("Online mods:", mods) - return mods, nil + + return ModReturnList, nil } -func GetRelayAddr() ([]string, error) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() +// ✅ Fetch DB nodes +func GetDBFromJSServer() ([]*models.Node, error) { + cf, err := config.ReadDBConfigFile() + if err != nil { + fmt.Println("Error reading dbconfig.json:", err) + return nil, err + } + serverURL := cf.JS_ServerURL + + req, err := http.NewRequest("GET", serverURL+"/api/getboot", nil) // 🔥 corrected endpoint + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + var NodeReturnList []*models.Node - collection := MongoClient.Database("Addrs").Collection("relays") - cursor, err := collection.Find(ctx, bson.M{}) + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) if err != nil { - return nil, fmt.Errorf("failed to fetch relay addresses: %w", err) + return nil, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("server returned non-200 status code: %d", resp.StatusCode) + } + + var payload NodeResp + if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + return nil, fmt.Errorf("failed to decode JSON response: %w", err) } - defer cursor.Close(ctx) - var relayList []string - for cursor.Next(ctx) { - var doc struct { - Address string `bson:"address"` - } - if err := cursor.Decode(&doc); err != nil { - return nil, fmt.Errorf("failed to decode relay document: %w", err) - } - if strings.HasPrefix(doc.Address, "/") { - relayList = append(relayList, strings.TrimSpace(doc.Address)) - } + for i := range payload.NodesLists { + n := payload.NodesLists[i] // copy to avoid pointer bug + NodeReturnList = append(NodeReturnList, &n) } - return relayList, nil + return NodeReturnList, nil } diff --git a/core/db/main.go b/core/db/main.go index 9fd2e55..f63c9cc 100644 --- a/core/db/main.go +++ b/core/db/main.go @@ -1,37 +1,140 @@ package main import ( + "bytes" + "encoding/json" "fmt" + "net/http" "os" "os/signal" + "path/filepath" "syscall" "time" + "github.com/joho/godotenv" + "github.com/libr-forum/Libr/core/db/config" "github.com/libr-forum/Libr/core/db/internal/keycache" peer "github.com/libr-forum/Libr/core/db/internal/network/peers" + "github.com/libr-forum/Libr/core/db/internal/node" "github.com/libr-forum/Libr/core/db/internal/routing" "github.com/libr-forum/Libr/core/db/internal/utils" ) +var JS_API_key string +var JS_ServerURL string + func main() { keycache.InitKeys() - utils.SetupMongo("mongodb+srv://peer:peerhehe@cluster0.vswojqe.mongodb.net/") - relayAddrs, err := utils.GetRelayAddr() + godotenv.Load() + err := CreateDbConfig() + if err != nil { + fmt.Println("Error creating config file. Kindly create your own if required") + } + cf, err := config.ReadDBConfigFile() + if err != nil { + fmt.Println("Error reading values from config file") + } + JS_API_key = cf.API_KEY + JS_ServerURL = cf.JS_ServerURL + + //utils.SetupMongo("mongodb+srv://peer:peerhehe@cluster0.vswojqe.mongodb.net/") + //utils.SetupMongo(JS_ServerURL) + relayAddrs, err := utils.GetRelayAddrFromJSServer() + if err != nil { fmt.Println("Error while getting relay address, ", err) } + fmt.Println(relayAddrs) - peer.StartNode(relayAddrs) + go func() { + for { + // Close old host if it exists + if peer.Peer != nil && peer.Peer.Host != nil { + fmt.Println("[DEBUG] Closing host") + peer.Peer.Host.Close() + } + + // Start a new node right away + fmt.Println("[DEBUG] Starting new node...") + peer.StartNode(relayAddrs) + + // ⏰ Calculate how long to wait until the next o'clock + time.Sleep(55*time.Minute) + } +}() + sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) <-sigChan fmt.Println("Interrupt received. Exiting gracefully.") + if(config.DBtype=="boot"){ + deleteFromJSServer() + } + fmt.Println("Sent delete request to JS server") if routing.GlobalRT != nil { routing.GlobalRT.SaveToDBAsync() time.Sleep(1 * time.Second) } } + + +func deleteFromJSServer() error { + deleteData := map[string]string{ + "node_id" : node.GenerateNodeIDFromPublicKey(), + } + + jsonData, err := json.Marshal(deleteData) + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + + req, err := http.NewRequest("DELETE", JS_ServerURL+"/api/deleteboot", bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("x-api-key", JS_API_key) + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("server returned non-200 status code: %d", resp.StatusCode) + } + + fmt.Printf("Successfully deleted relay") + return nil +} + +func CreateDbConfig() error { + path := config.GetDBConfigPath() + + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return fmt.Errorf("failed to create modconfig directory: %w", err) + } + + // Only create the file if it does not already exist + if _, err := os.Stat(path); os.IsNotExist(err) { + f, err := os.Create(path) + if err != nil { + return fmt.Errorf("failed to create config file: %w", err) + } + defer f.Close() + + // Write an empty JSON object if file is new + _, err = f.WriteString("{}") + if err != nil { + return fmt.Errorf("failed to write empty JSON to config file: %w", err) + } + } + return nil +} diff --git a/core/mod_client/main.go b/core/mod_client/main.go index d474eb6..46e9dae 100644 --- a/core/mod_client/main.go +++ b/core/mod_client/main.go @@ -8,7 +8,7 @@ import ( "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) -//go:embed all:frontend/dist +//go:embed frontend/dist/* var assets embed.FS func main() { diff --git a/core/mod_client/peers/peer.go b/core/mod_client/peers/peer.go index e4e72ba..891c39c 100644 --- a/core/mod_client/peers/peer.go +++ b/core/mod_client/peers/peer.go @@ -1,3 +1,454 @@ +// package peer + +// import ( +// "bufio" +// "bytes" +// "crypto/sha256" +// "crypto/tls" +// "crypto/x509" +// "log" +// "math/big" +// "sort" + +// "context" +// "encoding/hex" +// "encoding/json" +// "fmt" + +// //"io" + +// "strings" +// "time" + +// "github.com/devlup-labs/Libr/core/mod_client/logger" +// "github.com/libp2p/go-libp2p" +// "github.com/libp2p/go-libp2p/core/host" +// "github.com/libp2p/go-libp2p/core/network" +// "github.com/libp2p/go-libp2p/core/peer" +// "github.com/libp2p/go-libp2p/core/protocol" +// "github.com/libp2p/go-libp2p/p2p/net/connmgr" +// "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/client" +// "github.com/libp2p/go-libp2p/p2p/protocol/holepunch" +// "github.com/libp2p/go-libp2p/p2p/protocol/identify" +// "github.com/multiformats/go-multiaddr" + +// "github.com/libp2p/go-libp2p/p2p/transport/tcp" +// "github.com/libp2p/go-libp2p/p2p/transport/websocket" + +// //webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" +// libp2ptls "github.com/libp2p/go-libp2p/p2p/security/tls" +// ) + +// const ChatProtocol = protocol.ID("/chat/1.0.0") + +// var OwnPubIP string + +// type ChatPeer struct { +// Host host.Host +// relayAddr multiaddr.Multiaddr +// relayID peer.ID +// peers map[peer.ID]string // peer ID to nickname mapping +// } + +// type reqFormat struct { +// Type string `json:"type,omitempty"` +// PeerID string `json:"peer_id,omitempty"` +// ReqParams json.RawMessage `json:"reqparams,omitempty"` +// Body json.RawMessage `json:"body,omitempty"` +// } + +// // type RelayDist struct { +// // relayID string +// // dist *big.Int +// // } + +// func NewChatPeer(relayMultiAddrList []string) (*ChatPeer, error) { + +// var relayList []string +// for _, multiaddr := range relayMultiAddrList { +// parts := strings.Split(multiaddr, "/") +// relayList = append(relayList, parts[len(parts)-1]) +// } + +// caCertPool := x509.NewCertPool() + +// fmt.Println("[DEBUG] Creating connection manager") +// connMgr, err := connmgr.NewConnManager(100, 400) +// if err != nil { +// fmt.Println("[DEBUG] Failed to create connection manager:", err) +// return nil, err +// } + +// tlsConfig := &tls.Config{ +// RootCAs: caCertPool, +// InsecureSkipVerify: true, +// // Other TLS configurations like ClientAuth, InsecureSkipVerify, etc. +// } + +// fmt.Println("[DEBUG] Creating libp2p Host") +// h, err := libp2p.New( +// libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/0/ws"), // WebSocket +// libp2p.Security(libp2ptls.ID, libp2ptls.New), +// libp2p.ConnectionManager(connMgr), +// libp2p.EnableNATService(), +// libp2p.EnableRelay(), +// libp2p.Transport(tcp.NewTCPTransport), +// libp2p.Transport(websocket.New, websocket.WithTLSConfig(tlsConfig)), +// // libp2p.Transport(websocket.NewWithTLSConfig(tlsConfig)), +// // libp2p.Transport(websocket.New), +// ) + +// if err != nil { +// fmt.Println("[DEBUG] Failed to create Host:", err) +// return nil, err +// } + +// fmt.Println("[DEBUG] Creating identify service") +// idSvc, err := identify.NewIDService(h) +// if err != nil { +// fmt.Println("[DEBUG] Failed to create identify service:", err) +// h.Close() +// return nil, err +// } + +// getListenAddrs := func() []multiaddr.Multiaddr { +// var publicAddrs []multiaddr.Multiaddr +// for _, addr := range h.Addrs() { +// if !isPrivateAddr(addr) { +// publicAddrs = append(publicAddrs, addr) +// } +// } +// return publicAddrs +// } + +// fmt.Println("[DEBUG] Creating hole punching service") +// hps, err := holepunch.NewService(h, idSvc, getListenAddrs) +// if err != nil { +// fmt.Println("[DEBUG] Failed to create hole punching service:", err) +// h.Close() +// return nil, err +// } +// _ = hps + +// var distmap []RelayDist +// //OwnPubIP = GetPublicIP() +// h1 := sha256.New() +// h1.Write([]byte(h.ID().String())) +// peerIDhash := hex.EncodeToString(h1.Sum(nil)) + +// for _, relay := range relayList { + +// h_R := sha256.New() +// h_R.Write([]byte(relay)) +// RelayIDhash := hex.EncodeToString(h_R.Sum(nil)) + +// dist := XorHexToBigInt(peerIDhash, RelayIDhash) + +// distmap = append(distmap, RelayDist{dist: dist, relayID: relay}) +// } + +// sort.Slice(distmap, func(i, j int) bool { +// return distmap[i].dist.Cmp(distmap[j].dist) < 0 +// }) + +// relayIDused := distmap[0].relayID +// fmt.Println(relayIDused) +// var relayAddr string + +// for _, multiaddr := range relayMultiAddrList { +// parts := strings.Split(multiaddr, "/") +// if parts[len(parts)-1] == relayIDused { +// relayAddr = multiaddr +// break +// } +// } + +// fmt.Println("[DEBUG] Parsing relay address:", relayAddr) +// relayMA, err := multiaddr.NewMultiaddr(relayAddr) +// if err != nil { +// fmt.Println("[DEBUG] Failed to parse relay multiaddr:", err) +// return nil, err +// } + +// relayInfo, err := peer.AddrInfoFromP2pAddr(relayMA) +// if err != nil { +// fmt.Println("[DEBUG] Failed to extract relay peer info:", err) +// return nil, err +// } +// // Create circuit relay client +// fmt.Println("[DEBUG] Creating circuit relay client") +// // _ = client // Import for reservation function + +// cp := &ChatPeer{ +// Host: h, +// relayAddr: relayMA, +// relayID: relayInfo.ID, +// peers: make(map[peer.ID]string), +// } + +// fmt.Println(h.ID().String()) + +// fmt.Println("[DEBUG] Setting stream handler for chat protocol") +// h.SetStreamHandler(ChatProtocol, cp.handleChatStream) + +// return cp, nil +// } + +// func isPrivateAddr(addr multiaddr.Multiaddr) bool { +// addrStr := addr.String() +// return strings.Contains(addrStr, "127.0.0.1") || +// strings.Contains(addrStr, "192.168.") || +// strings.Contains(addrStr, "10.") || +// strings.Contains(addrStr, "172.16.") || +// strings.Contains(addrStr, "172.17.") || +// strings.Contains(addrStr, "172.18.") || +// strings.Contains(addrStr, "172.19.") || +// strings.Contains(addrStr, "172.2") || +// strings.Contains(addrStr, "172.30.") || +// strings.Contains(addrStr, "172.31.") +// } + +// // why???? + +// func (cp *ChatPeer) Start(ctx context.Context) error { +// fmt.Println("[DEBUG] Connecting to relay:", cp.relayAddr) +// relayInfo, _ := peer.AddrInfoFromP2pAddr(cp.relayAddr) +// if err := cp.Host.Connect(ctx, *relayInfo); err != nil { +// fmt.Println("[DEBUG] Failed to connect to relay:", err) +// return fmt.Errorf("failed to connect to relay: %w", err) +// } + +// // Make reservation with the relay +// fmt.Println("[DEBUG] Making reservation with relay...") +// reservation, err := client.Reserve(ctx, cp.Host, *relayInfo) +// if err != nil { +// fmt.Printf("[DEBUG] Failed to make reservation: %v\n", err) +// return fmt.Errorf("failed to make reservation: %w", err) +// } +// fmt.Printf("[DEBUG] Reservation successful! Expiry: %v\n", reservation.Expiration) + +// fmt.Printf("[DEBUG] Peer started!\n") +// fmt.Printf("[DEBUG] Peer ID: %s\n", cp.Host.ID()) + +// for _, addr := range cp.Host.Addrs() { +// fmt.Printf("[DEBUG] Address: %s/p2p/%s\n", addr, cp.Host.ID()) +// } + +// circuitAddr := cp.relayAddr.Encapsulate( +// multiaddr.StringCast(fmt.Sprintf("/p2p-circuit/p2p/%s", cp.Host.ID()))) + +// fmt.Printf("[INFO] Circuit Address (share this with other peers): %s\n", circuitAddr) + +// // Start a goroutine to periodically refresh reservations +// go cp.refreshReservations(ctx, *relayInfo) + +// var reqSent reqFormat +// reqSent.Type = "register" +// reqSent.PeerID = cp.Host.ID().String() // now sending the the peerID in the req to registeer in the relay +// //reqSent.PubIP = OwnPubIP // have too use a stun server to get public ip first and then send register command +// fmt.Println(reqSent.PeerID) +// logger.LogToFile("PeerID: " + reqSent.PeerID) +// stream, err := cp.Host.NewStream(context.Background(), relayInfo.ID, ChatProtocol) + +// if err != nil { +// fmt.Println("[DEBUG]Error Opening stream to relay") +// } +// fmt.Println("[DEBUG]Opened atream to relay successsfully") +// reqJson, err := json.Marshal(reqSent) +// if err != nil { +// fmt.Println("[DEBUG]Error marshalling the req to be sent") +// } +// stream.Write([]byte(reqJson)) + +// time.Sleep(1 * time.Second) + +// stream.Close() +// return nil +// } + +// func (cp *ChatPeer) refreshReservations(ctx context.Context, relayInfo peer.AddrInfo) { +// ticker := time.NewTicker(5 * time.Minute) // Refresh every 5 minutes +// defer ticker.Stop() + +// for { +// select { +// case <-ticker.C: +// fmt.Println("[DEBUG] Refreshing relay reservation...") +// if reservation, err := client.Reserve(ctx, cp.Host, relayInfo); err != nil { +// fmt.Printf("[DEBUG] Failed to refresh reservation: %v\n", err) +// } else { +// fmt.Printf("[DEBUG] Reservation refreshed! Expiry: %v\n", reservation.Expiration) +// } +// case <-ctx.Done(): +// return +// } +// } +// } + +// func (cp *ChatPeer) handleChatStream(s network.Stream) { +// fmt.Println("[DEBUG] Incoming chat stream from", s.Conn().RemotePeer()) +// defer s.Close() + +// reader := bufio.NewReader(s) +// for { + +// line, err := reader.ReadBytes('\n') +// if err != nil { +// fmt.Println("[DEBUG]Error reading the bytes from the stream") +// } +// line = bytes.TrimRight(line, "\n") +// line = bytes.TrimRight(line, "\x00") +// var reqStruct reqFormat +// err = json.Unmarshal(line, &reqStruct) + +// fmt.Println("[DEBUG] Raw input:", string(line)) + +// if err != nil { +// fmt.Println("[DEBUG]Error unmarshalling to reqStruc") +// } + +// var reqData map[string]interface{} +// reqStruct.ReqParams = bytes.TrimRight(reqStruct.ReqParams, "\x00") + +// if err := json.Unmarshal(reqStruct.ReqParams, &reqData); err != nil { +// fmt.Printf("[ERROR] Failed to unmarshal incoming request: %v\n", err) +// return +// } + +// fmt.Printf("[DEBUG]ReqData is : %+v \n", reqData) + +// if reqData["Method"] == "GET" { +// resp := ServeGetReq(reqStruct.ReqParams) +// resp = bytes.TrimRight(resp, "\x00") +// _, err = s.Write(resp) +// if err != nil { +// fmt.Println("[DEBUG]Error writing resp bytes to relay") +// return +// } +// } + +// if reqData["Method"] == "POST" { +// resp := ServePostReq(reqStruct.PeerID, reqStruct.ReqParams, reqStruct.Body) // have to set the new logic in serve post req now +// resp = bytes.TrimRight(resp, "\x00") +// _, err = s.Write(resp) +// if err != nil { +// fmt.Println("[DEBUG]Error writing resp bytes to relay") +// return +// } +// } + +// } +// } + +// func (cp *ChatPeer) Send(ctx context.Context, targetPeerID string, jsonReq []byte, body []byte) ([]byte, error) { +// //completeIP := TargetIP + ":" + targetPort + +// var req reqFormat +// req.Type = "SendMsg" +// //req.PeerID = completeIP +// req.PeerID = targetPeerID +// req.ReqParams = jsonReq +// req.Body = body +// stream, err := cp.Host.NewStream(ctx, cp.relayID, ChatProtocol) +// if err != nil { +// fmt.Println("[DEBUG]Error opneing a fetch ID stream to relay") +// return nil, err +// } + +// jsonReqRelay, err := json.Marshal(req) + +// if err != nil { +// fmt.Println("[DEBUG]Error marshalling get req to be sent to relay") +// return nil, err +// } + +// stream.Write([]byte(jsonReqRelay)) + +// fmt.Println("[DEBUG]Msg req sent to relay, waiting for ack") + +// reader := bufio.NewReader(stream) +// // ack, err := reader.ReadString('\n') + +// // if err != nil { +// // fmt.Println("[DEBUG]Error getting the acknowledgement") +// // return nil, err +// // } +// // _ = ack //can be used if required + +// var resp = make([]byte, 1024*50) +// reader.Read(resp) +// resp = bytes.TrimRight(resp, "\x00") +// defer stream.Close() + +// return resp, err +// } + +// func (cp *ChatPeer) GetConnectedPeers() []peer.ID { +// var peers []peer.ID +// for _, conn := range cp.Host.Network().Conns() { +// remotePeer := conn.RemotePeer() +// if remotePeer != cp.relayID { +// peers = append(peers, remotePeer) +// } +// } +// fmt.Printf("[DEBUG] Connected peers: %v\n", peers) +// return peers +// } + +// func (cp *ChatPeer) Close() error { +// fmt.Println("[DEBUG] Closing Host") +// return cp.Host.Close() +// } + +// // func GetPublicIP() string { +// // c, err := stun.Dial("udp4", "stun.l.google.com:19302") +// // if err != nil { +// // panic(err) +// // } +// // defer c.Close() + +// // var xorAddr stun.XORMappedAddress +// // message := stun.MustBuild(stun.TransactionID, stun.BindingRequest) +// // if err := c.Do(message, func(res stun.Event) { +// // if res.Error != nil { +// // panic(res.Error) +// // } +// // if err := xorAddr.GetFrom(res.Message); err != nil { +// // panic(err) +// // } +// // }); err != nil { +// // panic(err) +// // } + +// // if xorAddr.IP.To4() == nil { +// // panic("STUN returned an IPv6 address; IPv4 not available") +// // } + +// // peerAd := xorAddr.IP.String() + ":" + strconv.Itoa(xorAddr.Port) +// // return peerAd +// // } + +// func XorHexToBigInt(hex1, hex2 string) *big.Int { + +// bytes1, err1 := hex.DecodeString(hex1) +// bytes2, err2 := hex.DecodeString(hex2) + +// if err1 != nil || err2 != nil { +// log.Fatalf("Error decoding hex: %v %v", err1, err2) +// } + +// if len(bytes1) != len(bytes2) { +// log.Fatalf("Hex strings must be the same length") +// } + +// xorBytes := make([]byte, len(bytes1)) +// for i := 0; i < len(bytes1); i++ { +// xorBytes[i] = bytes1[i] ^ bytes2[i] +// } + +// result := new(big.Int).SetBytes(xorBytes) +// return result +// } package peer import ( @@ -8,6 +459,8 @@ import ( "crypto/x509" "log" "math/big" + "net/http" + "os" "sort" "context" @@ -20,6 +473,8 @@ import ( "strings" "time" + "github.com/libr-forum/Libr/core/mod_client/keycache" + "github.com/joho/godotenv" "github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" @@ -42,6 +497,10 @@ import ( const ChatProtocol = protocol.ID("/chat/1.0.0") var OwnPubIP string +var PeerID string +var PubKey string +var JS_ServerURL string +var JS_API_key string type ChatPeer struct { Host host.Host @@ -70,6 +529,13 @@ func NewChatPeer(relayMultiAddrList []string) (*ChatPeer, error) { relayList = append(relayList, parts[len(parts)-1]) } + godotenv.Load() + JS_API_key = os.Getenv("JS_API_key") + JS_ServerURL = os.Getenv("JS_ServerURL") + if JS_API_key == "" || JS_ServerURL == "" { + return nil, fmt.Errorf("[DEBUG] Missing JS API key or server URL") + } + caCertPool := x509.NewCertPool() fmt.Println("[DEBUG] Creating connection manager") @@ -86,7 +552,7 @@ func NewChatPeer(relayMultiAddrList []string) (*ChatPeer, error) { } fmt.Println("[DEBUG] Creating libp2p Host") - h, err := libp2p.New( + h, _ := libp2p.New( libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/0/ws"), // WebSocket libp2p.Security(libp2ptls.ID, libp2ptls.New), libp2p.ConnectionManager(connMgr), @@ -98,6 +564,14 @@ func NewChatPeer(relayMultiAddrList []string) (*ChatPeer, error) { // libp2p.Transport(websocket.New), ) + PubKey = keycache.LoadPubKey() + PeerID = h.ID().String() + + err = connectJSServer() + if err != nil { + fmt.Println("[DEBUG] Failed to connect to JS server:", err) + } + if err != nil { fmt.Println("[DEBUG] Failed to create Host:", err) return nil, err @@ -209,6 +683,39 @@ func isPrivateAddr(addr multiaddr.Multiaddr) bool { } // why???? +func connectJSServer() error { + postData := map[string]string{ + "peer_id": PeerID, + "public_key": PubKey, + } + + jsonData, err := json.Marshal(postData) + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + + req, err := http.NewRequest("POST", JS_ServerURL+"/api/postnode", bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("x-api-Key", JS_API_key) + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("server returned non-200 status code: %d", resp.StatusCode) + } + + fmt.Printf("Successfully connected to JS server:") + return nil +} func (cp *ChatPeer) Start(ctx context.Context) error { fmt.Println("[DEBUG] Connecting to relay:", cp.relayAddr) @@ -397,9 +904,46 @@ func (cp *ChatPeer) GetConnectedPeers() []peer.ID { func (cp *ChatPeer) Close() error { fmt.Println("[DEBUG] Closing Host") + err := deleteFromJSServer() + if err != nil { + return err + } return cp.Host.Close() } +func deleteFromJSServer() error { + deleteData := map[string]string{ + "peer_id": PeerID, + } + + jsonData, err := json.Marshal(deleteData) + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + + req, err := http.NewRequest("DELETE", JS_ServerURL+"/api/deleteboot", bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("x-api-key", JS_API_key) + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("server returned non-200 status code: %d", resp.StatusCode) + } + + fmt.Printf("Successfully deleted node") + return nil +} + // func GetPublicIP() string { // c, err := stun.Dial("udp4", "stun.l.google.com:19302") // if err != nil { @@ -448,4 +992,4 @@ func XorHexToBigInt(hex1, hex2 string) *big.Int { result := new(big.Int).SetBytes(xorBytes) return result -} +} \ No newline at end of file diff --git a/scripts/install-libr.sh b/scripts/install-libr.sh index c278f14..80a503d 100755 --- a/scripts/install-libr.sh +++ b/scripts/install-libr.sh @@ -1,55 +1,120 @@ -#!/bin/bash -set -e - -# Detect distribution -source /etc/os-release -DISTRO=$ID -ARCH=$(dpkg --print-architecture 2>/dev/null || uname -m) - -# Fetch latest or use provided version -LATEST_VERSION=$(curl -s https://api.github.com/repos/libr-forum/libr/releases/latest \ - | grep tag_name | cut -d '"' -f4) -VERSION=${1:-$LATEST_VERSION} - -# Check installed version -if command -v libr >/dev/null 2>&1; then - INSTALLED_VERSION=$(libr --version | awk '{print $2}') -else - INSTALLED_VERSION="none" -fi - -if [ "$INSTALLED_VERSION" = "$VERSION" ]; then - echo "✅ libr $VERSION already installed." - exit 0 -fi - -echo "📦 Installing libr $VERSION for $DISTRO ($ARCH)..." - -case "$DISTRO" in - ubuntu|debian) - DEB_VERSION=$(echo "$VERSION") - URL="https://github.com/libr-forum/libr/releases/download/$VERSION/libr_${DEB_VERSION}_${ARCH}.deb" - echo "⬇️ Downloading $URL..." - wget -O libr.deb "$URL" || { echo "❌ Failed to download $URL"; exit 1; } - sudo dpkg -i libr.deb || sudo apt-get install -f -y - rm libr.deb - ;; - fedora|rhel|centos) - URL="https://github.com/libr-forum/libr/releases/download/$VERSION/libr-${VERSION}.${ARCH}.rpm" - wget -qO libr.rpm "$URL" - sudo dnf install -y ./libr.rpm || sudo yum install -y ./libr.rpm - rm libr.rpm - ;; - arch) - URL="https://github.com/libr-forum/libr/releases/download/$VERSION/libr-${VERSION}-${ARCH}.pkg.tar.zst" - wget -qO libr.pkg.tar.zst "$URL" - sudo pacman -U --noconfirm libr.pkg.tar.zst - rm libr.pkg.tar.zst - ;; - *) - echo "❌ Unsupported distribution: $DISTRO" - exit 1 - ;; -esac - -echo "✅ libr $VERSION installed successfully." +#!/usr/bin/env bash + +set -euo pipefail + +# ------------------------------- +# Configuration +# ------------------------------- +VERSION="${1:-latest}" # default = latest release +ARCH="$(uname -m)" +DEBUG="${DEBUG:-1}" # set DEBUG=0 to silence +REPO="libr-forum/libr" + +log() { echo -e "🔹 $*"; } +debug() { [[ "$DEBUG" -eq 1 ]] && echo -e "🐞 DEBUG: $*"; } +err() { echo -e "❌ $*" >&2; exit 1; } + +# ------------------------------- +# Detect distro +# ------------------------------- +detect_distro() { + if [ -f /etc/os-release ]; then + . /etc/os-release + DISTRO=$ID + debug "Detected distro: $DISTRO" + else + err "Unable to detect distribution." + fi +} + +# ------------------------------- +# Detect installed version +# ------------------------------- +installed_version() { + if command -v libr >/dev/null 2>&1; then + libr --version 2>/dev/null | awk '{print $2}' + else + echo "" + fi +} + +# ------------------------------- +# Get latest release from GitHub +# ------------------------------- +latest_version() { + curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \ + | grep '"tag_name":' | cut -d'"' -f4 +} + +# ------------------------------- +# Download + Install package +# ------------------------------- +install_pkg() { + local url="$1" + local file="$2" + + log "⬇️ Downloading $url" + if ! curl -fL "$url" -o "$file"; then + err "Failed to download $url" + fi + + case "$DISTRO" in + ubuntu|debian) + sudo dpkg -i "$file" || sudo apt-get install -f -y + ;; + fedora|rhel|centos|rocky|almalinux|opensuse*) + sudo rpm -Uvh --force "$file" + ;; + arch|manjaro) + sudo pacman -U --noconfirm "$file" + ;; + *) + err "Unsupported distro: $DISTRO" + ;; + esac +} + +# ------------------------------- +# Main +# ------------------------------- +main() { + detect_distro + + if [[ "$VERSION" == "latest" ]]; then + VERSION="$(latest_version)" + fi + debug "Target version: $VERSION" + + CURRENT="$(installed_version)" + debug "Currently installed version: ${CURRENT:-none}" + + if [[ "$CURRENT" == "$VERSION" ]]; then + log "✅ libr $VERSION is already installed." + exit 0 + fi + + case "$DISTRO" in + ubuntu|debian) + FILE="libr_${VERSION}_${ARCH}.deb" + ;; + fedora|rhel|centos|rocky|almalinux|opensuse*) + FILE="libr-${VERSION}.${ARCH}.rpm" + ;; + arch|manjaro) + FILE="libr-${VERSION}-${ARCH}.pkg.tar.zst" + ;; + *) + err "Unsupported distro: $DISTRO" + ;; + esac + + URL="https://github.com/${REPO}/releases/download/${VERSION}/${FILE}" + debug "Download URL: $URL" + debug "Local filename: $FILE" + + install_pkg "$URL" "$FILE" + + log "🎉 libr $VERSION installed successfully!" +} + +main "$@"