Design accepted 2026-05-01. Locks in 5 architectural decisions
(GUI-only, UAC-per-launch, no DPI bypass, hide-to-tray with toast,
contextual recovery) and decomposes Phase 2 into 5 milestones with
explicit acceptance criteria + a 30-row edge case matrix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Frontend now renders three-tier status (passed/warn/failed) instead of
two. Warn rows get a yellow exclamation-mark dot, expand by default to
show the hint, and contribute to a new "(with warnings)" suffix on the
"All checks passed" header.
Test catalog gains "voice-quality" and "voice-srv" rows replacing the
single "stun" row, in the same position (after udp, before api). RU
descriptions explain what each test actually probes.
useDrover's lastSummary now reports {total, failed, warnings} so the
Classic header can pick the right tier color.
App.go counts StatusWarn as passed in the final {passed, failed} summary
emitted via "check:done" — warn is a soft pass per spec.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the single-packet `stun` test with two predictive voice tests:
- voice-quality: 30-packet STUN burst through the SOCKS5 UDP relay.
Computes loss%, jitter (RFC-3550-ish mean abs of inter-arrival
delta), p50/p95 RTT. Three-tier gating: pass (loss≤5%, jitter≤30,
p50≤250), warn (loss≤15%, jitter≤60, p50≤400 — voice glitches but
works), fail (anything worse, including 100% loss).
- voice-srv: parallel-DNS the 16-region <region>.discord.media
hostnames, then SOCKS5 CONNECT to :443 on each through the proxy.
Catches the very common Russian-DPI failure mode where the proxy
passes generic Discord.com TCP but blocks the .discord.media voice
CIDRs — a regression all 5 prior SOCKS5 sanity checks miss.
New StatusWarn = "warn" — soft pass with Hint kept visible. Counted as
passed in summary but flagged in UI.
Config gains VoiceBurstCount (default 30), VoiceBurstInterval (default
20ms), VoiceServerHostnames (default = built-in 16-region list).
Tests cover happy path, warn-tier (10% drop), fail-tier (100% drop),
voice-srv blocked, plus standalone unit tests on
runVoiceQualityBurst and runVoiceServerProbe with a fake UDP relay
and fake SOCKS5 server. Race + cover stays at 82.4%.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Old single-shot stun test only proved one UDP packet round-tripped
through the relay. To predict whether voice will actually work the
checker now does two stronger tests:
- voice-quality: 30-packet STUN burst with loss/jitter/p50 metrics,
with a "warn" tier between hard pass and hard fail.
- voice-srv: concurrent DNS resolve + SOCKS5 TCP probe to a list of
Discord voice region hostnames; passes if any region is reachable.
Adds StatusWarn ("soft pass — show hint anyway") so the GUI can
distinguish "voice will work but glitchy" from green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without these tags Wails aborts at startup with a "Wails applications
will not build without the correct build tags" MessageBox. Local
rebuild.sh, the cross-compile step in release.yml, and the windows
build step in build.yml all needed the flag — only the local Wails
dev wrapper was already passing it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- RunCheck now drives internal/checker.Run instead of the fake 7-step
sleep loop. Streams checker.Result events as "check:result" with
Duration converted to milliseconds; emits "check:done" summary on
channel close. Re-running while a check is in flight cancels the
previous run and waits for its goroutine to drain so two emitter
goroutines never race on event order.
- New CancelCheck method (no-op when nothing is running) is bound
through wailsjs/go/gui/App.js and surfaced in useDrover as
cancelCheck() with a "check cancelled by user" log line.
- Classic.jsx: while phase==='checking', the Check button collapses to
a disabled "Checking…" pill side-by-side with a Cancel button (uses
Stop's secondary visual weight, t.danger on hover). The expanded
failure row now renders r.rawHex (truncated to 64 chars) on its own
mono line and the copy button includes raw=<hex> when present.
- CheckResult struct gains RawHex (json:"rawHex") and Attempt fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Public Run(ctx, cfg) <-chan Result streams diagnostic events for the seven
tests (tcp, greet, auth?, connect, udp, stun, api) wired through the
SOCKS5 primitives, STUN codec, retry classification and RU hints.
- Per-test attempt loop with running/passed/failed events, transient-only
retries (per-attempt timeout treated as transient, parent ctx cancel as
permanent), context-aware backoff sleep.
- Connection lifecycle: tcpConn shared across greet/auth/connect (closed
and redialed on retry); separate udpConn2 control channel for UDP
ASSOCIATE kept alive for the duration of the stun test.
- STUN-via-SOCKS5: builds 10-byte SOCKS5 UDP header + STUN binding
request, decodes reply with ATYP-aware header strip (1/3/4).
- runAPI plugs SOCKS5 dial into http.Transport.DialContext; passes on
HTTP 200 OR 401.
- Skip semantics: dependency-failed tests emit single skipped result;
cancellation latches and propagates as cancelled-failed (current) +
cancelled-skipped (remaining).
- Defaults applied to a copy of cfg; UseAuth=false suppresses any "auth"
result entirely.
Tests: 10 TestRun_* covering happy/auth-rejected/all-rejected/
connect-refused/udp-unsupported/timeout-then-ok/cancelled-mid-flight/
defaults plus extractRawHex unit. Fake SOCKS5 proxy + UDP relay echoing
synthetic STUN binding success responses; httptest stub for API splice.
Combined coverage 84.3% (>=80% target). go test -race clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds:
- retry.go: classifyError() splits errors into Permanent vs Transient
(used to gate auto-retry); isContextErr() detects ctx cancellation
through wrapping (OpError, errors.Join).
- hints.go: hintFor(testID, err) returns short Russian explanation per
failure step, with dedicated branches for SOCKS5 sentinels, every
documented REP code (0x01..0x08), STUN sentinels, timeouts, and a
friendly-name fallback.
Coverage: retry.go 100%, hints.go 100%; package total 94.2%.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hand-rolled RFC 5389 binding-request encoder + binding-success-response
parser. Just enough to extract XOR-MAPPED-ADDRESS from a server's reply
after socks5UDPAssociate returns a relay endpoint. Avoids pulling in
pion/stun for ~80 LOC of encoding/binary work.
Provides NewTransactionID, EncodeBindingRequest, ParseBindingResponse and
six sentinel errors (ErrSTUN*) so HintFor (T11) can match specific
failure modes. Full TLV attribute walking with bounds checks; supports
both IPv4 and IPv6 XOR-MAPPED-ADDRESS values.
Tests cover encoder layout, IPv4/IPv6 happy paths, attribute walking
past unknown attributes, all error paths, sentinel uniqueness, and a
real loopback round-trip via net.ListenPacket. 90.0% combined coverage
(socks5+stun); stun.go funcs all >= 87%.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
socks5Greeting/Auth/Connect/UDPAssociate per docs/superpowers/specs/
2026-05-01-checker-design.md. RFC 1928 + RFC 1929 wire bytes, raw
reply bytes returned on every error path for RawHex display, ctx
deadline applied via SetDeadline, ctx.Err() joined into error chain
on cancellation. Sentinel errors and ErrSocks5Reply{Code} for code
matching via errors.Is.
Tests: 22 subtests with fake net.Listen server, table-driven per
primitive (happy paths, REP codes, short reads, bad version,
oversize input rejection without I/O, ctx-cancel mid-read).
go test -race -cover passes at 89.0%, go vet clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- app.go: App struct with stub bindings (RunCheck/StartEngine/
StopEngine/GetStatus/Version) — emits check:result, check:done,
engine:status, stats:update events. Real backend lands in Phase 1.
- run.go: wails.Run() with frameless 480x640 fixed window, Classic
dark bg matching theme.
- embed.go: //go:embed all:frontend/dist for the Vite build output.
- frontend/: Vite + React project derived from `wails init -t react`.
Removed default template assets and wired Classic variant from
docs/design/v2/.
- components/Classic.jsx: variant 1 with custom title bar
(drag region, sun/moon theme toggle, min/close hooked to
Wails WindowMinimise/Quit).
- components/shared.jsx: useDrover hook adapted to call Wails
bindings and listen on backend events instead of mock SCENARIOS.
Added IconSun + IconMoon for the title-bar toggle.
- App.jsx: owns mode state, wraps setMode in
document.startViewTransition so the title-bar toggle gives a
circle-reveal sweep from the cursor.
- style.css: clean reset (overflow hidden, no scrollbars, brand
background) — replaces the wails-react-template defaults.
- wailsjs/go/gui/App.js: hand-written bindings since our App
struct lives in package gui rather than the standard top-level
main; `wails generate module` would have written package main
bindings here.
- build/: standard wails artifacts (icon, manifest); will be
consumed by `wails build` once we wire it through CI.
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>
Chrome-style silent updater: no prompt, no progress UI, no buttons.
On startup we check for updates (8s timeout), and if one is found
we download + verify + apply + relaunch — total ~3-5s on a fast
connection. The user sees the new version's window instead of the
old one, period.
Errors that do warrant a dialog (apply failed mid-write, sha256
mismatch, can't relaunch) still surface as a message box so the
user knows their copy is on the previous version. 'No update' and
network timeouts stay silent.
Split timeouts so a 60s download doesn't get killed by the 8s
check budget.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apt cache save works (28s in v0.1.4) but the next run can't restore
it: 'getCacheEntry failed: Request timeout' — Gitea cache backend
chokes on the ~300MB archive. Drop the cache step rather than burn
30s every run on a save that the next run can't read.
Real fix: bake wine + innoextract + xauth + wine32:i386 into a
custom CI image on git.okcu.io's registry, use container.image to
pull it. Defer until release frequency justifies the setup work.
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>
Debian-based Docker images install /etc/apt/apt.conf.d/docker-clean
which runs 'apt-get clean' after every apt-get install — wipes out
/var/cache/apt/archives so there's nothing for actions/cache to save.
Fix: remove the docker-clean drop-in and add keep-cache config first
thing in each job, before any apt activity. Also bump apt cache key
to v2 since the previous v1 was always empty.
Wait time on the Wine + Inno Setup step should drop from ~1m20s to
~10s on warm runs (debs already in /var/cache/apt/archives, dpkg
just installs them locally).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stash the full claude.ai/design output (12 JSX variants — brutalist,
classic, cli, compact, fluent-live, glass, hero-live, minimal,
sketches, studio, wizard-live, workshop — plus shared hooks and a
standalone HTML preview) for reference when we get to the Wails
frontend in Phase 6/7.
Source archive: C:\Users\root\Downloads\app(1).zip (~1MB).
Not wired into any build target yet — current GUI is the temporary
MessageBox stub. Pulling these in is the goal of the Wails phase.
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>
Pay ~10s per job to install Node so actions/cache@v4 works, then
restore Go module cache (~/go/pkg/mod + ~/.cache/go-build) and apt
package cache (/var/cache/apt/archives + /var/lib/apt/lists).
Expected impact:
- Cold run (first push after this commit): same as before, plus ~10s
Node install. Cache populates.
- Warm runs: Go modules instant instead of ~15s download, apt install
~10s instead of ~60s. Net save ~60s per run.
Cache keys:
- Go: go-${runner.os}-${hashFiles('**/go.sum')} — invalidates on any
go.sum change.
- Apt: apt-trixie-wine-innoextract-v1 — bump version (-v2, -v3) when
the package list in 'Install Wine + Inno Setup' step changes.
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>
Inno Setup 6.2.2 (pinned because innoextract 1.9 in Debian trixie
can't parse 6.3+ headers) doesn't recognise the 'x64compatible'
keyword. Use the legacy 'x64' which works across 6.0-6.7.
Failure: 'Error on line 25 in installer.iss: Value of [Setup]
section directive ArchitecturesAllowed is invalid' (run #11).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wine bailed with 'init_peb ... experimental wow64 mode / failed to
load syswow64/ntdll.dll' on 'wine ISCC.exe'. wine32:i386 ships the
needed 32-bit Wine internals; we already had dpkg --add-architecture
i386 from earlier (carried over).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
xvfb-run wraps wine in a fake X display, but it shells out to xauth
to set up the cookie. xauth isn't part of the xvfb package on
Debian trixie. Add it explicitly.
Failure: 'xvfb-run: error: xauth command not found' (run #7, task 8).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
innoextract 1.11/1.10/1.9 in Debian trixie chokes on 6.4.3 too — actual line where innoextract added 6.4 support requires upstream HEAD. 6.2.2 is the last version reliably parsed by innoextract 1.9+ which is what trixie ships.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause of the earlier "Failed to authenticate" / "could not read
Username" failures: shell scripts in Gitea Actions don't automatically
inherit secrets — \${GITHUB_TOKEN} expanded to an empty string, so the
URL became "https://forgejo-runner:@..." (empty password) and Gitea's
auth layer rejected it.
Fix: explicit env: block on the Checkout step pulls the token in,
then the URL uses it via x-access-token (canonical token-as-password
username, accepted by Gitea, GitHub, Forgejo alike).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both 'forgejo-runner:$TOKEN' and 'x-access-token:$TOKEN' formulas
are rejected by Gitea's act_runner with HTTP 401:
remote: Failed to authenticate user
fatal: Authentication failed
For public repos the simplest fix is: don't send credentials at all.
Plain https://host/owner/repo.git clones unauthenticated and Gitea
serves it (root/drover-go is public).
If/when we move to private repos this'll need a different approach
(GITEA_TOKEN env, oauth2 username, or .netrc) — but that's a future
problem.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hardcoded "forgejo-runner" username worked on Forgejo because its
runner accepted any user when the password is a valid GITHUB_TOKEN.
Gitea's act_runner v0.6+ rejects unknown usernames with:
remote: Failed to authenticate user
fatal: Authentication failed for 'https://git.okcu.io/.../...'
x-access-token is the canonical "the password IS the token" username
on GitHub Actions and works equally on Gitea, Forgejo and gitea.com.
Run that surfaced the issue: gitea run #1, task 1, sha 0f63f15,
"Cloning into '/tmp/src'... remote: Failed to authenticate user".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After Forgejo→Gitea migration, the new server only scans
.gitea/workflows/ (and .github/workflows/ as fallback) for action
definitions. .forgejo/workflows/ is Forgejo-specific and ignored.
Both files (build.yml, release.yml) move as-is — the YAML schema
itself is identical between Forgejo Actions and Gitea Actions
(both forks of GitHub Actions schema).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous attempt with 6.7.1 failed at the innoextract step:
Warning: Unexpected setup loader revision: 2
Warning: Setup loader checksum mismatch!
Could not determine setup data version!
Done with 1 error and 2 warnings.
innoextract 1.11 (Debian trixie's latest) only supports Inno Setup
through 6.4.x. 6.5+ requires innoextract 1.12 which hasn't shipped
yet. 6.4.3 is the last release in the 6.4 line — newer than 6.4.0
(more bugfixes), still innoextract-1.11-compatible.
Verified URL https://github.com/jrsoftware/issrc/releases/download/is-6_4_3/innosetup-6.4.3.exe
returns HTTP 200.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous attempt (rc1, rc2) tried to run innosetup-6.7.1.exe under
xvfb-run wine /VERYSILENT — silently failed (|| true masked it),
ISCC.exe never appeared at C:\InnoSetup\ISCC.exe.
Switch strategy: apt-get install innoextract (Debian package), use
it to unpack the Inno Setup installer's payload directly to disk,
then copy the extracted Inno Setup tree into the Wine prefix where
ISCC.exe can be invoked normally. Wine is still needed to run ISCC
itself, but no longer for the install step.
Also: ignore /.claude/ directory (local Claude Code session settings).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pinned URL https://files.jrsoftware.org/is/6/innosetup-6.4.0.exe
returns 404 — jrsoftware no longer redistributes versioned files there.
Switch to GitHub releases (jrsoftware/issrc), pinned to the latest
stable Inno Setup 6.7.1 (2026-02-17). Verified the URL resolves with
HTTP 200 from the runner network.
Caught by Task #8 (E2E smoke test): tag v0.1.0-rc1 / run #4 / task 717
failed at "Install Wine + Inno Setup" with "curl: (22) ... 404".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Triggered on `push` of any `v*` tag. Single `release` job on the `go`
runner produces and uploads four assets to the matching Forgejo release:
- drover-vX.Y.Z-windows-amd64.exe (cross-compiled portable binary)
- drover-vX.Y.Z-windows-amd64.zip (portable bundle + WinDivert + docs)
- drover-vX.Y.Z-setup.exe (Inno Setup installer via Wine)
- SHA256SUMS.txt (sha256 of the three above)
The asset names match what internal/updater/updater.go looks for, so
selfupdate keeps working on tagged releases.
Notes mirroring build.yml constraints:
- manual git clone instead of actions/checkout (no Node in golang
image; JS-based actions fail with `node: not found`)
- apt-get installs wine/wine32:i386/xvfb/zip/jq in-job
- Inno Setup 6.4.0 pinned, /VERYSILENT /CURRENTUSER install under Wine
- prerelease auto-detected from a hyphen in the version (rc/beta/alpha)
- curl uses -fsS so 409 (release exists) and other API errors fail loud
- secrets.GITHUB_TOKEN handles both clone and Forgejo REST API writes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The runner image (golang:*) ships without Node.js, and actions/cache@v4
plus actions/upload-artifact@v4 transitively need it for hashFiles +
artifact upload. Verified by CI failure on first push (run #1, task 710):
"OCI runtime exec failed ... 'node': executable file not found in $PATH".
For now we accept ~20-30s of go module download per run (small project,
fine). Real release artifacts go via release.yml (next task) which will
use curl + Forgejo REST API to upload — pure bash, no Node needed.
Drop the smoke-build artifact upload too — it was only for inspection
during dev. Add `./bin/drover --version` to the linux smoke build so we
get exit-code verification that the CLI bootstraps correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two jobs: 'test' runs go vet + go test -race + a Linux smoke build to
catch generic compile errors cheaply. 'build-windows' cross-compiles
drover.exe with version metadata baked in and uploads it as an artifact
with 14-day retention. CGO is disabled - divert-go integration in a
later phase will switch it on with mingw.
Manual git clone (no actions/checkout/setup-go) because the runner's
golang:1.23-bookworm image lacks Node.js. Both jobs cache GOMODCACHE
and GOCACHE keyed by go.sum hash. Concurrency cancel-in-progress keeps
the queue tidy when commits land in quick succession.
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>
Embedded files (Microsoft-signed kernel driver + user-mode loader):
- WinDivert.dll (x64, 47 KB)
- WinDivert64.sys (x64, 92 KB)
- WinDivert.lib (cgo import library)
- windivert.h (C header for bindings)
- LICENSE-LGPL (upstream LGPLv3/GPLv2 license)
Source: https://github.com/basil00/WinDivert/releases/tag/v2.2.2
Archive sha256: 63cb41763bb4b20f600b6de04e991a9c2be73279e317d4d82f237b150c5f3f15
The kernel driver is auto-installed by Windows when WinDivertOpen() is
first called from a process running as administrator. Per LGPLv3 §6 we
preserve the upstream license and ship a pointer to the source repo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without this, Windows clients commit CRLF and Linux CI runner can't
parse YAML/IS/shell scripts cleanly. Binaries (dll/sys/exe/zip/ico)
are explicitly marked so git doesn't try to text-normalize them
(critical for embedded WinDivert.sys in third_party/).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drover-Go is a Discord proxy tool that routes traffic through SOCKS5
via kernel-level packet capture (WinDivert), surviving Discord
auto-updates and bypassing the limitations of in-app proxy settings.
This commit lays out base project files:
- README with architecture overview and acknowledgements
- LICENSE MIT
- .gitignore for Go + Wails + IDE artifacts
- go.mod with module git.okcu.io/root/drover-go on Go 1.23
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>