// cloud_version.go — t103 daemon 雲端版本偵測。 // 每輪 GET {cypher_url}/health 取 bundle_version,比對最低相容日期, // 過舊時 status.json 記錄,托盤顯示更新入口。 package main import ( "encoding/json" "io" "net/http" "strings" "time" ) // minCloudBuilt 是雲端最低相容建置日期(YYYY-MM-DD)。 // 雲端契約變更(新 API/schema 上線)時手動升此常數;daemon 下一輪自動偵測並提示更新。 const minCloudBuilt = "2026-07-28" // fetchCloudVersion 可在測試中替換為 stub,避免真實網路呼叫拖慢測試。 var fetchCloudVersion = fetchBundleVersion // fetchBundleVersion GET {cypherURL}/health 取 bundle_version。 // 5s timeout;失敗(逾時、連不上、非 JSON)靜默回 ("", false)——呼叫端不判定過舊。 func fetchBundleVersion(cypherURL string) (version string, ok bool) { client := &http.Client{Timeout: 5 * time.Second} resp, err := client.Get(strings.TrimSuffix(cypherURL, "/") + "/health") if err != nil { return "", false } defer resp.Body.Close() var payload struct { BundleVersion string `json:"bundle_version"` } if err := json.NewDecoder(io.LimitReader(resp.Body, 4096)).Decode(&payload); err != nil { return "", false } return payload.BundleVersion, true } // cloudVersionStale 判斷雲端是否需要更新。 // checkOK=false(/health 不可達)回 false(靜默不判定)。 // // 🔴 t177(wiki 事故 D;**這 bug 有兩份,tray 的 trayCloudVersionStale 是另一份,兩份都要修**): // 版本號 08-02 起改 semver(`1.4.6`),舊寫法 `date < "2026-07-28"` 是**字串比較**, // `"1" < "2"` 恆成立 ⇒ 任何 semver 都被判過舊、警告永遠消不掉 // (leo 08-04 實撞:「刷新完兩個雲端,為什麼還要我去更新?」,實例已 1.4.6)。 // ⇒ 照事故 D 定案③「兩種格式都要認」:semver 走版號比較、日期格式走日期比較。 func cloudVersionStale(version string, checkOK bool) bool { if !checkOK { return false } if version == "" { return true } // bundle_version 格式:semver(1.4.6,08-02 起)或 YYYY-MM-DD+(舊) head := strings.SplitN(version, "+", 2)[0] if isSemverLikeVersion(head) { // 吐得出 semver=雲端已是新契約 ⇒ 不算過舊。 return false } // 舊格式:維持日期比較,讓尚未更新的老實例仍被正確提示。 return head < minCloudBuilt } // isSemverLikeVersion 判斷字串是否為 `X.Y.Z`(純數字三段)。 // 與 tray 的 isSemverLike 同義;collector 與 tray 不同 package,故各持一份。 func isSemverLikeVersion(s string) bool { parts := strings.Split(s, ".") if len(parts) != 3 { return false } for _, p := range parts { if p == "" { return false } for _, r := range p { if r < '0' || r > '9' { return false } } } return true }