package main import ( "bytes" "encoding/json" "flag" "fmt" "io/ioutil" "log" "os" "regexp" "golang.org/x/crypto/ssh" ) type Config struct { Zfsnap map[string]string `json:"zfsnap"` Box map[string]*BoxConfig `json:"box"` Apps []AppConfig `json:apps` Timezone string `json:"timezone"` } type BoxConfig struct { Addr string `json:"addr"` User string `json:"user"` Key string `json:"key"` ssh *SSHConfig } var ( appFlag = flag.String("app", "", "run specific app") cfgFile = flag.String("config", "config.json", "config file") schedFlag = flag.String("schedule", "", "specific schedule") testFlag = flag.Bool("test", false, "test run") debugFlag = flag.Bool("debug", false, "debug") cfg Config ) //Load config from file func (c *Config) Load() error { if *debugFlag { log.Printf("SSHConfig.Load : Start") } b, err := ioutil.ReadFile(*cfgFile) if err != nil { if *debugFlag { log.Printf("Config.Load : ioutil.ReadFile(%s) : %s", *cfgFile, err) } return err } err = json.Unmarshal(b, &c) if err != nil { if *debugFlag { log.Printf("Config.Load : json.Unmarshal : %s", err) } return err } for k, v := range c.Box { s := &SSHConfig{ logged: false, name: k, } v.ssh = s keyRaw, err := ioutil.ReadFile(v.Key) if err != nil { if *debugFlag { log.Printf("Config.Load : ioutil.ReadFile(%s) : %s", k, err) } return err } key, err := ssh.ParseRawPrivateKey(keyRaw) if err != nil { if *debugFlag { log.Printf("Config.Load : ssh.ParseRawPrivateKey(%s) : %s", k, err) } return err } s.signer, err = ssh.NewSignerFromKey(key) if err != nil { if *debugFlag { log.Printf("Config.Load : ssh.NewSignerFromKey(%s) : %s", k, err) } return err } s.config = &ssh.ClientConfig{ User: v.User, Auth: []ssh.AuthMethod{ ssh.PublicKeys(s.signer), }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } s.client, err = ssh.Dial("tcp", v.Addr, s.config) if err != nil { if *debugFlag { log.Printf("Config.Load : ssh.Dial(%s) : %s", k, err) } return err } session, err := s.client.NewSession() if err != nil { if *debugFlag { log.Printf("Config.Load : client.NewSession(%s) : %s", k, err) } return err } var b bytes.Buffer session.Stdout = &b err = session.Run("TZ=\"" + cfg.Timezone + "\" /usr/sbin/zfsnap --version") if err != nil { if *debugFlag { log.Printf("Config.Load : client.NewSession(%s) : %s", k, err) } return err } if *debugFlag { log.Printf("Config.Load : logged into %s : %s", k, b.String()) } session.Close() s.logged = true } for _, box := range c.Box { err = box.ssh.getTime() if err != nil { if *debugFlag { log.Printf("Config.Load : ssh.getTime() : %s", err) } return err } } for _, app := range c.Apps { for _, src := range app.Sources { if !src.Valid() { return fmt.Errorf("Source not valid : %s", string(src)) } if _, ok := cfg.Box[src.Box()]; !ok { return fmt.Errorf("No box defined for source : %s", string(src)) } } for _, dest := range app.Destinations { if !dest.Valid() { return fmt.Errorf("Destination not valid : %s", string(dest)) } if _, ok := cfg.Box[dest.Box()]; !ok { return fmt.Errorf("No box defined for destination : %s", string(dest)) } } for val, before := range app.Before { _, err = regexp.Compile(val) if err != nil { if *debugFlag { log.Printf("Config.Load : invalid regex : %s", val) } return err } if !before.Valid() { return fmt.Errorf("Before not valid : %s", string(before)) } if _, ok := cfg.Box[before.Box()]; !ok { return fmt.Errorf("No box defined for before : %s", string(before)) } } for val, after := range app.After { _, err = regexp.Compile(val) if err != nil { if *debugFlag { log.Printf("Config.Load : invalid regex : %s", val) } return err } if !after.Valid() { return fmt.Errorf("After not valid : %s", string(after)) } if _, ok := cfg.Box[after.Box()]; !ok { return fmt.Errorf("No box defined for after : %s", string(after)) } } } return nil } //Close config func (c *Config) Close() error { return nil } func main() { flag.Parse() err := cfg.Load() if err != nil { log.Printf("Cannot load config (%s)", err) os.Exit(1) } err = RunBackup(*appFlag) if err != nil { log.Printf("Cannot run schedule (%s)", err) os.Exit(1) } } //RunBackup run all backup targets where schedule is registered func RunBackup(app string) error { if app == "" { if *debugFlag { log.Printf("RunBackup() : Start") } for _, a := range cfg.Apps { err := a.RunAppBackup() if err != nil { if *debugFlag { log.Printf("RunBackup() : Error running %s", a.Name) } return err } } } else { if *debugFlag { log.Printf("RunBackup() : Start %s", app) } for _, a := range cfg.Apps { if a.Name == app { err := a.RunAppBackup() if err != nil { if *debugFlag { log.Printf("RunBackup() : Error running %s", a.Name) } return err } } } } return nil }