package cmd import ( "bytes" "context" "errors" "fmt" "log" "net/smtp" "os" "time" "gitea.urkob.com/urko/backblaze-backup/internal/services/backblaze" "gitea.urkob.com/urko/backblaze-backup/internal/services/email" "gitea.urkob.com/urko/backblaze-backup/kit" "gitea.urkob.com/urko/backblaze-backup/kit/config" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var Check = &cobra.Command{ Use: "check", Short: "Compares local backup files with those in a Backblaze B2 bucket and sends a summary email.", Long: `This command compares the list of files in a local backup directory against the files in a specified Backblaze B2 bucket. The operation is performed concurrently and is time-bound, set by default to 5 minutes. If discrepancies are found, i.e., some files exist only locally or only on the cloud, these are logged and sent via email as attachments. The email contains two text attachments: - 'Local-Files-Not-In-B2.txt': Lists files that are in the local backup directory but not in the B2 bucket. - 'B2-Files-Not-In-Local.txt': Lists files that are in the B2 bucket but not in the local backup directory. The command requires two flags: - '--dir': The path of the local backup directory - '--bucket': The name of the Backblaze B2 bucket An environment variable 'BACKBLAZE_ENV' can be set to 'dev' to load environment variables from a .env file in the root directory.`, Run: func(cmd *cobra.Command, args []string) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) // or any appropriate time defer cancel() log.SetFlags(log.Ldate | log.Lmicroseconds) backupDir, err := cmd.Flags().GetString("dir") if err != nil { panic(fmt.Errorf("dir %w", err)) } bucketName, err := cmd.Flags().GetString("bucket") if err != nil { panic(fmt.Errorf("bucket %w", err)) } envFile := "" if os.Getenv("BACKBLAZE_ENV") == "dev" { envFile = kit.RootDir() + "/.env" } cfg := config.NewConfig(envFile) mailSrv := email.NewMailService(email.MailServiceConfig{ Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost), Host: cfg.MailHost, Port: cfg.MailPort, From: cfg.MailFrom, }, ) logger := logrus.New() logger.SetLevel(logrus.Level(cfg.LogLevel)) b, err := backblaze.NewBackBlaze(ctx, logger, cfg.BbId, cfg.BbKey) if err != nil { panic(fmt.Errorf("NewBackBlaze %w", err)) } logger.Info("start check") defer logger.Info("end check") errs := make(chan backblaze.B2LocalErr) go b.CompareConcurrent(ctx, backupDir, bucketName, errs) // Create two buffers for two kinds of errors cloudNotInLocalBuffer := new(bytes.Buffer) cloudNotInLocalBuffer.WriteString(fmt.Sprintf("List of B2 files within %s bucket not found in local path %s \n", bucketName, backupDir)) countNotInLocalBuffer := 0 localNotInCloudBuffer := new(bytes.Buffer) localNotInCloudBuffer.WriteString(fmt.Sprintf("List of local files in %s not found in B2 bucket %s \n", backupDir, bucketName)) countNotInCloudBuffer := 0 loop: for { select { case <-ctx.Done(): // Handle the timeout or cancellation // Release any resources here if ctx.Err() == context.DeadlineExceeded { logger.Error("Operation timed out") } else if ctx.Err() == context.Canceled { logger.Error("Operation canceled") } break loop case err := <-errs: if errors.Is(err.Err, backblaze.ErrCloudNotInLocal) { logger.Debug(err.File + ": B2 file not found in local") cloudNotInLocalBuffer.WriteString(err.File + "\n") countNotInLocalBuffer++ } if errors.Is(err.Err, backblaze.ErrLocalNotInCloud) { logger.Debug(err.File + ": local file not found in B2") localNotInCloudBuffer.WriteString(err.File + "\n") countNotInCloudBuffer++ } if err.Err == nil { break loop } } } var attachments []email.EmailAttachment if countNotInCloudBuffer > 0 { attachments = append(attachments, email.EmailAttachment{File: cloudNotInLocalBuffer, Title: "B2-Files-Not-In-Local.txt"}) } if countNotInLocalBuffer > 0 { attachments = append(attachments, email.EmailAttachment{File: localNotInCloudBuffer, Title: "Local-Files-Not-In-B2.txt"}) } if err := mailSrv.SendOK(email.EmailWithAttachments{ To: cfg.MailTo, Bucket: bucketName, BackupDir: backupDir, CountLocal: countNotInLocalBuffer, CountCloud: countNotInCloudBuffer, Attachments: attachments, }); err != nil { panic(fmt.Errorf("error while send email: %w", err)) } }, }