diff --git a/cmd/arcrun-tray/selfupdate.go b/cmd/arcrun-tray/selfupdate.go index e2efefa..6f54402 100644 --- a/cmd/arcrun-tray/selfupdate.go +++ b/cmd/arcrun-tray/selfupdate.go @@ -24,12 +24,23 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "time" ) -// manifestURL 是 bundle manifest(含 daemon 區塊)。與安裝器同源,不另開發佈通道。 -const manifestURL = "https://cdn.jsdelivr.net/gh/youlinhsieh/arcrun-rag-bundles@main/manifest.json" +// manifestBase 是 bundle manifest(含 daemon 區塊)。與安裝器同源,不另開發佈通道。 +// +// ⚠️ 必須帶時間戳查詢字串繞快取(2026-07-29 實測): +// jsDelivr 對 `@main` 回 `cache-control: max-age=604800`=**7 天**, +// 用戶的小幫手會連續七天看不到新版 ⇒ 整套更新機制形同虛設。 +// 帶 ?t= 實測即取得最新內容。 +// (不用 raw.githubusercontent:那是「實名讀 GitHub」,受 D20 頻率閘管制。) +const manifestBase = "https://cdn.jsdelivr.net/gh/youlinhsieh/arcrun-rag-bundles@main/manifest.json" + +func manifestURLNoCache() string { + return manifestBase + "?t=" + strconv.FormatInt(time.Now().Unix(), 10) +} // updateCheckInterval:背景檢查頻率。守「不輪詢」紅線——這是**單一 CDN 靜態檔**、 // 每天一次、非 GitHub API,不構成 fan-out 或高頻寫入。 @@ -67,7 +78,7 @@ var currentUpdate updateState // fetchLatestRelease 抓 manifest 的 daemon 區塊。 func fetchLatestRelease() (*daemonRelease, error) { cli := &http.Client{Timeout: 20 * time.Second} - resp, err := cli.Get(manifestURL) + resp, err := cli.Get(manifestURLNoCache()) if err != nil { return nil, err } @@ -117,11 +128,14 @@ func checkForUpdate() updateState { // downloadURLFor 回傳本平台的下載網址。 func downloadURLFor(rel *daemonRelease) string { - const base = "https://cdn.jsdelivr.net/gh/youlinhsieh/arcrun-rag-bundles@main/" + // 同 manifestURLNoCache 的理由:@main 有 7 天快取,下載也要繞, + // 否則會抓到舊 zip ⇒ sha256 校驗不符 ⇒ 更新永遠失敗。 + base := "https://cdn.jsdelivr.net/gh/youlinhsieh/arcrun-rag-bundles@main/" + suffix := "?t=" + strconv.FormatInt(time.Now().Unix(), 10) if isWindows() { - return base + rel.Win.File + return base + rel.Win.File + suffix } - return base + rel.Mac.File + return base + rel.Mac.File + suffix } // updateStagePath 是下載暫存位置(~/.arcrun-rag/updates/)。 @@ -186,7 +200,9 @@ func downloadUpdate(rel *daemonRelease) (string, error) { if resp.StatusCode != 200 { return "", fmt.Errorf("下載 HTTP %d", resp.StatusCode) } - dst := updateStagePath(rel.Version + "-" + filepath.Base(url)) + // 檔名要用「去掉查詢字串」的網址算,否則會變成 ...zip?t=1785... 這種怪檔名。 + cleanURL := strings.SplitN(url, "?", 2)[0] + dst := updateStagePath(rel.Version + "-" + filepath.Base(cleanURL)) f, err := os.Create(dst) if err != nil { return "", err