Skip to content

Commit 46a52ec

Browse files
committed
Add self-update command (sear update)
1 parent 642411a commit 46a52ec

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

main.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package main
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"io"
7+
"net/http"
68
"os"
79
"os/exec"
810
"os/signal"
911
"path/filepath"
12+
"runtime"
1013
"strings"
1114
"syscall"
1215
"time"
@@ -65,6 +68,8 @@ func main() {
6568
case "decrypt":
6669
requireCmd("age")
6770
cmdDecrypt(os.Args[2:])
71+
case "update":
72+
cmdUpdate()
6873
case "version", "--version", "-V":
6974
fmt.Printf("sear %s\n", version)
7075
case "help", "--help", "-h":
@@ -933,6 +938,100 @@ func cmdVerify(args []string) {
933938
}
934939
}
935940

941+
// ── update ──────────────────────────────────────────────────────────
942+
943+
func cmdUpdate() {
944+
const repo = "8ff/sear"
945+
946+
if version == "dev" {
947+
die("update not supported for dev builds (build with -ldflags to set version)")
948+
}
949+
950+
// Fetch latest release
951+
resp, err := http.Get("https://api.github.com/repos/" + repo + "/releases/latest")
952+
if err != nil {
953+
die("failed to check for updates: %v", err)
954+
}
955+
defer resp.Body.Close()
956+
957+
if resp.StatusCode != 200 {
958+
die("failed to check for updates: HTTP %d", resp.StatusCode)
959+
}
960+
961+
var release struct {
962+
TagName string `json:"tag_name"`
963+
}
964+
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
965+
die("failed to parse release info: %v", err)
966+
}
967+
968+
latest := release.TagName
969+
if latest == version || latest == "" {
970+
fmt.Printf("sear %s is already the latest version\n", version)
971+
return
972+
}
973+
974+
// Download new binary
975+
goos := runtime.GOOS
976+
goarch := runtime.GOARCH
977+
ext := ""
978+
if goos == "windows" {
979+
ext = ".exe"
980+
}
981+
binary := fmt.Sprintf("sear-%s-%s%s", goos, goarch, ext)
982+
url := fmt.Sprintf("https://github.com/%s/releases/download/%s/%s", repo, latest, binary)
983+
984+
fmt.Printf("Updating sear %s -> %s (%s/%s)\n", version, latest, goos, goarch)
985+
986+
dlResp, err := http.Get(url)
987+
if err != nil {
988+
die("failed to download update: %v", err)
989+
}
990+
defer dlResp.Body.Close()
991+
992+
if dlResp.StatusCode != 200 {
993+
die("failed to download update: HTTP %d", dlResp.StatusCode)
994+
}
995+
996+
// Write to temp file
997+
tmp, err := os.CreateTemp("", "sear-update-*")
998+
if err != nil {
999+
die("failed to create temp file: %v", err)
1000+
}
1001+
tmpPath := tmp.Name()
1002+
1003+
if _, err := io.Copy(tmp, dlResp.Body); err != nil {
1004+
tmp.Close()
1005+
os.Remove(tmpPath)
1006+
die("failed to download update: %v", err)
1007+
}
1008+
tmp.Close()
1009+
1010+
if err := os.Chmod(tmpPath, 0755); err != nil {
1011+
os.Remove(tmpPath)
1012+
die("failed to set permissions: %v", err)
1013+
}
1014+
1015+
// Replace current binary
1016+
self, err := os.Executable()
1017+
if err != nil {
1018+
os.Remove(tmpPath)
1019+
die("cannot locate current binary: %v", err)
1020+
}
1021+
self, err = filepath.EvalSymlinks(self)
1022+
if err != nil {
1023+
os.Remove(tmpPath)
1024+
die("cannot resolve binary path: %v", err)
1025+
}
1026+
1027+
if err := os.Rename(tmpPath, self); err != nil {
1028+
os.Remove(tmpPath)
1029+
die("failed to replace binary (try: sudo sear update): %v", err)
1030+
}
1031+
1032+
fmt.Printf("Done: sear %s\n", latest)
1033+
}
1034+
9361035
// ── helpers ─────────────────────────────────────────────────────────
9371036

9381037
// findLatestIdentityFile finds the most recently created age-yubikey-identity file
@@ -1157,6 +1256,9 @@ Key Management:
11571256
keygen Generate ed25519-sk signing key on YubiKey
11581257
age-keygen Generate age encryption key on YubiKey
11591258
1259+
Other:
1260+
update Update sear to the latest release
1261+
11601262
Run 'sear <command> -h' for command help.
11611263
Run 'sear help setup' for YubiKey getting started guide.
11621264
Run 'sear help yubikey' for YubiKey management cheatsheet.

0 commit comments

Comments
 (0)