Skip to content

Commit d0a3b8e

Browse files
authored
fix: update binary migration to ~/.verda/bin (#18)
* fix: update old binary in-place during migration to ~/.verda/bin When the running binary is outside ~/.verda/bin/ (e.g. /usr/local/bin), the update command now also replaces the old binary so the user gets the new version immediately regardless of PATH order. Prints clear instructions to add ~/.verda/bin to PATH and remove the old binary. This handles the migration path from pre-migration releases where verda update installed in-place at the old location. * fix: detect package-manager installs and skip in-place binary update When the running binary is managed by Homebrew, apt, rpm, apk, or Scoop, don't overwrite it — advise the user to use their package manager instead. Only replace in-place for manual installations (curl, direct download to /usr/local/bin). * fix: upgrade verdagostack to v1.1.3 and add package release configs Upgrade verdagostack to v1.1.3 which fixes wizard back-navigation: - Esc in Loader-managed steps (SSH keys, storage, startup script) now goes back instead of cancelling the wizard - Back navigates to the immediate prior step instead of skipping all completed steps Add goreleaser configs for Homebrew tap, Scoop bucket, and Linux packages (deb/rpm/apk). * docs: add package install methods to README and rename release test Add Homebrew, Scoop, and Linux package (deb/rpm/apk) install sections to README. Fix OS image in the non-interactive example. Rename install-test.sh to test-release-packages.sh for clarity. * fix: configure HOMEBREW_TAP_TOKEN for brew/scoop release Add token config to goreleaser brews/scoops sections and pass HOMEBREW_TAP_TOKEN secret in the release workflow so goreleaser can push formula/manifest to the homebrew-tap repo.
1 parent 15ee7ff commit d0a3b8e

7 files changed

Lines changed: 197 additions & 10 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ jobs:
7474
args: release --clean --release-notes RELEASE_NOTES.md
7575
env:
7676
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
77+
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
7778

7879
- name: Generate binary checksums
7980
run: |

.goreleaser.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,65 @@ archives:
3232
checksum:
3333
name_template: verda_{{ .Version }}_SHA256SUMS
3434

35+
brews:
36+
- repository:
37+
owner: verda-cloud
38+
name: homebrew-tap
39+
token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
40+
directory: Formula
41+
homepage: https://github.com/verda-cloud/verda-cli
42+
description: Verda CLI
43+
license: Apache-2.0
44+
45+
scoops:
46+
- repository:
47+
owner: verda-cloud
48+
name: homebrew-tap
49+
token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
50+
directory: bucket
51+
homepage: https://github.com/verda-cloud/verda-cli
52+
description: Verda CLI
53+
license: Apache-2.0
54+
55+
nfpms:
56+
- id: verda
57+
package_name: verda
58+
homepage: https://github.com/verda-cloud/verda-cli
59+
maintainer: Verda Cloud <support@verda.cloud>
60+
description: Verda CLI
61+
license: Apache-2.0
62+
formats:
63+
- deb
64+
- rpm
65+
- apk
66+
bindir: /usr/bin
67+
68+
# aurs:
69+
# - name: verda-bin
70+
# homepage: https://github.com/verda-cloud/verda-cli
71+
# description: Verda CLI
72+
# maintainers:
73+
# - Verda Cloud <support@verda.cloud>
74+
# license: Apache-2.0
75+
# private_key: "{{ .Env.AUR_KEY }}"
76+
# git_url: ssh://aur@aur.archlinux.org/verda-bin.git
77+
# url_template: "https://github.com/verda-cloud/verda-cli/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
78+
79+
# winget:
80+
# - name: verda
81+
# publisher: verda-cloud
82+
# publisher_url: https://verda.cloud
83+
# short_description: Verda CLI
84+
# license: Apache-2.0
85+
# repository:
86+
# owner: verda-cloud
87+
# name: winget-pkgs
88+
# pull_request:
89+
# enabled: true
90+
# base:
91+
# owner: microsoft
92+
# name: winget-pkgs
93+
# branch: master
94+
3595
# Release notes are provided by git-cliff via --release-notes flag.
3696
# Do NOT set changelog.disable: true — it also ignores --release-notes.

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,34 @@ Install to a custom directory:
2020
VERDA_INSTALL_DIR=~/.local/bin curl -sSL https://raw.githubusercontent.com/verda-cloud/verda-cli/main/scripts/install.sh | sh
2121
```
2222

23+
### Homebrew (macOS / Linux)
24+
25+
```bash
26+
brew install verda-cloud/tap/verda
27+
```
28+
29+
### Scoop (Windows)
30+
31+
```powershell
32+
scoop bucket add verda https://github.com/verda-cloud/homebrew-tap
33+
scoop install verda
34+
```
35+
36+
### Linux packages (deb / rpm / apk)
37+
38+
Download `.deb`, `.rpm`, or `.apk` packages from [GitHub Releases](https://github.com/verda-cloud/verda-cli/releases):
39+
40+
```bash
41+
# Debian / Ubuntu
42+
sudo dpkg -i verda_VERSION_linux_amd64.deb
43+
44+
# RHEL / Fedora
45+
sudo rpm -i verda_VERSION_linux_amd64.rpm
46+
47+
# Alpine
48+
sudo apk add --allow-untrusted verda_VERSION_linux_amd64.apk
49+
```
50+
2351
### Manual download
2452

2553
Download the binary for your platform from [GitHub Releases](https://github.com/verda-cloud/verda-cli/releases):
@@ -79,7 +107,7 @@ verda vm create \
79107
--kind gpu \
80108
--instance-type 1V100.6V \
81109
--location FIN-01 \
82-
--os ubuntu-24.04-cuda-12.8-open-docker \
110+
--os ubuntu-24.04-cuda-13.0-open-docker \
83111
--os-volume-size 100 \
84112
--hostname gpu-runner
85113
```

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/spf13/pflag v1.0.10
1010
github.com/spf13/viper v1.21.0
1111
github.com/verda-cloud/verdacloud-sdk-go v1.4.2
12-
github.com/verda-cloud/verdagostack v1.1.2
12+
github.com/verda-cloud/verdagostack v1.1.3
1313
go.yaml.in/yaml/v3 v3.0.4
1414
)
1515

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
104104
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
105105
github.com/verda-cloud/verdacloud-sdk-go v1.4.2 h1:oVb8fHVQOY+YPuuMYMee9gYCkPTwAw01LmkqxM21T/Y=
106106
github.com/verda-cloud/verdacloud-sdk-go v1.4.2/go.mod h1:pmlpiCL9fTSikZ3qWLJPpHOG0E8PKkQVUX5s4Z+SktY=
107-
github.com/verda-cloud/verdagostack v1.1.2 h1:J/TL9CPDnbLuUgd5Lj0ppNmTG1iXuMq4L9gT+H5vwrI=
108-
github.com/verda-cloud/verdagostack v1.1.2/go.mod h1:9uKLNxvH7JRkHj2sxZc4TI5O/JbbveCrLkOkekoY/sA=
107+
github.com/verda-cloud/verdagostack v1.1.3 h1:aJy8ixF5KR3bDy5k5n1fpEHKgZxCIt/FAH+HAvNidSM=
108+
github.com/verda-cloud/verdagostack v1.1.3/go.mod h1:9uKLNxvH7JRkHj2sxZc4TI5O/JbbveCrLkOkekoY/sA=
109109
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
110110
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
111111
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=

