mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
## 背景 仓库早已迁移到 GitHub(`github.com/JianFeeeee/HomeAgent`,CI/CD 都在那边跑), gitcode 保留为**国内只读镜像**(`ci.yml` 的 sync-gitcode-main 快进推送)。 但 go.mod 的 module 路径一直是 `gitcode.com/JianFeeeee/...` —— 既与权威源 不符,`meta.SourceURL`(AGPL §13 的源码提供入口)也指向镜像而非实际运行的代码。 ## 关键区分(这是本次改动安全的前提) 两个 module 是**不同**的,影响面差一个数量级: | module | 谁依赖 | 改它的后果 | |---|---|---| | `.../HomeAgent`(主仓) | **只有主仓自己** | 内部重构,零外部影响 | | `.../homeagent-sdk`(SDK 仓) | 主仓 49 文件 + 所有外部插件 | 需重编插件 | ★ 实测确认:**插件不 import 主仓 module**(`strings qq/plugin.bin | grep HomeAgent` = 0), 只依赖 SDK 仓。而插件与内核的边界是 **RPC 方法名字符串** (`plugin.init` / `tool.invoke`…,见 proc/protocol.go),**与 Go module 名无关**。 ⇒ module 改名**不改变任何 wire 协议**,现网已装的插件照常运行。 SDK 的 GitHub 仓名是 `homeagentsdk`(无连字符),module 路径必须与之精确匹配, 否则 `go get` 会因路径不符失败,故取 `github.com/JianFeeeee/homeagentsdk`。 ## 改动 - `go.mod` / `go.sum`:两个 module 路径 + require/replace - 277 个 Go 文件 + Makefile + 打包脚本的 import 与 ldflags 路径 - SDK 仓 `meta.CoreModule`、`tools/hmapdev` 的 module 声明与工程模板 - 对外文档/链接的仓库地址(README、PLUGIN_DEV、OVERVIEW 等) ## 明确**保留** gitcode 的地方(镜像机制,不能动) - `GITCODE_TOKEN`、`GC_URL="…@gitcode.com"`(CI 同步 job) - `gitcode.com/api/v5/...`(release 附件上传) - git remote `gitcode`(镜像拉取/推送) - `docs/zh/ci-cd-runbook.md`(整篇讲镜像运维) ## 验证 - `go build ./...` / `go vet ./...` 绿 - `go test ./... -count=1` → 48 包 ok / 0 FAIL - hmapdev 工具链编译通过(`third_party/homeagent-sdk/tools/hmapdev`) - 全仓残留检查:module 形态的 `gitcode.com/JianFeeeee` = 0 - 镜像机制抽验:GC_URL / @gitcode / api/v5 / remote 均完好 ## 顺带修正 `hmapdev/templates.go` 的注释此前写「gitcode 的模块不在 proxy.golang.org 上」—— module 已迁 GitHub,该论断不再成立。改为如实说明**为何仍默认 replace 到本机 SDK**: 离线可构建、避免版本漂移、内网可用。理由与托管地无关,故 replace 保留。
150 lines
3.6 KiB
Go
150 lines
3.6 KiB
Go
package tracker
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"time"
|
||
|
||
sdk "github.com/JianFeeeee/HomeAgent/internal/sdk"
|
||
)
|
||
|
||
// DTO 已迁入内置 SDK,此处保留别名以兼容现有调用方。
|
||
type ChangeType = sdk.ChangeType
|
||
|
||
const (
|
||
ChangeFileCreated = sdk.ChangeFileCreated
|
||
ChangeFileModified = sdk.ChangeFileModified
|
||
ChangeFileDeleted = sdk.ChangeFileDeleted
|
||
)
|
||
|
||
type FileChange = sdk.FileChange
|
||
type ChangeSet = sdk.ChangeSet
|
||
|
||
func NewChangeSet(action string) *ChangeSet {
|
||
return &ChangeSet{
|
||
ID: fmt.Sprintf("cs_%d", time.Now().UnixNano()),
|
||
Action: action,
|
||
Timestamp: time.Now(),
|
||
}
|
||
}
|
||
|
||
func fileHash(path string) (string, int64, error) {
|
||
data, err := os.ReadFile(path)
|
||
if err != nil {
|
||
return "", 0, err
|
||
}
|
||
h := sha256.Sum256(data)
|
||
return hex.EncodeToString(h[:]), int64(len(data)), nil
|
||
}
|
||
|
||
// maxCapturedContent 回滚内容捕获上限:超大文件不保存原文(回滚时跳过并告警)。
|
||
const maxCapturedContent = 8 << 20
|
||
|
||
func fileInfo(path string) (size int64, modTime time.Time, err error) {
|
||
info, err := os.Stat(path)
|
||
if err != nil {
|
||
return 0, time.Time{}, err
|
||
}
|
||
return info.Size(), info.ModTime(), nil
|
||
}
|
||
|
||
type FSState struct {
|
||
Files map[string]FileChange `json:"files"`
|
||
Root string `json:"root"`
|
||
}
|
||
|
||
// captureFSState 仅记录哈希/尺寸(用于"之后"快照,省内存)。
|
||
func captureFSState(root string) (*FSState, error) {
|
||
state := &FSState{
|
||
Files: make(map[string]FileChange),
|
||
Root: root,
|
||
}
|
||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||
if err != nil || info.IsDir() {
|
||
return err
|
||
}
|
||
rel, _ := filepath.Rel(root, path)
|
||
hash, size, err := fileHash(path)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
state.Files[rel] = FileChange{
|
||
Path: rel,
|
||
HashAfter: hash,
|
||
SizeAfter: size,
|
||
}
|
||
return nil
|
||
})
|
||
return state, err
|
||
}
|
||
|
||
// captureFSStateWithContent 额外捕获文件原文(用于"之前"基线,供回滚还原被改/被删文件)。
|
||
func captureFSStateWithContent(root string) (*FSState, error) {
|
||
state, err := captureFSState(root)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
for rel := range state.Files {
|
||
path := filepath.Join(root, rel)
|
||
info, err := os.Stat(path)
|
||
if err != nil || info.Size() > maxCapturedContent {
|
||
continue
|
||
}
|
||
if data, err := os.ReadFile(path); err == nil {
|
||
fc := state.Files[rel]
|
||
fc.Content = data
|
||
state.Files[rel] = fc
|
||
}
|
||
}
|
||
return state, nil
|
||
}
|
||
|
||
func diffStates(before, after *FSState) []FileChange {
|
||
var changes []FileChange
|
||
if before == nil || after == nil {
|
||
return changes
|
||
}
|
||
seen := make(map[string]bool)
|
||
|
||
for path, afterFile := range after.Files {
|
||
seen[path] = true
|
||
if beforeFile, ok := before.Files[path]; ok {
|
||
if beforeFile.HashAfter != afterFile.HashAfter {
|
||
changes = append(changes, FileChange{
|
||
Path: path,
|
||
Type: ChangeFileModified,
|
||
HashBefore: beforeFile.HashAfter,
|
||
HashAfter: afterFile.HashAfter,
|
||
SizeBefore: beforeFile.SizeAfter,
|
||
SizeAfter: afterFile.SizeAfter,
|
||
Content: beforeFile.Content, // 原始内容,供回滚还原
|
||
})
|
||
}
|
||
} else {
|
||
changes = append(changes, FileChange{
|
||
Path: path,
|
||
Type: ChangeFileCreated,
|
||
HashAfter: afterFile.HashAfter,
|
||
SizeAfter: afterFile.SizeAfter,
|
||
})
|
||
}
|
||
}
|
||
|
||
for path := range before.Files {
|
||
if !seen[path] {
|
||
changes = append(changes, FileChange{
|
||
Path: path,
|
||
Type: ChangeFileDeleted,
|
||
HashBefore: before.Files[path].HashAfter,
|
||
SizeBefore: before.Files[path].SizeAfter,
|
||
Content: before.Files[path].Content, // 原始内容,供回滚还原
|
||
})
|
||
}
|
||
}
|
||
|
||
return changes
|
||
}
|