# ipswap A tray-resident Windows utility for switching a network adapter between saved static-IP configurations. Built for field and support work, where a laptop hops between customer subnets — broadcast control networks, media networks, management VLANs — many times a day. Switching a preset is two clicks and no browser. Editing fifty presets happens in a real UI. Those are different problems, so they use different interfaces. ## How it works **Fast path (native, no browser).** The tray icon holds a menu of presets grouped into submenus. Clicking one reads the adapter's live configuration, shows a Win32 message box with a before/after diff, and on Yes runs the netsh sequence. The tray tooltip then names the preset that is actually applied. **Slow path (browser).** "Manage presets…" starts a local HTTP server on a random loopback port and opens your default browser to it. Full CRUD, adapter picker, import/export, settings. The server shuts itself down five minutes after the browser stops sending heartbeats, and on exit. ## Building Pure Go, no cgo anywhere, so a Linux or macOS host cross-compiles the Windows binary directly — no Wine, no mingw, no fyne-cross. ``` make build # -> bin/ipswap.exe make check # tests + vet + typecheck for both targets make dist # bin/ipswap.exe plus dist/SHA256SUMS ``` `make build` regenerates `cmd/ipswap/resource_windows.syso` first. That object carries the icon, the version resource and the manifest — the manifest is the part that matters, because it is what asks for `asInvoker` and per-monitor DPI. `GOOS=windows go vet ./...` is worth running on its own during development: the Windows-only files (netsh, user32, the registry) are most of the risk and a plain Linux build never looks at them. `make vet` does both targets. ## Layout ``` cmd/ipswap/ entrypoint, manifest, version resource internal/preset/ data model, store, prefix parsing, import/export internal/netcfg/ adapter reads (Win32) and the netsh command plan internal/tray/ tray menu, confirm-then-apply, icon internal/server/ the local editor server and its embedded web app internal/updater/ Gitea release check, download, verify, swap internal/dialog/ Win32 MessageBoxW wrappers internal/elevate/ token elevation check, relaunch via ShellExecuteW runas internal/desktop/ open-in-browser, HKCU Run key internal/config/ paths and settings internal/applog/ rotating log ``` Two rules shape the split. Anything portable lives in portable code and is tested on any host — most usefully `netcfg.Plan`, which builds the exact netsh sequence and is covered without a Windows box. Anything Windows-specific has a `_windows.go` and an `_other.go`, so the whole tree stays buildable and vettable during development on Linux. ## Elevation The manifest asks for `asInvoker`, not `requireAdministrator`. On a machine with UAC relaxed, or when the parent process is already elevated, that means zero prompts — including at login, if start-with-Windows is on. Membership of the Administrators group is not the same as holding an elevated token: with UAC on, a member's process gets a filtered token and `netsh interface ipv4 set address` fails with "The requested operation requires elevation." So ipswap checks the token at startup, and if it is not elevated it adds a "Relaunch as administrator" item to the tray. A failed apply offers the same thing. Nothing blocks startup either way. ## Data ``` %APPDATA%\ipswap\presets.json presets %APPDATA%\ipswap\config.json settings %APPDATA%\ipswap\ipswap.log rotating, 5 × 1 MB ``` `presets.json` is the export format too, so a preset pack is just this file and stays hand-editable. See `examples/preset-pack.json`. Masks are stored as an integer prefix. Both `/24` and `255.255.255.0` are accepted on input, in the editor and in a hand-written file; the display style is a single global setting. ## Why applies are destructive After an apply, the adapter has exactly the addresses in the preset and nothing else. `netsh interface ipv4 set address … static` replaces the primary address but leaves previously-added secondary addresses attached. A naive implementation therefore leaks addresses across switches — after visiting three presets the adapter is still carrying secondaries from the first two. So every apply enumerates the live addresses, deletes each one, then sets the primary and adds the preset's secondaries. `internal/netcfg/plan_test.go` pins that ordering. Reads use `GetAdaptersAddresses`, not `netsh show config`: netsh's output is localised and parsing it breaks on a non-English Windows. ## Updates ipswap checks `https://gitea.apointless.space/bsncubed/ipswap` by default. The check runs at startup in a goroutine, behind a short timeout, and fails silently to the log — a laptop on a customer site usually cannot reach the host, and that is not an error worth showing. Point it elsewhere, or clear it to switch the check off entirely, in Settings (`update_repo` in `config.json`). Forks should change it: otherwise they will offer their users an upstream binary. A newer tag adds "Update available — vX.Y.Z" to the top of the tray menu. Clicking it downloads the `.exe`, verifies its SHA256 against the release, and hands off to a small batch helper that waits for ipswap to exit, swaps the binary and relaunches. **A release that publishes no checksum is refused** — an unverified binary that is about to be run is not worth the convenience. It never installs on its own. ## Not in v1 Revert / restore-previous / timed auto-revert; post-apply connectivity checks (the confirmation diff is the check); global hotkeys; IPv6; adapter matching by MAC address; per-preset scripts.