gomcp/internal/infrastructure/audit/backup.go

89 lines
2.5 KiB
Go

package audit
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
)
// PeerBackupPayload is the data sent to trusted peers for log backup.
type PeerBackupPayload struct {
PeerID string `json:"peer_id"`
Timestamp time.Time `json:"timestamp"`
LogHash string `json:"log_hash"` // SHA-256 of decisions.log
Entries int `json:"entries"` // Number of decision entries
Snapshot string `json:"snapshot"` // Last N entries for quick restore
}
// PeerBackupResult describes the outcome of a backup attempt.
type PeerBackupResult struct {
BackedUp bool `json:"backed_up"`
FilePath string `json:"file_path,omitempty"`
Size int64 `json:"size,omitempty"`
}
// CreateBackupSnapshot creates a local backup snapshot of decisions.log
// that can be sent to trusted peers via P2P transport.
// Saves to .rlm/decisions_backup.json for peer sync.
func CreateBackupSnapshot(rlmDir, peerID string, maxEntries int) (*PeerBackupPayload, error) {
logPath := filepath.Join(rlmDir, decisionsFileName)
data, err := os.ReadFile(logPath)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("backup: no decisions.log to back up")
}
return nil, fmt.Errorf("backup: read: %w", err)
}
lines := splitLines(string(data))
// Filter empty lines.
var entries []string
for _, l := range lines {
if l != "" {
entries = append(entries, l)
}
}
// Take last N entries for snapshot.
if maxEntries <= 0 {
maxEntries = 100
}
snapshot := entries
if len(entries) > maxEntries {
snapshot = entries[len(entries)-maxEntries:]
}
snapshotJSON, _ := json.Marshal(snapshot)
payload := &PeerBackupPayload{
PeerID: peerID,
Timestamp: time.Now(),
Entries: len(entries),
Snapshot: string(snapshotJSON),
}
// Save backup file locally.
backupPath := filepath.Join(rlmDir, "decisions_backup.json")
backupData, _ := json.MarshalIndent(payload, "", " ")
if err := os.WriteFile(backupPath, backupData, 0o644); err != nil {
return nil, fmt.Errorf("backup: write: %w", err)
}
return payload, nil
}
// RestoreFromBackup loads a backup snapshot (received from a peer).
func RestoreFromBackup(rlmDir string) (*PeerBackupPayload, error) {
backupPath := filepath.Join(rlmDir, "decisions_backup.json")
data, err := os.ReadFile(backupPath)
if err != nil {
return nil, fmt.Errorf("restore: %w", err)
}
var payload PeerBackupPayload
if err := json.Unmarshal(data, &payload); err != nil {
return nil, fmt.Errorf("restore: parse: %w", err)
}
return &payload, nil
}