Back to list
Jul 21 2026

Development Update — July 21

Two of today’s fixes were the kind that take a service down or make it lie. One was a nil-dereference in IsRoot() that, on the static musl release binary, crash-looped every skywire command from init() — and had already taken the main hypervisor offline behind a wall of 502s. The other was the reward JSON endpoints returning each visor’s raw bandwidth byte count where the SKY amount should have been, so the hypervisor UI showed a visor’s 13-SKY reward as over a million. Alongside those, the VPN-router app finally got a config surface so it can be enabled the normal way, a wasm deep-link overlay stopped spinning against a visor that was already connected, and a routine vendor bump picked up a secp256k1 allocation win.

Skywire: A Crash in init()

3526 fix(visorconfig): IsRoot nil-deref panics static (musl) builds in init() — v1.3.86 regression is the critical one. IsRoot() ignored the error from user.Current() and dereferenced the result, and on a static musl build — which is the release binary format — user.Current() returns (nil, err) whenever the running UID has no resolvable /etc/passwd entry. The nil user was then dereferenced into a SIGSEGV, and because IsRoot() runs from pkg/visor’s init(), it panicked every skywire command on such a host before the command even ran. In production this took down the prod02 hv serve unit, which crash-looped on v1.3.86 with a restart counter past 1000 and dragged https://theskywirenetwork.net down to a Caddy 502. The fix resolves the effective UID first with os.Geteuid() == 0 — the canonical root test, which never needs a passwd lookup — and only falls back to a nil-guarded username check for the rare non-zero-uid root, applied to both the linux and darwin variants. It warranted an immediate v1.3.87.

Skywire: Rewards Were Reading the Wrong Column

3528 fix(rewards): JSON endpoints read the SKY amount, not bandwidth bytes fixes a bug that made the hypervisor reward pages show comically-high numbers — one visor’s true 13.347602 SKY reward rendered as 1,084,504 “SKY”. The reward system’s two JSON endpoints, /skycoin-rewards/hist/:date/json and the /skycoin-rewards/visor/:pk that the HV UI reads through the reward proxy, hardcoded Column(4) as the SKY amount. But a bandwidth-mode shares.csv has eleven columns where col4 is “Bandwidth (bytes)” and col5 is “Total Reward SKY” — only the legacy ten-column layout puts SKY in col4. The HTML hist page already detected this from the header row and read the right column; the JSON endpoints did not, so in bandwidth mode they returned each visor’s bandwidth byte count as the reward amount — which is why 1084504 is about 1 MB, and why the ratio to the true SKY varied wildly per visor rather than being a fixed units error. The fix detects bandwidth mode from the "Bandwidth (bytes)" header and reads col5 in that case, exactly mirroring the HTML page, so the hypervisor UI now agrees with haltingstate.net; it also trims a stray trailing comma on the PK in the hist result. The HV UI code itself was correct all along — it faithfully displayed what the API returned — and the fix reaches the live UI once v1.3.87 deploys and fiberreward on prod02 restarts.

Skywire: Enabling the VPN-Router

3534 feat(vpn-router): enableable via config-gen + autoconfig + skywire.conf gives the vpn-router app the config surface it had been missing. The app was built into the binary and registered as a launcher AppFunc, but it wasn’t in the generated app list — so it never appeared in the hypervisor Apps tab — and there was no config-gen, autoconfig, or .conf knob to enable it; you could only run skywire vpn-router by hand, the same class of gap the recent config-template audit turned up. This wires it up mirroring vpn-server, its structural twin: config gen gains --servevpnrouter and --vpnrouter-lan-ifc, so vpn-router now appears in both app-list templates — present but AutoStart=false by default, so it’s visible in the Apps tab, and autostarting with --lan-ifc when enabled. The autoconfig factory gets matching --vpnrouter/--vpnrouter-lan-ifc flags with their EnvMap entries so the install-command generator and skywire autoconfig can set them, the skywire.conf template documents VPNROUTER and VPNROUTERLANIFC in the Apps section, and a nominal skyenv.VPNRouterPort gives the app the unique launcher port every AppConfig carries even though it serves no dmsg endpoint. The config plumbing is what this PR delivers; whether the app brings up hostapd, dnsmasq, and NAT correctly from that config is best verified on an SBC with a spare interface.

3527 fix(wasm): deep-link connecting overlay spins on an already-connected visor fixes a wasm deep-link path that showed “Connecting to the mesh… dmsg sessions: 0 · still connecting” for a full ~30s timeout even when the shared visor was already connected with 18 transports registered — a plain tab load never hit it, only the reward-redirect deep-link render path did. browse.js keyed its connected check off st.dmsg_connected and st.dmsg_sessions, but the wasm visor’s jsStatus never populated them — it only set st.dmsg, meaning “the client exists,” not “has a live session” — so the check was always false and the counter always read zero. The fix populates dmsg_sessions = len(dmsgC.AllSessions()) and dmsg_connected = count > 0 in jsStatus, and additionally teaches browse.js to treat “client exists AND transports > 0” as connected, since transports run over dmsg sessions; that second half makes the fix work against already-deployed wasm blobs the moment the served browse.js updates, with no blob re-embed needed. 3529 chore(vendor): bump skycoin (secp256k1 ECmult stack precomp) picks up skycoin#2955, which stack-allocates ECmult’s precomp tables for a 41% cut in bytes-per-op per ECDH — trimming exactly the GC pressure behind dmsg-discovery’s high CPU under the pre-keep-alive handshake load from earlier in the week. Vendored-files only, go build ./... clean.