After 5+ hours of WinDivert NETWORK-layer NAT-rewrite debugging
(streamdump pattern, SOCKET-layer SYN preemption, lazy PID resolution,
UDP ASSOCIATE relay + manual reinject), Discord voice still wouldn't
connect. The fundamental issue is that WinDivert reinjected UDP
packets don't always reach connect()-bound application sockets — the
demux happens at a layer above the reinject point.
dvp/force-proxy avoids this entirely via DLL injection (above the
kernel demux). We avoid it the other way: embed sing-box, let it run
TUN inbound + per-process routing rule + SOCKS5 outbound. TUN packets
are read by sing-box from kernel as a normal flow; the application
socket sees a normal flow back. No reinject hairpin, no SYN race, no
spoofing concerns.
What this commit does:
- Drops internal/divert, internal/engine, internal/redirect,
internal/socks5, internal/procscan, plus cmd/drover/{proxy,
debugflow}_*.go subcommands (all WinDivert-only).
- Adds internal/sboxrun — embed sing-box.exe (1.12.25) + wintun.dll
(0.14.1) via //go:embed, install to %PROGRAMDATA%\Drover\sboxrun\
with SHA256 verify, generate JSON config from form, spawn as
subprocess, manage lifecycle.
- Wires sboxrun into internal/gui/app.go: StartEngine/StopEngine
now call sboxrun.Engine instead of windivert engine.
- Fixes Wails binding: StartEngine(cfg) now passes the form config
(was zero-arg, hit ProxyHost-required validation silently).
Manual test: Discord chat + voice work end-to-end through mihomo
upstream. Yandex Music / svchost / etc continue direct via
process_name routing rule.
Binary grew from 12 MB → 49 MB (37 MB sing-box embedded), but ships
fully self-contained. AV-friendly: wintun is Microsoft-signed, no
DLL injection.
WinDivert work preserved on experimental/windivert branch in case we
ever want to come back to that path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLI subcommands (check/version/update) don't need driver access and
run as user. Bare drover.exe (GUI/engine mode) requires admin for
WinDivertOpen — re-launches via ShellExecute("runas") and exits.
Per spec decision B1: prompt at every launch, no scheduled-task
trampoline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bare 'drover' (and 'drover gui') no longer pop a Win32 MessageBox —
they hand off to internal/gui which mounts a real Wails window with
the Classic React variant. The old gui_windows.go / gui_other.go
files are removed; their job is now done by internal/gui/run.go.
Auto-update on startup is unchanged — still runs before gui.Run().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Double-clicking drover.exe now silently checks for updates first
(8s timeout, ignores network failures). If a newer release is
published, a YES/NO message box asks the user; on YES we download,
verify SHA256, apply via selfupdate, and re-launch the binary so
the user immediately sees the new version's window.
NO or no update → straight to the smoke-test window as before.
Update errors show an error dialog, then continue.
Implementation:
- internal/updater is reused as-is — the existing CheckForUpdate +
ApplyUpdate API is enough.
- autoupdate_windows.go owns the dialog and re-launch logic
(golang.org/x/sys/windows for MessageBoxW, os/exec for fresh
process). autoupdate_other.go is the no-op stub for Linux CI.
- relaunchSelf uses cmd.Start (fire and forget) — no Wait. Current
process os.Exit(0) immediately so the OS doesn't keep both
generations of drover running.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
drover.exe is now a GUI subsystem binary:
- Double-click no longer flashes a console window — a clean
smoke-test message box opens immediately.
- When run from cmd / PowerShell, AttachConsole reattaches stdout
and stderr to the parent terminal so '--version', 'check', etc.
still print as expected.
- MB_TOPMOST flag added to MessageBox so the window can't be
obscured by other windows on launch (this was the actual cause
of "I clicked but nothing happened" reports).
Verified locally: built with GOOS=windows GOARCH=amd64 -H=windowsgui;
running drover-gui.exe --version prints to PowerShell, drover-gui.exe
gui shows the message box on top of the active window.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without this, double-clicking drover.exe printed CLI help to stdout —
fine in a terminal but invisible to a user who just clicked an icon.
Adding a RunE on the root command opens the smoke-test message box
when no subcommand was given. CLI flags (--version, --help) and
explicit subcommands keep their old behaviour.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shows a small message box with version metadata + build info. Used as
a fastest possible end-user smoke check after install: double-click
drover.exe in a portable bundle (or run 'drover gui' from cmd) and
see "OK — the binary launched and the Windows API is reachable" with
the right version on screen. No network calls, no admin needed.
Implementation: cgo-free, uses only golang.org/x/sys/windows (already
pulled in by the updater package). gui_other.go is a stub so the
package still compiles on Linux for the CI smoke build.
Locally verified: built with -X main.Version=test-local, ran
'drover.exe gui' on Windows 11, message box appeared with correct
version/commit/Go runtime info.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a small, well-tested package that:
- Queries /api/v1/repos/root/drover-go/releases/latest (404 = no updates,
not an error).
- Compares the published tag against the running Version using
golang.org/x/mod/semver, so v0.1.0-rc.2 < v0.1.0. "dev" or any
semver-invalid current version is treated as "always update".
- Downloads the windows-amd64 asset + SHA256SUMS.txt, verifies the
sha256 of the binary against its line in the sums file (tolerates
the asterisk binary-mode prefix), and atomically swaps the running
exe via github.com/minio/selfupdate.
- Uses a 15s connect timeout with no overall request deadline, so
large asset downloads aren't truncated.
- Reports progress via an optional callback.
Public surface: Source interface + ForgejoSource implementation,
CheckForUpdate, ApplyUpdate, SetVersion. No GUI/cobra/Wails imports
in the package, so the same code is reusable from the CLI, the
Windows service, and the future tray UI.
Wires the package into "drover update" / "drover update --check-only"
in cmd/drover/main.go. --check-only exits 0 whether or not an update
is available; only network/sha/apply errors are non-zero.
Tests cover CheckForUpdate (table-driven incl. semver pre-release
ordering, dev fallthrough, source errors), parseSHA256Sums (text and
binary modes, CRLF, malformed lines, missing entries),
ForgejoSource.Latest (httptest with canned JSON, 404, 500, missing
asset, missing SHA256SUMS), and downloadAndVerify (success, sha
mismatch, HTTP 404, context cancellation). All run with -race.
Smoke-tested manually: built drover.exe and "drover update --check-only"
against git.okcu.io prints "No updates available" and exits 0 (no
releases yet).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>