2021-11-14 03:53:13 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2023-06-29 22:58:24 +02:00
|
|
|
"errors"
|
2021-11-14 03:53:13 +01:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2023-06-29 23:20:46 +02:00
|
|
|
"regexp"
|
2023-06-29 22:58:24 +02:00
|
|
|
"sync"
|
2021-11-14 05:21:22 +01:00
|
|
|
"time"
|
2021-11-14 03:53:13 +01:00
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
log "github.com/sirupsen/logrus"
|
2023-06-30 22:04:22 +02:00
|
|
|
"github.com/tailscale/hujson"
|
2021-11-14 03:53:13 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type Config struct {
|
2023-06-29 22:58:24 +02:00
|
|
|
ScheduleDuration map[string]string `json:"schedule"`
|
|
|
|
Box map[string]BoxConfig `json:"box"`
|
|
|
|
Email EmailConfig `json:"email"`
|
|
|
|
Apps []AppConfig `json:"apps"`
|
|
|
|
Timezone string `json:"timezone"`
|
|
|
|
Debug bool `json:"debug"`
|
|
|
|
Admin AdminConfig `json:"admin"`
|
|
|
|
box map[string]*Box `json:"-"`
|
|
|
|
apps map[string]*App `json:"-"`
|
|
|
|
timezone *time.Location `json:"-"`
|
2021-11-14 03:53:13 +01:00
|
|
|
}
|
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
type AdminConfig struct {
|
|
|
|
Addr string `json:"addr"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Password string `json:"password"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type BoxConfig struct {
|
|
|
|
Addr string `json:"addr"`
|
|
|
|
User string `json:"user"`
|
|
|
|
Key string `json:"key"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type AppConfig struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Schedule []string `json:"schedule"`
|
|
|
|
Sources []string `json:"src"`
|
|
|
|
Destinations []string `json:"dest"`
|
|
|
|
Before map[string]string `json:"before"`
|
|
|
|
After map[string]string `json:"after"`
|
|
|
|
Active bool `json:"active"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load config from file
|
|
|
|
func (c *Config) LoadFile(path string) error {
|
|
|
|
log.WithFields(log.Fields{"path": path}).Debugf("starting")
|
|
|
|
defer log.WithFields(log.Fields{"path": path}).Debugf("done")
|
|
|
|
|
|
|
|
b, err := ioutil.ReadFile(path)
|
2021-11-14 03:53:13 +01:00
|
|
|
if err != nil {
|
2023-06-29 22:58:24 +02:00
|
|
|
log.WithFields(log.Fields{"path": path, "error": err, "call": "ioutil.ReadFile"}).Errorf("")
|
2021-11-14 03:53:13 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-06-30 22:04:22 +02:00
|
|
|
b, err = hujson.Standardize(b)
|
2021-11-14 03:53:13 +01:00
|
|
|
if err != nil {
|
2023-06-30 22:04:22 +02:00
|
|
|
log.WithFields(log.Fields{"path": path, "error": err, "call": "hujson.Standardize"}).Errorf("")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(b, &c); err != nil {
|
2023-06-29 22:58:24 +02:00
|
|
|
log.WithFields(log.Fields{"path": path, "error": err, "call": "json.Unmarshal"}).Errorf("")
|
2021-11-14 03:53:13 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
c.timezone, err = time.LoadLocation(cfg.Timezone)
|
2021-11-14 05:21:22 +01:00
|
|
|
if err != nil {
|
2023-06-29 22:58:24 +02:00
|
|
|
log.WithFields(log.Fields{"path": path, "error": err, "call": "time.LoadLocation", "attr": cfg.Timezone}).Errorf("")
|
2021-11-14 05:21:22 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-06-19 06:33:24 +02:00
|
|
|
if len(cfg.Email.SmtpHost) == 0 {
|
2023-06-29 22:58:24 +02:00
|
|
|
err := fmt.Errorf("no smtp")
|
|
|
|
log.WithFields(log.Fields{"path": path, "error": err}).Errorf("")
|
|
|
|
return err
|
2022-06-19 06:31:36 +02:00
|
|
|
}
|
|
|
|
|
2022-06-19 06:33:24 +02:00
|
|
|
if len(cfg.Email.FromEmail) == 0 {
|
2023-06-29 22:58:24 +02:00
|
|
|
err := fmt.Errorf("no email from")
|
|
|
|
log.WithFields(log.Fields{"path": path, "error": err}).Errorf("")
|
|
|
|
return err
|
2022-06-19 06:31:36 +02:00
|
|
|
}
|
|
|
|
|
2022-06-19 06:33:24 +02:00
|
|
|
if len(cfg.Email.ToEmail) == 0 {
|
2023-06-29 22:58:24 +02:00
|
|
|
err := fmt.Errorf("no email to")
|
|
|
|
log.WithFields(log.Fields{"path": path, "error": err}).Errorf("")
|
|
|
|
return err
|
2022-06-19 06:27:25 +02:00
|
|
|
}
|
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
for k, v := range c.ScheduleDuration {
|
|
|
|
switch k {
|
|
|
|
case "hourly":
|
|
|
|
case "daily":
|
|
|
|
case "weekly":
|
|
|
|
case "monthly":
|
|
|
|
case "yearly":
|
|
|
|
if _, err := Expiration(time.Now(), v); err != nil {
|
|
|
|
log.WithFields(log.Fields{"path": path, "schedule": k, "deadline": v, "error": err}).Errorf("")
|
|
|
|
return err
|
2021-11-14 03:53:13 +01:00
|
|
|
}
|
2023-06-29 22:58:24 +02:00
|
|
|
default:
|
|
|
|
err := errors.New("invalid schedule")
|
|
|
|
log.WithFields(log.Fields{"path": path, "schedule": k, "deadline": v, "error": err}).Errorf("")
|
2021-11-14 03:53:13 +01:00
|
|
|
return err
|
|
|
|
}
|
2023-06-29 22:58:24 +02:00
|
|
|
}
|
2021-11-14 03:53:13 +01:00
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
c.box = make(map[string]*Box)
|
|
|
|
for k, v := range c.Box {
|
|
|
|
if b, err := c.NewBox(k, v.Addr, v.User, v.Key); err != nil {
|
|
|
|
log.WithFields(log.Fields{"path": path, "call": "NewBox", "attr": k, "error": err}).Errorf("")
|
2021-11-14 03:53:13 +01:00
|
|
|
return err
|
2023-06-29 22:58:24 +02:00
|
|
|
} else {
|
|
|
|
if _, ok := c.box[k]; ok {
|
|
|
|
err := errors.New("already exists")
|
|
|
|
log.WithFields(log.Fields{"path": path, "attr": k, "error": err}).Errorf("")
|
|
|
|
return err
|
2021-11-14 03:53:13 +01:00
|
|
|
}
|
2023-06-29 22:58:24 +02:00
|
|
|
c.box[k] = b
|
2021-11-14 03:53:13 +01:00
|
|
|
}
|
2023-06-29 22:58:24 +02:00
|
|
|
}
|
2021-11-14 03:53:13 +01:00
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
c.apps = make(map[string]*App)
|
|
|
|
for _, v := range c.Apps {
|
|
|
|
if a, err := c.NewApp(v.Name, v.Sources, v.Destinations, v.Schedule, v.Before, v.After); err != nil {
|
|
|
|
log.WithFields(log.Fields{"path": path, "call": "NewApp", "attr": v.Name, "error": err}).Errorf("")
|
|
|
|
return err
|
2022-04-16 14:57:45 +02:00
|
|
|
} else {
|
2023-06-29 22:58:24 +02:00
|
|
|
if _, ok := c.apps[v.Name]; ok {
|
|
|
|
err := errors.New("app already exists")
|
|
|
|
log.WithFields(log.Fields{"path": path, "app": v.Name, "error": err}).Errorf("")
|
2022-04-16 14:57:45 +02:00
|
|
|
return err
|
2021-11-14 03:53:13 +01:00
|
|
|
}
|
2023-06-29 22:58:24 +02:00
|
|
|
c.apps[v.Name] = a
|
2023-06-29 23:20:46 +02:00
|
|
|
for k, _ := range a.schedule {
|
|
|
|
if dur, ok := c.ScheduleDuration[k]; ok {
|
|
|
|
re := regexp.MustCompile(`^forever|([0-9]+(h|d|m|y))+$`)
|
|
|
|
if !re.MatchString(dur) {
|
|
|
|
err := errors.New("incorrect schedule duration")
|
|
|
|
log.WithFields(log.Fields{"path": path, "app": v.Name, "schedule": k, "error": err}).Errorf("")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err := errors.New("undefined schedule duration")
|
|
|
|
log.WithFields(log.Fields{"path": path, "app": v.Name, "schedule": k, "error": err}).Errorf("")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2023-06-29 22:58:24 +02:00
|
|
|
}
|
|
|
|
}
|
2021-11-14 03:53:13 +01:00
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
return nil
|
|
|
|
}
|
2021-11-14 03:53:13 +01:00
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
func (c *Config) Start(e *Email) {
|
|
|
|
log.WithFields(log.Fields{}).Debugf("starting")
|
|
|
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for _, b := range c.box {
|
|
|
|
wg.Add(1)
|
|
|
|
go func(box *Box) {
|
|
|
|
defer wg.Done()
|
|
|
|
if err := box.Open(); err != nil {
|
|
|
|
log.WithFields(log.Fields{"name": box.name, "call": "Open", "error": err}).Errorf("")
|
|
|
|
if e != nil {
|
|
|
|
e.AddItem(fmt.Sprintf(" - Box : %s is down", box.name))
|
2022-04-16 14:57:45 +02:00
|
|
|
}
|
2023-06-29 22:58:24 +02:00
|
|
|
return
|
2022-04-16 14:57:45 +02:00
|
|
|
}
|
2023-06-29 22:58:24 +02:00
|
|
|
}(b)
|
2021-11-14 03:53:13 +01:00
|
|
|
}
|
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
wg.Wait()
|
|
|
|
}
|
2022-04-16 14:57:45 +02:00
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
// Run config
|
|
|
|
func (c *Config) Run(e *Email) {
|
|
|
|
log.WithFields(log.Fields{}).Debugf("starting")
|
|
|
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for _, a := range cfg.apps {
|
|
|
|
wg.Add(1)
|
|
|
|
go func(app *App) {
|
|
|
|
if err := app.Run(e.startTime); err != nil {
|
|
|
|
e.AddItem(fmt.Sprintf(" - App : Error running %s (%s)", app.name, err))
|
2022-04-16 15:09:06 +02:00
|
|
|
}
|
2023-06-29 22:58:24 +02:00
|
|
|
wg.Done()
|
|
|
|
}(a)
|
2021-11-14 03:53:13 +01:00
|
|
|
}
|
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
return
|
2021-11-14 03:53:13 +01:00
|
|
|
}
|
|
|
|
|
2023-07-31 10:13:36 +02:00
|
|
|
func (c *Config) Cleanup(e *Email) {
|
|
|
|
log.WithFields(log.Fields{}).Debugf("starting")
|
|
|
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
|
|
|
|
|
|
|
for _, a := range cfg.apps {
|
|
|
|
for _, src := range a.sources {
|
|
|
|
if b, ok := c.box[src.Box()]; ok {
|
|
|
|
if fs, ok := b.zfs.filesystems[src.Path()]; ok {
|
|
|
|
fs.srcApps = append(fs.srcApps, a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, dest := range a.destinations {
|
2023-07-31 10:46:02 +02:00
|
|
|
if b, ok := c.box[dest.Box()]; ok {
|
|
|
|
dest2 := dest.Append("/" + src.Box() + "/" + src.Path())
|
|
|
|
if fs, ok := b.zfs.filesystems[dest2.Path()]; ok {
|
2023-07-31 10:13:36 +02:00
|
|
|
fs.destApps = append(fs.destApps, a)
|
2023-07-31 10:30:59 +02:00
|
|
|
} else {
|
2023-07-31 10:46:02 +02:00
|
|
|
e.AddItem(fmt.Sprintf(" - Dest : No folder (%s)", dest2.String()))
|
2023-07-31 10:13:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, b := range cfg.box {
|
|
|
|
if b.online {
|
|
|
|
for _, fs := range b.zfs.filesystems {
|
|
|
|
if len(fs.srcApps) > 0 && !fs.backedUp {
|
|
|
|
log.WithFields(log.Fields{"box": b.name, "fs": fs.path}).Warnf("not backed up")
|
|
|
|
e.AddItem(fmt.Sprintf(" - Src : Folder not backed up (%s)", b.name+":"+fs.path))
|
|
|
|
}
|
2023-07-31 11:16:11 +02:00
|
|
|
if len(fs.destApps) == 0 && len(fs.srcApps) == 0 && fs.managed {
|
2023-07-31 10:13:36 +02:00
|
|
|
log.WithFields(log.Fields{"box": b.name, "fs": fs.path}).Warnf("managed")
|
|
|
|
e.AddItem(fmt.Sprintf(" - Dest : Folder managed (%s)", b.name+":"+fs.path))
|
|
|
|
}
|
2023-07-31 11:10:41 +02:00
|
|
|
if fs.managed {
|
|
|
|
log.WithFields(log.Fields{"box": b.name, "fs": fs.path, "src": len(fs.srcApps), "dest": len(fs.destApps)}).Warnf("managed")
|
|
|
|
}
|
2023-07-31 10:13:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-06-29 22:58:24 +02:00
|
|
|
func (c *Config) Stop(e *Email) {
|
|
|
|
log.WithFields(log.Fields{}).Debugf("starting")
|
|
|
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
|
|
|
|
|
|
|
for _, b := range c.box {
|
|
|
|
if err := b.Close(); err != nil {
|
|
|
|
log.WithFields(log.Fields{"name": b.name, "call": "Close", "error": err}).Errorf("")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(e.items) > 0 {
|
|
|
|
if err := e.Send(); err != nil {
|
|
|
|
log.WithFields(log.Fields{"call": "email.Send", "error": err}).Errorf("")
|
|
|
|
}
|
|
|
|
}
|
2021-11-14 03:53:13 +01:00
|
|
|
}
|