initial commit
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user