backup/config.go

316 lines
8.0 KiB
Go
Raw Normal View History

2021-11-14 03:53:13 +01:00
package main
import (
2023-08-20 16:57:53 +02:00
_ "embed"
2021-11-14 03:53:13 +01:00
"encoding/json"
2023-06-29 22:58:24 +02:00
"errors"
2021-11-14 03:53:13 +01:00
"fmt"
2023-08-20 16:57:53 +02:00
"os"
"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"
2024-11-17 16:25:39 +01:00
"github.com/tidwall/pretty"
2021-11-14 03:53:13 +01:00
)
type Config struct {
2024-11-17 17:00:52 +01:00
ScheduleDuration map[string]string `json:"schedule"`
Box map[string]*BoxConfig `json:"box"`
Email *EmailConfig `json:"email,omitempty"`
Apps []*AppConfig `json:"apps"`
Timezone string `json:"timezone"`
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-08-20 16:57:53 +02:00
var (
cfgMx sync.Mutex
cfgRun bool
)
//go:embed assets/backup.sample.json
var sampleCfg []byte
2023-06-29 22:58:24 +02:00
type BoxConfig struct {
2024-11-17 15:14:36 +01:00
Addr string `json:"addr"`
User string `json:"user"`
Key string `json:"key"`
2023-06-29 22:58:24 +02:00
}
type AppConfig struct {
Name string `json:"name"`
Schedule []string `json:"schedule"`
Sources []string `json:"src"`
Destinations []string `json:"dest"`
2024-11-17 16:50:04 +01:00
Before map[string]string `json:"before,omitempty"`
After map[string]string `json:"after,omitempty"`
Active bool `json:"active,omitempty"`
2023-06-29 22:58:24 +02:00
}
// Load config from file
2023-08-20 16:57:53 +02:00
func LoadConfigFile(path string) (*Config, error) {
2023-06-29 22:58:24 +02:00
log.WithFields(log.Fields{"path": path}).Debugf("starting")
defer log.WithFields(log.Fields{"path": path}).Debugf("done")
2023-08-20 16:57:53 +02:00
b, err := os.ReadFile(path)
2021-11-14 03:53:13 +01:00
if err != nil {
2023-08-20 16:57:53 +02:00
log.WithFields(log.Fields{"path": path, "error": err, "call": "os.ReadFile"}).Errorf("")
return nil, err
2021-11-14 03:53:13 +01:00
}
2023-08-20 16:57:53 +02:00
return LoadConfigByte(b)
}
// Load config from string
func LoadConfigByte(conf []byte) (*Config, error) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
2023-06-30 22:04:22 +02:00
2023-08-20 16:57:53 +02:00
c := &Config{}
if err := json.Unmarshal(sampleCfg, c); err != nil {
log.WithFields(log.Fields{"error": err, "call": "json.Unmarshal", "attr": "sampleCfg"}).Errorf("")
return nil, err
2021-11-14 03:53:13 +01:00
}
2023-08-20 16:57:53 +02:00
b, err := hujson.Standardize(conf)
2021-11-14 05:21:22 +01:00
if err != nil {
2023-08-20 16:57:53 +02:00
log.WithFields(log.Fields{"error": err, "call": "hujson.Standardize"}).Errorf("")
return nil, err
2021-11-14 05:21:22 +01:00
}
2023-08-20 16:57:53 +02:00
if err := json.Unmarshal(b, c); err != nil {
log.WithFields(log.Fields{"error": err, "call": "json.Unmarshal"}).Errorf("")
return nil, err
2022-06-19 06:31:36 +02:00
}
2023-08-20 16:57:53 +02:00
c.timezone, err = time.LoadLocation(c.Timezone)
if err != nil {
log.WithFields(log.Fields{"error": err, "call": "time.LoadLocation", "attr": cfg.Timezone}).Errorf("")
return nil, err
2022-06-19 06:31:36 +02:00
}
2024-11-17 17:00:52 +01:00
if c.Email != nil {
2023-08-20 16:57:53 +02:00
if len(c.Email.SmtpHost) == 0 {
err := fmt.Errorf("no smtp")
log.WithFields(log.Fields{"error": err}).Errorf("")
return nil, err
}
if len(c.Email.FromEmail) == 0 {
err := fmt.Errorf("no email from")
log.WithFields(log.Fields{"error": err}).Errorf("")
return nil, err
}
if len(c.Email.ToEmail) == 0 {
err := fmt.Errorf("no email to")
log.WithFields(log.Fields{"error": err}).Errorf("")
return nil, 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 {
2023-08-20 16:57:53 +02:00
log.WithFields(log.Fields{"schedule": k, "deadline": v, "error": err}).Errorf("")
return nil, err
2021-11-14 03:53:13 +01:00
}
2023-06-29 22:58:24 +02:00
default:
err := errors.New("invalid schedule")
2023-08-20 16:57:53 +02:00
log.WithFields(log.Fields{"schedule": k, "deadline": v, "error": err}).Errorf("")
return nil, err
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.box = make(map[string]*Box)
for k, v := range c.Box {
2024-11-17 15:14:36 +01:00
if b, err := c.NewBox(k, v.Addr, v.User, v.Key); err != nil {
2023-08-20 16:57:53 +02:00
log.WithFields(log.Fields{"call": "NewBox", "attr": k, "error": err}).Errorf("")
return nil, err
2023-06-29 22:58:24 +02:00
} else {
if _, ok := c.box[k]; ok {
err := errors.New("already exists")
2023-08-20 16:57:53 +02:00
log.WithFields(log.Fields{"attr": k, "error": err}).Errorf("")
return nil, 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 {
2023-08-20 16:57:53 +02:00
log.WithFields(log.Fields{"call": "NewApp", "attr": v.Name, "error": err}).Errorf("")
return nil, 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")
2023-08-20 16:57:53 +02:00
log.WithFields(log.Fields{"app": v.Name, "error": err}).Errorf("")
return nil, err
2021-11-14 03:53:13 +01:00
}
2023-06-29 22:58:24 +02:00
c.apps[v.Name] = a
2023-08-20 16:57:53 +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")
2023-08-20 16:57:53 +02:00
log.WithFields(log.Fields{"app": v.Name, "schedule": k, "error": err}).Errorf("")
return nil, err
}
} else {
err := errors.New("undefined schedule duration")
2023-08-20 16:57:53 +02:00
log.WithFields(log.Fields{"app": v.Name, "schedule": k, "error": err}).Errorf("")
return nil, err
}
}
2023-06-29 22:58:24 +02:00
}
}
2021-11-14 03:53:13 +01:00
2023-08-20 16:57:53 +02:00
return c, nil
2023-06-29 22:58:24 +02:00
}
2021-11-14 03:53:13 +01:00
2024-11-17 16:25:39 +01:00
// Save config
func (c *Config) Save() error {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
cfgMx.Lock()
defer cfgMx.Unlock()
b, err := json.Marshal(cfg)
if err != nil {
2024-11-17 16:34:13 +01:00
log.WithFields(log.Fields{"error": err, "call": "json.Marshal"}).Errorf("")
2024-11-17 16:25:39 +01:00
return err
}
2024-11-17 16:41:46 +01:00
r := pretty.PrettyOptions(b, &pretty.Options{Indent: " "})
2024-11-17 16:25:39 +01:00
2024-11-17 16:39:00 +01:00
f, err := os.Create(*cfgFile)
2024-11-17 16:25:39 +01:00
if err != nil {
2024-11-17 16:34:13 +01:00
log.WithFields(log.Fields{"error": err, "call": "os.Open"}).Errorf("")
2024-11-17 16:25:39 +01:00
return err
}
if _, err := f.Write(r); err != nil {
2024-11-17 16:34:13 +01:00
log.WithFields(log.Fields{"error": err, "call": "File.Write"}).Errorf("")
2024-11-17 16:25:39 +01:00
return err
}
return nil
}
2023-07-31 18:11:29 +02:00
// Run config
func (c *Config) Run() {
2023-06-29 22:58:24 +02:00
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
2023-08-20 16:57:53 +02:00
if cfgRun {
return
}
cfgMx.Lock()
defer cfgMx.Unlock()
cfgRun = true
defer func() { cfgRun = false }()
2023-07-31 18:11:29 +02:00
e := NewEmail(time.Now())
2023-06-29 22:58:24 +02:00
var wg sync.WaitGroup
2023-07-31 18:11:29 +02:00
// Setup boxes
2023-06-29 22:58:24 +02:00
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("")
2023-07-31 18:11:29 +02:00
e.AddItem(fmt.Sprintf(" - Box : %s is down", box.name))
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-07-31 18:11:29 +02:00
// Run each app
2023-06-29 22:58:24 +02:00
for _, a := range cfg.apps {
wg.Add(1)
go func(app *App) {
2023-08-01 09:35:45 +02:00
if sched, err := app.Run(e.startTime); err != nil {
2023-06-29 22:58:24 +02:00
e.AddItem(fmt.Sprintf(" - App : Error running %s (%s)", app.name, err))
2023-08-01 09:35:45 +02:00
} else if *debug {
2023-08-01 11:18:00 +02:00
if sched != "" {
e.AddItem(fmt.Sprintf(" - App : Success backing up %s (%s)", app.name, sched))
} else {
e.AddItem(fmt.Sprintf(" - App : No backup for %s", app.name))
}
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()
2023-07-31 18:11:29 +02:00
// Cleanup
2023-07-31 10:13:36 +02:00
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 18:11:29 +02:00
if len(fs.destApps) == 0 && !fs.backedUp && 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 18:11:29 +02:00
// Stop
2023-06-29 22:58:24 +02:00
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 {
2023-08-20 16:57:53 +02:00
if err := e.Send(cfg.Email.SmtpHost, cfg.Email.FromEmail, cfg.Email.ToEmail); err != nil {
2023-06-29 22:58:24 +02:00
log.WithFields(log.Fields{"call": "email.Send", "error": err}).Errorf("")
}
}
2021-11-14 03:53:13 +01:00
}