package main import ( "os" "os/exec" "encoding/json" "io/ioutil" "log" "bytes" "time" "fmt" "path/filepath" "strings" ) var ( configFile = "config.json" ) // Config -- General config type Config struct { TimeHour int TimeMinute int WorkingDir string BackupPeriods []BackupPeriod Hosts []Host } // Host -- Struct for each host in the config type Host struct { Name string IPAddr string Port int Username string PrivKey string RemoteDir string } // BackupPeriod -- Struct for individual backup period type BackupPeriod struct { Interval int Count int } func loadConfig(configFile string) Config { var config Config fileBytes, err := ioutil.ReadFile(configFile) if err != nil { log.Println(err) } err = json.Unmarshal(fileBytes, &config) if err != nil { log.Println(err) } return config } func runCommand(cmdline string) { var errBuff bytes.Buffer var outBuff bytes.Buffer cmd := exec.Command("/bin/bash", "-c", cmdline) cmd.Stderr = &errBuff cmd.Stdout = &outBuff log.Printf("Executing: " + cmdline) err := cmd.Run() log.Printf("Command finished with error: %v", err) log.Printf("Std Out: %s", outBuff.String()) log.Printf("Std Err: %s", errBuff.String()) } func getNDay(hour int, min int, offset int) time.Time { today := time.Now() nextDate := time.Date(today.Year(), today.Month(), today.Day() + offset, hour, min, 0, 0, today.Location()) return nextDate } func getDir(path string, filter string) ([]string, []time.Time) { var names []string var dates []time.Time files, err := ioutil.ReadDir(path) if err != nil { log.Fatal(err) } for _, file := range files { if strings.Contains(file.Name(), filter) { name := file.Name() timeNow := time.Now() workingDateStr := name[len(filter)+1:len(name)] workingDate, err := time.Parse("2006-01-02", workingDateStr) workingDate = time.Date(workingDate.Year(), workingDate.Month(), workingDate.Day(), 0, 0, 0, 0, timeNow.Location()) if err!= nil { log.Println(err) } else { name = path + "/" + file.Name() names = append(names, name) dates = append(dates, workingDate) log.Printf("%v found\n", workingDate) } } } log.Printf("%s: %v\n", path, dates) return names, dates } func backupToday(timeToday time.Time, config Config, host Host) { dirDateStr := fmt.Sprintf("%04d-%02d-%02d", timeToday.Year(), timeToday.Month(), timeToday.Day()) dirCommand := fmt.Sprintf("mkdir \"%s/%s-%s\"", config.WorkingDir, host.Name, dirDateStr) runCommand(dirCommand) log.Printf("Current Host: %s", host.Name) dateStr := fmt.Sprintf("%04d-%02d-%02d", timeToday.Year(), timeToday.Month(), timeToday.Day()) command := fmt.Sprintf("rsync -azvhe \"ssh -i %s -p %d\" %s@%s:\"%s\" \"%s/%s-%s/\"", host.PrivKey, host.Port, host.Username, host.IPAddr, host.RemoteDir, config.WorkingDir, host.Name, dateStr) runCommand(command) } func findBackupDate(dates []time.Time, findDate time.Time) int { for i, date := range(dates) { if date == findDate { log.Printf("%v found", date) return i } } log.Printf("%v not found", findDate) return 0 } func enumerateDates(dates []time.Time, currDate time.Time, searchDayNum int) bool { for i:=1; i<=searchDayNum; i++ { for _, date := range(dates) { if currDate.Add(-time.Hour * time.Duration(24 * i)) == date { log.Printf("Existing backup exists at %v\n", date) return true } } } return false } func main() { ex, err := os.Executable() if err != nil { panic(err) } exPath := filepath.Dir(ex) config := loadConfig(exPath + "/" + configFile) logFile, err := os.OpenFile(config.WorkingDir + "backup.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Println(err) } defer logFile.Close() log.SetOutput(logFile) for true { timeNow := time.Now() timeToday := time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 0, 0, 0, 0, timeNow.Location()) log.Printf("New Date: %v", timeToday) var workingTime time.Time for _, host := range config.Hosts { log.Printf("New Host: %s", host.Name) // creating backup for today backupToday(timeToday, config, host) // go through each interval for deletion workingTime = timeToday filePaths, fileDates := getDir(config.WorkingDir, host.Name) for i, backupPeriod := range(config.BackupPeriods) { // get to the time if err != nil { fmt.Println(err) } workingTime = workingTime.Add(-time.Duration(backupPeriod.Interval * backupPeriod.Count) * 24 * time.Hour) log.Printf("Working time: %v\n", workingTime) // check if file exists workingFileCount := findBackupDate(fileDates, workingTime) // file exist... if workingFileCount != 0 { // if there's a copy out there within interval... if i+1 == len(config.BackupPeriods) || enumerateDates(fileDates, workingTime, config.BackupPeriods[i+1].Interval) { // delete it removeCommand := fmt.Sprintf("rm -rf \"%s\"", filePaths[workingFileCount]) runCommand(removeCommand) } } } log.Printf("Final working time: %v", workingTime) // delete the rest for i, date := range(fileDates) { if date.Before(workingTime) { removeCommand := fmt.Sprintf("rm -rf \"%s\"", filePaths[i]) runCommand(removeCommand) } } // deleteDate := getNDay(0, 0, -1 * config.MaxRecords) // deleteDateStr := fmt.Sprintf("%04d-%02d-%02d", deleteDate.Year(), deleteDate.Month(), deleteDate.Day()) // removeCommand := fmt.Sprintf("rm -rf \"%s/%s-%s\"", config.WorkingDir, host.Name, deleteDateStr) // runCommand(removeCommand) } nextDay := getNDay(config.TimeHour, config.TimeMinute, 1) log.Printf("Sleeping until: %v", nextDay) time.Sleep(time.Until(nextDay)) } }