internal/verda-cli/cmd/update/update.go

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,31 @@ func runUpdate(ctx context.Context, f cmdutil.Factory, ioStreams cmdutil.IOStrea
158158

159159
_, _ = fmt.Fprintf(ioStreams.Out, "Updated to %s\n", target)
160160

161-
// Warn if old binary exists in a system path.
161+
// Migrate: if the currently running binary is outside ~/.verda/bin/,
162+
// handle the old location based on how it was installed.
162163
oldExe, _ := resolveExecutable()
163164
if oldExe != "" && oldExe != dst {
164-
_, _ = fmt.Fprintf(ioStreams.ErrOut,
165-
"\nNote: old binary still exists at %s\n"+
166-
" Remove it to avoid conflicts: sudo rm %s\n"+
167-
" Ensure %s is in your PATH.\n",
168-
oldExe, oldExe, binDir)
165+
if isManagedByPackageManager(oldExe) {
166+
// Installed via Homebrew, apt, rpm, etc. — don't touch it.
167+
// Let the package manager handle upgrades for that path.
168+
_, _ = fmt.Fprintf(ioStreams.ErrOut,
169+
"\nNote: %s appears to be managed by a package manager.\n"+
170+
" Future updates via 'verda update' will install to %s.\n"+
171+
" Use your package manager to update or remove the old binary.\n",
172+
oldExe, dst)
173+
} else {
174+
// Manual install (curl, manual download) — safe to replace in-place.
175+
if err := replaceBinary(oldExe, binary); err != nil {
176+
_, _ = fmt.Fprintf(ioStreams.ErrOut,
177+
"\nWarning: could not update old binary at %s: %v\n", oldExe, err)
178+
}
179+
180+
_, _ = fmt.Fprintf(ioStreams.ErrOut,
181+
"\nNote: verda is now installed at %s\n"+
182+
" Add it to your PATH: export PATH=\"%s:$PATH\"\n"+
183+
" Then remove the old binary: sudo rm %s\n",
184+
dst, binDir, oldExe)
185+
}
169186
}
170187

171188
return nil
@@ -314,6 +331,28 @@ func readZipEntry(f *zip.File) ([]byte, error) {
314331
return io.ReadAll(rc)
315332
}
316333

334+
// isManagedByPackageManager returns true if the binary path looks like it was
335+
// installed by a package manager (Homebrew, apt/dpkg, rpm, apk, Scoop).
336+
func isManagedByPackageManager(exePath string) bool {
337+
managedPrefixes := []string{
338+
"/opt/homebrew/", // Homebrew (Apple Silicon)
339+
"/usr/local/Cellar/", // Homebrew (Intel Mac)
340+
"/home/linuxbrew/", // Homebrew (Linux)
341+
"/usr/bin/", // apt/dpkg, rpm, apk system packages
342+
"/snap/", // Snap packages
343+
}
344+
for _, prefix := range managedPrefixes {
345+
if strings.HasPrefix(exePath, prefix) {
346+
return true
347+
}
348+
}
349+
// Scoop on Windows: ~/scoop/apps/
350+
if runtime.GOOS == osWindows && strings.Contains(exePath, `\scoop\apps\`) {
351+
return true
352+
}
353+
return false
354+
}
355+
317356
// --- Binary replacement ---
318357

319358
func resolveExecutable() (string, error) {

test/test-release-packages.sh

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
DIST_DIR="${1:-dist}"
5+
PASS=0
6+
FAIL=0
7+
8+
run_test() {
9+
local name="$1"
10+
shift
11+
echo "=== Testing: ${name} ==="
12+
if "$@"; then
13+
echo "--- PASS: ${name}"
14+
((PASS++))
15+
else
16+
echo "--- FAIL: ${name}"
17+
((FAIL++))
18+
fi
19+
echo
20+
}
21+
22+
# Snapshot build (skip if dist already exists)
23+
if [ ! -d "${DIST_DIR}" ]; then
24+
echo "=== Building snapshot ==="
25+
goreleaser release --snapshot --clean
26+
echo
27+
fi
28+
29+
# deb (Ubuntu)
30+
run_test "deb/ubuntu" docker run --rm -v "${PWD}/${DIST_DIR}:/dist:ro" ubuntu:24.04 sh -c '
31+
dpkg -i /dist/verda_*_linux_amd64.deb && verda version
32+
'
33+
34+
# rpm (Fedora)
35+
run_test "rpm/fedora" docker run --rm -v "${PWD}/${DIST_DIR}:/dist:ro" fedora:41 sh -c '
36+
rpm -i /dist/verda_*_linux_amd64.rpm && verda version
37+
'
38+
39+
# apk (Alpine)
40+
run_test "apk/alpine" docker run --rm -v "${PWD}/${DIST_DIR}:/dist:ro" alpine:3.20 sh -c '
41+
apk add --allow-untrusted /dist/verda_*_linux_amd64.apk && verda version
42+
'
43+
44+
# tar.gz binary
45+
run_test "tar.gz/binary" docker run --rm -v "${PWD}/${DIST_DIR}:/dist:ro" ubuntu:24.04 sh -c '
46+
tar xzf /dist/verda_*_linux_amd64.tar.gz -C /usr/local/bin && verda version
47+
'
48+
49+
# Homebrew (only works after a real release)
50+
if [ "${TEST_HOMEBREW:-}" = "1" ]; then
51+
run_test "homebrew" docker run --rm homebrew/brew sh -c '
52+
brew tap verda-cloud/tap && brew install verda && verda version
53+
'
54+
fi
55+
56+
# Scoop (skip — no good container option for Windows)
57+
58+
echo "=== Results: ${PASS} passed, ${FAIL} failed ==="
59+
[ "${FAIL}" -eq 0 ]

0 commit comments

Comments
 (0)