Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions cli/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,12 @@ weekly schedule to catch GH-API / release-asset drift.
## Self-update (`orva upgrade`)

Uses `github.com/creativeprojects/go-selfupdate`. The library queries
GitHub for the latest release matching `Filters: ["^orva-cli-"]`,
downloads the right OS/arch asset, verifies against `checksums.txt`,
and atomically replaces the running binary via rename-and-hide on
Windows / unlink-and-replace on Unix.
GitHub for the latest release, downloads the OS/arch asset matching
`Filters: ["^orva-cli-<os>-<arch>"]` (pinned to the exact platform token
by `upgradeAssetFilter` — a loose `^orva-cli-` filter let it fall back to
arch-only matching and pick a wrong-OS binary → intermittent "exec format
error"), verifies against `checksums.txt`, and atomically replaces the
running binary via rename-and-hide on Windows / unlink-and-replace on Unix.

If the install path is not writable, `orva upgrade` exits non-zero with
a "re-run with `sudo orva upgrade`" hint. Never silently elevates.
18 changes: 18 additions & 0 deletions cli/commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,24 @@ func TestKeysCreateDefaultPermission(t *testing.T) {
}
}

// TestUpgradeAssetFilterPinsOSArch guards the upgrade asset matcher: it must
// anchor to the exact orva-cli-<os>-<arch> token so go-selfupdate can't pick a
// wrong-OS binary that merely shares the arch (the "exec format error" bug).
func TestUpgradeAssetFilterPinsOSArch(t *testing.T) {
cases := []struct {
goos, goarch, want string
}{
{"linux", "amd64", "^orva-cli-linux-amd64"},
{"darwin", "arm64", "^orva-cli-darwin-arm64"},
{"windows", "amd64", "^orva-cli-windows-amd64"},
}
for _, c := range cases {
if got := upgradeAssetFilter(c.goos, c.goarch); got != c.want {
t.Errorf("upgradeAssetFilter(%q,%q) = %q, want %q", c.goos, c.goarch, got, c.want)
}
}
}

// TestNewRootSetsVersion confirms the version template is wired up so
// `orva --version` returns the value of commands.Version (set by main()).
func TestNewRootSetsVersion(t *testing.T) {
Expand Down
16 changes: 15 additions & 1 deletion cli/commands/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ import (
// orvaRepo is the GitHub repo to query for releases. Overridable for tests.
var orvaRepo = "Harsh-2002/Orva"

// upgradeAssetFilter pins go-selfupdate's asset match to the exact
// orva-cli-<os>-<arch> release artifact for the running platform.
//
// A loose "^orva-cli-" filter let go-selfupdate fall back to matching on
// arch alone, so on a linux/amd64 host it could pick orva-cli-darwin-amd64
// (a Mach-O binary) whenever that asset happened to sort first — releases
// upload the build matrix in parallel, so asset order is non-deterministic.
// The result was an intermittent "exec format error" after a "successful"
// upgrade. Anchoring to the full os-arch token removes the ambiguity; the
// trailing .exe on Windows assets is still matched by the prefix.
func upgradeAssetFilter(goos, goarch string) string {
return fmt.Sprintf("^orva-cli-%s-%s", goos, goarch)
}

var upgradeCmd = &cobra.Command{
Use: "upgrade",
Short: "Upgrade orva to the latest GitHub release",
Expand Down Expand Up @@ -53,7 +67,7 @@ func runUpgrade(cmd *cobra.Command, _ []string) error {
},
OS: runtime.GOOS,
Arch: runtime.GOARCH,
Filters: []string{"^orva-cli-"},
Filters: []string{upgradeAssetFilter(runtime.GOOS, runtime.GOARCH)},
})
if err != nil {
return fmt.Errorf("init updater: %w", err)
Expand Down
Loading