diff --git a/cmd/update.go b/cmd/update.go index b72029c..7dbf41b 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -23,183 +23,30 @@ THE SOFTWARE. package cmd import ( - "archive/tar" - "compress/gzip" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "runtime" "strings" - "github.com/Masterminds/semver/v3" - "github.com/marianozunino/sdm-ui/internal/logger" - "github.com/minio/selfupdate" - "github.com/rs/zerolog/log" - "github.com/spf13/afero" + "github.com/marianozunino/selfupdater" + "github.com/spf13/cobra" ) -var updateCmd = &cobra.Command{ - Use: "update", - Short: "Update sdm-ui to the latest version", - Run: func(cmd *cobra.Command, args []string) { - logger.ConfigureLogger(confData.Verbose) - if err := runSelfUpdate(http.DefaultClient, afero.NewOsFs(), os.Args[0]); err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) - } - }, +func NewUpdateCmd() *cobra.Command { + updateCmd := &cobra.Command{ + Use: "update", + Short: "Update the smdui tool to the latest available version.", + Long: `Automatically update the smdui tool to the latest available version from the official source. +This command checks for updates, downloads the new version, and replaces the current executable with the updated one.`, + Example: `smdui update`, + Run: func(cmd *cobra.Command, args []string) { + currentVersionStr := strings.TrimPrefix(Version, "v") + updater := selfupdater.NewUpdater("marianozunino", "sdm-ui", "sdm-ui", currentVersionStr) + updater.Update() + }, + } + + return updateCmd } func init() { - rootCmd.AddCommand(updateCmd) -} - -func getAssetName() string { - os := runtime.GOOS - arch := runtime.GOARCH - - switch os { - case "darwin": - os = "Darwin" - case "linux": - os = "Linux" - } - - switch arch { - case "amd64": - arch = "x86_64" - case "386": - arch = "i386" - } - - return fmt.Sprintf("sdm-ui_%s_%s.tar.gz", os, arch) -} - -func runSelfUpdate(httpClient *http.Client, fs afero.Fs, executablePath string) error { - log.Info().Msg("Checking for updates...") - - releaseURL := "https://github.com/marianozunino/sdm-ui/releases/latest" - - resp, err := httpClient.Get(releaseURL) - if err != nil { - return fmt.Errorf("error checking for updates: %v", err) - } - defer resp.Body.Close() - - latestVersionStr := filepath.Base(resp.Request.URL.Path) - latestVersionStr = strings.TrimPrefix(latestVersionStr, "v") - currentVersionStr := strings.TrimPrefix(Version, "v") - - latestVersion, err := semver.NewVersion(latestVersionStr) - if err != nil { - return fmt.Errorf("error parsing latest version: %v", err) - } - - currentVersion, err := semver.NewVersion(currentVersionStr) - if err != nil { - return fmt.Errorf("error parsing current version: %v", err) - } - - if !latestVersion.GreaterThan(currentVersion) { - log.Info().Msg("Current version is the latest") - return nil - } - - log.Info().Msgf("New version available: %s (current: %s)", latestVersion, currentVersion) - - assetName := getAssetName() - downloadURL := fmt.Sprintf("https://github.com/marianozunino/sdm-ui/releases/download/v%s/%s", latestVersion, assetName) - - log.Debug().Msgf("Downloading %s from %s", assetName, downloadURL) - resp, err = httpClient.Get(downloadURL) - if err != nil { - return fmt.Errorf("error downloading update: %v", err) - } - defer resp.Body.Close() - - // Extract the tar.gz file - tmpDir, err := os.MkdirTemp("", "sdm-ui-update") - if err != nil { - return fmt.Errorf("error creating temp directory: %v", err) - } - defer os.RemoveAll(tmpDir) - - log.Info().Msg("Extracting update package...") - if err := extractTarGz(resp.Body, tmpDir); err != nil { - return fmt.Errorf("error extracting update: %v", err) - } - - // Locate the new binary in the extracted files - newBinaryPath := filepath.Join(tmpDir, "sdm-ui") - - // Apply the update - newBinary, err := os.Open(newBinaryPath) - if err != nil { - return fmt.Errorf("error opening new binary: %v", err) - } - defer newBinary.Close() - - log.Info().Msg("Applying update...") - err = selfupdate.Apply(newBinary, selfupdate.Options{}) - if err != nil { - if rerr := selfupdate.RollbackError(err); rerr != nil { - return fmt.Errorf("failed to rollback from bad update: %v", rerr) - } - return fmt.Errorf("error updating binary: %v", err) - } - - log.Info().Msgf("Successfully updated to version %s", latestVersion) - return nil -} - -// extractTarGz extracts a tar.gz file to a specified directory - -func extractTarGz(r io.Reader, dst string) error { - gz, err := gzip.NewReader(r) - if err != nil { - return fmt.Errorf("error creating gzip reader: %v", err) - } - defer gz.Close() - - tr := tar.NewReader(gz) - for { - header, err := tr.Next() - if err == io.EOF { - break // End of archive - } - if err != nil { - return fmt.Errorf("error reading tar header: %v", err) - } - - target := filepath.Join(dst, header.Name) - - // Ensure the directory exists - if header.Typeflag == tar.TypeDir { - if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil { - return fmt.Errorf("error creating directory %s: %v", target, err) - } - continue - } - - // Create the necessary directories - if err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil { - return fmt.Errorf("error creating directory %s: %v", filepath.Dir(target), err) - } - - // Create a new file - outFile, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY, os.FileMode(header.Mode)) - if err != nil { - return fmt.Errorf("error creating file %s: %v", target, err) - } - - // Write the file content - if _, err := io.Copy(outFile, tr); err != nil { - return fmt.Errorf("error writing to file %s: %v", target, err) - } - outFile.Close() - } - return nil + rootCmd.AddCommand(NewUpdateCmd()) } diff --git a/go.mod b/go.mod index 6134456..b7daa52 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/marianozunino/sdm-ui -go 1.21.6 +go 1.23.0 require ( git.sr.ht/~marianozunino/go-rofi v0.3.0 github.com/Masterminds/semver/v3 v3.3.0 github.com/adrg/xdg v0.5.0 github.com/jarcoal/httpmock v1.3.1 - github.com/minio/selfupdate v0.6.0 + github.com/marianozunino/selfupdater v1.0.1 github.com/rs/zerolog v1.33.0 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/spf13/afero v1.11.0 @@ -33,6 +33,8 @@ require ( github.com/gdamore/encoding v1.0.0 // indirect github.com/gdamore/tcell/v2 v2.6.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/go-github/v66 v66.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josephspurrier/goversioninfo v1.4.0 // indirect @@ -42,6 +44,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/minio/selfupdate v0.6.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nsf/termbox-go v1.1.1 // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect diff --git a/go.sum b/go.sum index 0704821..a422339 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,13 @@ github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v66 v66.0.0 h1:ADJsaXj9UotwdgK8/iFZtv7MLc8E8WBl62WLd/D/9+M= +github.com/google/go-github/v66 v66.0.0/go.mod h1:+4SO9Zkuyf8ytMj0csN1NR/5OTR+MfqPp8P8dVlcvY4= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -57,6 +62,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/marianozunino/selfupdater v1.0.1 h1:4eAQmEbsspsWadxX1tAtaAFkJZw/Fpu2Wem3OH8YJ4Q= +github.com/marianozunino/selfupdater v1.0.1/go.mod h1:p/bi7u0UXAni5HDX4J9N5Rb6/IeTWswnpFLoPW435SY= github.com/martinlindhe/notify v0.0.0-20181008203735-20632c9a275a h1:nQcAxLK581HrmqF0TVy2GC3iFjB8X+aWGtxQ/t2uyGE= github.com/martinlindhe/notify v0.0.0-20181008203735-20632c9a275a/go.mod h1:zL1p4SieQ27ZZ4V4KdVYdEcSkVl1OwNoi8xI1r5hJkc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -192,6 +199,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=