update_apply_unix.go

 1//go:build !windows
 2
 3package update
 4
 5import (
 6	"fmt"
 7	"os"
 8	"path/filepath"
 9)
10
11// Apply replaces the current executable with the downloaded binary.
12func Apply(binaryPath string) error {
13	// Get path to current executable.
14	exe, err := os.Executable()
15	if err != nil {
16		return fmt.Errorf("failed to get executable path: %w", err)
17	}
18
19	// Resolve symlinks.
20	exe, err = filepath.EvalSymlinks(exe)
21	if err != nil {
22		return fmt.Errorf("failed to resolve symlinks: %w", err)
23	}
24
25	// Get the directory of the executable.
26	exeDir := filepath.Dir(exe)
27
28	// Check if we have write permissions to the directory.
29	if err := checkWritePermission(exeDir); err != nil {
30		return fmt.Errorf("cannot write to %s: %w (you may need to run with elevated privileges)", exeDir, err)
31	}
32
33	// Copy binary to exe directory first to ensure same filesystem.
34	// os.Rename fails across filesystems, and binaryPath may be in /tmp.
35	localBinary := filepath.Join(exeDir, ".crush-update-new")
36	if err := copyFile(binaryPath, localBinary); err != nil {
37		return fmt.Errorf("failed to copy new binary: %w", err)
38	}
39
40	// Create a backup of the current executable.
41	backupPath := filepath.Join(exeDir, filepath.Base(exe)+".old")
42	if err := os.Rename(exe, backupPath); err != nil {
43		os.Remove(localBinary)
44		return fmt.Errorf("failed to backup current executable: %w", err)
45	}
46
47	// Move new binary to executable location.
48	if err := os.Rename(localBinary, exe); err != nil {
49		// Try to restore backup on failure.
50		_ = os.Rename(backupPath, exe)
51		os.Remove(localBinary)
52		return fmt.Errorf("failed to install new version: %w", err)
53	}
54
55	// Remove backup on success.
56	_ = os.Remove(backupPath)
57
58	return nil
59}
60
61// HasPendingUpdate returns false on Unix since updates are applied immediately.
62func HasPendingUpdate() bool {
63	return false
64}
65
66// ApplyPendingUpdate is a no-op on Unix since updates are applied immediately.
67func ApplyPendingUpdate() error {
68	return nil
69}