Skip to content

feat(release): ship the switch binary to Linux installs, as gvproxy-min - #994

Merged
norrietaylor merged 1 commit into
mainfrom
feat/gvproxy-min-rename
Jul 28, 2026
Merged

feat(release): ship the switch binary to Linux installs, as gvproxy-min#994
norrietaylor merged 1 commit into
mainfrom
feat/gvproxy-min-rename

Conversation

@norrietaylor

@norrietaylor norrietaylor commented Jul 28, 2026

Copy link
Copy Markdown
Member

Split out of #988 to shrink its review surface. Independent of the rest of that PR.

Two halves of one story: the switch binary is unusable on installs — on macOS because of a PATH collision, on Linux because it is absent entirely.

Linux: own-ip is dead on every installed host

The native (DM2) own-ip datapath is implemented (#542) and proven by netns_root_integration.rs — but minimald resolves a switch binary that no Linux install ships, so the spawn fails on a path that was never populated. CI only passes because it fetches gvproxy itself and sets GVPROXY_BIN.

The release already builds pin-verified gvproxy-linux-{amd64,arm64}; they were never mapped to install components. This adds the two rows. The installer is manifest-driven and needs no change.

macOS: the name collides

The bin prefix is ~/.local/bin, which is on PATH, and podman/crc/Docker Desktop all ship their own gvproxy there. Under the upstream name whichever was installed last wins a lookup — in either direction, so we can shadow theirs as easily as they shadow ours.

The binary therefore installs as gvproxy-min. The bytes are stock gvproxy, still pinned and SHA-256-verified by fetch-gvproxy.sh; only the installed name changes, and the release artifacts keep gvproxy-*.

A second bug fixed on the way past

switch::installed_gvproxy_bin() becomes the single definition of where an install puts that binary — in the crate whose charter is already "one definition rather than drifting copies".

Doing that surfaced an existing defect: minimald only ever consulted the fixed system path, so even a user-local install was invisible to it. minvmd already had that tier; it now delegates to the shared definition instead of keeping its own copy.

Migration

The renamed component is a new manifest row, so the old bin/gvproxy stops appearing in future manifests and the uninstall record walk would never revisit it — it would sit on PATH forever, which is the collision this change exists to remove.

install.sh reverses the rename from the prior record, on the same bytes-still-ours terms uninstall uses (R7.3): removed only when its SHA-256 still equals what we recorded writing. Three rules keep that safe:

  1. Skip when the manifest still ships gvproxy. Channels advance independently, so a post-rename installer will meet a pre-rename manifest. There the file on disk is the one this run just installed, and deleting it would leave the host with no switch binary at all, on every run. This is the sharp edge; its regression test is verified to fail without the guard.
  2. Report a refusal. A hash mismatch means the user replaced the file; it is kept and named.
  3. Retry a failure. If removal fails (read-only bin), the row is carried into this run's record so the next run retries — without it the migration gets one attempt, since the record it reads is replaced immediately after.

Specified as R6.1a in spec 07, which had no requirement covering a record-driven migration during install.

The system-wide path

/usr/lib/minimal/bin/gvproxy.../gvproxy-min too, for one name everywhere. But that directory is not on PATH, so the collision rationale never applied there, and an operator who provisioned it has no install record to migrate. The resolver still honours the pre-rename path when only that exists.

What is deliberately not here

minvmd, libkrun, and the guest payload. That was already the plan for splitting #988; gominimal/pkgs#533 has since proven minvmd can ship as a single static-musl binary (builds, boots a VM, passes the session e2e, verified against a stub control). Shipping the dynamic-glibc version now would put ~29 MB of libraries, RUNPATH rewriting, a bin/lib sibling constraint and a new glibc floor onto users' disks — then delete all of it days later.

Verification

  • just ci green; lint-shell clean; shellcheck clean under 0.10.0 as well as 0.11 (CI runs an older build that flags SC2015 where 0.11 no longer does).
  • Installer harness: 221 cases (4 new), under sh, dash, and macOS sh.
  • switch unit tests cover both system-path branches deterministically via an internal resolve_installed(bin_dir, system, legacy), so they never probe the real /usr/lib/minimal/bin — otherwise they would flip on precisely the pre-rename hosts the legacy branch serves.
  • stage-release.sh --dry-run shows all three rows landing at bin/gvproxy-min.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change renames the installed gvproxy binary to gvproxy-min, centralizes path resolution in switch, updates runtime consumers and diagnostics, and adds safe installer migration for existing gvproxy installations.

Changes

gvproxy-min rollout

Layer / File(s) Summary
Centralized installed-path resolution
crates/switch/...
Adds shared gvproxy-min constants and resolution across user-local, legacy system, and current system paths, with environment-safe tests.
Runtime consumer integration
crates/minimald/..., crates/minvmd/..., crates/minimal/src/diag/..., docs/reference/...
Updates daemon and VM path fallbacks, preserves explicit overrides, documents resolution behavior, and recognizes gvproxy-min in diagnostics.
Installer rename migration
scripts/install.sh, scripts/install_test.sh, scripts/stage-release.sh, docs/specs/07-spec-installer/...
Installs gvproxy-min and safely removes prior gvproxy files only when recorded hashes match, with migration tests and specification updates.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Installer
  participant InstallRecord
  participant Filesystem
  participant Manifest
  Installer->>Manifest: Read current component set
  Installer->>InstallRecord: Read prior gvproxy entry
  Installer->>Filesystem: Hash and inspect bin/gvproxy
  Filesystem-->>Installer: File state and SHA-256
  Installer->>Filesystem: Remove unchanged renamed file
  Installer->>InstallRecord: Persist migrated record
Loading

Possibly related PRs

Suggested reviewers: 0chroma

Poem

I’m a rabbit with a renamed little trail,
gvproxy-min now follows the scale.
Old paths are checked by hash and light,
Safe files vanish, changed ones stay right.
The switch finds home, local or wide—
And diagnostics hop along beside.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly states the main change: renaming the installed gvproxy switch binary to gvproxy-min.
Description check ✅ Passed The description is detailed and covers summary, testing, and checklist content, though the headings differ from the template.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

The installer's `bin` prefix is `~/.local/bin`, which is on PATH, and
podman/crc/Docker Desktop all ship their own `gvproxy` there. Under the
upstream name whichever was installed last wins a PATH lookup — in
either direction — so we take a minimal-specific name. The bytes are
stock gvproxy, still pinned and SHA-256-verified by fetch-gvproxy.sh;
only the installed name changes, and the release artifacts keep theirs.

`switch::installed_gvproxy_bin()` becomes the one definition of where an
install puts that binary, in the crate whose charter is already "one
definition rather than drifting copies". This fixes a real bug on the
way past: minimald only ever looked at the fixed system path, so a
user-local install was invisible to it and own-IP could not find a
switch it had. minvmd already had that tier and now delegates to the
shared one.

The system-wide fallback moves too, but that directory is not on PATH,
so the collision rationale never applied there — and an operator who
provisioned the old path has no install record for us to migrate. The
resolver therefore still honours `/usr/lib/minimal/bin/gvproxy` when
only that exists.

install.sh reverses the rename from the prior record, on the same
bytes-still-ours terms uninstall uses: the old file is removed only when
its hash still matches what we recorded writing, so a user-replaced
binary is kept and reported. Three rules keep that safe, all covered by
the harness and specified as R6.1a:

- skip entirely when the manifest THIS run installed still ships a
  `gvproxy` component. Channels advance independently, so a post-rename
  installer will be pointed at a pre-rename manifest, where the file on
  disk is the one we just placed — deleting it would leave the host with
  no switch binary at all, on every run.
- report a refusal rather than deleting a modified file.
- carry the row forward when removal fails, so the next run retries
  instead of stranding the binary the rename exists to remove.

Split out of #988 to keep that PR to release packaging; it depends on
none of it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@norrietaylor
norrietaylor force-pushed the feat/gvproxy-min-rename branch from 9d5bb52 to 30fa133 Compare July 28, 2026 16:08

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/specs/07-spec-installer/07-spec-installer.md`:
- Around line 353-356: In the “Retry a failure” text, replace “afterwards” with
the US-English spelling “afterward”; leave the rest of the migration behavior
description unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2581f4ab-bf86-4145-aeb1-ff513359e141

📥 Commits

Reviewing files that changed from the base of the PR and between 264f1d5 and 30fa133.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • crates/minimal/src/diag/mod.rs
  • crates/minimald/src/main.rs
  • crates/minimald/src/server.rs
  • crates/minvmd/src/image.rs
  • crates/switch/Cargo.toml
  • crates/switch/src/lib.rs
  • docs/reference/cli-minimald.md
  • docs/specs/07-spec-installer/07-spec-installer.md
  • scripts/install.sh
  • scripts/install_test.sh
  • scripts/stage-release.sh

Comment on lines +353 to +356
- **Retry a failure.** If removal fails (a read-only or root-owned `bin`), the
row is carried into this run's record so the next run tries again — without
it the migration gets exactly one attempt, because the record it reads is
replaced immediately afterwards.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Minor US-locale wording nit.

"afterwards" → "afterward" for American English consistency.

✏️ Proposed fix
-  it, because the record it reads is   replaced immediately afterwards.
+  it, because the record it reads is   replaced immediately afterward.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- **Retry a failure.** If removal fails (a read-only or root-owned `bin`), the
row is carried into this run's record so the next run tries again — without
it the migration gets exactly one attempt, because the record it reads is
replaced immediately afterwards.
- **Retry a failure.** If removal fails (a read-only or root-owned `bin`), the
row is carried into this run's record so the next run tries again — without
it the migration gets exactly one attempt, because the record it reads is
replaced immediately afterward.
🧰 Tools
🪛 LanguageTool

[locale-violation] ~356-~356: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...cord it reads is replaced immediately afterwards. R6.2, If the resolved bin direc...

(AFTERWARDS_US)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/specs/07-spec-installer/07-spec-installer.md` around lines 353 - 356, In
the “Retry a failure” text, replace “afterwards” with the US-English spelling
“afterward”; leave the rest of the migration behavior description unchanged.

Source: Linters/SAST tools

@norrietaylor
norrietaylor enabled auto-merge (squash) July 28, 2026 16:50
@norrietaylor
norrietaylor merged commit 6b30427 into main Jul 28, 2026
31 checks passed
@norrietaylor
norrietaylor deleted the feat/gvproxy-min-rename branch July 28, 2026 16:58
@norrietaylor norrietaylor changed the title fix(switch): install the gvproxy switch as gvproxy-min feat(release): ship the switch binary to Linux installs, as gvproxy-min Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants