Files
bsncubed f267c430d9 Add installer, launch-to-browser, and switching from the web GUI
Three features plus the design-system swap.

Installer. Inno-compile.iss builds a per-user installer into %APPDATA%\ipswap
with PrivilegesRequired=lowest, so it never shows a UAC prompt. That matches
the app's asInvoker manifest, and it is also what lets the in-app updater
replace the .exe later without elevation — a Program Files install could not.
It offers a desktop shortcut and an optional start-with-Windows entry, and
reuses the app's own single-instance mutex as AppMutex so setup notices a
running copy, since a running .exe cannot be overwritten. Uninstall leaves
presets.json, config.json and the log in place.

Launching. Opening ipswap opens the editor in a browser, starting the tray
first if it is not already up; if it is, the running copy opens the browser and
the second process exits. internal/instance does this with a loopback control
listener whose port and token live in a mode-600 session.json, plus a
session-local named mutex on Windows to settle a launch race. A stale session
file from a crash is detected by a failed call and treated as "no primary", so
it can never wedge startup. The Run-key entry now passes --background, because
a browser tab at every login is not wanted.

Switching from the browser. New endpoints for active state, apply preview and
apply. The browser confirms against the same before/after text the tray's
MessageBox shows, built from a live read at prompt time. Apply requires the
session token in a header like every other mutation — it changes the machine's
network, so it is not a weaker case than editing a preset. A netsh refusal for
lack of elevation comes back as 409 with a flag, so the page can point at
"Relaunch as administrator" instead of showing a generic failure.

To avoid two copies of that sequence, internal/switcher now owns everything
between "the user said yes" and "the adapter changed", and both front ends call
it. It serialises applies: two interleaved netsh sequences on one adapter would
leave it matching neither preset, and there are now two ways to start one.

CSS. apointless.css is vendored from bsncubed/css and left untouched; the
previous file was the boarding-pass stylesheet and its class names did not
match this markup. ipswap.css adds only what the design system does not ship —
page chrome, tables, modals, stat tiles, a success alert — on its tokens, so
re-pulling apointless.css restyles the app.

Not verified: the installer is uncompiled (no Inno Setup or wine on this box)
and the UI is not visually rendered (headless Firefox hangs here). Both are
checked as far as the tooling allows — assets and endpoints serve, and every
DOM id the JS touches exists in the markup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 14:32:15 +10:00

86 lines
3.5 KiB
Makefile

VERSION ?= 0.1.0
# -H windowsgui suppresses the console window that would otherwise sit behind
# the tray icon. -s -w strips the symbol table and DWARF; nothing here needs a
# Go stack trace off a customer's laptop, and it roughly halves the binary.
LDFLAGS := -s -w -H windowsgui -X main.version=$(VERSION)
# Go is installed at ~/.local/go on this box and deliberately kept off PATH, so
# prefer that toolchain and fall back to whatever `go` is in PATH elsewhere.
GO ?= $(shell test -x $(HOME)/.local/go/bin/go && echo $(HOME)/.local/go/bin/go || echo go)
# Pinned and run through `go run` so CI needs no separate install step.
GOVERSIONINFO ?= $(GO) run github.com/josephspurrier/goversioninfo/cmd/goversioninfo@v1.4.1
SYSO := cmd/ipswap/resource_windows.syso
.PHONY: build syso test vet check dist sums installer clean help
help: ## List targets
@grep -hE '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN{FS=":.*?## "}{printf " %-14s %s\n", $$1, $$2}'
# ipswap is pure Go with no cgo anywhere, which is the whole reason the tray is
# fyne.io/systray and the dialogs are raw user32 rather than a GUI toolkit:
# `GOOS=windows go build` cross-compiles straight from Linux, no Wine, no
# mingw, no fyne-cross.
build: syso ## Cross-compile bin/ipswap.exe from any host
@mkdir -p bin
GOOS=windows GOARCH=amd64 $(GO) build -trimpath -ldflags "$(LDFLAGS)" -o bin/ipswap.exe ./cmd/ipswap
# The .syso carries the icon, the version resource and — the part that matters
# — the manifest that asks for asInvoker and per-monitor DPI. A plain `go
# build` with no .syso produces a binary with the generic executable icon and
# the default (unaware, asInvoker-by-omission) manifest.
#
# -64 is not optional: goversioninfo emits a 386 object by default, and linking
# that into an amd64 build fails with "unknown relocation type 7".
syso: ## Generate the Windows resource object
cd cmd/ipswap && $(GOVERSIONINFO) \
-64 \
-o resource_windows.syso \
-platform-specific=false \
versioninfo.json
test: ## Run tests
$(GO) test ./...
vet: ## Run go vet for both targets
$(GO) vet ./...
GOOS=windows GOARCH=amd64 $(GO) vet ./...
# The Windows-only files (netsh, user32, the registry) are the bulk of the
# risk, and a Linux `go build` never looks at them. This is the cheap way to
# keep them compiling.
check: test vet ## Typecheck both targets and run tests
GOOS=windows GOARCH=amd64 $(GO) build ./...
dist: build sums ## Build the release artifacts into dist/
@echo "dist/ contains:" && ls -1 dist
# Inno Setup is a Windows tool. On Windows use the ISCC on PATH; on Linux it
# runs perfectly well under wine, which is why this looks for both. Note that
# the installer is the ONLY part of the build that wants wine — the .exe itself
# still cross-compiles natively, so CI never needs it.
ISCC ?= $(shell command -v iscc 2>/dev/null || command -v ISCC 2>/dev/null)
installer: build ## Compile Output/ipswap-$(VERSION)-setup.exe (needs Inno Setup 6)
ifeq ($(ISCC),)
@echo "Inno Setup not found."; \
echo " Windows: install Inno Setup 6 and put ISCC.exe on PATH"; \
echo " Linux: apt install wine && download innosetup-6.x.exe, then"; \
echo " make installer ISCC='wine $$HOME/.wine/drive_c/Program Files (x86)/Inno Setup 6/ISCC.exe'"; \
exit 1
else
$(ISCC) /DAppVersion=$(VERSION) Inno-compile.iss
@echo "built Output/ipswap-$(VERSION)-setup.exe"
endif
sums: ## Write dist/SHA256SUMS for the built binary
@mkdir -p dist
@cp bin/ipswap.exe dist/ipswap.exe
cd dist && sha256sum ipswap.exe > SHA256SUMS
@cat dist/SHA256SUMS
clean: ## Remove build artifacts
rm -rf bin dist Output $(SYSO)