122 lines
3.7 KiB
Go
122 lines
3.7 KiB
Go
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
|
|
}
|