Skip to content

Commit 0252087

Browse files
committed
feat: enhance install script and upgrade command
1 parent eadd1b3 commit 0252087

File tree

3 files changed

+174
-31
lines changed

3 files changed

+174
-31
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ installs to `~/.local/bin` if available, otherwise `/usr/local/bin`. override wi
5050
curl -sSL https://raw.githubusercontent.com/karol-broda/snitch/master/install.sh | INSTALL_DIR=~/bin sh
5151
```
5252

53+
> **macos:** the install script automatically removes the quarantine attribute (`com.apple.quarantine`) from the binary to allow it to run without gatekeeper warnings. to disable this, set `KEEP_QUARANTINE=1`.
54+
5355
### binary
5456

5557
download from [releases](https://github.com/karol-broda/snitch/releases):
@@ -144,6 +146,16 @@ snitch watch -i 1s | jq '.count'
144146
snitch watch -l -i 500ms
145147
```
146148

149+
### `snitch upgrade`
150+
151+
check for updates and upgrade in-place.
152+
153+
```bash
154+
snitch upgrade # check for updates
155+
snitch upgrade --yes # upgrade automatically
156+
snitch upgrade -v 0.1.7 # install specific version
157+
```
158+
147159
## filters
148160

149161
shortcut flags work on all commands:

cmd/upgrade.go

Lines changed: 156 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,38 @@ import (
1010
"os"
1111
"path/filepath"
1212
"runtime"
13+
"strconv"
1314
"strings"
1415

16+
"github.com/fatih/color"
1517
"github.com/spf13/cobra"
1618
)
1719

1820
const (
1921
repoOwner = "karol-broda"
2022
repoName = "snitch"
2123
githubAPI = "https://api.github.com"
24+
firstUpgradeVersion = "0.1.8"
2225
)
2326

24-
var upgradeYes bool
27+
var (
28+
upgradeYes bool
29+
upgradeVersion string
30+
)
2531

