initial
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
package bsdaily
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func Run() error {
|
||||
snapshotDir, snapshotDate, err := FindLatestDailySnapshot()
|
||||
if err != nil {
|
||||
return fmt.Errorf("finding latest snapshot: %w", err)
|
||||
}
|
||||
slog.Info("found latest daily snapshot", "dir", snapshotDir, "snapshot_date", snapshotDate.Format("2006-01-02"))
|
||||
|
||||
targetDay := snapshotDate.AddDate(0, 0, -1)
|
||||
slog.Info("target day for extraction", "date", targetDay.Format("2006-01-02"))
|
||||
|
||||
// Check if output already exists
|
||||
outputDir := filepath.Join(DailiesBase, targetDay.Format("2006-01"))
|
||||
outputFinal := filepath.Join(outputDir, targetDay.Format("2006-01-02")+".sql.zst")
|
||||
if _, err := os.Stat(outputFinal); err == nil {
|
||||
return fmt.Errorf("output file already exists: %s", outputFinal)
|
||||
}
|
||||
|
||||
// Check disk space
|
||||
if err := CheckFreeSpace(TmpBase, MinTmpFreeBytes, "tmpBase"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := CheckFreeSpace(DailiesBase, MinDailiesFreeBytes, "dailiesBase"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create temp directory
|
||||
tmpDir, err := os.MkdirTemp(TmpBase, "bsarchivesegment-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating temp directory in %s: %w", TmpBase, err)
|
||||
}
|
||||
slog.Info("created temp directory", "path", tmpDir)
|
||||
defer func() {
|
||||
slog.Info("cleaning up temp directory", "path", tmpDir)
|
||||
if err := os.RemoveAll(tmpDir); err != nil {
|
||||
slog.Error("failed to remove temp directory", "path", tmpDir, "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Copy database files from snapshot to temp
|
||||
srcDB := filepath.Join(snapshotDir, DBFilename)
|
||||
srcWAL := filepath.Join(snapshotDir, WALFilename)
|
||||
srcSHM := filepath.Join(snapshotDir, SHMFilename)
|
||||
dstDB := filepath.Join(tmpDir, DBFilename)
|
||||
dstWAL := filepath.Join(tmpDir, WALFilename)
|
||||
dstSHM := filepath.Join(tmpDir, SHMFilename)
|
||||
|
||||
for _, f := range []string{srcDB, srcWAL} {
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("source file missing: %s: %w", f, err)
|
||||
}
|
||||
if info.Size() == 0 {
|
||||
return fmt.Errorf("source file is empty: %s", f)
|
||||
}
|
||||
slog.Info("source file", "path", f, "size_bytes", info.Size())
|
||||
}
|
||||
|
||||
if err := CopyFile(srcDB, dstDB); err != nil {
|
||||
return fmt.Errorf("copying database: %w", err)
|
||||
}
|
||||
if err := CopyFile(srcWAL, dstWAL); err != nil {
|
||||
return fmt.Errorf("copying WAL: %w", err)
|
||||
}
|
||||
if _, err := os.Stat(srcSHM); err == nil {
|
||||
if err := CopyFile(srcSHM, dstSHM); err != nil {
|
||||
return fmt.Errorf("copying SHM: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := CheckFreeSpace(TmpBase, PostCopyTmpMinFree, "tmpBase (post-copy)"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune database to target day only
|
||||
slog.Info("opening database for pruning", "path", dstDB)
|
||||
if err := PruneDatabase(dstDB, targetDay); err != nil {
|
||||
return fmt.Errorf("pruning database: %w", err)
|
||||
}
|
||||
|
||||
// Dump to SQL and compress
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
return fmt.Errorf("creating output directory %s: %w", outputDir, err)
|
||||
}
|
||||
|
||||
outputTmp := filepath.Join(outputDir, "."+targetDay.Format("2006-01-02")+".sql.zst.tmp")
|
||||
|
||||
slog.Info("dumping and compressing", "tmp_output", outputTmp)
|
||||
if err := DumpAndCompress(dstDB, outputTmp); err != nil {
|
||||
os.Remove(outputTmp)
|
||||
return fmt.Errorf("dump and compress: %w", err)
|
||||
}
|
||||
|
||||
slog.Info("verifying compressed output")
|
||||
if err := VerifyOutput(outputTmp); err != nil {
|
||||
os.Remove(outputTmp)
|
||||
return fmt.Errorf("verification failed: %w", err)
|
||||
}
|
||||
|
||||
// Atomic rename to final path
|
||||
slog.Info("renaming to final output", "from", outputTmp, "to", outputFinal)
|
||||
if err := os.Rename(outputTmp, outputFinal); err != nil {
|
||||
return fmt.Errorf("atomic rename: %w", err)
|
||||
}
|
||||
|
||||
info, err := os.Stat(outputFinal)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stat final output: %w", err)
|
||||
}
|
||||
slog.Info("final output written", "path", outputFinal, "size_bytes", info.Size())
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user