97 lines
3.3 KiB
Go
97 lines
3.3 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/owen/vibedns/internal/auditlog"
|
|
"github.com/owen/vibedns/internal/backup"
|
|
)
|
|
|
|
// BackupStatus returns the backup configuration and inventory.
|
|
func (a *App) BackupStatus() backup.Status { return a.Backups.Status() }
|
|
|
|
// BackupList lists the available backup files, newest first.
|
|
func (a *App) BackupList() ([]backup.Info, error) {
|
|
list, err := a.Backups.List()
|
|
if err != nil {
|
|
return nil, Internal(err, "The backup directory could not be read.")
|
|
}
|
|
return list, nil
|
|
}
|
|
|
|
// RunBackup creates a backup immediately.
|
|
func (a *App) RunBackup(ctx context.Context, actor auditlog.Actor) (backup.Info, error) {
|
|
info, err := a.Backups.Run(ctx)
|
|
if err != nil {
|
|
return info, Invalid("Database backup failed: %s", err.Error())
|
|
}
|
|
a.Audit.Record(ctx, actor, "backup.create", auditlog.ObjectBackup, info.Name, info.Name,
|
|
auditlog.Changes("bytes", fmt.Sprint(info.SizeBytes)))
|
|
return info, nil
|
|
}
|
|
|
|
// OpenBackup opens a backup file for download.
|
|
func (a *App) OpenBackup(name string) (*os.File, backup.Info, error) {
|
|
path, err := backup.Resolve(a.Backups.Directory(), name)
|
|
if err != nil {
|
|
return nil, backup.Info{}, NotFound("%s", err.Error())
|
|
}
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, backup.Info{}, Internal(err, "The backup could not be opened.")
|
|
}
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
f.Close()
|
|
return nil, backup.Info{}, Internal(err, "The backup could not be read.")
|
|
}
|
|
return f, backup.Info{Name: name, Path: path, SizeBytes: fi.Size(), CreatedAt: fi.ModTime()}, nil
|
|
}
|
|
|
|
// DeleteBackup removes a backup file.
|
|
func (a *App) DeleteBackup(ctx context.Context, actor auditlog.Actor, name string) error {
|
|
if err := backup.Delete(a.Backups.Directory(), name); err != nil {
|
|
return NotFound("%s", err.Error())
|
|
}
|
|
a.Audit.Record(ctx, actor, "backup.delete", auditlog.ObjectBackup, name, name, "")
|
|
return nil
|
|
}
|
|
|
|
// StageRestore validates a backup and schedules it to replace the live
|
|
// database on the next start.
|
|
//
|
|
// The swap is deliberately deferred: overwriting the database file while
|
|
// connections are open would leave the running process reading a file that no
|
|
// longer exists. The operator restarts, and the restore is applied cleanly
|
|
// before anything opens the database.
|
|
func (a *App) StageRestore(ctx context.Context, actor auditlog.Actor, name string) error {
|
|
path, err := backup.Resolve(a.Backups.Directory(), name)
|
|
if err != nil {
|
|
return NotFound("%s", err.Error())
|
|
}
|
|
if err := backup.StageRestore(a.DB.Path(), path); err != nil {
|
|
return Invalid("%s", err.Error())
|
|
}
|
|
a.Audit.Record(ctx, actor, "backup.restore_staged", auditlog.ObjectBackup, name, name,
|
|
"applies on the next restart")
|
|
a.Log.Warn("database restore staged; it will be applied on the next start", "backup", name)
|
|
return nil
|
|
}
|
|
|
|
// PendingRestore reports whether a restore is waiting for a restart.
|
|
func (a *App) PendingRestore() bool {
|
|
_, pending := backup.PendingRestore(a.DB.Path())
|
|
return pending
|
|
}
|
|
|
|
// CancelRestore discards a staged restore.
|
|
func (a *App) CancelRestore(ctx context.Context, actor auditlog.Actor) error {
|
|
if err := backup.CancelRestore(a.DB.Path()); err != nil {
|
|
return Internal(err, "The staged restore could not be cancelled.")
|
|
}
|
|
a.Audit.Record(ctx, actor, "backup.restore_cancelled", auditlog.ObjectBackup, "", "", "")
|
|
return nil
|
|
}
|