2632
var upgradeCmd = &cobra.Command{
2733
Use: "upgrade",
2834
Short: "Check for updates and optionally upgrade snitch",
2935
Long: `Check for available updates and show upgrade instructions.
3036
31-
Use --yes to perform an in-place upgrade automatically.`,
37+
Use --yes to perform an in-place upgrade automatically.
38+
Use --version to install a specific version.`,
3239
RunE: runUpgrade,
3340
}
3441

3542
func init() {
3643
upgradeCmd.Flags().BoolVarP(&upgradeYes, "yes", "y", false, "Perform the upgrade automatically")
44+
upgradeCmd.Flags().StringVarP(&upgradeVersion, "version", "v", "", "Install a specific version (e.g., v0.1.7)")
3745
rootCmd.AddCommand(upgradeCmd)
3846
}
3947

@@ -44,6 +52,11 @@ type githubRelease struct {
4452

4553
func runUpgrade(cmd *cobra.Command, args []string) error {
4654
current := Version
55+
56+
if upgradeVersion != "" {
57+
return handleSpecificVersion(current, upgradeVersion)
58+
}
59+
4760
latest, err := fetchLatestVersion()
4861
if err != nil {
4962
return fmt.Errorf("failed to check for updates: %w", err)
@@ -52,35 +65,113 @@ func runUpgrade(cmd *cobra.Command, args []string) error {
5265
currentClean := strings.TrimPrefix(current, "v")
5366
latestClean := strings.TrimPrefix(latest, "v")
5467

55-
fmt.Printf("current: %s\n", current)
56-
fmt.Printf("latest: %s\n", latest)
57-
fmt.Println()
68+
printVersionComparison(current, latest)
5869

5970
if currentClean == latestClean {
60-
fmt.Println("you are running the latest version")
71+
green := color.New(color.FgGreen)
72+
green.Println("✓ you are running the latest version")
6173
return nil
6274
}
6375

6476
if current == "dev" {
65-
fmt.Println("you are running a development build")
77+
yellow := color.New(color.FgYellow)
78+
yellow.Println("⚠ you are running a development build")
79+
fmt.Println()
6680
fmt.Println("use one of the methods below to install a release version:")
81+
fmt.Println()
6782
printUpgradeInstructions()
6883
return nil
6984
}
7085

71-
fmt.Printf("update available: %s -> %s\n", current, latest)
86+
green := color.New(color.FgGreen, color.Bold)
87+
green.Printf("✓ update available: %s → %s\n", current, latest)
7288
fmt.Println()
7389

7490
if !upgradeYes {
7591
printUpgradeInstructions()
7692
fmt.Println()
77-
fmt.Println("or run 'snitch upgrade --yes' to upgrade in-place")
93+
faint := color.New(color.Faint)
94+
cmdStyle := color.New(color.FgCyan)
95+
faint.Print(" in-place ")
96+
cmdStyle.Println("snitch upgrade --yes")
7897
return nil
7998
}
8099

81100
return performUpgrade(latest)
82101
}
83102

103+
func handleSpecificVersion(current, target string) error {
104+
if !strings.HasPrefix(target, "v") {
105+
target = "v" + target
106+
}
107+
targetClean := strings.TrimPrefix(target, "v")
108+
109+
printVersionComparisonTarget(current, target)
110+
111+
if isVersionLower(targetClean, firstUpgradeVersion) {
112+
yellow := color.New(color.FgYellow)
113+
yellow.Printf("⚠ warning: the upgrade command was introduced in v%s\n", firstUpgradeVersion)
114+
faint := color.New(color.Faint)
115+
faint.Printf(" version %s does not include this command\n", target)
116+
faint.Println(" you will need to use other methods to upgrade from that version")
117+
fmt.Println()
118+
}
119+
120+
currentClean := strings.TrimPrefix(current, "v")
121+
if currentClean == targetClean {
122+
green := color.New(color.FgGreen)
123+
green.Println("✓ you are already running this version")
124+
return nil
125+
}
126+
127+
if !upgradeYes {
128+
faint := color.New(color.Faint)
129+
cmdStyle := color.New(color.FgCyan)
130+
if isVersionLower(targetClean, currentClean) {
131+
yellow := color.New(color.FgYellow)
132+
yellow.Printf("↓ this will downgrade from %s to %s\n", current, target)
133+
} else {
134+
green := color.New(color.FgGreen)
135+
green.Printf("↑ this will upgrade from %s to %s\n", current, target)
136+
}
137+
fmt.Println()
138+
faint.Print("run ")
139+
cmdStyle.Printf("snitch upgrade --version %s --yes", target)
140+
faint.Println(" to proceed")
141+
return nil
142+
}
143+
144+
return performUpgrade(target)
145+
}
146+
147+
func isVersionLower(v1, v2 string) bool {
148+
parts1 := parseVersion(v1)
149+
parts2 := parseVersion(v2)
150+
151+
for i := 0; i < 3; i++ {
152+
if parts1[i] < parts2[i] {
153+
return true
154+
}
155+
if parts1[i] > parts2[i] {
156+
return false
157+
}
158+
}
159+
return false
160+
}
161+
162+
func parseVersion(v string) [3]int {
163+
var parts [3]int
164+
segments := strings.Split(v, ".")
165+
166+
for i := 0; i < len(segments) && i < 3; i++ {
167+
n, err := strconv.Atoi(segments[i])
168+
if err == nil {
169+
parts[i] = n
170+
}
171+
}
172+
return parts
173+
}
174+
84175
func fetchLatestVersion() (string, error) {
85176
url := fmt.Sprintf("%s/repos/%s/%s/releases/latest", githubAPI, repoOwner, repoName)
86177

@@ -106,20 +197,47 @@ func fetchLatestVersion() (string, error) {
106197
return release.TagName, nil
107198
}
108199

109-
func printUpgradeInstructions() {
110-
fmt.Println("upgrade options:")
111-
fmt.Println()
112-
fmt.Println(" go install:")
113-
fmt.Printf(" go install github.com/%s/%s@latest\n", repoOwner, repoName)
200+
func printVersionComparison(current, latest string) {
201+
faint := color.New(color.Faint)
202+
version := color.New(color.FgCyan)
203+
204+
faint.Print("current ")
205+
version.Println(current)
206+
faint.Print("latest ")
207+
version.Println(latest)
114208
fmt.Println()
115-
fmt.Println(" shell script:")
116-
fmt.Printf(" curl -sSL https://raw.githubusercontent.com/%s/%s/master/install.sh | sh\n", repoOwner, repoName)
209+
}
210+
211+
func printVersionComparisonTarget(current, target string) {
212+
faint := color.New(color.Faint)
213+
version := color.New(color.FgCyan)
214+
215+
faint.Print("current ")
216+
version.Println(current)
217+
faint.Print("target ")
218+
version.Println(target)
117219
fmt.Println()
118-
fmt.Println(" arch linux (aur):")
119-
fmt.Println(" yay -S snitch-bin")
220+
}
221+
222+
func printUpgradeInstructions() {
223+
bold := color.New(color.Bold)
224+
faint := color.New(color.Faint)
225+
cmd := color.New(color.FgCyan)
226+
227+
bold.Println("upgrade options:")
120228
fmt.Println()
121-
fmt.Println(" nix:")
122-
fmt.Printf(" nix profile upgrade --inputs-from github:%s/%s\n", repoOwner, repoName)
229+
230+
faint.Print(" go install ")
231+
cmd.Printf("go install github.com/%s/%s@latest\n", repoOwner, repoName)
232+
233+
faint.Print(" shell script ")
234+
cmd.Printf("curl -sSL https://raw.githubusercontent.com/%s/%s/master/install.sh | sh\n", repoOwner, repoName)
235+
236+
faint.Print(" arch (aur) ")
237+
cmd.Println("yay -S snitch-bin")
238+
239+
faint.Print(" nix ")
240+
cmd.Printf("nix profile upgrade --inputs-from github:%s/%s\n", repoOwner, repoName)
123241
}
124242

125243
func performUpgrade(version string) error {
@@ -141,7 +259,11 @@ func performUpgrade(version string) error {
141259
downloadURL := fmt.Sprintf("https://github.com/%s/%s/releases/download/%s/%s",
142260
repoOwner, repoName, version, archiveName)
143261

144-
fmt.Printf("downloading %s...\n", archiveName)
262+
faint := color.New(color.Faint)
263+
cyan := color.New(color.FgCyan)
264+
faint.Print("↓ downloading ")
265+
cyan.Printf("%s", archiveName)
266+
faint.Println("...")
145267

146268
resp, err := http.Get(downloadURL)
147269
if err != nil {
@@ -167,13 +289,17 @@ func performUpgrade(version string) error {
167289
// check if we can write to the target location
168290
targetDir := filepath.Dir(execPath)
169291
if !isWritable(targetDir) {
170-
fmt.Printf("elevated permissions required to install to %s\n", targetDir)
292+
yellow := color.New(color.FgYellow)
293+
cmdStyle := color.New(color.FgCyan)
294+
295+
yellow.Printf("⚠ elevated permissions required to install to %s\n", targetDir)
171296
fmt.Println()
172-
fmt.Println("run with sudo or install to a user-writable location:")
173-
fmt.Printf(" sudo snitch upgrade --yes\n")
297+
faint.Println("run with sudo or install to a user-writable location:")
174298
fmt.Println()
175-
fmt.Println("or use the install script with a custom directory:")
176-
fmt.Printf(" curl -sSL https://raw.githubusercontent.com/%s/%s/master/install.sh | INSTALL_DIR=~/.local/bin sh\n",
299+
faint.Print(" sudo ")
300+
cmdStyle.Println("sudo snitch upgrade --yes")
301+
faint.Print(" custom dir ")
302+
cmdStyle.Printf("curl -sSL https://raw.githubusercontent.com/%s/%s/master/install.sh | INSTALL_DIR=~/.local/bin sh\n",
177303
repoOwner, repoName)
178304
return nil
179305
}
@@ -198,10 +324,12 @@ func performUpgrade(version string) error {
198324

199325
if err := os.Remove(backupPath); err != nil {
200326
// non-fatal, just warn
201-
fmt.Fprintf(os.Stderr, "warning: failed to remove backup file %s: %v\n", backupPath, err)
327+
yellow := color.New(color.FgYellow)
328+
yellow.Fprintf(os.Stderr, "⚠ warning: failed to remove backup file %s: %v\n", backupPath, err)
202329
}
203330

204-
fmt.Printf("successfully upgraded to %s\n", version)
331+
green := color.New(color.FgGreen, color.Bold)
332+
green.Printf("✓ successfully upgraded to %s\n", version)
205333
return nil
206334
}
207335

install.sh

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ BINARY_NAME="snitch"
66

77
# allow override via environment
88
INSTALL_DIR="${INSTALL_DIR:-}"
9+
KEEP_QUARANTINE="${KEEP_QUARANTINE:-}"
910

1011
detect_install_dir() {
1112
if [ -n "$INSTALL_DIR" ]; then
@@ -86,9 +87,11 @@ main() {
8687
exit 1
8788
fi
8889

89-
# remove macos quarantine attribute
90-
if [ "$os" = "darwin" ]; then
91-
xattr -d com.apple.quarantine "${tmp_dir}/${BINARY_NAME}" 2>/dev/null || true
90+
# remove macos quarantine attribute unless disabled
91+
if [ "$os" = "darwin" ] && [ -z "$KEEP_QUARANTINE" ]; then
92+
if xattr -d com.apple.quarantine "${tmp_dir}/${BINARY_NAME}" 2>/dev/null; then
93+
echo "warning: removed macOS quarantine attribute from binary"
94+
fi
9295
fi
9396

9497
# install binary

0 commit comments

Comments
 (0)