mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-18 11:06:19 +00:00
* feat: add browseros-cli self-updater * fix: address review comments for 0327-cli_self_updater * fix: address PR review comments for 0327-cli_self_updater * fix: replace goreleaser with Makefile-based release build Remove .goreleaser.yml (required Pro license for monorepo field) and consolidate cross-compilation into `make release`. CI now uses the same Makefile target, fixing a bug where POSTHOG_API_KEY was missing from release ldflags. * fix: address critical self-updater bugs from code review - Fix SHA256 checksum mismatch: verify archive checksum before extraction instead of verifying extracted binary against archive hash (was always failing). Add VerifyChecksum() and integration test. - Fix JSON field name mismatch: TypeScript was emitting camelCase (publishedAt, archiveFormat) but Go expected snake_case (published_at, archive_format). Manifest parsing was silently broken. - Add decompression size limit (256 MB) to prevent zip/gzip bombs. - Don't update LastCheckedAt on transient errors so retry happens on next CLI invocation instead of waiting 24h.
81 lines
1.6 KiB
Go
81 lines
1.6 KiB
Go
package update
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"browseros-cli/config"
|
|
)
|
|
|
|
type State struct {
|
|
LastCheckedAt time.Time `json:"last_checked_at"`
|
|
LatestVersion string `json:"latest_version,omitempty"`
|
|
LatestPublishedAt string `json:"latest_published_at,omitempty"`
|
|
AssetURL string `json:"asset_url,omitempty"`
|
|
CheckError string `json:"check_error,omitempty"`
|
|
}
|
|
|
|
func StatePath() string {
|
|
return filepath.Join(config.Dir(), "update-state.json")
|
|
}
|
|
|
|
func LoadState() (*State, error) {
|
|
data, err := os.ReadFile(StatePath())
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return &State{}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var state State
|
|
if err := json.Unmarshal(data, &state); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &state, nil
|
|
}
|
|
|
|
func SaveState(state *State) error {
|
|
if state == nil {
|
|
state = &State{}
|
|
}
|
|
|
|
dir := config.Dir()
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
tmpFile, err := os.CreateTemp(dir, "update-state-*.json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
encoder := json.NewEncoder(tmpFile)
|
|
encoder.SetIndent("", " ")
|
|
if err := encoder.Encode(state); err != nil {
|
|
tmpFile.Close()
|
|
os.Remove(tmpFile.Name())
|
|
return err
|
|
}
|
|
if err := tmpFile.Close(); err != nil {
|
|
os.Remove(tmpFile.Name())
|
|
return err
|
|
}
|
|
if err := os.Rename(tmpFile.Name(), StatePath()); err != nil {
|
|
os.Remove(tmpFile.Name())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *State) IsStale(now time.Time, ttl time.Duration) bool {
|
|
if s == nil || s.LastCheckedAt.IsZero() {
|
|
return true
|
|
}
|
|
return now.Sub(s.LastCheckedAt) >= ttl
|
|
}
|