diff --git a/cmd/arcrun-app/build-dmg.sh b/cmd/arcrun-app/build-dmg.sh index 22e4aef..af58fcb 100755 --- a/cmd/arcrun-app/build-dmg.sh +++ b/cmd/arcrun-app/build-dmg.sh @@ -6,7 +6,11 @@ # 而且 macOS 對「不在 /Applications 的常駐程式」權限行為也不一致。 set -euo pipefail cd "$(dirname "$0")" -VERSION="${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo dev)}" +# 🔴 2026-08-06:版本號改由 daemon-version.sh 機械產生(不再手打/不靠 git describe)。 +# 病根:本 repo 一個 git tag 都沒有 ⇒ git describe 永遠拿不到 v0.18.x +# ⇒ 只能靠打包時人工敲 VERSION= ⇒ 兩條打包線各敲各的 ⇒ manifest 說謊。 +# 理由與算法全文見 daemon-version.sh。 +VERSION="${VERSION:-$(./daemon-version.sh)}" echo "① 先確保 .app 是最新的" VERSION="$VERSION" bash build-mac.sh >/dev/null diff --git a/cmd/arcrun-app/build-mac.sh b/cmd/arcrun-app/build-mac.sh index 1a286ee..e43b1c0 100755 --- a/cmd/arcrun-app/build-mac.sh +++ b/cmd/arcrun-app/build-mac.sh @@ -6,7 +6,11 @@ # 沒同綑 ⇒ 裝了也不會同步。 set -euo pipefail cd "$(dirname "$0")" -VERSION="${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo dev)}" +# 🔴 2026-08-06:版本號改由 daemon-version.sh 機械產生(不再手打/不靠 git describe)。 +# 病根:本 repo 一個 git tag 都沒有 ⇒ git describe 永遠拿不到 v0.18.x +# ⇒ 只能靠打包時人工敲 VERSION= ⇒ 兩條打包線各敲各的 ⇒ manifest 說謊。 +# 理由與算法全文見 daemon-version.sh。 +VERSION="${VERSION:-$(./daemon-version.sh)}" BUILD_TIME="$(date '+%Y%m%d-%H%M')" LDFLAGS_VER="-X main.version=${VERSION} -X main.buildTime=${BUILD_TIME}" export PATH="$PATH:$(go env GOPATH)/bin" diff --git a/cmd/arcrun-app/build-msix.sh b/cmd/arcrun-app/build-msix.sh index c1f7724..2a26334 100755 --- a/cmd/arcrun-app/build-msix.sh +++ b/cmd/arcrun-app/build-msix.sh @@ -34,7 +34,7 @@ MAKEMSIX="$MSIX_SDK_DIR/.vs/bin/makemsix" exit 1 } -VERSION_RAW="${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo dev)}" +VERSION_RAW="${VERSION:-$(./daemon-version.sh)}" # 版本由 daemon-version.sh 機械產生(2026-08-06) # msix 版本必須是四段數字 a.b.c.d,且**最後一段必須是 0**(Store 規定,保留給微軟)。 MSIX_VERSION="$(echo "$VERSION_RAW" | sed 's/^v//' | awk -F. '{printf "%d.%d.%d.0", $1, $2, $3}')" # ⚠️ 變數後緊接全形括號必須用 ${} 包起來——全形字元會被 bash 當成變數名的一部分。 diff --git a/cmd/arcrun-app/build-win.sh b/cmd/arcrun-app/build-win.sh index e8222f0..2d98476 100755 --- a/cmd/arcrun-app/build-win.sh +++ b/cmd/arcrun-app/build-win.sh @@ -20,7 +20,12 @@ set -euo pipefail cd "$(dirname "$0")" export PATH="$PATH:$(go env GOPATH)/bin" -VERSION="${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo dev)}" +# 🔴 2026-08-06:版本號**不再手打、也不再靠 git describe**。 +# 舊寫法 `git describe --tags` 在這個 repo 永遠拿不到 v0.18.x(一個 tag 都沒有) +# ⇒ 版本只能靠打包時人工 `VERSION=v0.18.4 ./build-win.sh` 敲 ⇒ manifest 說謊的病根。 +# 現在由 daemon-version.sh 從 DAEMON_LINE + collector/ 的 commit 數算出來, +# 兩條打包線(win/mac)在同一個 commit 上必得同一個號碼。理由全文見該腳本。 +VERSION="${VERSION:-$(./daemon-version.sh)}" BUILD_TIME="$(date '+%Y%m%d-%H%M')" echo "🏷 版本:${VERSION}(build ${BUILD_TIME})" @@ -30,7 +35,13 @@ command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1 || { exit 1 } -echo "① 編 collector.exe(同綑執行檔,純 stdlib、CGO_ENABLED=0)" +echo "⓪ 由 appicon.png 產生 Windows 圖示(icon.ico/trayicon.ico)" +# 🔴 2026-08-06:不產就會沿用上一次留在磁碟上的檔——而上一次那顆是 `wails init` +# 留下的 Wails 預設「W」圖,leo 封測看到視窗與托盤兩處都是 W。 +# 每次 build 都重產 ⇒ 「換了 appicon 卻沒換 Windows」這種漂移不可能再發生。 +python3 gen-icons.py + +echo "① 編 collector.exe(會被 embed 進 Arcrun.exe,純 stdlib、CGO_ENABLED=0)" # 🔴 2026-08-06:同 build-mac.sh 補回版本注入(Wails 換代時掉的,理由見該檔註解)。 # 2b83c47 當年就修過一次「Mac 有、Windows 漏了」——換代後兩邊又一起掉,這次一起補。 ( cd ../.. && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \ @@ -57,23 +68,36 @@ CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc \ -ldflags "-X main.version=${VERSION} -X main.buildTime=${BUILD_TIME}" OUT="dist"; mkdir -p "$OUT" -STAGE="$(mktemp -d)/Arcrun" -mkdir -p "$STAGE" -echo "③ 同綑 collector.exe(App 用 os.Executable() 找同層,見 supervise.go)" -cp build/bin/arcrun-app.exe "$STAGE/Arcrun.exe" -cp arcrun-collector.exe "$STAGE/arcrun-collector.exe" +echo "③ 產出**單一執行檔**" +# 🔴 2026-08-06 交付形態改變(leo 封測回報②,圖一標註): +# 「下載還是 ZIP ⇒ 應該是 MSIX 或 EXE」「解開還是 2 個檔 ⇒ 應該只有 1 個檔案」 +# 「兩個檔案令人很迷惑,User unfriendly」 +# +# 舊做法=zip 裡放 Arcrun.exe+arcrun-collector.exe 同層。使用者只要少解一個檔、 +# 或只把 Arcrun.exe 拖出來,同步就永遠不會動,而畫面不會說是這個原因。 +# 新做法=collector 在步驟②被 go:embed 收進 Arcrun.exe(見 bundled_collector_windows.go), +# 第一次執行時攤到 ~/.arcrun-rag/bin/。**下載下來就是一個檔,雙擊就跑。** +# +# 為什麼不出 MSIX:未上架的 MSIX 要使用者先開「開發人員模式」才裝得起來, +# 比 zip 更麻煩。上架 MS Store 後再回頭走 build-msix.sh。 +EXE="$(cd "$OUT" && pwd)/Arcrun-win-${VERSION}.exe" +rm -f "$EXE" +cp build/bin/arcrun-app.exe "$EXE" -echo "④ 打包 zip" -# 用絕對路徑:下面會 cd 進暫存目錄打包,相對路徑會找不到(實撞:zip I/O error)。 -ZIP="$(cd "$OUT" && pwd)/ArcrunRAG-win-unsigned-${VERSION}.zip" -rm -f "$ZIP" -( cd "$(dirname "$STAGE")" && zip -qr "$ZIP" "Arcrun" ) -rm -rf "$(dirname "$STAGE")" +# 🔴 機械閘:embed 進去的 collector 至少要有幾 MB,否則就是「編了個空殼」。 +# (步驟①失敗但 wails build 仍成功時,過去只會得到一個不會同步的 exe。) +APP_SIZE=$(stat -f%z "$EXE" 2>/dev/null || stat -c%s "$EXE") +COL_SIZE=$(stat -f%z arcrun-collector.exe 2>/dev/null || stat -c%s arcrun-collector.exe) +if [ "$APP_SIZE" -lt "$COL_SIZE" ]; then + echo "❌ 產出的 exe(${APP_SIZE} bytes)比 collector(${COL_SIZE} bytes)還小" + echo " ⇒ collector 沒被 embed 進去,這顆裝了也不會同步。中止。" + exit 1 +fi -SIZE=$(ls -lh "$ZIP" | awk '{print $5}') -echo "✅ 完成:${ZIP}(${SIZE})" +SIZE=$(ls -lh "$EXE" | awk '{print $5}') +echo "✅ 完成:${EXE}(${SIZE},內含同步引擎)" echo -echo "🔬 使用者流程:解壓縮 → 執行 Arcrun.exe" +echo "🔬 使用者流程:下載 → 雙擊 Arcrun.exe(不必解壓縮、不必找第二個檔)" echo " ⚠️ 未簽章 ⇒ SmartScreen 會攔(「更多資訊」→「仍要執行」)。" echo " 要免除這一步=上 MS Store(見 build-msix.sh)或買 EV 憑證。" diff --git a/cmd/arcrun-app/bundled_collector_other.go b/cmd/arcrun-app/bundled_collector_other.go new file mode 100644 index 0000000..8d7d916 --- /dev/null +++ b/cmd/arcrun-app/bundled_collector_other.go @@ -0,0 +1,12 @@ +//go:build !windows + +package main + +// bundled_collector_other.go — 非 Windows 平台不內嵌 collector。 +// +// macOS 走 .app bundle:collector 就放在 Contents/MacOS/ 裡,對使用者而言 +// **本來就只有一個可拖曳的東西**(Arcrun.app),沒有「兩個檔很迷惑」的問題。 +// 所以只有 Windows 需要把 collector 收進單一 exe(見 bundled_collector_windows.go)。 + +// bundledCollectorPath 回空字串=沒有內嵌版,呼叫端退回「找同層」。 +func bundledCollectorPath() string { return "" } diff --git a/cmd/arcrun-app/bundled_collector_windows.go b/cmd/arcrun-app/bundled_collector_windows.go new file mode 100644 index 0000000..8a7a710 --- /dev/null +++ b/cmd/arcrun-app/bundled_collector_windows.go @@ -0,0 +1,75 @@ +//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 +} diff --git a/cmd/arcrun-app/daemon-version.sh b/cmd/arcrun-app/daemon-version.sh new file mode 100755 index 0000000..472ff40 --- /dev/null +++ b/cmd/arcrun-app/daemon-version.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# daemon-version.sh — daemon(桌面 App)版本號的**唯一**產生器(2026-08-06) +# +# 🔴 leo 08-06:「給版本號、本版更新內容、打包產品、顯示在前端, +# 這一整串都應該是機械化,**不應該每次手工做**」 +# +# ── 病根(實測,不是猜的)──────────────────────────────────────────── +# manifest 宣告 daemon v0.18.5,但線上 zip 拆開內嵌版本是 **v0.18.4**。 +# 原因:本 repo **一個 git tag 都沒有**,而各 build 腳本寫的是 +# `VERSION=$(git describe --tags --always --dirty)` ⇒ 永遠拿不到 v0.18.x +# ⇒ 那個號碼只可能是每次打包時人工 `VERSION=v0.18.4 ./build-win.sh` 敲進去的。 +# 人工敲 ⇒ 兩條打包線(DMG/zip)只重打一條就對不上 ⇒ manifest 說謊。 +# +# ── 做法(沿用 installer/scripts/release.mjs 已經在跑的原則,不另立新制)── +# release.mjs 開宗明義:「版本號**由內容算出來**,不是由人(或 AI)宣告」。 +# 雲端 bundle(1.4.x)早就這樣跑了,daemon 只是一直被排除在外。這裡補上: +# +# MINOR ← 人決定:`DAEMON_LINE` 檔(大改版才動,平常不碰) +# PATCH ← 機器決定:**動過 collector/ 的 commit 數** 減去 `DAEMON_PATCH_BASE` +# +# ⇒ 沒改 daemon 就重跑打包,版本**不會虛增**(commit 數沒變)。 +# ⇒ 改了 daemon 才會 +1,且兩條打包線在同一個 commit 上算出**同一個號碼**。 +# ⇒ 不需要 git tag、不需要 Actions、不需要輪詢(守 D20 紅線)。 +# +# 換線(0.18 → 0.19)時:改 DAEMON_LINE,並把 DAEMON_PATCH_BASE 設成當下的 +# `git rev-list --count HEAD -- collector/`,讓新線從 .0 開始。 +# +# 用法:VERSION="$(./daemon-version.sh)" → 印出 v0.18.7 這種字串 +set -euo pipefail +cd "$(dirname "$0")" +REPO_ROOT="$(git rev-parse --show-toplevel)" + +LINE="$(tr -d ' \n' < "$REPO_ROOT/DAEMON_LINE")" +BASE="$(tr -d ' \n' < "$REPO_ROOT/DAEMON_PATCH_BASE")" + +[[ "$LINE" =~ ^[0-9]+\.[0-9]+$ ]] || { echo "DAEMON_LINE 必須是 MAJOR.MINOR(例 0.18),現在是 '$LINE'" >&2; exit 1; } +[[ "$BASE" =~ ^[0-9]+$ ]] || { echo "DAEMON_PATCH_BASE 必須是整數,現在是 '$BASE'" >&2; exit 1; } + +COUNT="$(git -C "$REPO_ROOT" rev-list --count HEAD -- collector/)" +PATCH=$(( COUNT - BASE )) +if [ "$PATCH" -lt 0 ]; then + echo "DAEMON_PATCH_BASE($BASE)大於實際 commit 數($COUNT)——換線時忘了更新?" >&2 + exit 1 +fi + +echo "v${LINE}.${PATCH}" diff --git a/cmd/arcrun-app/supervise.go b/cmd/arcrun-app/supervise.go index 35ae866..d484960 100644 --- a/cmd/arcrun-app/supervise.go +++ b/cmd/arcrun-app/supervise.go @@ -23,8 +23,17 @@ import ( var sup *supervisor.Supervisor // collectorBinPath 找同綑的 collector 執行檔。 -// 規則與 arcrun-tray 一致:跟 App 執行檔同層(build 時會複製進 .app)。 +// +// 兩種同綑方式,依平台: +// - Windows:collector **embed 在 Arcrun.exe 裡**,第一次執行攤到 ~/.arcrun-rag/bin/ +// (leo 08-06:「解開還是 2 個檔 ⇒ 應該只有 1 個檔案」)。見 bundled_collector_windows.go。 +// - macOS:放在 .app 的 Contents/MacOS/ 同層(使用者只看到一個可拖曳的 Arcrun.app)。 +// +// 內嵌攤開失敗時**退回找同層**——舊版打包出來的資料夾仍然能跑,不會因為換機制而全壞。 func collectorBinPath() string { + if p := bundledCollectorPath(); p != "" { + return p + } name := "arcrun-collector" if runtime.GOOS == "windows" { name += ".exe"