Simplifying my work with moving everything to one go file

Yes, doesn't look so pretty, but I have to keep my overview, improvments
can be done anytime afterwards aswell.
This commit is contained in:
Marcel Otte 2021-12-10 22:28:09 +01:00
parent 627c6cc622
commit 17ec990dc9
1 changed files with 298 additions and 8 deletions

View File

@ -1,26 +1,316 @@
package main package backive
import ( import (
"container/list"
"encoding/json"
"fmt" "fmt"
"io"
"net"
"os"
"time"
"github.com/qwc/backive/config" "github.com/spf13/viper"
) )
// Global variables for backive
var (
database Database
config Configuration
runs Runs
events EventHandler
)
// Database is a simple string to string mapping, where arbitrary strings can be stored and safed to disk or loaded
type Database struct {
data map[string]string
path string "/var/lib/backive/data.json"
}
// SaveDb saves the database
func (d Database) Save() {
jsonstr, merr := json.Marshal(d.data)
if merr != nil {
panic(merr)
}
err := os.WriteFile(d.path, []byte(jsonstr), 0644)
if err != nil {
panic(err)
}
}
// LoadDb loads the database
func (d Database) Load() {
data, err := os.ReadFile(d.path)
if err != nil {
panic(err)
}
json.Unmarshal(data, &d.data)
}
// 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
}
// Mount will mount a device
func (d Device) Mount() {
d.isMounted = true
}
// Unmount will unmount a device
func (d Device) Unmount() {
d.isMounted = false
}
func (d Device) IsMounted() bool {
return d.isMounted
}
// Backup contains all necessary information for executing a configured backup.
type Backup struct {
Name string `mapstructure:",omitempty"`
TargetDevice string `mapstructure:"targetDevice"`
TargetDir string `mapstructure:"targetDir"`
SourceDir string `mapstructure:"sourceDir"`
ScriptPath string `mapstructure:"scriptPath"`
Frequency int `mapstructure:"frequency"`
ExeUser string `mapstructure:"user,omitempty"`
}
// Configuration struct holding the settings and config items of devices and backups
type Configuration struct {
Settings Settings `mapstructure:"settings"`
Devices Devices `mapstructure:"devices"`
Backups Backups `mapstructure:"backups"`
vconfig *viper.Viper
}
// Settings struct holds the global configuration items
type Settings struct {
SystemMountPoint string `mapstructure:"systemMountPoint"`
UserMountPoint string `mapstructure:"userMountPoint"`
UnixSocketLocation string `mapstructure:"unixSocketLocation"`
}
// Devices is nothing else than a name to Device type mapping
type Devices map[string]Device
// Backups is nothing else than a name to Backup type mapping
type Backups map[string]Backup
// CreateViper creates a viper instance for usage later
func (c Configuration) CreateViper() {
vconfig := viper.New()
vconfig.SetConfigName("backive")
vconfig.SetConfigType("yaml")
vconfig.AddConfigPath("/etc/backive/") // system config
vconfig.AddConfigPath("$HOME/.backive/")
vconfig.AddConfigPath(".")
c.vconfig = vconfig
}
// Load loads the configuration from the disk
func (c Configuration) Load() {
c.CreateViper()
if err := c.vconfig.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
panic(fmt.Errorf("Fatal: No config file could be found"))
}
panic(fmt.Errorf("Fatal error config file: %w ", err))
}
//Unmarshal all into Configuration type
err := c.vconfig.Unmarshal(c)
if err != nil {
fmt.Printf("Error occured when loading config: %v\n", err)
panic("No configuration available!")
}
for k, v := range c.Backups {
v.Name = k
}
for k, v := range c.Devices {
v.Name = k
}
}
type EventHandler struct {
ls net.Listener
done <-chan struct{}
callbacks []func(map[string]string)
}
// Init initializes the unix socket.
func (eh EventHandler) Init(socketPath string) {
var err error
eh.ls, err = net.Listen("unix", socketPath)
if err != nil {
panic(err)
}
eh.callbacks = make([]func(map[string]string), 3)
}
// Listen starts the event loop.
func (eh EventHandler) Listen() {
for {
go func() {
eh.process()
}()
}
}
// RegisterCallback adds a function to the list of callback functions for processing of events.
func (eh EventHandler) RegisterCallback(cb func(map[string]string)) {
eh.callbacks = append(eh.callbacks, cb)
}
// 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()
if err != nil {
panic(err)
}
data := make([]byte, 2048)
for {
buf := make([]byte, 512)
nr, err := client.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
data = append(data, buf[0:nr]...)
if err == io.EOF {
break
}
}
env := map[string]string{}
errjson := json.Unmarshal(data, &env)
if errjson != nil {
panic(errjson)
}
for _, v := range eh.callbacks {
v(env)
}
}
// Run runs the backup script with appropriate rights.
func (b Backup) Run() error {
cfg := config
if cfg.Devices[b.Name].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
// run script
}
// quit with error that the device is not available.
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
type Runs struct {
data map[string]backupRuns
}
// Load loads the data from the json database
func (r Runs) Load(db Database) {
runerr := json.Unmarshal([]byte(db.data["runs"]), &r.data)
if runerr != nil {
panic(runerr)
}
}
// Save saves the data into the json database
func (r Runs) Save(db Database) {
str, err := json.Marshal(r.data)
if err != nil {
panic(err)
}
db.data["runs"] = string(str)
}
// ShouldRun Takes a backup key and returns a bool if a backup should run now.
func (b Backup) ShouldRun() bool {
freq := b.Frequency
// calculate time difference from last run, return true if no run has taken place
lr, ok := runs.LastRun(b)
if ok == nil {
dur := time.Since(lr)
days := dur.Hours() / 24
if days > float64(freq) {
return true
}
}
if freq == 0 {
return true
}
return false
}
// RegisterRun saves a date of a backup run into the internal storage
func (r Runs) RegisterRun(b Backup) {
nbl, ok := r.data[b.Name]
if !ok {
nbl.runlist = list.New()
r.data[b.Name] = nbl
}
nbl.runlist.PushFront(time.Now())
r.Save(database)
}
// LastRun returns the time.Time of the last run of the backup given.
func (r Runs) LastRun(b Backup) (time.Time, error) {
_, ok := r.data[b.Name]
if ok {
var t = time.Time(r.data[b.Name].runlist.Front().Value.(time.Time))
return t, nil
}
return time.Unix(0, 0), fmt.Errorf("Backup name not found and therefore has never run")
}
func main() { func main() {
// TODO: do proper signal handling! // TODO: do proper signal handling!
fmt.Println("vim-go") fmt.Println("backive starting up...")
// find and load config // find and load config
database.Load()
config.Load() config.Load()
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)
// accept event // accept event
// find associated device and it's backups events.Listen()
// mount device
// run backups, one after another if multiple
// unmount device
// end loop
// cleanup if anything is there to cleanup // cleanup if anything is there to cleanup
database.Save()
fmt.Println("backive shuting down.")
} }