Windows 交付改單一 exe+版本號不再手打(leo 08-06 回報②與「整串該機械化」)

## ② 兩個檔很迷惑 → 一個檔
leo 圖一標註:「下載還是 ZIP ⇒ 應該是 MSIX 或 EXE;解開還是 2 個檔 ⇒ 應該只有 1 個檔案」。
舊做法 zip 裡放 Arcrun.exe+arcrun-collector.exe 同層,少解一個檔就永遠不會同步,
而畫面不會說是這個原因。
修:collector 用 go:embed 收進 Arcrun.exe,第一次執行攤到 ~/.arcrun-rag/bin/
    (冪等:sha256 相同不重寫;先寫暫存再 rename,避免留半截執行檔)。
    build-win.sh 直接產單一 Arcrun-win-<ver>.exe,不再打 zip。
    加機械閘:產出的 exe 若比 collector 還小 ⇒ 沒 embed 成功 ⇒ 中止(過去只會
    默默得到一顆「裝了也不會同步」的空殼)。
不走 MSIX:未上架的 MSIX 要使用者先開「開發人員模式」,比 zip 更麻煩。

## 版本號機械化(leo:「這一整串都應該是機械化,不應該每次手工做」)
病根實測:本 repo **一個 git tag 都沒有**,而四支 build 腳本寫的是
`git describe --tags` ⇒ 永遠拿不到 v0.18.x ⇒ 只能靠打包時人工敲 VERSION=
⇒ 兩條打包線各敲各的 ⇒ manifest 宣告 0.18.5、產物其實是 0.18.4。

修:新增 daemon-version.sh 當唯一產生器,沿用 release.mjs 已在跑的原則
   (「版本號由內容算出來,不是由人宣告」),不另立新制:
     MINOR ← DAEMON_LINE(人決定,大改版才動)
     PATCH ← 動過 collector/ 的 commit 數 − DAEMON_PATCH_BASE(機器決定)
   ⇒ 沒改 daemon 重跑打包不會虛增;改了才 +1;四支腳本在同一 commit 必得同號。
   ⇒ 不需要 git tag/Actions/輪詢(守 D20 紅線)。
   校準:DAEMON_LINE=0.18、BASE=87 ⇒ 現在的樹算出 v0.18.7(上次出貨過 0.18.6)。
   build-win/mac/dmg/msix 四支全部改用它。

## icon 漂移根治接上打包
build-win.sh 步驟⓪ 每次都跑 gen-icons.py 重產 icon.ico/trayicon.ico。

## 驗(實測輸出,非推測)
· ./build-win.sh 全程跑通,產出 dist/Arcrun-win-v0.18.7.exe(27,445,248 bytes)
· strings 抽內嵌版本 = v0.18.7、buildTime = 20260806-1233(機器算的,沒人敲)
· 位元組比對該 exe:
    CIS trayicon 在裡面        = True
    Wails「W」圖還在           = False
    collector 有 embed 進去    = True(16,104,960 bytes)
· go vet 全綠(darwin + windows);mac build OK
· 未驗:Windows 真機行為(需封測者實跑)⇒ 狀態 ◐,不是 
· 未做:CHANGELOG 閘、manifest「宣告版本 == 產物版本」驗證閘、前端顯示版本與下載入口
This commit is contained in:
2026-08-06 12:35:54 +08:00
parent ee2fc2a803
commit a55488040d
8 changed files with 194 additions and 20 deletions
@@ -0,0 +1,75 @@
//go:build windows
package main
// bundled_collector_windows.go — 把 collector 內嵌進單一 Arcrun.exe2026-08-06
//
// 🔴 leo 08-06 封測回報②:
//
// 「下載還是 ZIP ⇒ 應該是 MSIX 或 EXE;解開還是 2 個檔 ⇒ 應該只有 1 個檔案」
// 「兩個檔案令人很迷惑,User unfriendly」
//
// 舊做法:zip 裡放 Arcrun.exearcrun-collector.exe 同層,App 用 os.Executable()
// 找隔壁那支。使用者只要少解壓一個檔、或只把 Arcrun.exe 拖出來,同步就永遠不會動
// ——而畫面不會說是這個原因。
//
// 新做法:collector 直接 embed 進 Arcrun.exe,第一次執行時攤到
// `~/.arcrun-rag/bin/arcrun-collector.exe`(與 config.jsoncollector.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("已攤開內嵌同步引擎:%ssha256 %s", dst, hex.EncodeToString(want[:8]))
return dst
}