Skip to content

Commit eaa4fdb

Browse files
chopperbrianoclaude
andcommitted
Dashboard: add [V] Verify key to emit swarm-connect commands (win.25)
Fetches /api/v0/id from the local Kubo, filters out LAN/loopback addresses, and prints copy-pasteable `ipfs swarm connect` lines so a known-good remote box can confirm the announced multiaddrs are dialable. Shortens the PSP-registration feedback loop from hours (waiting for map.json) to seconds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c586679 commit eaa4fdb

3 files changed

Lines changed: 134 additions & 7 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ SET(PATCH_VERSION 0)
4040
SET(SO_VERSION 0)
4141

4242
# Windows port build number (increment for each Windows-specific release)
43-
SET(WIN_BUILD 24)
43+
SET(WIN_BUILD 25)
4444

4545
# Add source directory
4646
include_directories(src)

src/ConsoleDashboard.cpp

Lines changed: 128 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
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

8931015
void ConsoleDashboard::checkPorts() {

src/ConsoleDashboard.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ class ConsoleDashboard {
109109
void checkIpfsAnnounce(); // background: diagnose state, set hint
110110
bool applyIpfsAnnounceFix(); // [F] handler: POST to IPFS config API
111111

112+
// [V] handler: fetch /api/v0/id and emit copy-pasteable
113+
// `ipfs swarm connect` commands so a known-good remote box (e.g. a Linux
114+
// node) can confirm our announced multiaddrs are actually dialable.
115+
void printSwarmConnectCommands();
116+
112117
// Console dimensions
113118
int _width = 80;
114119
int _height = 25;

0 commit comments

Comments
 (0)