diff --git a/internal/core/core.go b/internal/core/core.go index 24ce23c9..6fbb440b 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -35,6 +35,7 @@ import ( "github.com/bluenviron/mediamtx/internal/servers/rtsp" "github.com/bluenviron/mediamtx/internal/servers/srt" "github.com/bluenviron/mediamtx/internal/servers/webrtc" + "github.com/bluenviron/mediamtx/internal/upgrade" ) //go:generate go run ./versiongetter @@ -97,9 +98,10 @@ func getRTPMaxPayloadSize(udpMaxPayloadSize int, rtspEncryption conf.Encryption) } var cli struct { - Confpath string `arg:"" default:""` - Version bool `help:"print version"` - Upgrade bool `help:"upgrade executable to the latest version"` + Confpath string `arg:"" default:""` + Version bool `help:"print version"` + CheckVersion bool `help:"check whether a new version is available"` + Upgrade bool `help:"upgrade executable to the latest version"` } // Core is an instance of MediaMTX. @@ -159,9 +161,22 @@ func New(args []string) (*Core, bool) { os.Exit(0) } + if cli.CheckVersion { + var newVersionAvailable bool + newVersionAvailable, err = upgrade.CheckVersion(string(version), getArch()) + if err != nil { + fmt.Printf("ERR: %v\n", err) + os.Exit(1) + } + if newVersionAvailable { + os.Exit(2) + } + os.Exit(0) + } + if cli.Upgrade { - err = upgrade() //nolint:staticcheck - if err != nil { //nolint:staticcheck + err = upgrade.Upgrade(string(version), getArch()) + if err != nil { fmt.Printf("ERR: %v\n", err) os.Exit(1) } diff --git a/internal/core/upgrade_disabled.go b/internal/core/upgrade_disabled.go deleted file mode 100644 index c28e1a79..00000000 --- a/internal/core/upgrade_disabled.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !enable_upgrade - -package core - -import "fmt" - -func upgrade() error { - return fmt.Errorf("upgrade command is not available") -} diff --git a/internal/staticsources/rpicamera/camera_arm_.go b/internal/staticsources/rpicamera/camera_arm_.go index d0e86f24..780e5477 100644 --- a/internal/staticsources/rpicamera/camera_arm_.go +++ b/internal/staticsources/rpicamera/camera_arm_.go @@ -30,7 +30,6 @@ var ( dumpPath = "" ) - func multiplyAndDivide(v, m, d int64) int64 { secs := v / d dec := v % d diff --git a/internal/upgrade/check_version.go b/internal/upgrade/check_version.go new file mode 100644 index 00000000..2b905748 --- /dev/null +++ b/internal/upgrade/check_version.go @@ -0,0 +1,32 @@ +package upgrade + +import ( + "fmt" + + "github.com/Masterminds/semver/v3" +) + +// CheckVersion checks whether a new version is available. +// Returns true if a newer version is available. +func CheckVersion(version, _ string) (bool, error) { + if !currentRegexp.MatchString(version) { + return false, fmt.Errorf("current version (%v) is not official and cannot be checked", version) + } + + fmt.Println("getting latest version...") + + latest, err := latestRemoteVersion() + if err != nil { + return false, err + } + + current, _ := semver.NewVersion(version) + + if current.GreaterThanEqual(latest) { + fmt.Printf("current version (%v) is up to date\n", "v"+current.String()) + return false, nil + } + + fmt.Printf("a new version is available: %v (current: %v)\n", "v"+latest.String(), "v"+current.String()) + return true, nil +} diff --git a/internal/upgrade/extract_executable.go b/internal/upgrade/extract_executable.go new file mode 100644 index 00000000..bd0e45b0 --- /dev/null +++ b/internal/upgrade/extract_executable.go @@ -0,0 +1,43 @@ +//go:build !windows + +package upgrade + +import ( + "archive/tar" + "compress/gzip" + "errors" + "fmt" + "io" +) + +const extension = "tar.gz" + +func extractExecutable(r io.Reader) ([]byte, error) { + gzReader, err := gzip.NewReader(r) + if err != nil { + return nil, err + } + defer gzReader.Close() //nolint:errcheck + + tarReader := tar.NewReader(gzReader) + + for { + var header *tar.Header + header, err = tarReader.Next() + if err != nil { + if errors.Is(err, io.EOF) { + return nil, fmt.Errorf("executable not found") + } + return nil, err + } + + if header.Name == executable { + var buf []byte + buf, err = io.ReadAll(tarReader) + if err != nil { + return nil, err + } + return buf, nil + } + } +} diff --git a/internal/upgrade/extract_executable_windows.go b/internal/upgrade/extract_executable_windows.go new file mode 100644 index 00000000..288001b4 --- /dev/null +++ b/internal/upgrade/extract_executable_windows.go @@ -0,0 +1,43 @@ +package upgrade + +import ( + "archive/zip" + "bytes" + "fmt" + "io" +) + +const extension = "zip" + +func extractExecutable(r io.Reader) ([]byte, error) { + data, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + zipReader, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) + if err != nil { + return nil, err + } + + for _, file := range zipReader.File { + if file.Name == executable+".exe" { + var rc io.ReadCloser + rc, err = file.Open() + if err != nil { + return nil, err + } + defer rc.Close() + + var buf []byte + buf, err = io.ReadAll(rc) + if err != nil { + return nil, err + } + + return buf, nil + } + } + + return nil, fmt.Errorf("executable not found") +} diff --git a/internal/core/upgrade.go b/internal/upgrade/upgrade.go similarity index 56% rename from internal/core/upgrade.go rename to internal/upgrade/upgrade.go index 2dc02587..b75388c5 100644 --- a/internal/core/upgrade.go +++ b/internal/upgrade/upgrade.go @@ -1,14 +1,9 @@ -//go:build enable_upgrade - -package core +// Package upgrade contains functions to upgrade the executable. +package upgrade import ( - "archive/tar" - "archive/zip" "bytes" - "compress/gzip" "fmt" - "io" "net/http" "regexp" "runtime" @@ -61,68 +56,10 @@ func latestRemoteVersion() (*semver.Version, error) { return versions[0], nil } -func extractExecutable(r io.Reader) ([]byte, error) { - gzReader, err := gzip.NewReader(r) - if err != nil { - return nil, err - } - defer gzReader.Close() - - tarReader := tar.NewReader(gzReader) - - for { - header, err := tarReader.Next() - if err == io.EOF { - return nil, fmt.Errorf("executable not found") - } - if err != nil { - return nil, err - } - - if header.Name == executable { - buf, err := io.ReadAll(tarReader) - if err != nil { - return nil, err - } - return buf, nil - } - } -} - -func extractExecutableWin(r io.Reader) ([]byte, error) { - data, err := io.ReadAll(r) - if err != nil { - return nil, err - } - - zipReader, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) - if err != nil { - return nil, err - } - - for _, file := range zipReader.File { - if file.Name == executable+".exe" { - rc, err := file.Open() - if err != nil { - return nil, err - } - defer rc.Close() - - buf, err := io.ReadAll(rc) - if err != nil { - return nil, err - } - - return buf, nil - } - } - - return nil, fmt.Errorf("executable not found") -} - -func upgrade() error { - if !currentRegexp.MatchString(string(version)) { - return fmt.Errorf("current version (%v) is not official and cannot be upgraded", string(version)) +// Upgrade downloads the latest executable and replaces the current one with it. +func Upgrade(version, arch string) error { + if !currentRegexp.MatchString(version) { + return fmt.Errorf("current version (%v) is not official and cannot be upgraded", version) } fmt.Println("getting latest version...") @@ -132,7 +69,7 @@ func upgrade() error { return err } - current, _ := semver.NewVersion(string(version)) + current, _ := semver.NewVersion(version) if current.GreaterThanEqual(latest) { fmt.Printf("current version (%v) is up to date\n", "v"+current.String()) @@ -141,14 +78,7 @@ func upgrade() error { fmt.Printf("downloading version %v...\n", "v"+latest.String()) - var extension string - if runtime.GOOS == "windows" { - extension = "zip" - } else { - extension = "tar.gz" - } - - ur := fmt.Sprintf(downloadURL, "v"+latest.String(), "v"+latest.String(), runtime.GOOS, getArch(), extension) + ur := fmt.Sprintf(downloadURL, "v"+latest.String(), "v"+latest.String(), runtime.GOOS, arch, extension) res, err := http.Get(ur) if err != nil { @@ -160,11 +90,9 @@ func upgrade() error { return fmt.Errorf("bad status code: %v", res.StatusCode) } - var exe []byte - if runtime.GOOS == "windows" { - exe, err = extractExecutableWin(res.Body) - } else { - exe, err = extractExecutable(res.Body) + exe, err := extractExecutable(res.Body) + if err != nil { + return err } err = selfupdate.Apply(bytes.NewReader(exe), selfupdate.Options{}) diff --git a/scripts/binaries.mk b/scripts/binaries.mk index 825a74ee..365dda71 100644 --- a/scripts/binaries.mk +++ b/scripts/binaries.mk @@ -14,33 +14,33 @@ RUN cp mediamtx.yml LICENSE tmp/ RUN go generate ./... FROM build-base AS build-windows-amd64 -RUN GOOS=windows GOARCH=amd64 go build -tags enable_upgrade -o "tmp/$(BINARY_NAME).exe" +RUN GOOS=windows GOARCH=amd64 go build -o "tmp/$(BINARY_NAME).exe" RUN go install github.com/tc-hib/go-winres@v0.3.3 RUN go-winres patch --in scripts/winres.json --product-version "$$(git describe --tags --abbrev=0 | sed 's/^v//')" --file-version "$$(git describe --tags --abbrev=0 | sed 's/^v//')" tmp/mediamtx.exe RUN cd tmp && zip -q "../binaries/$(BINARY_NAME)_$$(cat ../internal/core/VERSION)_windows_amd64.zip" "$(BINARY_NAME).exe" mediamtx.yml LICENSE FROM build-base AS build-linux-amd64 -RUN GOOS=linux GOARCH=amd64 go build -tags enable_upgrade -o "tmp/$(BINARY_NAME)" +RUN GOOS=linux GOARCH=amd64 go build -o "tmp/$(BINARY_NAME)" RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_amd64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE FROM build-base AS build-darwin-amd64 -RUN GOOS=darwin GOARCH=amd64 go build -tags enable_upgrade -o "tmp/$(BINARY_NAME)" +RUN GOOS=darwin GOARCH=amd64 go build -o "tmp/$(BINARY_NAME)" RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_darwin_amd64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE FROM build-base AS build-darwin-arm64 -RUN GOOS=darwin GOARCH=arm64 go build -tags enable_upgrade -o "tmp/$(BINARY_NAME)" +RUN GOOS=darwin GOARCH=arm64 go build -o "tmp/$(BINARY_NAME)" RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_darwin_arm64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE FROM build-base AS build-linux-armv6 -RUN GOOS=linux GOARCH=arm GOARM=6 go build -tags enable_upgrade -o "tmp/$(BINARY_NAME)" +RUN GOOS=linux GOARCH=arm GOARM=6 go build -o "tmp/$(BINARY_NAME)" RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_armv6.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE FROM build-base AS build-linux-armv7 -RUN GOOS=linux GOARCH=arm GOARM=7 go build -tags enable_upgrade -o "tmp/$(BINARY_NAME)" +RUN GOOS=linux GOARCH=arm GOARM=7 go build -o "tmp/$(BINARY_NAME)" RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_armv7.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE FROM build-base AS build-linux-arm64 -RUN GOOS=linux GOARCH=arm64 go build -tags enable_upgrade -o "tmp/$(BINARY_NAME)" +RUN GOOS=linux GOARCH=arm64 go build -o "tmp/$(BINARY_NAME)" RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_arm64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE FROM $(BASE_IMAGE)