Skip to content

Commit

Permalink
feat: self update
Browse files Browse the repository at this point in the history
  • Loading branch information
marianozunino committed Oct 4, 2024
1 parent 6752f75 commit 294b9ce
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 1 deletion.
168 changes: 168 additions & 0 deletions cmd/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package cmd

import (
"archive/tar"
"bytes"
"compress/gzip"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"

update "github.com/fynelabs/selfupdate"
"github.com/spf13/cobra"
)

var updateCmd = &cobra.Command{
Use: "update",
Short: "Update rop to the latest version",
Long: `Update rop to the latest version either using go install or by downloading the binary from GitHub.`,
Run: runUpdate,
}

func init() {
rootCmd.AddCommand(updateCmd)
}

func runUpdate(cmd *cobra.Command, args []string) {
fmt.Println("Checking for updates...")

// Check if the binary was installed using go install
if installedWithGo() {
fmt.Println("Updating using go install...")
if err := updateWithGoInstall(); err != nil {
fmt.Printf("Error updating with go install: %v\n", err)
os.Exit(1)
}
} else {
fmt.Println("Updating by downloading the latest release...")
if err := updateFromGitHub(); err != nil {
fmt.Printf("Error updating from GitHub: %v\n", err)
os.Exit(1)
}
}

fmt.Println("Update completed successfully!")
}

func installedWithGo() bool {
// Check if the binary is in GOPATH
gopath := os.Getenv("GOPATH")
if gopath == "" {
gopath = filepath.Join(os.Getenv("HOME"), "go")
}
binPath := filepath.Join(gopath, "bin", "rop")
fmt.Printf("Checking if %s exists...\n", binPath)
_, err := os.Stat(binPath)
return err == nil
}

func updateWithGoInstall() error {
cmd := exec.Command("go", "install", "github.com/marianozunino/rop@latest")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

func updateFromGitHub() error {
// Construct the URL for the latest release
// Platform with first letter capitalized
platform := strings.ToUpper(runtime.GOOS[:1]) + runtime.GOOS[1:]

arch := ""
switch runtime.GOARCH {
case "amd64":
arch = "x86_64"
case "arm64":
arch = "arm64"
case "386":
arch = "i386"
default:
return fmt.Errorf("unsupported architecture: %s", runtime.GOARCH)
}

url := fmt.Sprintf("https://github.com/marianozunino/rop/releases/latest/download/rop_%s_%s.tar.gz", platform, arch)
fmt.Printf("URL: %s\n", url)

// Download the file
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("error downloading update: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}

// Create a temporary file to store the downloaded content
tmpfile, err := os.CreateTemp("", "rop_update_*.tar.gz")
if err != nil {
return fmt.Errorf("can't create temporary file: %v", err)
}
defer os.Remove(tmpfile.Name())

// Write the body to file
_, err = io.Copy(tmpfile, resp.Body)
if err != nil {
return fmt.Errorf("error writing to temporary file: %v", err)
}

// Extract the binary from the tar.gz file
binary, err := extractBinary(tmpfile.Name())
if err != nil {
return fmt.Errorf("error extracting binary: %v", err)
}
fmt.Printf("Extracted binary, size: %d bytes\n", len(binary))

// Apply the update
err = update.Apply(bytes.NewReader(binary), update.Options{})
if err != nil {
return fmt.Errorf("error applying update: %v", err)
}

return nil
}

func extractBinary(archivePath string) ([]byte, error) {
// Open the tar.gz file
f, err := os.Open(archivePath)
if err != nil {
return nil, err
}
defer f.Close()

gzr, err := gzip.NewReader(f)
if err != nil {
return nil, err
}
defer gzr.Close()

tr := tar.NewReader(gzr)

// Find the binary in the archive
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}

if strings.HasSuffix(header.Name, "rop") {
// Read the entire contents of the file
binary, err := io.ReadAll(tr)
if err != nil {
return nil, err
}
return binary, nil
}
}

return nil, fmt.Errorf("binary not found in the archive")
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ go 1.23.0

require (
github.com/charmbracelet/huh v0.6.0
github.com/charmbracelet/lipgloss v0.13.0
github.com/fynelabs/selfupdate v0.2.0
github.com/spf13/cobra v1.8.1
k8s.io/api v0.31.1
k8s.io/apimachinery v0.31.1
Expand All @@ -16,7 +18,6 @@ require (
github.com/catppuccin/go v0.2.0 // indirect
github.com/charmbracelet/bubbles v0.20.0 // indirect
github.com/charmbracelet/bubbletea v1.1.1 // indirect
github.com/charmbracelet/lipgloss v0.13.0 // indirect
github.com/charmbracelet/x/ansi v0.2.3 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/fynelabs/selfupdate v0.2.0 h1:IDqwgV7BYj4lCcoD8hHvIapVGmS5ifWrc0sQTWh1eFw=
github.com/fynelabs/selfupdate v0.2.0/go.mod h1:rCdliRnLw+koUanA+lrqub9wWlNc2wPDTsyRC6A+vfc=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
Expand Down

0 comments on commit 294b9ce

Please sign in to comment.