mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 17:10:10 +00:00
backups: add backup daemon to run every n minutes, keep x most recent backups
This commit is contained in:
parent
c0c91b4aad
commit
733ab37539
@ -112,6 +112,9 @@ func (app *appContext) loadConfig() error {
|
|||||||
|
|
||||||
app.MustSetValue("telegram", "show_on_reg", "true")
|
app.MustSetValue("telegram", "show_on_reg", "true")
|
||||||
|
|
||||||
|
app.MustSetValue("backups", "every_n_minutes", "1440")
|
||||||
|
app.MustSetValue("backups", "path", filepath.Join(app.dataPath, "backups"))
|
||||||
|
|
||||||
app.config.Section("jellyfin").Key("version").SetValue(version)
|
app.config.Section("jellyfin").Key("version").SetValue(version)
|
||||||
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
|
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
|
||||||
app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", version, commit))
|
app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", version, commit))
|
||||||
|
@ -1557,6 +1557,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"backups": {
|
||||||
|
"order": [],
|
||||||
|
"meta": {
|
||||||
|
"name": "Backups",
|
||||||
|
"description": "Settings for database backups."
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"enabled": {
|
||||||
|
"name": "Enabled",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "bool",
|
||||||
|
"value": false,
|
||||||
|
"description": "Enable to generate database backups on a schedule."
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"name": "Backup Path",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Path to directory to store backups in. defaults to <data_directory>/backups."
|
||||||
|
},
|
||||||
|
"every_n_minutes": {
|
||||||
|
"name": "Backup frequency (Minutes)",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "number",
|
||||||
|
"value": 1440,
|
||||||
|
"description": "Backup after this many minutes has passed since the last. Resets every restart."
|
||||||
|
},
|
||||||
|
"keep_n_backups": {
|
||||||
|
"name": "Number of backups to keep",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "number",
|
||||||
|
"value": 20,
|
||||||
|
"description": "Number of most recent backups to keep. Once this is hit, the oldest backup will be deleted before doing a new one."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"welcome_email": {
|
"welcome_email": {
|
||||||
"order": [],
|
"order": [],
|
||||||
"meta": {
|
"meta": {
|
||||||
|
109
daemon.go
109
daemon.go
@ -1,6 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v3"
|
"github.com/dgraph-io/badger/v3"
|
||||||
@ -8,6 +12,12 @@ import (
|
|||||||
"github.com/timshannon/badgerhold/v4"
|
"github.com/timshannon/badgerhold/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BACKUP_PREFIX = "jfa-go-db-"
|
||||||
|
BACKUP_DATEFMT = "2006-01-02T15-04-05"
|
||||||
|
BACKUP_SUFFIX = ".bak"
|
||||||
|
)
|
||||||
|
|
||||||
// clearEmails removes stored emails for users which no longer exist.
|
// clearEmails removes stored emails for users which no longer exist.
|
||||||
// meant to be called with other such housekeeping functions, so assumes
|
// meant to be called with other such housekeeping functions, so assumes
|
||||||
// the user cache is fresh.
|
// the user cache is fresh.
|
||||||
@ -74,6 +84,87 @@ func (app *appContext) clearTelegram() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BackupList struct {
|
||||||
|
files []os.DirEntry
|
||||||
|
dates []time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl BackupList) Len() int { return len(bl.files) }
|
||||||
|
func (bl BackupList) Swap(i, j int) {
|
||||||
|
bl.files[i], bl.files[j] = bl.files[j], bl.files[i]
|
||||||
|
bl.dates[i], bl.dates[j] = bl.dates[j], bl.dates[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl BackupList) Less(i, j int) bool {
|
||||||
|
// Push non-backup files to the end of the array,
|
||||||
|
// Since they didn't have a date parsed.
|
||||||
|
if bl.dates[i].IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if bl.dates[j].IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Sort by oldest first
|
||||||
|
return bl.dates[j].After(bl.dates[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *appContext) makeBackup() {
|
||||||
|
toKeep := app.config.Section("backups").Key("keep_n_backups").MustInt(20)
|
||||||
|
fname := BACKUP_PREFIX + time.Now().Local().Format(BACKUP_DATEFMT) + BACKUP_SUFFIX
|
||||||
|
path := app.config.Section("backups").Key("path").String()
|
||||||
|
err := os.MkdirAll(path, 0755)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to create backup directory \"%s\": %v\n", path, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
items, err := os.ReadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to read backup directory \"%s\": %v\n", path, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
backups := BackupList{}
|
||||||
|
backups.files = items
|
||||||
|
backups.dates = make([]time.Time, len(items))
|
||||||
|
backupCount := 0
|
||||||
|
for i, item := range items {
|
||||||
|
if item.IsDir() || !(strings.HasSuffix(item.Name(), BACKUP_SUFFIX)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t, err := time.Parse(BACKUP_DATEFMT, strings.TrimSuffix(strings.TrimPrefix(item.Name(), BACKUP_PREFIX), BACKUP_SUFFIX))
|
||||||
|
if err != nil {
|
||||||
|
app.debug.Printf("Failed to parse backup filename \"%s\": %v\n", item.Name(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
backups.dates[i] = t
|
||||||
|
backupCount++
|
||||||
|
}
|
||||||
|
toDelete := backupCount + 1 - toKeep
|
||||||
|
if toDelete > 0 {
|
||||||
|
sort.Sort(backups)
|
||||||
|
for _, item := range backups.files[:toDelete] {
|
||||||
|
fullpath := filepath.Join(path, item.Name())
|
||||||
|
app.debug.Printf("Deleting old backup \"%s\"\n", item.Name())
|
||||||
|
err := os.Remove(fullpath)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to delete old backup \"%s\": %v\n", fullpath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fullpath := filepath.Join(path, fname)
|
||||||
|
f, err := os.Create(fullpath)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to open backup file \"%s\": %v\n", fullpath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = app.storage.db.Badger().Backup(f, 0)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to create backup: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (app *appContext) clearActivities() {
|
func (app *appContext) clearActivities() {
|
||||||
app.debug.Println("Husekeeping: Cleaning up Activity log...")
|
app.debug.Println("Husekeeping: Cleaning up Activity log...")
|
||||||
keepCount := app.config.Section("activity_log").Key("keep_n_records").MustInt(1000)
|
keepCount := app.config.Section("activity_log").Key("keep_n_records").MustInt(1000)
|
||||||
@ -116,6 +207,24 @@ type housekeepingDaemon struct {
|
|||||||
app *appContext
|
app *appContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newBackupDaemon(app *appContext) *housekeepingDaemon {
|
||||||
|
interval := time.Duration(app.config.Section("backups").Key("every_n_minutes").MustInt(1440)) * time.Minute
|
||||||
|
daemon := housekeepingDaemon{
|
||||||
|
Stopped: false,
|
||||||
|
ShutdownChannel: make(chan string),
|
||||||
|
Interval: interval,
|
||||||
|
period: interval,
|
||||||
|
app: app,
|
||||||
|
}
|
||||||
|
daemon.jobs = []func(app *appContext){
|
||||||
|
func(app *appContext) {
|
||||||
|
app.debug.Println("Backups: Creating backup")
|
||||||
|
app.makeBackup()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &daemon
|
||||||
|
}
|
||||||
|
|
||||||
func newInviteDaemon(interval time.Duration, app *appContext) *housekeepingDaemon {
|
func newInviteDaemon(interval time.Duration, app *appContext) *housekeepingDaemon {
|
||||||
daemon := housekeepingDaemon{
|
daemon := housekeepingDaemon{
|
||||||
Stopped: false,
|
Stopped: false,
|
||||||
|
7
main.go
7
main.go
@ -475,6 +475,13 @@ func start(asDaemon, firstCall bool) {
|
|||||||
go app.checkForUpdates()
|
go app.checkForUpdates()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var backupDaemon *housekeepingDaemon
|
||||||
|
if app.config.Section("backups").Key("enabled").MustBool(false) {
|
||||||
|
backupDaemon = newBackupDaemon(app)
|
||||||
|
go backupDaemon.run()
|
||||||
|
defer backupDaemon.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
if telegramEnabled {
|
if telegramEnabled {
|
||||||
app.telegram, err = newTelegramDaemon(app)
|
app.telegram, err = newTelegramDaemon(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -161,7 +161,6 @@ func newUpdater(buildroneURL, namespace, repo, version, commit, buildType string
|
|||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
binary += ".exe"
|
binary += ".exe"
|
||||||
}
|
}
|
||||||
fmt.Println("monitoring", tag)
|
|
||||||
return &Updater{
|
return &Updater{
|
||||||
httpClient: &http.Client{Timeout: 10 * time.Second},
|
httpClient: &http.Client{Timeout: 10 * time.Second},
|
||||||
timeoutHandler: common.NewTimeoutHandler("updater", buildroneURL, true),
|
timeoutHandler: common.NewTimeoutHandler("updater", buildroneURL, true),
|
||||||
|
Loading…
Reference in New Issue
Block a user