diff --git a/.gitignore b/.gitignore index 67cdec9..f6920ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ rs-backup.json +build/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2acdf67 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +.PHONY: all +all: build_linux_amd64 build_darwin_amd64 build_windows_amd64 checksums + +.PHONY: build_linux_amd64 +build_linux_amd64: + GOOS=linux GOARCH=amd64 go build -v -a -gcflags=-trimpath=$$PWD -asmflags=-trimpath=$$PWD -o build/rs-backup-linux-amd64 + +.PHONY: build_linux_i386 +build_linux_i386: + GOOS=linux GOARCH=386 go build -v -a -gcflags=-trimpath=$$PWD -asmflags=-trimpath=$$PWD -o build/rs-backup-linux-i386 + +.PHONY: build_darwin_amd64 +build_darwin_amd64: + GOOS=darwin GOARCH=amd64 go build -v -a -gcflags=-trimpath=$$PWD -asmflags=-trimpath=$$PWD -o build/rs-backup-darwin-amd64 + +.PHONY: build_darwin_i386 +build_darwin_i386: + GOOS=darwin GOARCH=386 go build -v -a -gcflags=-trimpath=$$PWD -asmflags=-trimpath=$$PWD -o build/rs-backup-darwin-i386 + +.PHONY: build_windows_amd64 +build_windows_amd64: + CC=/usr/local/bin/x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64 go build -v -a -gcflags=-trimpath=$$PWD -asmflags=-trimpath=$$PWD -o build/rs-backup-windows-amd64.exe + +.PHONY: build_windows_i386 +build_windows_i386: + CC=/usr/local/bin/x86_64-w64-mingw32-gcc GOOS=windows GOARCH=386 go build -v -a -gcflags=-trimpath=$$PWD -asmflags=-trimpath=$$PWD -o build/rs-backup-windows-i386.exe + +.PHONY: checksums +checksums: + shasum -a 256 build/* > build/checksum.txt + +test: diff --git a/README.md b/README.md index 0ffa871..3aa0666 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ ## rs-backup -A simple wrapper around rsync written in Go with mail sending possibilities. \ No newline at end of file +A simple wrapper around rsync written in Go with mail sending possibilities. + +The config is minimalistic, set the flags to true which you would like to be included in the command. + +If you set `log` to true, the app will create a temporary file in the system tmp folder and will log the rsync output to that file and will attach this in the email sent at the end. + +If the process is complete, there will be an email sent to the specified to adresses in the config. (accepts multiple adresses i.e. array of strings). + +If there are any questions or bugs, please feel free to open an issue. \ No newline at end of file diff --git a/helpers/file.go b/helpers/file.go new file mode 100644 index 0000000..a4d0595 --- /dev/null +++ b/helpers/file.go @@ -0,0 +1,15 @@ +package helpers + +import ( + "fmt" + "io/ioutil" +) + +func ReadFileContent(fileName string) []byte { + b, err := ioutil.ReadFile(fileName) // just pass the file name + if err != nil { + fmt.Print(err) + } + + return b +} diff --git a/rs-backup.go b/rs-backup.go index e0a1497..b0051bf 100644 --- a/rs-backup.go +++ b/rs-backup.go @@ -1,14 +1,26 @@ package main import ( + "encoding/base64" "fmt" + "github.com/0x111/rs-backup/helpers" "github.com/spf13/viper" + "io/ioutil" "log" + "net/smtp" + "os" "os/exec" + "strconv" + "strings" + _ "text/template" + "time" ) func main() { var err error + // log start time + start := time.Now() + log.Printf("Starting backup process %s", start) // load config viper.SetConfigName("rs-backup") // name of config file (without extension) viper.AddConfigPath("/etc/rs-backup/") // path to look for the config file in @@ -27,12 +39,117 @@ func main() { log.Fatal("We could not find rsync in your path!") } - fmt.Println(path) - // cmd + var args []string + // Dynamically append arguments to the args + if viper.GetBool("show_progress") == true { + args = append(args, "--progress") + } + + if viper.GetBool("force_ipv4") == true { + args = append(args, "--ipv4") + } + + if viper.GetBool("archive") == true { + args = append(args, "--archive") + } + + if viper.GetBool("verbose") == true { + args = append(args, "--verbose") + } + + if viper.GetBool("compress") == true { + args = append(args, "--compress") + } + + remoteShellCommand := viper.GetString("remote_shell_command") + + if remoteShellCommand != "" && len(remoteShellCommand) > 0 { + args = append(args, "--rsh") + sshCmd := fmt.Sprintf("%s", remoteShellCommand) + args = append(args, sshCmd) + } + + // create a temporary file + file, err := ioutil.TempFile("", "rs-backup") + logFileName := file.Name() + ".log" + if err != nil { + log.Fatal(err) + } + + defer os.Remove(logFileName) + + if viper.GetBool("log") == true { + args = append(args, "--log-file="+logFileName) + } + + localDirectoryPath := viper.GetString("local_directory_path") + + if localDirectoryPath != "" && len(localDirectoryPath) > 0 { + args = append(args, localDirectoryPath) + } + + remoteDirectoryPath := viper.GetString("remote_directory_path") + + if remoteDirectoryPath != "" && len(remoteDirectoryPath) > 0 { + args = append(args, remoteDirectoryPath) + } - cmd := exec.Command("sleep", "5") log.Println("Running command and waiting for it to finish...") - err = cmd.Run() - log.Printf("Command finished with error: %v", err) + _, err = exec.Command(path, args...).Output() + + if err != nil { + //log.Fatal(fmt.Printf("Command finished with error: %v", err)) + fmt.Println("asd") + } + + sendTo := viper.GetStringSlice("mail.to") + // Content-Type: text/html; charset="UTF-8"; + fileContent := helpers.ReadFileContent(logFileName) + mailBody := `From: %s +To: %s +Subject: Backup run at %s +Content-Type: multipart/mixed; boundary=_rssbckkgthbscrpt14467_ +Content-Transfer-Encoding: 7bit +--_rssbckkgthbscrpt14467_ +MIME-Version: 1.0 +Content-Type: text/html; charset="UTF-8"; +Content-Transfer-Encoding: 7bit + +Backup started at: %s
+Backup ended at: %s
+Backup took: %s
+Find the contents of the rsync log in the attached log file. +
+ +Mailed by 0x111/rs-backup.
+--_rssbckkgthbscrpt14467_ +Content-Type: application/octet-stream; name="rsync.log" +Content-Disposition: attachment; filename="rsync.log" +Content-Transfer-Encoding: base64 + +%s +--_rssbckkgthbscrpt14467_ +` + + // variables to make ExamplePlainAuth compile, without adding + // unnecessary noise there. + var ( + from = viper.GetString("mail.from") + recipients = sendTo + ) + + end := time.Now().Format(time.RFC822) + mailBody = fmt.Sprintf(mailBody, from, strings.Join(sendTo, ","), start.Format(time.RFC822), start.Format(time.RFC822), end, time.Since(start), base64.StdEncoding.EncodeToString(fileContent)) + msg := []byte(mailBody) + + // hostname is used by PlainAuth to validate the TLS certificate. + hostname := viper.GetString("smtp.host") + port := strconv.Itoa(viper.GetInt("smtp.port")) + auth := smtp.PlainAuth("", viper.GetString("smtp.user"), viper.GetString("smtp.password"), hostname) + + err = smtp.SendMail(hostname+":"+port, auth, from, recipients, msg) + if err != nil { + log.Fatal(err) + } } diff --git a/rs-backup.sample.json b/rs-backup.sample.json new file mode 100644 index 0000000..c30163e --- /dev/null +++ b/rs-backup.sample.json @@ -0,0 +1,24 @@ +{ + "show_progress": true, + "force_ipv4": true, + "archive": true, + "verbose": true, + "compress": true, + "log": true, + "local_directory_path": "/path/to/the/directory", + "remote_directory_path": "remote_user@remote_host:/remote_path/to/directory", + "remote_shell_command": "ssh -p23", + "mail": { + "from": "from@domain.com", + "to": [ + "to@email.com", + "to2@email.com" + ] + }, + "smtp": { + "host": "mailhost.com", + "user": "username@domain.com", + "password": "password", + "port": 25 + } +} \ No newline at end of file