148 lines
3.6 KiB
Go
Raw Normal View History

2025-01-30 22:14:23 +08:00
package watchtower
import (
2025-01-30 22:53:43 +08:00
"bytes"
2025-01-30 22:14:23 +08:00
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var dbWatchlist []string
func AddWatchDb(dsn string) {
dbWatchlist = append(dbWatchlist, dsn)
}
func BackupDb() error {
backupPath := viper.GetString("watchtower.database_backups")
if err := os.MkdirAll(backupPath, 0775); err != nil {
return fmt.Errorf("failed to create backup path: %v", err)
}
outFile := filepath.Join(
backupPath,
fmt.Sprintf("watchtower_db_backup_%s", time.Now().Format("2006-01-02 15:04:05")),
)
var password string
var user string
var host string
var port string
dsnParts := strings.Split(viper.GetString("database.dsn"), " ")
for _, part := range dsnParts {
if strings.HasPrefix(part, "password=") {
password = strings.Replace(part, "password=", "", 1)
} else if strings.HasPrefix(part, "user=") {
2025-01-30 23:08:07 +08:00
user = strings.Replace(part, "user=", "", 1)
2025-01-30 22:14:23 +08:00
} else if strings.HasPrefix(part, "host=") {
host = strings.Replace(part, "host=", "", 1)
} else if strings.HasPrefix(part, "port=") {
port = strings.Replace(part, "port=", "", 1)
}
}
2025-01-30 23:08:07 +08:00
log.Info().
Str("password", password).Str("user", user).
Str("host", host).Str("port", port).
Msg("Starting backup database...")
2025-01-30 22:14:23 +08:00
cmd := exec.Command("pg_dumpall",
"-h", host,
"-p", port,
"-U", user,
"-f", outFile,
2025-01-30 23:08:07 +08:00
"-W",
2025-01-30 22:14:23 +08:00
)
2025-01-30 23:08:07 +08:00
cmd.Env = os.Environ()
2025-01-30 22:14:23 +08:00
2025-01-30 22:53:43 +08:00
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
2025-01-30 23:08:07 +08:00
stdin, err := cmd.StdinPipe()
if err != nil {
return fmt.Errorf("failed to get dumpall stdin: %v", err)
}
2025-01-30 22:14:23 +08:00
start := time.Now()
2025-01-30 23:08:07 +08:00
if err := cmd.Start(); err != nil {
log.Error().
Err(err).Str("stdout", stdout.String()).Str("stderr", stderr.String()).
Msg("Failed to start backing up the database...")
return err
}
if _, err = stdin.Write([]byte(password + "\n")); err != nil {
log.Error().
Err(err).Str("stdout", stdout.String()).Str("stderr", stderr.String()).
Msg("Failed to passing the password for backuping up the database...")
return err
}
if err = stdin.Close(); err != nil {
log.Error().
Err(err).Str("stdout", stdout.String()).Str("stderr", stderr.String()).
Msg("Failed to end backing up stdin...")
return err
}
if err = cmd.Wait(); err != nil {
2025-01-30 22:53:43 +08:00
log.Error().
Err(err).Str("stdout", stdout.String()).Str("stderr", stderr.String()).
Msg("Failed to backup the database...")
2025-01-30 22:14:23 +08:00
return err
}
2025-01-30 23:08:07 +08:00
2025-01-30 22:14:23 +08:00
took := time.Since(start)
2025-01-30 23:08:07 +08:00
2025-01-30 22:53:43 +08:00
log.Info().
Str("out", outFile).Dur("took", took).
Str("stdout", stdout.String()).Str("stderr", stderr.String()).
Msg("Backed up database successfully!")
2025-01-30 22:14:23 +08:00
return nil
}
func CleanDb(dsn string) error {
conn, err := gorm.Open(postgres.Open(dsn))
if err != nil {
return fmt.Errorf("failed to open database: %v", err)
}
var tables []string
if err := conn.Raw("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'").Scan(&tables).Error; err != nil {
return fmt.Errorf("failed to scan tables: %v", err)
}
deadline := time.Now().Add(-30 * 24 * time.Hour) // 30 days before
for _, table := range tables {
sql := fmt.Sprintf("DELETE FROM %s WHERE deleted_at < ?", table)
if err := conn.Raw(sql, deadline).Error; err != nil {
log.Warn().Err(err).Str("table", table).Str("dsn", dsn).Msg("Unable to clean soft deleted records in this table...")
}
}
return nil
}
func CleanAllDb() {
for _, database := range dbWatchlist {
if err := CleanDb(database); err != nil {
log.Error().Err(err).Msg("Failed to clean up a database...")
}
}
}
func RunDbMaintenance() {
if err := BackupDb(); err != nil {
return
}
CleanAllDb()
}