feat: add checksum-based update detection
This commit is contained in:
6
Makefile
6
Makefile
@@ -64,8 +64,10 @@ release: lint
|
||||
@xattr -cr "build/bin/Daily Timer.app" 2>/dev/null || true
|
||||
@rm -rf dist && mkdir -p dist
|
||||
cd build/bin && zip -r "../../dist/Daily-Timer-$(VERSION)-macos-arm64.zip" "Daily Timer.app"
|
||||
@shasum -a 256 "build/bin/Daily Timer.app/Contents/MacOS/daily-timer" | awk '{print $$1}' > "dist/Daily-Timer-$(VERSION)-macos-arm64.sha256"
|
||||
@echo "Release package: dist/Daily-Timer-$(VERSION)-macos-arm64.zip"
|
||||
@ls -lh dist/*.zip
|
||||
@echo "Checksum: $$(cat dist/Daily-Timer-$(VERSION)-macos-arm64.sha256)"
|
||||
@ls -lh dist/*
|
||||
|
||||
# Release for both architectures
|
||||
release-all: lint
|
||||
@@ -89,7 +91,7 @@ release-upload:
|
||||
-d '{"tag_name": "$(VERSION)", "name": "$(VERSION)", "body": "Release $(VERSION)"}' \
|
||||
| jq -r '.id'); \
|
||||
echo "Created release ID: $$RELEASE_ID"; \
|
||||
for file in dist/*.zip; do \
|
||||
for file in dist/*; do \
|
||||
filename=$$(basename "$$file"); \
|
||||
echo "Uploading $$filename..."; \
|
||||
curl -s -X POST \
|
||||
|
||||
@@ -309,7 +309,11 @@
|
||||
</div>
|
||||
{:else if updateInfo?.available}
|
||||
<div class="update-status available">
|
||||
{$t('updates.updateAvailable')}: <strong>{updateInfo.latestVersion}</strong>
|
||||
{#if updateInfo.isRebuild}
|
||||
{$t('updates.rebuildAvailable')}: <strong>{updateInfo.latestVersion}</strong>
|
||||
{:else}
|
||||
{$t('updates.updateAvailable')}: <strong>{updateInfo.latestVersion}</strong>
|
||||
{/if}
|
||||
</div>
|
||||
<button class="update-btn primary" on:click={downloadAndInstall}>
|
||||
{$t('updates.downloadAndInstall')}
|
||||
|
||||
@@ -114,6 +114,7 @@ export const translations = {
|
||||
currentVersion: 'Текущая версия',
|
||||
checkingForUpdates: 'Проверка обновлений...',
|
||||
updateAvailable: 'Доступно обновление',
|
||||
rebuildAvailable: 'Доступна пересборка',
|
||||
upToDate: 'У вас последняя версия',
|
||||
downloadAndInstall: 'Скачать и установить',
|
||||
downloading: 'Загрузка...',
|
||||
@@ -281,6 +282,7 @@ export const translations = {
|
||||
currentVersion: 'Current version',
|
||||
checkingForUpdates: 'Checking for updates...',
|
||||
updateAvailable: 'Update available',
|
||||
rebuildAvailable: 'Rebuild available',
|
||||
upToDate: 'You have the latest version',
|
||||
downloadAndInstall: 'Download and install',
|
||||
downloading: 'Downloading...',
|
||||
|
||||
@@ -2,6 +2,8 @@ package updater
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -43,6 +45,7 @@ type UpdateInfo struct {
|
||||
ReleaseNotes string `json:"releaseNotes"`
|
||||
DownloadURL string `json:"downloadURL"`
|
||||
DownloadSize int64 `json:"downloadSize"`
|
||||
IsRebuild bool `json:"isRebuild"`
|
||||
}
|
||||
|
||||
type Updater struct {
|
||||
@@ -89,16 +92,40 @@ func (u *Updater) CheckForUpdates() (*UpdateInfo, error) {
|
||||
|
||||
u.downloadURL = downloadAsset.BrowserDownloadURL
|
||||
|
||||
// Find checksum asset
|
||||
var checksumAsset *Asset
|
||||
for i := range release.Assets {
|
||||
if strings.Contains(release.Assets[i].Name, "macos-arm64") && strings.HasSuffix(release.Assets[i].Name, ".sha256") {
|
||||
checksumAsset = &release.Assets[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
latestVersion := strings.TrimPrefix(release.TagName, "v")
|
||||
currentVersion := strings.TrimPrefix(version.Version, "v")
|
||||
|
||||
isNewer := isNewerVersion(latestVersion, currentVersion)
|
||||
isRebuild := false
|
||||
|
||||
// Check if same version but different checksum (rebuild)
|
||||
if !isNewer && checksumAsset != nil {
|
||||
remoteChecksum, err := u.downloadChecksum(checksumAsset.BrowserDownloadURL)
|
||||
if err == nil {
|
||||
localChecksum, err := u.calculateBinaryChecksum()
|
||||
if err == nil && remoteChecksum != localChecksum {
|
||||
isRebuild = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info := &UpdateInfo{
|
||||
Available: isNewerVersion(latestVersion, currentVersion),
|
||||
Available: isNewer || isRebuild,
|
||||
CurrentVersion: version.Version,
|
||||
LatestVersion: release.TagName,
|
||||
ReleaseNotes: release.Body,
|
||||
DownloadURL: downloadAsset.BrowserDownloadURL,
|
||||
DownloadSize: downloadAsset.Size,
|
||||
IsRebuild: isRebuild,
|
||||
}
|
||||
|
||||
return info, nil
|
||||
@@ -304,6 +331,54 @@ func (u *Updater) copyDir(src, dst string) error {
|
||||
})
|
||||
}
|
||||
|
||||
// downloadChecksum fetches the remote SHA256 checksum file
|
||||
func (u *Updater) downloadChecksum(url string) (string, error) {
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("failed to download checksum: status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(data)), nil
|
||||
}
|
||||
|
||||
// calculateBinaryChecksum calculates SHA256 of the current running binary
|
||||
func (u *Updater) calculateBinaryChecksum() (string, error) {
|
||||
execPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Resolve symlinks to get actual binary path
|
||||
execPath, err = filepath.EvalSymlinks(execPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
file, err := os.Open(execPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
hasher := sha256.New()
|
||||
if _, err := io.Copy(hasher, file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hasher.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// isNewerVersion compares semver-like versions (e.g., "0.1.0" vs "0.2.0")
|
||||
func isNewerVersion(latest, current string) bool {
|
||||
if current == "dev" || current == "unknown" {
|
||||
|
||||
Reference in New Issue
Block a user