talas-group/talas-wiki/internal/wiki/backup.go
senke 66471934af Initial commit: Talas Group project management & documentation
Knowledge base of ~80+ markdown files across 14 domains (00-13),
Logseq graph, hardware design files (KiCAD), infrastructure configs,
and talas-wiki static site.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 20:10:41 +02:00

112 lines
2.5 KiB
Go

package wiki
import (
"archive/tar"
"compress/gzip"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
type BackupManager struct {
docsRoot string
backupDir string
maxKeep int
}
func NewBackupManager(docsRoot string, maxKeep int) *BackupManager {
dir := filepath.Join(docsRoot, ".wiki-data", "backups")
os.MkdirAll(dir, 0755)
return &BackupManager{docsRoot: docsRoot, backupDir: dir, maxKeep: maxKeep}
}
// CreateBackup creates a tar.gz backup of all markdown files
func (bm *BackupManager) CreateBackup() (string, error) {
filename := fmt.Sprintf("backup-%s.tar.gz", time.Now().Format("2006-01-02-150405"))
path := filepath.Join(bm.backupDir, filename)
f, err := os.Create(path)
if err != nil {
return "", err
}
defer f.Close()
gzw := gzip.NewWriter(f)
defer gzw.Close()
tw := tar.NewWriter(gzw)
defer tw.Close()
filepath.Walk(bm.docsRoot, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return nil
}
relPath, _ := filepath.Rel(bm.docsRoot, path)
if strings.HasPrefix(relPath, ".wiki-data") || strings.HasPrefix(relPath, ".") ||
strings.HasPrefix(relPath, "talas-wiki") || strings.HasPrefix(relPath, "13_ARCHIVES") {
return nil
}
if !strings.HasSuffix(relPath, ".md") {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return nil
}
tw.WriteHeader(&tar.Header{
Name: relPath,
Size: int64(len(data)),
Mode: 0644,
ModTime: info.ModTime(),
})
tw.Write(data)
return nil
})
bm.rotate()
return filename, nil
}
// ListBackups returns available backups sorted by date
func (bm *BackupManager) ListBackups() []string {
entries, err := os.ReadDir(bm.backupDir)
if err != nil {
return nil
}
var backups []string
for _, e := range entries {
if strings.HasSuffix(e.Name(), ".tar.gz") {
backups = append(backups, e.Name())
}
}
sort.Sort(sort.Reverse(sort.StringSlice(backups)))
return backups
}
// GetBackupPath returns the absolute path of a backup file
func (bm *BackupManager) GetBackupPath(filename string) string {
return filepath.Join(bm.backupDir, filepath.Base(filename))
}
func (bm *BackupManager) rotate() {
backups := bm.ListBackups()
if len(backups) <= bm.maxKeep {
return
}
for _, name := range backups[bm.maxKeep:] {
os.Remove(filepath.Join(bm.backupDir, name))
}
}
// StartAutoBackup runs a backup every interval, keeping maxKeep copies
func (bm *BackupManager) StartAutoBackup(interval time.Duration) {
go func() {
for {
time.Sleep(interval)
bm.CreateBackup()
}
}()
}