4d3a6a09a6
leo 08-06 裁決:「不要兩支,寫成一支檔案」。
## 為什麼
v0.18.7-8 的「單一 exe」其實是**一支包著另一支**:collector.exe 被 go:embed
進 Arcrun.exe,執行時攤到 ~/.arcrun-rag/bin/ 再跑。
那正是防毒軟體眼中的 dropper 特徵 —— 封測者實撞
`Trojan:Win32/Sabsik.FL.A!ml`,檔案當場被隔離、自動刪除。
⚠️ 誠實界定:`!ml` 結尾=**機器學習判定**,Sabsik 是最常見的通用誤判家族,
主因是「未簽章+下載次數少」,**不是**特別指向 dropper 行為。
所以本次改動**不保證**解除誤判——真正的解是上架 MS Store(微軟簽章)。
但「執行時把第二支 PE 寫到磁碟再執行」本來就該拿掉,這是對的方向且順手變小。
## 怎麼做
- `collector/` 39 個檔 `package main` → `package collector`,`main()` → 匯出的 `Run(args) int`
- 新增 `collector/cmd/collector/`(薄殼 CLI,讓單獨跑 collector 這條路仍可用)
- App 直接 import 該套件;`main()` 第一件事就判 `--collector`,是的話走 `collector.Run` 不碰 GUI
- `supervisor` 加 `ArgPrefix`,App 把 `BinPath` 指向 `os.Executable()` 自己
- 刪掉 `bundled_collector_{windows,other}.go`(embed + 攤檔那套)
- 三支打包腳本不再編/複製第二支;版本注入同時打到兩個 package
- build-win.sh 的機械閘改成**直接問它**:`--collector --version` 回得出版本才放行
(舊閘是比大小,只能證明「有 embed」,證明不了「分派是對的」)
## 驗(真機實跑)
· `.app/Contents/MacOS/` 只有 **一個** 執行檔(原本兩個)
· 跑起來兩個行程是**同一個 exe**:
…/MacOS/arcrun-app
…/MacOS/arcrun-app --collector direct --config …
· `~/.arcrun-rag/bin` **不存在**(沒有任何東西被攤出來)
· 端到端:丟檔進看守資料夾 → collector.log `"status":"ingested","http_status":200`
· `lsappinfo` 仍是 `type="UIElement"`、`Version="0.18.9"`
· collector 39 檔測試全過;app 測試過;go vet 全綠;mac + windows 交叉編譯皆過
· 單檔 26MB → 22MB(不再夾帶第二份完整程式)
117 lines
3.5 KiB
Go
117 lines
3.5 KiB
Go
// template_install.go — daemon 代裝 system-dev-template(daemon-beta task 2)。
|
||
//
|
||
// leo 四步定稿第 1 步:「幫你在指定資料夾安裝我們現有的 template,這個測過很多次,
|
||
// 但要讓 daemon 可用」——即**不走 git clone**:template 快照(templatefs/,vendored)
|
||
// 打包進二進位,daemon 直接鋪檔。
|
||
//
|
||
// 冪等鐵律:**已存在的檔案一律不覆寫**(用戶的 status.md/wiki 是他的資產;
|
||
// 升級 template 版本=另案 update 流程,不在本函式)。
|
||
package collector
|
||
|
||
import (
|
||
"embed"
|
||
"fmt"
|
||
"io/fs"
|
||
"os"
|
||
"path/filepath"
|
||
)
|
||
|
||
// templateFS 是 system-dev-template 的 vendored 快照(版本見 templatefs/system-dev/VERSION)。
|
||
//
|
||
//go:embed all:templatefs
|
||
var templateFS embed.FS
|
||
|
||
const templateFSRoot = "templatefs"
|
||
|
||
// TemplateInstallResult 是一次代裝的結果帳目。
|
||
type TemplateInstallResult struct {
|
||
Installed []string `json:"installed"` // 本次新鋪的檔(相對路徑)
|
||
Skipped []string `json:"skipped"` // 已存在故跳過的檔
|
||
Version string `json:"version"` // 快照版本(templatefs/system-dev/VERSION)
|
||
}
|
||
|
||
// TemplateVersion 讀出內嵌快照的版本號。
|
||
func TemplateVersion() string {
|
||
data, err := templateFS.ReadFile(templateFSRoot + "/system-dev/VERSION")
|
||
if err != nil {
|
||
return "unknown"
|
||
}
|
||
v := string(data)
|
||
for len(v) > 0 && (v[len(v)-1] == '\n' || v[len(v)-1] == '\r') {
|
||
v = v[:len(v)-1]
|
||
}
|
||
return v
|
||
}
|
||
|
||
// InstallTemplate 把內嵌 template 鋪進 root。冪等:既有檔案跳過不覆寫。
|
||
func InstallTemplate(root string) (*TemplateInstallResult, error) {
|
||
res := &TemplateInstallResult{Version: TemplateVersion()}
|
||
absRoot, err := filepath.Abs(root)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := os.MkdirAll(absRoot, 0o755); err != nil {
|
||
return nil, fmt.Errorf("建目標資料夾失敗:%w", err)
|
||
}
|
||
err = fs.WalkDir(templateFS, templateFSRoot, func(p string, d fs.DirEntry, werr error) error {
|
||
if werr != nil {
|
||
return werr
|
||
}
|
||
if d.IsDir() {
|
||
return nil
|
||
}
|
||
rel, rerr := filepath.Rel(templateFSRoot, p)
|
||
if rerr != nil {
|
||
return rerr
|
||
}
|
||
dest := filepath.Join(absRoot, rel)
|
||
if _, serr := os.Stat(dest); serr == nil {
|
||
res.Skipped = append(res.Skipped, rel)
|
||
return nil // 冪等:不覆寫用戶既有檔
|
||
}
|
||
if merr := os.MkdirAll(filepath.Dir(dest), 0o755); merr != nil {
|
||
return merr
|
||
}
|
||
data, derr := templateFS.ReadFile(p)
|
||
if derr != nil {
|
||
return derr
|
||
}
|
||
if werr2 := os.WriteFile(dest, data, 0o644); werr2 != nil {
|
||
return werr2
|
||
}
|
||
res.Installed = append(res.Installed, rel)
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return res, nil
|
||
}
|
||
|
||
// TemplateInstalled 判斷 root 是否已鋪過(以 wiki 標記檔存在為準)。
|
||
func TemplateInstalled(root string) bool {
|
||
_, err := os.Stat(filepath.Join(root, "system-dev", "wiki", "status.md"))
|
||
return err == nil
|
||
}
|
||
|
||
// runTemplateInstall 是 `collector template-install` 子命令主體。
|
||
func runTemplateInstall(args []string) int {
|
||
fs2 := newFlagSet()
|
||
folder := fs2.String("folder", "", "要鋪 template 的資料夾(必填)")
|
||
if err := fs2.Parse(args); err != nil {
|
||
return 2
|
||
}
|
||
if *folder == "" {
|
||
fmt.Fprintln(os.Stderr, "錯誤:--folder 為必填")
|
||
return 2
|
||
}
|
||
res, err := InstallTemplate(*folder)
|
||
if err != nil {
|
||
fmt.Fprintln(os.Stderr, "collector template-install:", err)
|
||
return 1
|
||
}
|
||
fmt.Printf("template v%s:新鋪 %d 檔、跳過既有 %d 檔 → %s\n",
|
||
res.Version, len(res.Installed), len(res.Skipped), *folder)
|
||
return 0
|
||
}
|