Summary
Users currently have no in-tool way to update snipemgr itself. This issue adds a self-upgrade subcommand that fetches the latest GitHub release, downloads the platform-appropriate asset, and replaces the current binary in-place.
Desired behaviour
$ snipemgr self-upgrade
Checking for updates...
Upgrading snipemgr from v1.3.0 to v1.4.0...
✓ snipemgr upgraded to v1.4.0
$ snipemgr self-upgrade # already current
Checking for updates...
Already up to date (v1.4.0).
Asset naming
Release assets follow the pattern already used by the release workflow:
| Platform |
Asset name |
| macOS arm64 |
snipemgr-darwin-arm64 |
| macOS amd64 |
snipemgr-darwin-amd64 |
| Linux amd64 |
snipemgr-linux-amd64 |
| Linux arm64 |
snipemgr-linux-arm64 |
| Windows amd64 |
snipemgr-windows-amd64.exe |
Constructed at runtime as snipemgr-{runtime.GOOS}-{runtime.GOARCH} (+ .exe on Windows).
Implementation notes
New file: cmd/self_upgrade.go
- Call
installer.FetchLatestRelease(selfupdate.Owner, selfupdate.Repo, token) (existing function, 30-second timeout — acceptable for an explicitly invoked command)
- Compare
rel.TagName against rootCmd.Version with registry.CompareVersions; exit early if already current
- Locate the matching asset in
rel.Assets; return a clear error if not found (e.g. unsupported platform)
os.Executable() to get the current binary path
- Write new binary to a temp file in the same directory as the current binary (
os.CreateTemp(dir, "snipemgr-*.tmp"))
os.Chmod(tmp, 0755)
os.Rename(tmp, exePath) — atomic on Linux/macOS (same filesystem); defer os.Remove(tmp) as a safety net in case rename fails
- Update the update-check cache with the new version and current timestamp
- No
--dry-run support needed — this is an explicit, user-initiated action
Download helper (inline in cmd/self_upgrade.go)
A small downloadToTemp(url, dir, token string) (string, error) that:
http.NewRequest + optional Authorization header
http.Client{Timeout: 5 * time.Minute} (binary can be several MB)
os.CreateTemp(dir, "snipemgr-*.tmp") + io.Copy
This is intentionally not shared with internal/installer to avoid coupling the self-upgrade path to the integration installer's internals.
Edge cases
| Case |
Behaviour |
Binary is in a system path (/usr/local/bin) |
os.Rename will fail with a permission error; print "replacing binary (permission denied? try sudo)" |
| No matching asset for current platform |
Clear error: "no asset found for darwin/arm64 (expected snipemgr-darwin-arm64)" |
Dev build (version == "dev") |
Upgrade proceeds normally — CompareVersions("dev", "1.4.0") treats "dev" as 0.0.0 so it will always attempt an upgrade. This is intentional for testing. |
| Windows |
os.Rename cannot replace a running .exe; the rename will fail with a clear OS error. No special handling needed for v1 — the error message is self-explanatory. |
| Network failure during download |
Temp file is removed by the deferred os.Remove; binary is untouched |
Acceptance criteria
Related
Depends on #44 for the update-check cache path constant (selfupdate.DefaultCachePath).
Summary
Users currently have no in-tool way to update snipemgr itself. This issue adds a
self-upgradesubcommand that fetches the latest GitHub release, downloads the platform-appropriate asset, and replaces the current binary in-place.Desired behaviour
registry.github_tokenfromsnipemgr.yamlif present (avoids unauthenticated rate limits)~/.snipemgr/update-check.json) so the nudge from feat: passive update nudge — notify when a newer snipemgr release is available #44 is suppressed on the next runAsset naming
Release assets follow the pattern already used by the release workflow:
snipemgr-darwin-arm64snipemgr-darwin-amd64snipemgr-linux-amd64snipemgr-linux-arm64snipemgr-windows-amd64.exeConstructed at runtime as
snipemgr-{runtime.GOOS}-{runtime.GOARCH}(+.exeon Windows).Implementation notes
New file:
cmd/self_upgrade.goinstaller.FetchLatestRelease(selfupdate.Owner, selfupdate.Repo, token)(existing function, 30-second timeout — acceptable for an explicitly invoked command)rel.TagNameagainstrootCmd.Versionwithregistry.CompareVersions; exit early if already currentrel.Assets; return a clear error if not found (e.g. unsupported platform)os.Executable()to get the current binary pathos.CreateTemp(dir, "snipemgr-*.tmp"))os.Chmod(tmp, 0755)os.Rename(tmp, exePath)— atomic on Linux/macOS (same filesystem); deferos.Remove(tmp)as a safety net in case rename fails--dry-runsupport needed — this is an explicit, user-initiated actionDownload helper (inline in
cmd/self_upgrade.go)A small
downloadToTemp(url, dir, token string) (string, error)that:http.NewRequest+ optionalAuthorizationheaderhttp.Client{Timeout: 5 * time.Minute}(binary can be several MB)os.CreateTemp(dir, "snipemgr-*.tmp")+io.CopyThis is intentionally not shared with
internal/installerto avoid coupling the self-upgrade path to the integration installer's internals.Edge cases
/usr/local/bin)os.Renamewill fail with a permission error; print"replacing binary (permission denied? try sudo)""no asset found for darwin/arm64 (expected snipemgr-darwin-arm64)"version == "dev")CompareVersions("dev", "1.4.0")treats "dev" as 0.0.0 so it will always attempt an upgrade. This is intentional for testing.os.Renamecannot replace a running.exe; the rename will fail with a clear OS error. No special handling needed for v1 — the error message is self-explanatory.os.Remove; binary is untouchedAcceptance criteria
snipemgr self-upgradedownloads and replaces the binary when a newer version is availablesnipemgr --versionshowing the new versionsudo)Related
Depends on #44 for the update-check cache path constant (
selfupdate.DefaultCachePath).