diff --git a/backive.yml b/backive.yml index 9f1915c..3f2bbec 100644 --- a/backive.yml +++ b/backive.yml @@ -1,17 +1,18 @@ devices: scanner-usbstick: uuid: 72A9-D8C5 - mountname: scanner_usbstick + #mountname: scanner_usbstick owner: qwc server-backups: uuid: d220b335-ded9-48f5-bc03-800a3f953c34 - mountname: server_backups + #mountname: server_backups owner: qwc backups: scanner_usbstick_test: user: qwc - target_device: scanner-usbstick + targetDevice: scanner-usbstick frequency: 0 + scriptPath: ./testbackup.sh script: | #! /usr/bin/env bash # @@ -23,9 +24,12 @@ backups: cp -Rv ${BACKIVE_FROM}/* ${BACKIVE_MOUNT}/${BACKIVE_TO}/ ls -la ${BACKIVE_MOUNT}/${BACKIVE_TO} ls -la ${BACKIVE_MOUNT} - from: /home/qwc/web/worktime - to: worktime -preferences: - mount_root: /mnt/backups + sourcePath: /home/qwc/web/worktime + targetPath: worktime +settings: + systemMountPoint: /mnt/backups + unixSocketLocation: /tmp/backive/backive.sock + logLocation: /var/log/backive + dbLocation: /var/lib/backive diff --git a/cmd/backive/main.go b/cmd/backive/main.go index 55966df..d82b332 100644 --- a/cmd/backive/main.go +++ b/cmd/backive/main.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "container/list" "encoding/json" "fmt" @@ -8,6 +9,8 @@ import ( "log" "net" "os" + "os/exec" + "path" "time" "github.com/spf13/viper" @@ -15,14 +18,20 @@ import ( var logfile os.File -func setupLogging() { - logdir := "/var/log/backive" - logname := "/var/log/backive/backive.log" - if _, err := os.Stat(logdir); err == nil { +func createDirectoryIfNotExists(dir string) { + if _, err := os.Stat(dir); err == nil { //ignore } else if os.IsNotExist(err) { - os.MkdirAll(logdir, 0755) + os.MkdirAll(dir, 0755) + } else { + log.Fatal(err) } +} + +func setupLogging() { + logname := "/var/log/backive/backive.log" + logdir, _ := path.Split(logname) + createDirectoryIfNotExists(logdir) logfile, err := os.OpenFile(logname, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) if err != nil { fmt.Println("Error creating logfile!") @@ -45,13 +54,15 @@ type Database struct { path string "/var/lib/backive/data.json" } -// SaveDb saves the database +// Save saves the database func (d *Database) Save() { jsonstr, merr := json.Marshal(d.data) if merr != nil { panic(merr) } - + log.Printf("Writing database output to file: %s", jsonstr) + saveDir, _ := path.Split(d.path) + createDirectoryIfNotExists(saveDir) err := os.WriteFile(d.path, []byte(jsonstr), 0644) if err != nil { panic(err) @@ -74,22 +85,49 @@ func (d *Database) Load() { // Device represents a device, with a name easy to remember and the UUID to identify it, optionally an owner. type Device struct { - Name string `mapstructure:",omitempty"` - UUID string `mapstructure:"uuid"` - OwnerUser string `mapstructure:"owner,omitempty"` - isMounted bool + Name string `mapstructure:",omitempty"` + UUID string `mapstructure:"uuid"` + OwnerUser string `mapstructure:"owner,omitempty"` + isMounted bool + devsByUuid string "/dev/disk/by-uuid/" } // Mount will mount a device -func (d *Device) Mount() { - +func (d *Device) Mount() error { + createDirectoryIfNotExists(config.Settings.SystemMountPoint) + cmd := exec.Command( + "mount", + path.Join(d.devsByUuid, d.UUID), + path.Join(config.Settings.SystemMountPoint, d.Name), + ) + err := cmd.Run() + if err != nil { + log.Fatal(err) + return err + } d.isMounted = true + return nil } // Unmount will unmount a device -func (d *Device) Unmount() { - +func (d *Device) Unmount() error { + sync := exec.Command("sync") + syncErr := sync.Run() + if syncErr != nil { + log.Fatal(syncErr) + return syncErr + } + cmd := exec.Command( + "umount", + path.Join(config.Settings.SystemMountPoint, d.Name), + ) + err := cmd.Run() + if err != nil { + log.Fatal(err) + return err + } d.isMounted = false + return nil } func (d *Device) IsMounted() bool { @@ -100,11 +138,12 @@ func (d *Device) IsMounted() bool { type Backup struct { Name string `mapstructure:",omitempty"` TargetDevice string `mapstructure:"targetDevice"` - TargetDir string `mapstructure:"targetDir"` - SourceDir string `mapstructure:"sourceDir"` + TargetPath string `mapstructure:"targetPath"` + SourcePath string `mapstructure:"sourcePath"` ScriptPath string `mapstructure:"scriptPath"` Frequency int `mapstructure:"frequency"` ExeUser string `mapstructure:"user,omitempty"` + logger *log.Logger } // Configuration struct holding the settings and config items of devices and backups @@ -120,6 +159,8 @@ type Settings struct { SystemMountPoint string `mapstructure:"systemMountPoint"` UserMountPoint string `mapstructure:"userMountPoint"` UnixSocketLocation string `mapstructure:"unixSocketLocation"` + LogLocation string `mapstructure:"logLocation"` + DbLocation string `mapstructure:"dbLocation"` } // Devices is nothing else than a name to Device type mapping @@ -128,6 +169,15 @@ type Devices map[string]Device // Backups is nothing else than a name to Backup type mapping type Backups map[string]Backup +func (bs *Backups) findBackupForDevice(d Device) (*Backup, bool) { + for _, b := range *bs { + if d.Name == b.TargetDevice { + return &b, true + } + } + return nil, false +} + // CreateViper creates a viper instance for usage later func (c *Configuration) CreateViper() { vconfig := viper.New() @@ -175,7 +225,10 @@ type EventHandler struct { // Init initializes the unix socket. func (eh *EventHandler) Init(socketPath string) { + log.Println("Initializing EventHandler...") var err error + dir, _ := path.Split(socketPath) + createDirectoryIfNotExists(dir) eh.ls, err = net.Listen("unix", socketPath) if err != nil { panic(err) @@ -185,11 +238,12 @@ func (eh *EventHandler) Init(socketPath string) { // Listen starts the event loop. func (eh *EventHandler) Listen() { - for { - go func() { + log.Println("Running eventloop") + func() { + for { eh.process() - }() - } + } + }() } // RegisterCallback adds a function to the list of callback functions for processing of events. @@ -200,6 +254,7 @@ func (eh *EventHandler) RegisterCallback(cb func(map[string]string)) { // process processes each and every unix socket event, Unmarshals the json data and calls the list of callbacks. func (eh *EventHandler) process() { client, err := eh.ls.Accept() + log.Println("Accepted client") if err != nil { log.Fatal(err) } @@ -215,45 +270,68 @@ func (eh *EventHandler) process() { break } } - log.Println(data) + sdata := string(bytes.Trim(data, "\x00")) + log.Println(sdata) env := map[string]string{} - errjson := json.Unmarshal(data, &env) + errjson := json.Unmarshal([]byte(sdata), &env) if errjson != nil { log.Fatal(errjson) } for _, v := range eh.callbacks { - v(env) + if v != nil { + v(env) + } } } +func (b *Backup) CanRun() error { + // target path MUST exist + if b.TargetPath == "" { + return fmt.Errorf("The setting targetPath MUST exist within a backup configuration.") + } + // script must exist, having only script means this is handled in the script + if b.ScriptPath == "" { + return fmt.Errorf("The setting scriptPath must exist within a backup configuration.") + } + return nil +} + +func (b *Backup) PrepareRun() error { + createDirectoryIfNotExists(path.Join( + config.Settings.SystemMountPoint, + b.TargetDevice, + b.TargetPath, + )) + // configure extra logger + logname := "/var/log/backive/backive.log" + logdir, _ := path.Split(logname) + createDirectoryIfNotExists(logdir) + logname = path.Join(logdir, b.Name) + logfile, err := os.OpenFile(logname, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + log.Fatalln("Error creating logfile!") + } + writer := io.MultiWriter(logfile) + b.logger = log.New(writer, b.Name, log.LstdFlags) + return nil +} + // Run runs the backup script with appropriate rights. func (b *Backup) Run() error { - cfg := config - if dev := cfg.Devices[b.Name]; dev.IsMounted() { - checkExistence := func(path string, name string) error { - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("%s does not exist", name) - } else { - return fmt.Errorf("Error when checking %s: %w", name, err) - } - } - return nil - } - // Check for existence of target dir - if err := checkExistence(b.TargetDir, "target directory"); err != nil { - return err - } - // check for existence of source dir - if err := checkExistence(b.SourceDir, "source directory"); err != nil { - return err - } - // check for existence of script path - if err := checkExistence(b.ScriptPath, "script path"); err != nil { - return err - } + if dev, ok := config.Devices[b.Name]; ok && dev.IsMounted() { // setup script environment including user to use + cmd := exec.Command("/usr/bin/sh", b.ScriptPath) + b.logger.Printf("Running backup script of '%s'", b.Name) + // does this work? + cmd.Stdout = b.logger.Writer() + cmd.Stderr = b.logger.Writer() // run script + err := cmd.Run() + if err != nil { + log.Fatalf("Backup '%s' run failed", b.Name) + return err + } + return nil } // quit with error that the device is not available. return fmt.Errorf("The device is not mounted") @@ -327,6 +405,48 @@ func (r *Runs) LastRun(b Backup) (time.Time, error) { return time.Unix(0, 0), fmt.Errorf("Backup name not found and therefore has never run") } +func defaultCallback(envMap map[string]string) { + if action, ok := envMap["ACTION"]; ok && action == "add" { + var dev Device + var uuid string + if fs_uuid, ok := envMap["ID_FS_UUID"]; !ok { + log.Fatalln("ID_FS_UUID not available ?!") + } else { + uuid = fs_uuid + } + log.Println("Device Added") + var uuidFound bool + // Check the devices if the UUID is in the config + for _, device := range config.Devices { + if uuid == device.UUID { + uuidFound = true + dev = device + } + } + if uuidFound { + dev.Mount() + backup, found := config.Backups.findBackupForDevice(dev) + if found { + err := backup.CanRun() + if err == nil { + prepErr := backup.PrepareRun() + if prepErr != nil { + log.Fatalf("Error running the backup routine: %v", err) + } + rerr := backup.Run() + if rerr != nil { + log.Fatalf("Error running the backup routine: %v", err) + } + } else { + log.Fatalf("Error running the backup routine: %v", err) + } + } + dev.Unmount() + } + + } +} + func main() { setupLogging() // TODO: do proper signal handling! @@ -337,10 +457,10 @@ func main() { runs.Load(database) // init scheduler and check for next needed runs? - // start event loop events.Init(config.Settings.UnixSocketLocation) - // accept event + events.RegisterCallback(defaultCallback) + // accept events events.Listen() // cleanup if anything is there to cleanup