//go:build windows package main // bundled_collector_windows.go — 把 collector 內嵌進單一 Arcrun.exe(2026-08-06) // // 🔴 leo 08-06 封測回報②: // // 「下載還是 ZIP ⇒ 應該是 MSIX 或 EXE;解開還是 2 個檔 ⇒ 應該只有 1 個檔案」 // 「兩個檔案令人很迷惑,User unfriendly」 // // 舊做法:zip 裡放 Arcrun.exe+arcrun-collector.exe 同層,App 用 os.Executable() // 找隔壁那支。使用者只要少解壓一個檔、或只把 Arcrun.exe 拖出來,同步就永遠不會動 // ——而畫面不會說是這個原因。 // // 新做法:collector 直接 embed 進 Arcrun.exe,第一次執行時攤到 // `~/.arcrun-rag/bin/arcrun-collector.exe`(與 config.json/collector.log 同一個資料夾, // 使用者要備份或清乾淨只有一個地方)。 // // 為什麼不選 MSIX:未上架的 MSIX 要使用者先開「開發人員模式」才裝得起來, // 比 zip 更麻煩(見 docs-site 的 install-windows 方式 B)。上架後再回頭走 MSIX。 // // ⚠️ 編譯前提:`arcrun-collector.exe` 必須先存在同目錄(build-win.sh 步驟①會先編)。 // 它被 .gitignore 排除,所以**不能**直接 `go build` 一個乾淨 clone——一律走 build-win.sh。 import ( "crypto/sha256" _ "embed" "encoding/hex" "os" "path/filepath" ) //go:embed arcrun-collector.exe var bundledCollector []byte // bundledCollectorPath 把內嵌的 collector 攤到磁碟並回傳路徑。 // // 冪等:內容一樣就不重寫(避免每次啟動都寫 16MB,也避免正在跑的那支被覆寫)。 // 出錯回空字串——呼叫端會退回「找同層」的舊行為,並由 appLog 留下痕跡。 func bundledCollectorPath() string { if len(bundledCollector) == 0 { return "" } dir := filepath.Join(appDir(), "bin") dst := filepath.Join(dir, "arcrun-collector.exe") want := sha256.Sum256(bundledCollector) if have, err := os.ReadFile(dst); err == nil { if got := sha256.Sum256(have); got == want { return dst // 已是同一版,不動它 } } if err := os.MkdirAll(dir, 0o755); err != nil { appLog("攤開內嵌同步引擎失敗(建資料夾):%v", err) return "" } // 先寫暫存再 rename:中途失敗不會留下半截的執行檔。 tmp := dst + ".new" if err := os.WriteFile(tmp, bundledCollector, 0o755); err != nil { appLog("攤開內嵌同步引擎失敗(寫檔):%v", err) return "" } // Windows 不允許覆寫「正在執行」的檔案;先移開舊的再換上新的。 _ = os.Remove(dst + ".old") _ = os.Rename(dst, dst+".old") if err := os.Rename(tmp, dst); err != nil { appLog("攤開內嵌同步引擎失敗(換檔):%v", err) return "" } _ = os.Remove(dst + ".old") appLog("已攤開內嵌同步引擎:%s(sha256 %s)", dst, hex.EncodeToString(want[:8])) return dst }