mirror of
https://github.com/MizuchiLabs/mantrae.git
synced 2025-12-16 20:05:17 -06:00
248 lines
4.9 KiB
Go
248 lines
4.9 KiB
Go
package build
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/mizuchilabs/mantrae/pkg/meta"
|
|
)
|
|
|
|
const (
|
|
RepoURL = "https://api.github.com/repos/mizuchilabs/mantrae"
|
|
)
|
|
|
|
type releaseAsset struct {
|
|
Name string `json:"name"`
|
|
DownloadURL string `json:"browser_download_url"`
|
|
ID int `json:"id"`
|
|
Size int `json:"size"`
|
|
}
|
|
|
|
type release struct {
|
|
Name string `json:"name"`
|
|
Tag string `json:"tag_name"`
|
|
Published string `json:"published_at"`
|
|
URL string `json:"html_url"`
|
|
Body string `json:"body"`
|
|
Assets []*releaseAsset `json:"assets"`
|
|
ID int `json:"id"`
|
|
}
|
|
|
|
func Update(update bool) {
|
|
if runningInDocker() {
|
|
slog.Info("Running in docker, skipping update")
|
|
return
|
|
}
|
|
|
|
latest, err := fetchLatestRelease()
|
|
if err != nil {
|
|
slog.Error("Update failed", "Error", err)
|
|
return
|
|
}
|
|
|
|
if !update {
|
|
if compareVersions(
|
|
strings.TrimPrefix(meta.Version, "v"),
|
|
strings.TrimPrefix(latest.Tag, "v"),
|
|
) <= 0 {
|
|
slog.Info("You are running the latest version!")
|
|
return
|
|
}
|
|
slog.Info("New version available!", "latest", latest.Tag, "current", meta.Version)
|
|
return
|
|
}
|
|
|
|
asset := latest.findBinary(filepath.Base(os.Args[0]))
|
|
if asset == nil {
|
|
slog.Info("Unsupported platform", "platform", runtime.GOOS+"/"+runtime.GOARCH)
|
|
return
|
|
}
|
|
exec, err := os.Executable()
|
|
if err != nil {
|
|
slog.Error("Update failed", "Error", err)
|
|
return
|
|
}
|
|
if err := os.Remove(exec); err != nil {
|
|
slog.Error("Failed to remove current executable", "Error", err)
|
|
return
|
|
}
|
|
|
|
slog.Info("Downloading...", "release", latest.Tag, "binary", asset.Name)
|
|
if err := downloadFile(asset.DownloadURL, exec); err != nil {
|
|
slog.Error("Failed to download", "Error", err)
|
|
return
|
|
}
|
|
|
|
slog.Info("Update success!")
|
|
}
|
|
|
|
func runningInDocker() bool {
|
|
if _, err := os.Stat("/.dockerenv"); err == nil {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func fetchLatestRelease() (*release, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
|
|
url := fmt.Sprintf("%s/releases/latest", RepoURL)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
res, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
if err = res.Body.Close(); err != nil {
|
|
slog.Error("failed to close response body", "error", err)
|
|
}
|
|
}()
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("(%d) failed to send latest release request", res.StatusCode)
|
|
}
|
|
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := &release{}
|
|
if err := json.Unmarshal(body, result); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func downloadFile(url string, dest string) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
res, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err = res.Body.Close(); err != nil {
|
|
slog.Error("failed to close response body", "error", err)
|
|
}
|
|
}()
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("(%d) failed to send download file request", res.StatusCode)
|
|
}
|
|
|
|
out, err := os.Create(dest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err = out.Close(); err != nil {
|
|
slog.Error("failed to close output file", "error", err)
|
|
}
|
|
}()
|
|
|
|
if _, err := io.Copy(out, res.Body); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := out.Chmod(0o755); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *release) findBinary(name string) *releaseAsset {
|
|
var assetName string
|
|
|
|
switch runtime.GOOS {
|
|
case "linux":
|
|
switch runtime.GOARCH {
|
|
case "amd64":
|
|
assetName = name + "_linux_amd64"
|
|
case "arm64":
|
|
assetName = name + "_linux_arm64"
|
|
case "arm":
|
|
assetName = name + "_linux_armv7"
|
|
}
|
|
case "darwin":
|
|
switch runtime.GOARCH {
|
|
case "amd64":
|
|
assetName = name + "_darwin_amd64"
|
|
case "arm64":
|
|
assetName = name + "_darwin_arm64"
|
|
}
|
|
case "windows":
|
|
switch runtime.GOARCH {
|
|
case "amd64":
|
|
assetName = name + "_windows_amd64"
|
|
case "arm64":
|
|
assetName = name + "_windows_arm64"
|
|
}
|
|
}
|
|
|
|
for _, asset := range r.Assets {
|
|
if assetName == asset.Name {
|
|
return asset
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func compareVersions(a, b string) int {
|
|
aSplit := strings.Split(a, ".")
|
|
aTotal := len(aSplit)
|
|
|
|
bSplit := strings.Split(b, ".")
|
|
bTotal := len(bSplit)
|
|
|
|
limit := max(aTotal, bTotal)
|
|
|
|
for i := range limit {
|
|
var x, y int
|
|
|
|
if i < aTotal {
|
|
x, _ = strconv.Atoi(aSplit[i])
|
|
}
|
|
|
|
if i < bTotal {
|
|
y, _ = strconv.Atoi(bSplit[i])
|
|
}
|
|
|
|
if x < y {
|
|
return 1 // b is newer
|
|
}
|
|
|
|
if x > y {
|
|
return -1 // a is newer
|
|
}
|
|
}
|
|
|
|
return 0 // equal
|
|
}
|