Getting some parts to run as expected.

This commit is contained in:
Marcel Otte 2021-12-20 23:17:24 +01:00
parent d91e1abc58
commit 6aa693c7cb
2 changed files with 181 additions and 57 deletions

View File

@ -1,17 +1,18 @@
devices: devices:
scanner-usbstick: scanner-usbstick:
uuid: 72A9-D8C5 uuid: 72A9-D8C5
mountname: scanner_usbstick #mountname: scanner_usbstick
owner: qwc owner: qwc
server-backups: server-backups:
uuid: d220b335-ded9-48f5-bc03-800a3f953c34 uuid: d220b335-ded9-48f5-bc03-800a3f953c34
mountname: server_backups #mountname: server_backups
owner: qwc owner: qwc
backups: backups:
scanner_usbstick_test: scanner_usbstick_test:
user: qwc user: qwc
target_device: scanner-usbstick targetDevice: scanner-usbstick
frequency: 0 frequency: 0
scriptPath: ./testbackup.sh
script: | script: |
#! /usr/bin/env bash #! /usr/bin/env bash
# #
@ -23,9 +24,12 @@ backups:
cp -Rv ${BACKIVE_FROM}/* ${BACKIVE_MOUNT}/${BACKIVE_TO}/ cp -Rv ${BACKIVE_FROM}/* ${BACKIVE_MOUNT}/${BACKIVE_TO}/
ls -la ${BACKIVE_MOUNT}/${BACKIVE_TO} ls -la ${BACKIVE_MOUNT}/${BACKIVE_TO}
ls -la ${BACKIVE_MOUNT} ls -la ${BACKIVE_MOUNT}
from: /home/qwc/web/worktime sourcePath: /home/qwc/web/worktime
to: worktime targetPath: worktime
preferences: settings:
mount_root: /mnt/backups systemMountPoint: /mnt/backups
unixSocketLocation: /tmp/backive/backive.sock
logLocation: /var/log/backive
dbLocation: /var/lib/backive

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"container/list" "container/list"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -8,6 +9,8 @@ import (
"log" "log"
"net" "net"
"os" "os"
"os/exec"
"path"
"time" "time"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -15,14 +18,20 @@ import (
var logfile os.File var logfile os.File
func setupLogging() { func createDirectoryIfNotExists(dir string) {
logdir := "/var/log/backive" if _, err := os.Stat(dir); err == nil {
logname := "/var/log/backive/backive.log"
if _, err := os.Stat(logdir); err == nil {
//ignore //ignore
} else if os.IsNotExist(err) { } 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) logfile, err := os.OpenFile(logname, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
if err != nil { if err != nil {
fmt.Println("Error creating logfile!") fmt.Println("Error creating logfile!")
@ -45,13 +54,15 @@ type Database struct {
path string "/var/lib/backive/data.json" path string "/var/lib/backive/data.json"
} }
// SaveDb saves the database // Save saves the database
func (d *Database) Save() { func (d *Database) Save() {
jsonstr, merr := json.Marshal(d.data) jsonstr, merr := json.Marshal(d.data)
if merr != nil { if merr != nil {
panic(merr) 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) err := os.WriteFile(d.path, []byte(jsonstr), 0644)
if err != nil { if err != nil {
panic(err) 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. // Device represents a device, with a name easy to remember and the UUID to identify it, optionally an owner.
type Device struct { type Device struct {
Name string `mapstructure:",omitempty"` Name string `mapstructure:",omitempty"`
UUID string `mapstructure:"uuid"` UUID string `mapstructure:"uuid"`
OwnerUser string `mapstructure:"owner,omitempty"` OwnerUser string `mapstructure:"owner,omitempty"`
isMounted bool isMounted bool
devsByUuid string "/dev/disk/by-uuid/"
} }
// Mount will mount a device // 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 d.isMounted = true
return nil
} }
// Unmount will unmount a device // 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 d.isMounted = false
return nil
} }
func (d *Device) IsMounted() bool { func (d *Device) IsMounted() bool {
@ -100,11 +138,12 @@ func (d *Device) IsMounted() bool {
type Backup struct { type Backup struct {
Name string `mapstructure:",omitempty"` Name string `mapstructure:",omitempty"`
TargetDevice string `mapstructure:"targetDevice"` TargetDevice string `mapstructure:"targetDevice"`
TargetDir string `mapstructure:"targetDir"` TargetPath string `mapstructure:"targetPath"`
SourceDir string `mapstructure:"sourceDir"` SourcePath string `mapstructure:"sourcePath"`
ScriptPath string `mapstructure:"scriptPath"` ScriptPath string `mapstructure:"scriptPath"`
Frequency int `mapstructure:"frequency"` Frequency int `mapstructure:"frequency"`
ExeUser string `mapstructure:"user,omitempty"` ExeUser string `mapstructure:"user,omitempty"`
logger *log.Logger
} }
// Configuration struct holding the settings and config items of devices and backups // Configuration struct holding the settings and config items of devices and backups
@ -120,6 +159,8 @@ type Settings struct {
SystemMountPoint string `mapstructure:"systemMountPoint"` SystemMountPoint string `mapstructure:"systemMountPoint"`
UserMountPoint string `mapstructure:"userMountPoint"` UserMountPoint string `mapstructure:"userMountPoint"`
UnixSocketLocation string `mapstructure:"unixSocketLocation"` UnixSocketLocation string `mapstructure:"unixSocketLocation"`
LogLocation string `mapstructure:"logLocation"`
DbLocation string `mapstructure:"dbLocation"`
} }
// Devices is nothing else than a name to Device type mapping // 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 // Backups is nothing else than a name to Backup type mapping
type Backups map[string]Backup 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 // CreateViper creates a viper instance for usage later
func (c *Configuration) CreateViper() { func (c *Configuration) CreateViper() {
vconfig := viper.New() vconfig := viper.New()
@ -175,7 +225,10 @@ type EventHandler struct {
// Init initializes the unix socket. // Init initializes the unix socket.
func (eh *EventHandler) Init(socketPath string) { func (eh *EventHandler) Init(socketPath string) {
log.Println("Initializing EventHandler...")
var err error var err error
dir, _ := path.Split(socketPath)
createDirectoryIfNotExists(dir)
eh.ls, err = net.Listen("unix", socketPath) eh.ls, err = net.Listen("unix", socketPath)
if err != nil { if err != nil {
panic(err) panic(err)
@ -185,11 +238,12 @@ func (eh *EventHandler) Init(socketPath string) {
// Listen starts the event loop. // Listen starts the event loop.
func (eh *EventHandler) Listen() { func (eh *EventHandler) Listen() {
for { log.Println("Running eventloop")
go func() { func() {
for {
eh.process() eh.process()
}() }
} }()
} }
// RegisterCallback adds a function to the list of callback functions for processing of events. // 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. // process processes each and every unix socket event, Unmarshals the json data and calls the list of callbacks.
func (eh *EventHandler) process() { func (eh *EventHandler) process() {
client, err := eh.ls.Accept() client, err := eh.ls.Accept()
log.Println("Accepted client")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -215,45 +270,68 @@ func (eh *EventHandler) process() {
break break
} }
} }
log.Println(data) sdata := string(bytes.Trim(data, "\x00"))
log.Println(sdata)
env := map[string]string{} env := map[string]string{}
errjson := json.Unmarshal(data, &env) errjson := json.Unmarshal([]byte(sdata), &env)
if errjson != nil { if errjson != nil {
log.Fatal(errjson) log.Fatal(errjson)
} }
for _, v := range eh.callbacks { 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. // Run runs the backup script with appropriate rights.
func (b *Backup) Run() error { func (b *Backup) Run() error {
cfg := config if dev, ok := config.Devices[b.Name]; ok && dev.IsMounted() {
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
}
// setup script environment including user to use // 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 // 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. // 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")
@ -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") 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() { func main() {
setupLogging() setupLogging()
// TODO: do proper signal handling! // TODO: do proper signal handling!
@ -337,10 +457,10 @@ func main() {
runs.Load(database) runs.Load(database)
// init scheduler and check for next needed runs? // init scheduler and check for next needed runs?
// start event loop // start event loop
events.Init(config.Settings.UnixSocketLocation) events.Init(config.Settings.UnixSocketLocation)
// accept event events.RegisterCallback(defaultCallback)
// accept events
events.Listen() events.Listen()
// cleanup if anything is there to cleanup // cleanup if anything is there to cleanup