Files
ipswap/README.md
T
bsncubed 23dcfb393f Scaffold ipswap: tray-driven Windows IP preset switcher
Implements the design in claude.md as a building skeleton: pure Go, no cgo,
cross-compiles to a single Windows .exe from Linux.

Architecture follows the spec's deliberate split. The fast path is native —
tray icon, grouped submenus, a Win32 MessageBox showing a live before/after
diff, then netsh. The slow path is an embedded web editor served on a random
loopback port and opened in the default browser.

Two decisions worth recording:

Reads use GetAdaptersAddresses, writes use netsh. The spec left the
enumeration mechanism open; parsing `netsh show config` breaks on a
non-English Windows because the output is localised. DNS static-vs-DHCP
origin is not exposed by that API, so it comes from one registry read.

The netsh command plan is built in portable code. That puts the delete-every-
existing-address step — the one that stops secondary addresses leaking across
switches — under test without needing a Windows box.

The editor requires the session token in a header for mutations, not just the
cookie: any page in the browser can make it POST to 127.0.0.1 with the cookie
attached, but it cannot set a header. The updater refuses to install a release
that publishes no SHA256.

Not yet done: no group picker for export (the API supports it), no
single-instance guard, and internal/server/web/app.css is reconstructed from
the description in claude.md rather than the canonical apointless.css.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 13:29:35 +10:00

126 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
Set an update repository in Settings (`https://host/owner/repo`) and ipswap
checks its Gitea releases API at startup, in a goroutine, behind a short
timeout, failing silently to the log — a laptop on a customer site usually
cannot reach the host, and that is not an error worth showing.
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.