diff --git a/VERSION b/VERSION index 7c32728..8f9174b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.1 \ No newline at end of file +2.1.2 \ No newline at end of file diff --git a/update.go b/update.go index ebfdc9f..0ad673c 100644 --- a/update.go +++ b/update.go @@ -67,8 +67,8 @@ func cleanStaleUpdateBinaries() { exeDir := filepath.Dir(self) patterns := []string{ - filepath.Join(exeDir, "EggLedger_*_new"), - filepath.Join(exeDir, "EggLedger_*_new.exe"), + filepath.Join(exeDir, "EggLedger*_new"), + filepath.Join(exeDir, "EggLedger*_new.exe"), } for _, pattern := range patterns { matches, err := filepath.Glob(pattern) diff --git a/updater_bindings.go b/updater_bindings.go index 7076e7c..1ad237b 100644 --- a/updater_bindings.go +++ b/updater_bindings.go @@ -15,7 +15,11 @@ package main // } import ( + "archive/tar" + "archive/zip" + "compress/gzip" "fmt" + "io" "os" "os/exec" "path/filepath" @@ -57,12 +61,12 @@ func handleDownloadAndInstallUpdate(tag string) error { return wrap(err) } - assetName := expectedAssetName() - tempName := strings.TrimSuffix(assetName, ".exe") + "_new" + exeName := filepath.Base(exePath) + tempBinName := strings.TrimSuffix(exeName, ".exe") + "_new" if runtime.GOOS == "windows" { - tempName += ".exe" + tempBinName += ".exe" } - tempPath := filepath.Join(exeDir, tempName) + tempPath := filepath.Join(exeDir, tempBinName) // Clean up any previous failed attempt. _ = os.Remove(tempPath) @@ -71,13 +75,28 @@ func handleDownloadAndInstallUpdate(tag string) error { go _ui.Eval(fmt.Sprintf(`globalThis.updateDownloadProgress && globalThis.updateDownloadProgress(%d, %d)`, downloaded, total)) } - if err := downloadUpdate(assetURL, tempPath, progressCb); err != nil { - _ = os.Remove(tempPath) - return wrap(err) - } + if runtime.GOOS == "windows" { + // Windows asset is a raw binary; download directly to tempPath. + if err := downloadUpdate(assetURL, tempPath, progressCb); err != nil { + _ = os.Remove(tempPath) + return wrap(err) + } + } else { + // Non-Windows assets are archives; download then extract the binary. + archivePath := filepath.Join(os.TempDir(), expectedAssetName()) + _ = os.Remove(archivePath) + + if err := downloadUpdate(assetURL, archivePath, progressCb); err != nil { + _ = os.Remove(archivePath) + return wrap(err) + } + if err := extractBinaryFromArchive(archivePath, tempPath); err != nil { + _ = os.Remove(archivePath) + _ = os.Remove(tempPath) + return wrap(err) + } + _ = os.Remove(archivePath) - // Make executable on Unix. - if runtime.GOOS != "windows" { if err := os.Chmod(tempPath, 0755); err != nil { _ = os.Remove(tempPath) return wrap(err) @@ -99,3 +118,107 @@ func handleDownloadAndInstallUpdate(tag string) error { _ui.Close() return nil } + +// extractBinaryFromArchive dispatches to the correct extractor based on archive extension. +func extractBinaryFromArchive(archivePath, destPath string) error { + wrap := func(err error) error { return errors.Wrap(err, "extractBinaryFromArchive") } + switch { + case strings.HasSuffix(archivePath, ".tar.gz"): + return wrap(extractFromTarGz(archivePath, destPath)) + case strings.HasSuffix(archivePath, ".zip"): + return wrap(extractFromZip(archivePath, destPath)) + default: + return wrap(errors.Errorf("unsupported archive format: %s", filepath.Base(archivePath))) + } +} + +// extractFromTarGz extracts the first regular file from a .tar.gz archive. +func extractFromTarGz(archivePath, destPath string) error { + f, err := os.Open(archivePath) + if err != nil { + return err + } + defer f.Close() + + gr, err := gzip.NewReader(f) + if err != nil { + return err + } + defer gr.Close() + + tr := tar.NewReader(gr) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + if hdr.Typeflag != tar.TypeReg || hdr.Size == 0 { + continue + } + out, err := os.Create(destPath) + if err != nil { + return err + } + if _, err := io.Copy(out, tr); err != nil { + out.Close() + return err + } + return out.Close() + } + return errors.New("no regular file found in archive") +} + +// extractFromZip extracts the EggLedger binary from a .zip archive. +// Prefers an entry under a MacOS/ path (macOS app bundle layout), then +// falls back to the first extensionless regular file. +func extractFromZip(archivePath, destPath string) error { + zr, err := zip.OpenReader(archivePath) + if err != nil { + return err + } + defer zr.Close() + + var target *zip.File + for _, f := range zr.File { + if f.FileInfo().IsDir() { + continue + } + if strings.Contains(f.Name, "/MacOS/") { + target = f + break + } + } + if target == nil { + for _, f := range zr.File { + if f.FileInfo().IsDir() { + continue + } + if filepath.Ext(f.Name) == "" { + target = f + break + } + } + } + if target == nil { + return errors.New("no suitable binary found in zip archive") + } + + rc, err := target.Open() + if err != nil { + return err + } + defer rc.Close() + + out, err := os.Create(destPath) + if err != nil { + return err + } + if _, err := io.Copy(out, rc); err != nil { + out.Close() + return err + } + return out.Close() +} diff --git a/version.go b/version.go index b296671..79e3d7c 100644 --- a/version.go +++ b/version.go @@ -146,14 +146,21 @@ func getLatestTagIncludingPreReleases() (string, string, error) { return highestTag, highestBody, nil } -// expectedAssetName returns the expected binary name for the current platform. -// e.g. "EggLedger_windows_amd64.exe" or "EggLedger_darwin_arm64" +// expectedAssetName returns the release asset filename for the current platform. func expectedAssetName() string { - name := fmt.Sprintf("EggLedger_%s_%s", runtime.GOOS, runtime.GOARCH) - if runtime.GOOS == "windows" { - name += ".exe" + switch runtime.GOOS { + case "windows": + return "EggLedger.exe" + case "linux": + return "EggLedger-linux.tar.gz" + case "darwin": + if runtime.GOARCH == "arm64" { + return "EggLedger-mac-arm64.zip" + } + return "EggLedger-mac.zip" + default: + return fmt.Sprintf("EggLedger_%s_%s", runtime.GOOS, runtime.GOARCH) } - return name } // getUpdateAssetURL fetches the GitHub releases API for the given tag and returns diff --git a/www/src/components/modals/UpdateModal.vue b/www/src/components/modals/UpdateModal.vue index a540066..aeec0f0 100644 --- a/www/src/components/modals/UpdateModal.vue +++ b/www/src/components/modals/UpdateModal.vue @@ -17,12 +17,18 @@ class="text-left flex-1 bg-darkerer overflow-auto gh-markdown-content max-h-60vh p-1rem rounded-md" v-html="renderedNotes" > -
- - - +
+
+ Downloading - {{ Math.round(updateProgress.downloaded / updateProgress.total * 100) }}%... +
+

{{ updateError }}

+
@@ -30,7 +36,7 @@