mirror of https://github.com/qwc/backive.git
Getting some parts to run as expected.
This commit is contained in:
parent
d91e1abc58
commit
6aa693c7cb
18
backive.yml
18
backive.yml
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -78,18 +89,45 @@ type Device struct {
|
||||||
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() {
|
||||||
|
log.Println("Running eventloop")
|
||||||
|
func() {
|
||||||
for {
|
for {
|
||||||
go func() {
|
|
||||||
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,46 +270,69 @@ 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 {
|
||||||
|
if v != nil {
|
||||||
v(env)
|
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() {
|
// setup script environment including user to use
|
||||||
checkExistence := func(path string, name string) error {
|
cmd := exec.Command("/usr/bin/sh", b.ScriptPath)
|
||||||
if _, err := os.Stat(path); err != nil {
|
b.logger.Printf("Running backup script of '%s'", b.Name)
|
||||||
if os.IsNotExist(err) {
|
// does this work?
|
||||||
return fmt.Errorf("%s does not exist", name)
|
cmd.Stdout = b.logger.Writer()
|
||||||
} else {
|
cmd.Stderr = b.logger.Writer()
|
||||||
return fmt.Errorf("Error when checking %s: %w", name, err)
|
// run script
|
||||||
}
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Backup '%s' run failed", b.Name)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
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
|
|
||||||
// run script
|
|
||||||
}
|
|
||||||
// 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
|
||||||
|
|
Loading…
Reference in New Issue