From 13a271ab6e3c5cd60c73060c13928c8d4a924247 Mon Sep 17 00:00:00 2001 From: Ryanmello07 <67509637+Ryanmello07@users.noreply.github.com> Date: Thu, 20 Aug 2026 23:12:32 -0700 Subject: [PATCH] gui: initialize the SDK on the primary instance only, and fix the resolv.conf log line Two independent fixes, both found by reading a tester's log bundle rather than by reading the code. 1. SDK INIT RAN ON EVERY LAUNCH, INCLUDING DUPLICATES. SdkHost::Initialize() was called before app->run(). GApplication only decides primary-vs-remote inside run(), so that code executes in every process that starts -- including a second launch that is about to hand its arguments to the running instance and exit. That is not a harmless double-init. Initialize() calls urnet::setLogDir(), which runs the SDK's glog init: it sweeps the log directory down to a keep-N budget and rewrites the urnetwork-gui.{INFO,WARNING,ERROR} symlinks. In the bundle, a second launch deleted three of the seven log files and left urnetwork-gui.INFO -- the file every "grab the current log" instruction follows -- pointing at its own 846-byte stub, while the session that was actually running had a 1.5 MB log the symlink no longer named. Anyone diagnosing from that log was reading the wrong file. Initialize() also opens the shared storage directory, which a process about to exit has no business touching. Moved into signal_startup, which is emitted on the PRIMARY instance only. A failure there calls app->quit() instead of returning 1, because by that point main() has already handed control to run(). 2. THE resolv.conf PROBE PRINTED A SELF-REFERENTIAL SYMLINK. The log line rendered " -> " unconditionally. On Arch and CachyOS /etc/resolv.conf is commonly a REGULAR FILE that systemd-resolved writes in place rather than a symlink into its runtime directory, so realpath() returns the path itself and the line came out as /etc/resolv.conf -> /etc/resolv.conf (resolved's) which reads as a symlink loop and sent a tester's triage chasing a misdetection that did not exist. The arrow is now rendered only when the path actually resolves somewhere else; a regular file and a missing file each say so. DNS tier selection was always correct -- only this line was wrong. --- app/src/Tunnel.cpp | 17 +++++++++++++++-- app/src/main.cpp | 24 ++++++++++++++++++++---- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/app/src/Tunnel.cpp b/app/src/Tunnel.cpp index d82af93..b10ea28 100644 --- a/app/src/Tunnel.cpp +++ b/app/src/Tunnel.cpp @@ -2887,8 +2887,21 @@ DnsHostProbe ProbeDnsHost() { (!p.resolvconf_present ? "absent" : (p.resolvconf_is_resolvectl ? "is resolvectl's shim" : "present")) + - ", " + kResolvConfPath + " -> " + - (p.resolv_conf_realpath.empty() ? std::string("(missing)") : p.resolv_conf_realpath) + + ", " + kResolvConfPath + + // ONLY render the arrow when the path actually resolves somewhere ELSE. + // On Arch/CachyOS /etc/resolv.conf is commonly a REGULAR FILE that + // systemd-resolved writes in place rather than a symlink into its + // runtime dir, so realpath() returns the path itself and the old + // unconditional " -> " printed + // /etc/resolv.conf -> /etc/resolv.conf (resolved's) + // which reads as a self-referential symlink and sent a tester's log + // triage chasing a misdetection that was not there. The tier + // selection was always correct; only this line was wrong. + (p.resolv_conf_realpath.empty() + ? std::string(" missing") + : (p.resolv_conf_realpath == kResolvConfPath + ? std::string(" is a regular file") + : " -> " + p.resolv_conf_realpath)) + (p.resolv_conf_points_at_resolved ? " (resolved's)" : ""); return p; } diff --git a/app/src/main.cpp b/app/src/main.cpp index 6eb543f..3742a8b 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -69,10 +69,6 @@ int main(int argc, char** argv) { // glibmm on core24 (the 2.68 ABI series) has no Glib::get_user_state_dir wrapper; // call the C g_get_user_state_dir() (glib 2.72+) directly for XDG_STATE_HOME. const std::string logDir = EnsureDir(g_get_user_state_dir(), "urnetwork"); - if (!host->Initialize(storageDir, logDir)) { - g_printerr("failed to initialize SDK\n"); - return 1; - } // Must match the .desktop StartupWMClass + common-id so the shell associates // the window with the app (and the hide-to-tray window keeps its identity). @@ -88,6 +84,26 @@ int main(int argc, char** argv) { std::shared_ptr tray; app->signal_startup().connect([&] { + // SDK INIT BELONGS HERE, NOT BEFORE app->run(). GApplication only decides + // primary-vs-remote inside run(), so anything above it executes in EVERY + // launch -- including a duplicate that is about to hand off to the running + // instance and exit. signal_startup is emitted on the PRIMARY only. + // + // That distinction is not cosmetic. SdkHost::Initialize calls + // urnet::setLogDir(), which runs the SDK's glog init: it sweeps the log + // directory down to a keep-N budget and rewrites the + // urnetwork-gui.{INFO,WARNING,ERROR} symlinks. A tester's bundle caught it + // -- a second launch deleted three of the seven log files and left + // urnetwork-gui.INFO, the file any "grab the current log" step follows, + // naming its own 846-byte stub while the session that was actually running + // had a 1.5 MB log the symlink no longer pointed at. Initialize also opens + // the shared storage dir, which a process about to exit has no business + // touching. + if (!host->Initialize(storageDir, logDir)) { + g_printerr("failed to initialize SDK\n"); + app->quit(); + return; + } adw_init(); // libadwaita stylesheet + platform integration // the brand visual system is dark (mac app parity); the Ui.cpp stylesheet // layers the exact palette on top