Skip to content

Commit 0a916b5

Browse files
authored
fix: preserve symlinks during install and uninstall (#31)
## Summary - When `~/.local/bin/greyproxy` is a symlink (e.g. managed by mise), `greyproxy install` no longer replaces it with a copy of the binary. It detects the symlink, skips the binary copy, and only manages the service registration and CA certificate. - `greyproxy uninstall` also preserves the symlink instead of deleting it, since it is managed externally. - Clear messaging informs the user that the symlink was detected and the binary will not be touched. Closes #25
1 parent 8a9721e commit 0a916b5

File tree

1 file changed

+96
-9
lines changed

1 file changed

+96
-9
lines changed

cmd/greyproxy/install.go

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ func handleInstall(args []string) {
101101
binDst := installBinPath()
102102

103103
if isInstalled() {
104+
// If the destination is a symlink (e.g. managed by mise), skip
105+
// the binary copy and register the service using the symlink path.
106+
if isSymlink(binDst) {
107+
handleSymlinkInstall(binDst, force)
108+
return
109+
}
104110
handleReinstall(binSrc, binDst, force)
105111
return
106112
}
@@ -176,6 +182,74 @@ func handleBrewInstall(brewBin string, force bool) {
176182
fmt.Println("\nDashboard: http://localhost:43080")
177183
}
178184

185+
// isSymlink reports whether path is a symbolic link.
186+
func isSymlink(path string) bool {
187+
fi, err := os.Lstat(path)
188+
if err != nil {
189+
return false
190+
}
191+
return fi.Mode()&os.ModeSymlink != 0
192+
}
193+
194+
func handleSymlinkInstall(binDst string, force bool) {
195+
target, _ := os.Readlink(binDst)
196+
label := serviceLabel()
197+
fmt.Printf("Symlink detected at %s -> %s\n", binDst, target)
198+
fmt.Printf("\nThe binary will not be replaced. This will:\n")
199+
200+
step := 1
201+
fmt.Printf(" %d. Stop the running service\n", step)
202+
step++
203+
fmt.Printf(" %d. Remove the current service registration\n", step)
204+
step++
205+
fmt.Printf(" %d. Re-register the %s\n", step, label)
206+
step++
207+
if _, err := os.Stat(filepath.Join(greyproxyDataHome(), "ca-cert.pem")); os.IsNotExist(err) {
208+
fmt.Printf(" %d. Generate and trust CA certificate (requires sudo)\n", step)
209+
step++
210+
} else if !isCertInstalled() {
211+
fmt.Printf(" %d. Install CA certificate into OS trust store (requires sudo)\n", step)
212+
step++
213+
}
214+
fmt.Printf(" %d. Start the service\n", step)
215+
216+
if !force {
217+
fmt.Printf("\nProceed? [Y/n] ")
218+
if !askConfirm() {
219+
fmt.Println("You can start the server manually with: greyproxy serve")
220+
fmt.Println("Dashboard: http://localhost:43080")
221+
return
222+
}
223+
}
224+
225+
s, err := newServiceControl()
226+
if err != nil {
227+
fmt.Fprintf(os.Stderr, "error: %v\n", err)
228+
os.Exit(1)
229+
}
230+
231+
_ = service.Control(s, "stop")
232+
fmt.Println("Service stopped")
233+
234+
_ = service.Control(s, "uninstall")
235+
fmt.Println("Removed old service registration")
236+
237+
if err := service.Control(s, "install"); err != nil {
238+
fmt.Fprintf(os.Stderr, "error: registering service: %v\n", err)
239+
os.Exit(1)
240+
}
241+
fmt.Printf("Registered %s\n", label)
242+
243+
ensureCert()
244+
245+
if err := service.Control(s, "start"); err != nil {
246+
fmt.Fprintf(os.Stderr, "error: starting service: %v\n", err)
247+
os.Exit(1)
248+
}
249+
fmt.Println("Service started")
250+
fmt.Println("\nDashboard: http://localhost:43080")
251+
}
252+
179253
func handleReinstall(binSrc, binDst string, force bool) {
180254
label := serviceLabel()
181255
fmt.Printf("An existing installation was found at %s\n", binDst)
@@ -338,13 +412,22 @@ func handleUninstall(args []string) {
338412
label := serviceLabel()
339413

340414
certInstalled := isCertInstalled()
415+
symlink := isSymlink(binDst)
341416

417+
step := 1
342418
fmt.Printf("Ready to uninstall greyproxy. This will:\n")
343-
fmt.Printf(" 1. Stop the greyproxy service\n")
344-
fmt.Printf(" 2. Remove the %s\n", label)
345-
fmt.Printf(" 3. Remove %s\n", binDst)
419+
fmt.Printf(" %d. Stop the greyproxy service\n", step)
420+
step++
421+
fmt.Printf(" %d. Remove the %s\n", step, label)
422+
step++
423+
if symlink {
424+
fmt.Printf(" (Skipping binary removal: %s is a symlink)\n", binDst)
425+
} else {
426+
fmt.Printf(" %d. Remove %s\n", step, binDst)
427+
step++
428+
}
346429
if certInstalled {
347-
fmt.Printf(" 4. Remove CA certificate from system trust store\n")
430+
fmt.Printf(" %d. Remove CA certificate from system trust store\n", step)
348431
}
349432

350433
if !force {
@@ -371,12 +454,16 @@ func handleUninstall(args []string) {
371454
}
372455
fmt.Printf("Removed %s\n", label)
373456

374-
// 3. Remove binary
375-
if err := os.Remove(binDst); err != nil && !os.IsNotExist(err) {
376-
fmt.Fprintf(os.Stderr, "error: removing binary: %v\n", err)
377-
os.Exit(1)
457+
// 3. Remove binary (skip if symlink -- managed externally)
458+
if !symlink {
459+
if err := os.Remove(binDst); err != nil && !os.IsNotExist(err) {
460+
fmt.Fprintf(os.Stderr, "error: removing binary: %v\n", err)
461+
os.Exit(1)
462+
}
463+
fmt.Printf("Removed %s\n", binDst)
464+
} else {
465+
fmt.Printf("Symlink %s left intact\n", binDst)
378466
}
379-
fmt.Printf("Removed %s\n", binDst)
380467

381468
// 4. Remove CA certificate from trust store
382469
if certInstalled {

0 commit comments

Comments
 (0)