1414#include < ctime>
1515#include < algorithm>
1616#include < cmath>
17+ #include < vector>
1718
1819#ifdef _WIN32
1920#include < windows.h>
@@ -170,6 +171,16 @@ void ConsoleDashboard::processInput() {
170171 std::thread ([this ]() { applyIpfsAnnounceFix (); }).detach ();
171172 }
172173 break ;
174+ case ' v' :
175+ case ' V' :
176+ // Emit copy-pasteable swarm-connect commands for all announced
177+ // addresses so the user can verify dial-back from a remote box.
178+ {
179+ Log* log = Log::GetInstance ();
180+ log->addMessage (" Fetching announced multiaddrs from IPFS..." );
181+ std::thread ([this ]() { printSwarmConnectCommands (); }).detach ();
182+ }
183+ break ;
173184 case ' h' :
174185 case ' H' :
175186 case ' ?' :
@@ -195,7 +206,7 @@ void ConsoleDashboard::processInput() {
195206 log->addMessage (" External IP: " + ip);
196207 }
197208 }
198- log->addMessage (" Keys: Q=Quit A=Assets P=Port check L=Log level F=Fix IPFS announce H=This info" );
209+ log->addMessage (" Keys: Q=Quit A=Assets P=Port check L=Log level F=Fix IPFS announce V=Verify multiaddrs H=This info" );
199210 }
200211 break ;
201212 default :
@@ -494,15 +505,21 @@ void ConsoleDashboard::render() {
494505
495506 // Row: Time and Uptime
496507 {
497- auto now = std::chrono::system_clock::now ();
498- auto time_t_val = std::chrono::system_clock::to_time_t (now);
499- std::tm* timeinfo = std::localtime (&time_t_val);
508+ auto currentTime = std::chrono::system_clock::now ();
509+ auto time_t_val = std::chrono::system_clock::to_time_t (currentTime);
510+ std::tm timeinfo_buffer{};
511+ std::tm* timeinfo = &timeinfo_buffer;
512+ #ifdef _WIN32
513+ localtime_s (&timeinfo_buffer, &time_t_val);
514+ #else
515+ timeinfo = std::localtime (&time_t_val);
516+ #endif
500517 std::ostringstream timeOss;
501518 timeOss << std::put_time (timeinfo, " %H:%M:%S" );
502519 std::string timeStr = timeOss.str ();
503520
504521 // Calculate uptime
505- auto uptime_seconds = std::chrono::duration_cast<std::chrono::seconds>(now - _startTime).count ();
522+ auto uptime_seconds = std::chrono::duration_cast<std::chrono::seconds>(currentTime - _startTime).count ();
506523 std::string uptimeStr = formatDuration ((double )uptime_seconds);
507524
508525 out << ERASE_LINE << " "
@@ -621,7 +638,7 @@ void ConsoleDashboard::render() {
621638 if (showFixKey) {
622639 out << " " << RESET << FG_YELLOW << " [F] Fix IPFS" << RESET << DIM;
623640 }
624- out << " [H] Info" << RESET;
641+ out << " [V] Verify [ H] Info" << RESET;
625642
626643 // Write everything in one shot to minimize flicker
627644 std::cout << out.str () << std::flush;
@@ -888,6 +905,111 @@ bool ConsoleDashboard::applyIpfsAnnounceFix() {
888905 return true ;
889906}
890907
908+ // ---- [V] Verify: dump swarm-connect commands for announced multiaddrs ------
909+
910+ void ConsoleDashboard::printSwarmConnectCommands () {
911+ Log* log = Log::GetInstance ();
912+
913+ std::string idJson;
914+ try {
915+ idJson = CurlHandler::post (getIpfsApiBase () + " id" , {}, 5000 );
916+ } catch (const std::exception& e) {
917+ log->addMessage (std::string (" Verify failed: IPFS API unreachable: " ) + e.what (),
918+ Log::WARNING);
919+ return ;
920+ }
921+
922+ // Extract "ID":"<peerId>"
923+ std::string peerId;
924+ {
925+ size_t k = idJson.find (" \" ID\" " );
926+ if (k != std::string::npos) {
927+ size_t colon = idJson.find (' :' , k);
928+ size_t q1 = (colon != std::string::npos) ? idJson.find (' "' , colon + 1 ) : std::string::npos;
929+ size_t q2 = (q1 != std::string::npos) ? idJson.find (' "' , q1 + 1 ) : std::string::npos;
930+ if (q2 != std::string::npos) peerId = idJson.substr (q1 + 1 , q2 - q1 - 1 );
931+ }
932+ }
933+ if (peerId.empty ()) {
934+ log->addMessage (" Verify failed: could not parse peer ID from /id response" , Log::WARNING);
935+ return ;
936+ }
937+
938+ // Extract "Addresses":[ ... ]
939+ std::vector<std::string> addrs;
940+ {
941+ size_t k = idJson.find (" \" Addresses\" " );
942+ size_t start = (k != std::string::npos) ? idJson.find (' [' , k) : std::string::npos;
943+ size_t end = (start != std::string::npos) ? idJson.find (' ]' , start) : std::string::npos;
944+ if (start != std::string::npos && end != std::string::npos) {
945+ size_t pos = start + 1 ;
946+ while (pos < end) {
947+ size_t q1 = idJson.find (' "' , pos);
948+ if (q1 == std::string::npos || q1 >= end) break ;
949+ size_t q2 = idJson.find (' "' , q1 + 1 );
950+ if (q2 == std::string::npos || q2 >= end) break ;
951+ addrs.push_back (idJson.substr (q1 + 1 , q2 - q1 - 1 ));
952+ pos = q2 + 1 ;
953+ }
954+ }
955+ }
956+ if (addrs.empty ()) {
957+ log->addMessage (" Verify failed: no addresses in /id response" , Log::WARNING);
958+ return ;
959+ }
960+
961+ // Filter out LAN/loopback/link-local — can't be dialed from another host.
962+ auto isLan = [](const std::string& a) -> bool {
963+ size_t p = a.find (" /ip4/" );
964+ if (p != std::string::npos) {
965+ p += 5 ;
966+ size_t e = a.find (' /' , p);
967+ std::string ip = (e == std::string::npos) ? a.substr (p) : a.substr (p, e - p);
968+ if (ip.rfind (" 127." , 0 ) == 0 ) return true ;
969+ if (ip.rfind (" 10." , 0 ) == 0 ) return true ;
970+ if (ip.rfind (" 192.168." , 0 ) == 0 ) return true ;
971+ if (ip.rfind (" 169.254." , 0 ) == 0 ) return true ;
972+ if (ip.rfind (" 172." , 0 ) == 0 ) {
973+ try {
974+ int o2 = std::stoi (ip.substr (4 ));
975+ if (o2 >= 16 && o2 <= 31 ) return true ;
976+ } catch (...) {}
977+ }
978+ return false ;
979+ }
980+ if (a.find (" /ip6/::1" ) != std::string::npos) return true ;
981+ if (a.find (" /ip6/fe80" ) != std::string::npos) return true ;
982+ return false ;
983+ };
984+
985+ log->addMessage (" --- Verify: run these from a known-good remote box (e.g. your Linux node) ---" );
986+ log->addMessage (" Peer ID: " + peerId);
987+ std::string selfSuffix = " /p2p/" + peerId;
988+
989+ int emitted = 0 ;
990+ for (const auto & a : addrs) {
991+ if (isLan (a)) continue ;
992+
993+ // Kubo sometimes omits the trailing /p2p/<selfId>; swarm connect needs it.
994+ std::string full = a;
995+ if (full.size () < selfSuffix.size () ||
996+ full.compare (full.size () - selfSuffix.size (), selfSuffix.size (), selfSuffix) != 0 ) {
997+ full += selfSuffix;
998+ }
999+ log->addMessage (" ipfs swarm connect " + full);
1000+ emitted++;
1001+ }
1002+
1003+ if (emitted == 0 ) {
1004+ log->addMessage (" (no public addresses announced - press [F] to set Announce, "
1005+ " or wait for relay addresses to appear)" , Log::WARNING);
1006+ } else {
1007+ log->addMessage (" Then: ipfs swarm peers | grep " + peerId.substr (0 , 20 ));
1008+ log->addMessage (" If the connect succeeds, mctrivia's server can reach you too." );
1009+ }
1010+ log->addMessage (" --- end verify ---" );
1011+ }
1012+
8911013// ---- Port checking ----------------------------------------------------------
8921014
8931015void ConsoleDashboard::checkPorts () {
0 commit comments