Added signal handling and saving the database

This commit is contained in:
Marcel Otte 2022-01-04 19:18:13 +01:00
parent 0af8e5cda5
commit 23cee291cf
1 changed files with 107 additions and 59 deletions

View File

@ -2,7 +2,6 @@ package main
import ( import (
"bytes" "bytes"
"container/list"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@ -10,8 +9,10 @@ import (
"net" "net"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"path" "path"
"strings" "strings"
"syscall"
"time" "time"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -120,23 +121,26 @@ func (d *Device) Mount() error {
// Unmount will unmount a device // Unmount will unmount a device
func (d *Device) Unmount() error { func (d *Device) Unmount() error {
log.Printf("Unmounting %s", d.Name) if d.isMounted {
sync := exec.Command("sync") log.Printf("Unmounting %s", d.Name)
syncErr := sync.Run() sync := exec.Command("sync")
if syncErr != nil { syncErr := sync.Run()
log.Println(syncErr) if syncErr != nil {
return syncErr log.Println(syncErr)
return syncErr
}
cmd := exec.Command(
"umount",
path.Join(config.Settings.SystemMountPoint, d.Name),
)
log.Printf("About to run: %s", cmd.String())
err := cmd.Run()
if err != nil {
log.Println(err)
return err
}
d.isMounted = false
} }
cmd := exec.Command(
"umount",
path.Join(config.Settings.SystemMountPoint, d.Name),
)
err := cmd.Run()
if err != nil {
log.Println(err)
return err
}
d.isMounted = false
return nil return nil
} }
@ -179,14 +183,16 @@ type Devices map[string]*Device
// Backups is nothing else than a name to Backup type mapping // Backups is nothing else than a name to Backup type mapping
type Backups map[string]*Backup type Backups map[string]*Backup
// findBackupForDevice only finds the first backup which is configured for a given device. // findBackupsForDevice only finds the first backup which is configured for a given device.
func (bs *Backups) findBackupForDevice(d Device) (*Backup, bool) { func (bs *Backups) findBackupsForDevice(d Device) ([]*Backup, bool) {
var backups []*Backup = []*Backup{}
for _, b := range *bs { for _, b := range *bs {
if d.Name == b.TargetDevice { if d.Name == b.TargetDevice {
return b, true backups = append(backups, b)
} }
} }
return nil, false var ret bool = len(backups) > 0
return backups, ret
} }
// CreateViper creates a viper instance for usage later // CreateViper creates a viper instance for usage later
@ -309,6 +315,9 @@ func (b *Backup) CanRun() error {
if b.ScriptPath == "" { if b.ScriptPath == "" {
return fmt.Errorf("The setting scriptPath must exist within a backup configuration.") return fmt.Errorf("The setting scriptPath must exist within a backup configuration.")
} }
if !b.ShouldRun() {
return fmt.Errorf("Frequency (days inbetween) not reached.")
}
return nil return nil
} }
@ -355,15 +364,14 @@ func (b *Backup) Run() error {
log.Printf("ERROR: Script path is relative, aborting.") log.Printf("ERROR: Script path is relative, aborting.")
return fmt.Errorf("Script path is relative, aborting.") return fmt.Errorf("Script path is relative, aborting.")
} }
// setup script environment including user to use
cmd := exec.Command("/usr/bin/sh", b.ScriptPath) cmd := exec.Command("/usr/bin/sh", b.ScriptPath)
if b.ExeUser != "" { if b.ExeUser != "" {
// setup script environment including user to use
cmd = exec.Command("sudo", "-E", "-u", b.ExeUser, "/usr/bin/sh", b.ScriptPath) cmd = exec.Command("sudo", "-E", "-u", b.ExeUser, "/usr/bin/sh", b.ScriptPath)
} }
b.logger.Printf("Running backup script of '%s'", b.Name) b.logger.Printf("Running backup script of '%s'", b.Name)
b.logger.Printf("Script is: %s", b.ScriptPath) b.logger.Printf("Script is: %s", b.ScriptPath)
b.logger.Printf("Full command is: %s", cmd.String()) b.logger.Printf("Full command is: %s", cmd.String())
// does this work?
cmd.Stdout = b.logger.Writer() cmd.Stdout = b.logger.Writer()
cmd.Stderr = b.logger.Writer() cmd.Stderr = b.logger.Writer()
cmd.Env = []string{ cmd.Env = []string{
@ -383,19 +391,16 @@ func (b *Backup) Run() error {
log.Printf("Backup '%s' run failed", b.Name) log.Printf("Backup '%s' run failed", b.Name)
return err return err
} }
runs.RegisterRun(b)
return nil return nil
} }
// quit with error that the device is not available. // quit with error that the device is not available.
return fmt.Errorf("The device is not mounted") return fmt.Errorf("The device is not mounted")
} }
type backupRuns struct {
runlist *list.List
}
// Runs contains the Data for the scheduler: mapping from backups to a list of timestamps of the last 10 backups // Runs contains the Data for the scheduler: mapping from backups to a list of timestamps of the last 10 backups
type Runs struct { type Runs struct {
data map[string]backupRuns data map[string][]time.Time
} }
// Load loads the data from the json database // Load loads the data from the json database
@ -426,7 +431,7 @@ func (b *Backup) ShouldRun() bool {
if ok == nil { if ok == nil {
dur := time.Since(lr) dur := time.Since(lr)
days := dur.Hours() / 24 days := dur.Hours() / 24
if days > float64(freq) { if days >= float64(freq) {
return true return true
} }
} }
@ -437,13 +442,16 @@ func (b *Backup) ShouldRun() bool {
} }
// RegisterRun saves a date of a backup run into the internal storage // RegisterRun saves a date of a backup run into the internal storage
func (r *Runs) RegisterRun(b Backup) { func (r *Runs) RegisterRun(b *Backup) {
if r.data == nil {
r.data = map[string][]time.Time{}
}
nbl, ok := r.data[b.Name] nbl, ok := r.data[b.Name]
if !ok { if !ok {
nbl.runlist = list.New() nbl = make([]time.Time, 1)
r.data[b.Name] = nbl
} }
nbl.runlist.PushFront(time.Now()) nbl = append([]time.Time{time.Now()}, nbl...)
r.data[b.Name] = nbl
r.Save(database) r.Save(database)
} }
@ -451,8 +459,11 @@ func (r *Runs) RegisterRun(b Backup) {
func (r *Runs) LastRun(b Backup) (time.Time, error) { func (r *Runs) LastRun(b Backup) (time.Time, error) {
_, ok := r.data[b.Name] _, ok := r.data[b.Name]
if ok { if ok {
var t = time.Time(r.data[b.Name].runlist.Front().Value.(time.Time)) slice := r.data[b.Name]
return t, nil if len(slice) > 0 {
var t = time.Time(slice[0])
return t, nil
}
} }
return time.Unix(0, 0), fmt.Errorf("Backup name not found and therefore has never run") return time.Unix(0, 0), fmt.Errorf("Backup name not found and therefore has never run")
} }
@ -479,32 +490,35 @@ func defaultCallback(envMap map[string]string) {
if uuidFound { if uuidFound {
log.Println("Device recognized.") log.Println("Device recognized.")
log.Printf("Device: Name: %s, UUID: %s", dev.Name, dev.UUID) log.Printf("Device: Name: %s, UUID: %s", dev.Name, dev.UUID)
dev.Mount() backups, found := config.Backups.findBackupsForDevice(*dev)
log.Println("Device mounted.")
backup, found := config.Backups.findBackupForDevice(*dev)
log.Println("Searching configured backups...") log.Println("Searching configured backups...")
if found { if found {
log.Println("Backup found.") for _, backup := range backups {
err := backup.CanRun() log.Printf("Backup found: %s", backup.Name)
if err == nil { err := backup.CanRun()
log.Println("Backup is able to run (config check passed).") if err == nil {
prepErr := backup.PrepareRun() // only mount device if we really have to do a backup!
log.Println("Prepared run.") dev.Mount()
if prepErr != nil { log.Println("Device mounted.")
log.Printf("Error running the backup routine: %v", err) log.Println("Backup is able to run (config check passed).")
prepErr := backup.PrepareRun()
log.Println("Prepared run.")
if prepErr != nil {
log.Printf("Error running the backup routine: %v", err)
}
log.Println("Running backup.")
rerr := backup.Run()
if rerr != nil {
log.Printf("Error running the backup routine: %v", err)
}
dev.Unmount()
} else {
log.Printf("Backup '%s' can not run (error or frequency not reached): %s", backup.Name, err)
} }
log.Println("Running backup.")
rerr := backup.Run()
if rerr != nil {
log.Printf("Error running the backup routine: %v", err)
}
} else {
log.Printf("Error running the backup routine: %v", err)
} }
} else { } else {
log.Println("No backup found, unmounting.") log.Println("No backup found.")
} }
dev.Unmount()
} }
} }
@ -512,6 +526,44 @@ func defaultCallback(envMap map[string]string) {
func main() { func main() {
setupLogging() setupLogging()
signal_chan := make(chan os.Signal, 1)
signal.Notify(signal_chan,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
)
exit_chan := make(chan int)
go func() {
for {
s := <-signal_chan
switch s {
case syscall.SIGHUP:
log.Println("hungup")
case syscall.SIGINT:
log.Println("Ctrl+C, quitting.")
exit_chan <- 0
case syscall.SIGTERM:
log.Println("Terminating.")
exit_chan <- 0
case syscall.SIGQUIT:
log.Println("Quitting")
exit_chan <- 0
default:
log.Println("Unknown signal.")
exit_chan <- 1
}
}
}()
go func() {
// exit function only does something when the exit_chan has an item
// cleaning up stuff
code := <-exit_chan
database.Save()
log.Printf("Received exit code (%d), shutting down.", code)
os.Exit(code)
}()
// TODO: do proper signal handling! // TODO: do proper signal handling!
log.Println("backive starting up...") log.Println("backive starting up...")
// find and load config // find and load config
@ -525,8 +577,4 @@ func main() {
events.RegisterCallback(defaultCallback) events.RegisterCallback(defaultCallback)
// accept events // accept events
events.Listen() events.Listen()
// cleanup if anything is there to cleanup
database.Save()
log.Println("backive shuting down.")
} }