Skip to content

Commit 5b6b118

Browse files
committed
Fix server WireGuard setup and separate WG port from hole punch
- Derive server node ID from identity pubkey when no server cert exists, so genesis servers get a tunnel IP without requiring --enroll-server - Add dedicated wg_port (default 51820) separate from udp_port (51940) to prevent WireGuard and hole punch from fighting over the same socket - Update /api/join to return wg_port in the WireGuard endpoint - Publish wg= field in DNS _config TXT record - Add --wg-port CLI flag, SP_WG_PORT env var, JSON config support
1 parent 5a93504 commit 5b6b118

File tree

7 files changed

+38
-12
lines changed

7 files changed

+38
-12
lines changed

projects/LemonadeNexus/include/LemonadeNexus/Core/ServerConfig.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ namespace nexus::core {
1111
struct ServerConfig {
1212
// Network
1313
uint16_t http_port{9100};
14-
uint16_t udp_port{51940}; // WireGuard + hole-punch (shared UDP port)
14+
uint16_t udp_port{51940}; // UDP hole-punch
15+
uint16_t wg_port{51820}; // WireGuard listen port
1516
uint16_t gossip_port{9102};
1617
uint16_t stun_port{3478};
1718
uint16_t relay_port{9103};

projects/LemonadeNexus/include/LemonadeNexus/Network/DnsService.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ class DnsService : public core::IService<DnsService>,
145145
struct PortConfig {
146146
uint16_t http_port{9100};
147147
uint16_t udp_port{51940};
148+
uint16_t wg_port{51820};
148149
uint16_t gossip_port{9102};
149150
uint16_t stun_port{3478};
150151
uint16_t relay_port{9103};

projects/LemonadeNexus/src/Api/TreeApiHandler.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,10 @@ void TreeApiHandler::do_register_routes(httplib::Server& pub, httplib::Server& p
171171
}
172172
}
173173

174-
// Build the WireGuard endpoint: public_ip:udp_port
174+
// Build the WireGuard endpoint: public_ip:wg_port
175175
std::string wg_endpoint;
176176
if (!ctx_.server_public_ip.empty()) {
177-
wg_endpoint = ctx_.server_public_ip + ":" + std::to_string(ctx_.config.udp_port);
177+
wg_endpoint = ctx_.server_public_ip + ":" + std::to_string(ctx_.config.wg_port);
178178
}
179179

180180
nlohmann::json resp = {

projects/LemonadeNexus/src/Core/ServerConfig.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ void to_json(json& j, const ServerConfig& c) {
2020
j = json{
2121
{"http_port", c.http_port},
2222
{"udp_port", c.udp_port},
23+
{"wg_port", c.wg_port},
2324
{"gossip_port", c.gossip_port},
2425
{"stun_port", c.stun_port},
2526
{"relay_port", c.relay_port},
@@ -68,6 +69,7 @@ void to_json(json& j, const ServerConfig& c) {
6869
void from_json(const json& j, ServerConfig& c) {
6970
if (j.contains("http_port")) j.at("http_port").get_to(c.http_port);
7071
if (j.contains("udp_port")) j.at("udp_port").get_to(c.udp_port);
72+
if (j.contains("wg_port")) j.at("wg_port").get_to(c.wg_port);
7173
if (j.contains("gossip_port")) j.at("gossip_port").get_to(c.gossip_port);
7274
if (j.contains("stun_port")) j.at("stun_port").get_to(c.stun_port);
7375
if (j.contains("relay_port")) j.at("relay_port").get_to(c.relay_port);
@@ -122,7 +124,8 @@ void print_usage(const char* prog) {
122124
spdlog::info("Options:");
123125
spdlog::info(" --config <path> JSON config file (default: lemonade-nexus.json)");
124126
spdlog::info(" --http-port <N> HTTP port (default: 9100)");
125-
spdlog::info(" --udp-port <N> WireGuard + hole-punch UDP port (default: 51940)");
127+
spdlog::info(" --udp-port <N> UDP hole-punch port (default: 51940)");
128+
spdlog::info(" --wg-port <N> WireGuard listen port (default: 51820)");
126129
spdlog::info(" --gossip-port <N> Gossip protocol UDP port (default: 9102)");
127130
spdlog::info(" --stun-port <N> STUN UDP port (default: 3478)");
128131
spdlog::info(" --relay-port <N> Relay UDP port (default: 9103)");
@@ -200,6 +203,8 @@ ServerConfig load_config(int argc, char* argv[]) {
200203
config.http_port = static_cast<uint16_t>(std::atoi(argv[++i]));
201204
} else if (std::strcmp(argv[i], "--udp-port") == 0 && i + 1 < argc) {
202205
config.udp_port = static_cast<uint16_t>(std::atoi(argv[++i]));
206+
} else if (std::strcmp(argv[i], "--wg-port") == 0 && i + 1 < argc) {
207+
config.wg_port = static_cast<uint16_t>(std::atoi(argv[++i]));
203208
} else if (std::strcmp(argv[i], "--gossip-port") == 0 && i + 1 < argc) {
204209
config.gossip_port = static_cast<uint16_t>(std::atoi(argv[++i]));
205210
} else if (std::strcmp(argv[i], "--stun-port") == 0 && i + 1 < argc) {
@@ -280,6 +285,7 @@ ServerConfig load_config(int argc, char* argv[]) {
280285
if (const char* v = std::getenv("SP_LOG_LEVEL")) config.log_level = v;
281286
if (const char* v = std::getenv("SP_HTTP_PORT")) config.http_port = static_cast<uint16_t>(std::atoi(v));
282287
if (const char* v = std::getenv("SP_UDP_PORT")) config.udp_port = static_cast<uint16_t>(std::atoi(v));
288+
if (const char* v = std::getenv("SP_WG_PORT")) config.wg_port = static_cast<uint16_t>(std::atoi(v));
283289
if (const char* v = std::getenv("SP_GOSSIP_PORT")) config.gossip_port = static_cast<uint16_t>(std::atoi(v));
284290
if (const char* v = std::getenv("SP_STUN_PORT")) config.stun_port = static_cast<uint16_t>(std::atoi(v));
285291
if (const char* v = std::getenv("SP_RELAY_PORT")) config.relay_port = static_cast<uint16_t>(std::atoi(v));
@@ -346,18 +352,19 @@ bool validate_config(const ServerConfig& config) {
346352
};
347353
check_port(config.http_port, "http_port");
348354
check_port(config.udp_port, "udp_port");
355+
check_port(config.wg_port, "wg_port");
349356
check_port(config.gossip_port, "gossip_port");
350357
check_port(config.stun_port, "stun_port");
351358
check_port(config.relay_port, "relay_port");
352359
check_port(config.dns_port, "dns_port");
353360

354361
// Check ports are unique
355362
std::set<uint16_t> ports = {
356-
config.http_port, config.udp_port, config.gossip_port,
363+
config.http_port, config.udp_port, config.wg_port, config.gossip_port,
357364
config.stun_port, config.relay_port, config.dns_port
358365
};
359-
if (ports.size() < 6) {
360-
spdlog::error("Config: port conflict — all 6 ports must be unique");
366+
if (ports.size() < 7) {
367+
spdlog::error("Config: port conflict — all 7 ports must be unique");
361368
valid = false;
362369
}
363370

@@ -394,8 +401,8 @@ bool validate_config(const ServerConfig& config) {
394401
spdlog::warn("Config: rp_id is empty — WebAuthn passkeys will not validate");
395402
}
396403

397-
spdlog::info("Config: HTTP:{} UDP/WG:{} Gossip:{} STUN:{} Relay:{} DNS:{} data={}",
398-
config.http_port, config.udp_port, config.gossip_port,
404+
spdlog::info("Config: HTTP:{} UDP:{} WG:{} Gossip:{} STUN:{} Relay:{} DNS:{} data={}",
405+
config.http_port, config.udp_port, config.wg_port, config.gossip_port,
399406
config.stun_port, config.relay_port, config.dns_port,
400407
config.data_root);
401408

projects/LemonadeNexus/src/Core/ServerIdentity.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,28 @@ std::string resolve_jwt_secret(
4545
}
4646

4747
std::string resolve_server_node_id(storage::FileStorageService& storage) {
48+
// Prefer server certificate (from --enroll-server)
4849
auto cert_env = storage.read_file("identity", "server_cert.json");
4950
if (cert_env) {
5051
try {
5152
auto cert_j = nlohmann::json::parse(cert_env->data);
52-
return cert_j.value("server_id", "");
53+
auto id = cert_j.value("server_id", "");
54+
if (!id.empty()) return id;
5355
} catch (...) {}
5456
}
57+
58+
// Fall back to identity pubkey — every server has one after first boot.
59+
// Derive a stable node ID so IPAM allocations persist across restarts.
60+
auto pk_env = storage.read_file("identity", "keypair.pub");
61+
if (pk_env && !pk_env->data.empty()) {
62+
// Use first 16 hex chars of the pubkey as a stable server node ID
63+
auto pubkey = pk_env->data;
64+
if (pubkey.size() > 16) pubkey = pubkey.substr(0, 16);
65+
auto node_id = "server-" + pubkey;
66+
spdlog::info("Derived server node ID from identity pubkey: {}", node_id);
67+
return node_id;
68+
}
69+
5570
return {};
5671
}
5772

projects/LemonadeNexus/src/Network/DnsService.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ void DnsService::publish_port_config(const std::string& server_id, const std::st
440440
std::string txt = "v=sp1"
441441
" http=" + std::to_string(port_config_.http_port) +
442442
" udp=" + std::to_string(port_config_.udp_port) +
443+
" wg=" + std::to_string(port_config_.wg_port) +
443444
" gossip=" + std::to_string(port_config_.gossip_port) +
444445
" stun=" + std::to_string(port_config_.stun_port) +
445446
" relay=" + std::to_string(port_config_.relay_port) +

projects/LemonadeNexus/src/main.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ int main(int argc, char* argv[]) {
351351
nexus::network::DnsService::PortConfig dns_ports;
352352
dns_ports.http_port = http_port;
353353
dns_ports.udp_port = udp_port;
354+
dns_ports.wg_port = config.wg_port;
354355
dns_ports.gossip_port = gossip_port;
355356
dns_ports.stun_port = stun_port;
356357
dns_ports.relay_port = relay_port;
@@ -487,11 +488,11 @@ int main(int argc, char* argv[]) {
487488
nexus::wireguard::WgInterfaceConfig wg_iface;
488489
wg_iface.private_key = wg_server_privkey_b64;
489490
wg_iface.address = tunnel_bind_ip + "/10"; // 10.64.0.0/10 mesh subnet
490-
wg_iface.listen_port = config.udp_port;
491+
wg_iface.listen_port = config.wg_port;
491492

492493
if (wireguard_service.setup_interface(wg_iface, {})) {
493494
spdlog::info("WireGuard: wg0 up on :{} with tunnel IP {}/10",
494-
config.udp_port, tunnel_bind_ip);
495+
config.wg_port, tunnel_bind_ip);
495496
} else {
496497
spdlog::warn("WireGuard: failed to set up wg0 — clients will not be able to connect. "
497498
"Ensure wireguard-tools and kernel module are installed.");

0 commit comments

Comments
 (0)