Files
Felis/internal/backup/verify_restored.go
T
Lemon-miaow adf563ffe3 feat(distributed): 支持单主控多节点部署和停服迁移
复用现有 k3s 调度和 Job 生命周期,增加 worker 接入与批准、受保护节点身份、归档传输、持久迁移锁及活动 PVC 切换;同步管理员 API、CLI、面板和隔离规则。分布式模式默认关闭,保持单机兼容。

验证:Go 全量测试与 vet;面板 874 个测试、lint/build;Linux VM 安装器测试、清单服务端 dry-run、网络命名空间防火墙实测。A/B/C 三机 WireGuard、Velocity 和迁移验收仍待完成。
2026-10-01 19:48:37 +08:00

70 lines
1.4 KiB
Go

package backup
import (
"archive/tar"
"compress/gzip"
"context"
"crypto/sha256"
"errors"
"fmt"
"io"
"os"
)
// VerifyRestored reads target files back and compares them with every regular archive entry.
// OpenRoot keeps a malicious path or existing symlink inside the world volume.
func VerifyRestored(ctx context.Context, ref, world string) error {
root, err := os.OpenRoot(world)
if err != nil {
return err
}
defer root.Close()
f, err := os.Open(ref)
if err != nil {
return err
}
defer f.Close()
gz, err := gzip.NewReader(f)
if err != nil {
return err
}
defer gz.Close()
tr := tar.NewReader(gz)
for {
if err := ctx.Err(); err != nil {
return err
}
h, err := tr.Next()
if errors.Is(err, io.EOF) {
return nil
}
if err != nil {
return err
}
if h.Typeflag != tar.TypeReg && h.Typeflag != tar.TypeRegA {
continue
}
dst, err := root.Open(h.Name)
if err != nil {
return err
}
st, err := dst.Stat()
if err != nil || !st.Mode().IsRegular() || st.Size() != h.Size {
dst.Close()
return fmt.Errorf("restored entry %q has the wrong type or size", h.Name)
}
sourceHash, targetHash := sha256.New(), sha256.New()
_, err = io.Copy(sourceHash, tr)
if err == nil {
_, err = io.Copy(targetHash, dst)
}
dst.Close()
if err != nil {
return err
}
if string(sourceHash.Sum(nil)) != string(targetHash.Sum(nil)) {
return fmt.Errorf("restored entry %q failed SHA-256 read-back", h.Name)
}
}
}