63 lines
1.6 KiB
Go
63 lines
1.6 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"syscall"
|
|
)
|
|
|
|
// selfUpdate downloads a new binary, verifies its checksum, replaces the current binary, and re-execs.
|
|
func selfUpdate(downloadURL, expectedChecksum string) error {
|
|
slog.Info("downloading update", "url", downloadURL)
|
|
|
|
resp, err := http.Get(downloadURL)
|
|
if err != nil {
|
|
return fmt.Errorf("download: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("download failed: status %d", resp.StatusCode)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("read body: %w", err)
|
|
}
|
|
slog.Info("downloaded update", "bytes", len(body))
|
|
|
|
// Verify SHA256 checksum
|
|
if expectedChecksum != "" {
|
|
actual := fmt.Sprintf("%x", sha256.Sum256(body))
|
|
if actual != expectedChecksum {
|
|
return fmt.Errorf("checksum mismatch: expected %s, got %s", expectedChecksum, actual)
|
|
}
|
|
slog.Info("checksum verified")
|
|
}
|
|
|
|
// Write to temp file next to current binary
|
|
currentExe, err := os.Executable()
|
|
if err != nil {
|
|
return fmt.Errorf("get executable path: %w", err)
|
|
}
|
|
tempPath := currentExe + ".update"
|
|
|
|
if err := os.WriteFile(tempPath, body, 0755); err != nil {
|
|
return fmt.Errorf("write temp: %w", err)
|
|
}
|
|
|
|
// Replace current binary
|
|
if err := os.Rename(tempPath, currentExe); err != nil {
|
|
os.Remove(tempPath)
|
|
return fmt.Errorf("rename: %w", err)
|
|
}
|
|
slog.Info("binary replaced", "path", currentExe)
|
|
|
|
// Re-exec with same arguments
|
|
slog.Info("re-executing", "args", os.Args)
|
|
return syscall.Exec(currentExe, os.Args, os.Environ())
|
|
}
|