Compare commits
No commits in common. "master" and "0.0.5" have entirely different histories.
|
@ -1,5 +1,4 @@
|
|||
.env
|
||||
.env.*
|
||||
.vscode
|
||||
coverage
|
||||
.notes
|
||||
|
|
2
License
2
License
|
@ -1,6 +1,6 @@
|
|||
---- Definitions ----
|
||||
license means right to use
|
||||
author means who had initial idea, who research about jurisprudence and who started, in this case : Urko: Bein.
|
||||
author means who had initial idea, who research about jurisprudence and the who who started, in this case : Urko: Bein.
|
||||
contributors means every man who has helped to improve this software
|
||||
|
||||
|
||||
|
|
19
Readme.md
19
Readme.md
|
@ -69,19 +69,9 @@ The application uses the following environment variables:
|
|||
|
||||
- **BB_ID:** Your Backblaze account ID
|
||||
- **BB_KEY:** Your Backblaze application key
|
||||
- **LOG_LEVEL:** Sets the log level (DEBUG, INFO, WARN, ERROR). Default is INFO.
|
||||
|
||||
You can set these variables in your environment, or you can use a **.env** file in the root directory of the project. If the **BACKBLAZE_ENV** environment variable is set to dev, the application will load the **.env** file.
|
||||
|
||||
## Configurable Logging
|
||||
|
||||
The application supports configurable logging levels. Set the LOG_LEVEL environment variable to one of the following values:
|
||||
|
||||
- **DEBUG:** Detailed debug information
|
||||
- **INFO:** Informational messages
|
||||
- **WARN:** Warnings
|
||||
- **ERROR:** Errors only
|
||||
|
||||
## Syncing with Backblaze
|
||||
The application uses the Sync method of the BackBlaze service to sync files or directories to Backblaze. If there's an error during the sync, the application will log the error and exit.
|
||||
|
||||
|
@ -89,6 +79,7 @@ The application uses the Sync method of the BackBlaze service to sync files or d
|
|||
./bin/backblaze-backup sync
|
||||
```
|
||||
|
||||
|
||||
## Checking for Duplicate Versions
|
||||
The application can also check for duplicate versions of files in your Backblaze buckets. To do this, run the following command:
|
||||
|
||||
|
@ -104,11 +95,3 @@ The application can also clean up duplicate versions of files in your Backblaze
|
|||
./bin/backblaze-backup cleanup
|
||||
```
|
||||
If there are any errors during the cleanup, the application will log them and exit.
|
||||
|
||||
## Check discrepancy between local backup and B2
|
||||
The application can also check that all local files has been backed up into B2 cloud. To do this, run the following command:
|
||||
|
||||
```bash
|
||||
./bin/backblaze-backup check --dir "/var/tmp/test-upload" --bucket "b2-bucket"
|
||||
```
|
||||
If there are any errors during the check, the application will log them and exit.
|
174
cmd/check.go
174
cmd/check.go
|
@ -1,174 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"net/smtp"
|
||||
"os"
|
||||
"strings"
|
||||
"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")
|
||||
|
||||
localChan := make(chan backblaze.B2Local)
|
||||
b2Chan := make(chan backblaze.B2Local)
|
||||
countChan := make(chan int, 1)
|
||||
go b.CompareConcurrent(ctx, backupDir, bucketName, localChan, b2Chan, countChan)
|
||||
|
||||
reportBuilder := strings.Builder{}
|
||||
reportBuilder.WriteString(fmt.Sprintf("Local files within `%s` path already in `%s` bucket:\n", backupDir, bucketName))
|
||||
|
||||
countLocal := 0
|
||||
countB2 := 0
|
||||
|
||||
cloudBuilder := strings.Builder{}
|
||||
cloudBuilder.WriteString(fmt.Sprintf("B2 files within `%s` bucket\n", bucketName))
|
||||
cloudBuilder.WriteString("| B2 File Name | Size | ModTime | Status |\n")
|
||||
cloudBuilder.WriteString("|----------------------------------------------------------|------------------|--------------------------------|------------------------------------------\n")
|
||||
countNotInLocal := 0
|
||||
|
||||
localBuilder := strings.Builder{}
|
||||
localBuilder.WriteString(fmt.Sprintf("Local files in `%s`\n", backupDir))
|
||||
localBuilder.WriteString("| Local File Name | Size | ModTime | Status |\n")
|
||||
localBuilder.WriteString("|----------------------------------------------------------|------------------|--------------------------------|------------------------------------------\n")
|
||||
countNotInCloud := 0
|
||||
|
||||
countOK := 0
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Handle the timeout or cancellation
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
logger.Error("Operation timed out")
|
||||
} else if ctx.Err() == context.Canceled {
|
||||
logger.Error("Operation canceled")
|
||||
}
|
||||
break loop
|
||||
case countOK = <-countChan:
|
||||
logger.Debugln("done chan")
|
||||
break loop
|
||||
case localMsg, ok := <-localChan:
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
countLocal++
|
||||
logger.Debugln("localChan", localMsg.File)
|
||||
writeTableRow(&localBuilder, &countNotInCloud, localMsg.File, localMsg.Err)
|
||||
case b2Msg, ok := <-b2Chan:
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
countB2++
|
||||
logger.Debugln("cloudBuilder", b2Msg.File)
|
||||
writeTableRow(&cloudBuilder, &countNotInLocal, b2Msg.File, b2Msg.Err)
|
||||
}
|
||||
}
|
||||
|
||||
reportBuilder.WriteString("\n")
|
||||
reportBuilder.WriteString(cloudBuilder.String())
|
||||
reportBuilder.WriteString("\n")
|
||||
reportBuilder.WriteString(localBuilder.String())
|
||||
|
||||
if err := mailSrv.SendOK(email.EmailWithAttachments{
|
||||
To: cfg.MailTo,
|
||||
Bucket: bucketName,
|
||||
BackupDir: backupDir,
|
||||
Count: map[string]int{
|
||||
"count_ok": countOK,
|
||||
"count_local": countLocal,
|
||||
"count_b2": countB2,
|
||||
"count_not_in_local": countNotInLocal,
|
||||
"count_not_in_cloud": countNotInCloud,
|
||||
},
|
||||
Attachments: []email.EmailAttachment{{File: bytes.NewReader([]byte(reportBuilder.String())), Title: fmt.Sprintf("%s-check-report.txt", bucketName)}},
|
||||
}); err != nil {
|
||||
panic(fmt.Errorf("error while send email: %w", err))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// New function to write a table row
|
||||
func writeTableRow(builder *strings.Builder, count *int, file backblaze.File, err error) {
|
||||
status := "OK"
|
||||
if err != nil {
|
||||
status = err.Error()
|
||||
(*count)++
|
||||
}
|
||||
|
||||
builder.WriteString(fmt.Sprintf("| %-56s | %-16s | %-30s | %-40s |\n", file.Path, prettyByteSize(file.Size), file.ModTime.Format("2006-01-02 15:04:05.000"), status))
|
||||
}
|
||||
|
||||
func prettyByteSize(b int) string {
|
||||
bf := float64(b)
|
||||
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
|
||||
if math.Abs(bf) < 1024.0 {
|
||||
return fmt.Sprintf("%3.1f%sB", bf, unit)
|
||||
}
|
||||
bf /= 1024.0
|
||||
}
|
||||
return fmt.Sprintf("%.1fYiB", bf)
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_prettyByteSize(t *testing.T) {
|
||||
require.Equal(t, prettyByteSize(1505099776), "1.4GiB")
|
||||
}
|
|
@ -2,14 +2,11 @@ package cmd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gitea.urkob.com/urko/backblaze-backup/internal/services/backblaze"
|
||||
"gitea.urkob.com/urko/backblaze-backup/kit"
|
||||
"gitea.urkob.com/urko/backblaze-backup/internal/services"
|
||||
"gitea.urkob.com/urko/backblaze-backup/kit/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -17,29 +14,19 @@ var Cleanup = &cobra.Command{
|
|||
Use: "cleanup",
|
||||
Short: "Handle clenaup for multiple versions of file in Backblaze",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
log.SetFlags(log.Ldate | log.Lmicroseconds)
|
||||
log.SetFlags(log.Lmicroseconds)
|
||||
ctx, cancel := context.WithCancel(signalContext(cmd.Context()))
|
||||
defer cancel()
|
||||
|
||||
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"
|
||||
envFile = ".env"
|
||||
}
|
||||
cfg := config.NewConfig(envFile)
|
||||
logger := logrus.New()
|
||||
logger.SetLevel(logrus.Level(cfg.LogLevel))
|
||||
|
||||
bbService, err := backblaze.NewBackBlaze(ctx, logger, cfg.BbId, cfg.BbKey)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("NewBackBlaze %w", err))
|
||||
}
|
||||
if err := bbService.CleanUp(ctx, cancel, bucketName); err != nil {
|
||||
panic(fmt.Errorf("bbService.CleanUp %w", err))
|
||||
bbService := services.NewBackBlaze(cfg.BbId, cfg.BbKey)
|
||||
if err := bbService.CleanUp(ctx, cancel); err != nil {
|
||||
log.Fatalln("bbService.CleanUp()", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -2,16 +2,13 @@ package cmd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"gitea.urkob.com/urko/backblaze-backup/internal/services/backblaze"
|
||||
"gitea.urkob.com/urko/backblaze-backup/kit"
|
||||
"gitea.urkob.com/urko/backblaze-backup/internal/services"
|
||||
"gitea.urkob.com/urko/backblaze-backup/kit/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -19,24 +16,19 @@ var Versions = &cobra.Command{
|
|||
Use: "versions",
|
||||
Short: "Handle versions of files in Backblaze",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
log.SetFlags(log.Ldate | log.Lmicroseconds)
|
||||
log.SetFlags(log.Lmicroseconds)
|
||||
ctx, cancel := context.WithCancel(signalContext(cmd.Context()))
|
||||
defer cancel()
|
||||
|
||||
envFile := ""
|
||||
if os.Getenv("BACKBLAZE_ENV") == "dev" {
|
||||
envFile = kit.RootDir() + "/.env"
|
||||
envFile = ".env"
|
||||
}
|
||||
cfg := config.NewConfig(envFile)
|
||||
logger := logrus.New()
|
||||
logger.SetLevel(logrus.Level(cfg.LogLevel))
|
||||
|
||||
bbService, err := backblaze.NewBackBlaze(ctx, logger, cfg.BbId, cfg.BbKey)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("NewBackBlaze %w", err))
|
||||
}
|
||||
bbService := services.NewBackBlaze(cfg.BbId, cfg.BbKey)
|
||||
if err := bbService.ListDuplicateVersions(ctx, cancel); err != nil {
|
||||
panic(fmt.Errorf("bbService.ListDuplicateVersions %w", err))
|
||||
log.Fatalln("bbService.ListDuplicateVersions()", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
45
cmd/sync.go
45
cmd/sync.go
|
@ -2,14 +2,11 @@ package cmd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gitea.urkob.com/urko/backblaze-backup/internal/services/backblaze"
|
||||
"gitea.urkob.com/urko/backblaze-backup/kit"
|
||||
"gitea.urkob.com/urko/backblaze-backup/internal/services"
|
||||
"gitea.urkob.com/urko/backblaze-backup/kit/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -18,58 +15,32 @@ var Sync = &cobra.Command{
|
|||
Short: "Sync files or directories to Backblaze",
|
||||
Long: `A tool to backup files and directories to Backblaze.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
ctx, cancel := context.WithCancel(signalContext(cmd.Context()))
|
||||
defer cancel()
|
||||
|
||||
log.SetFlags(log.Ldate | log.Lmicroseconds)
|
||||
log.SetFlags(log.Lmicroseconds)
|
||||
filePath, err := cmd.Flags().GetString("file")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("file %w", err))
|
||||
log.Fatalln("file %w", err)
|
||||
}
|
||||
dir, err := cmd.Flags().GetString("dir")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("dir %w", err))
|
||||
log.Fatalln("dir %w", err)
|
||||
}
|
||||
bucketName, err := cmd.Flags().GetString("bucket")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("bucket %w", err))
|
||||
}
|
||||
|
||||
workers, err := cmd.Flags().GetInt("workers")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("workers %w", err))
|
||||
}
|
||||
|
||||
concurrentUploads, err := cmd.Flags().GetInt("concurrent-uploads")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("workers %w", err))
|
||||
log.Fatalln("bucket %w", err)
|
||||
}
|
||||
|
||||
envFile := ""
|
||||
if os.Getenv("BACKBLAZE_ENV") == "dev" {
|
||||
envFile = kit.RootDir() + "/.env"
|
||||
envFile = ".env"
|
||||
}
|
||||
|
||||
cfg := config.NewConfig(envFile)
|
||||
logger := logrus.New()
|
||||
logger.SetLevel(logrus.Level(cfg.LogLevel))
|
||||
|
||||
bbService, err := backblaze.NewBackBlaze(ctx, logger, cfg.BbId, cfg.BbKey)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("NewBackBlaze %w", err))
|
||||
}
|
||||
|
||||
bbService = bbService.WithOptions(backblaze.BackBlazeOptions{
|
||||
Bucket: bucketName,
|
||||
Dir: dir,
|
||||
FilePath: filePath,
|
||||
MaxWorkers: workers,
|
||||
ConcurrentUploads: concurrentUploads,
|
||||
})
|
||||
|
||||
bbService := services.NewBackBlaze(cfg.BbId, cfg.BbKey).WithBucket(bucketName).WithDir(dir).WithFile(filePath)
|
||||
if err := bbService.Sync(ctx); err != nil {
|
||||
panic(fmt.Errorf("bbService.Sync %w", err))
|
||||
log.Fatalln("bbService.Sync()", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
20
go.mod
20
go.mod
|
@ -1,6 +1,6 @@
|
|||
module gitea.urkob.com/urko/backblaze-backup
|
||||
|
||||
go 1.21
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
gitea.urkob.com/urko/go-root-dir v0.0.0-20230311113851-2f6d4355888a
|
||||
|
@ -8,24 +8,19 @@ require (
|
|||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/kurin/blazer v0.5.3
|
||||
github.com/rclone/rclone v1.63.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.8.3
|
||||
golang.org/x/sync v0.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd // indirect
|
||||
github.com/abbot/go-http-auth v0.4.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.8 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
|
@ -33,7 +28,6 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
|
@ -42,18 +36,16 @@ require (
|
|||
github.com/rfjakob/eme v1.1.2 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.5 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/oauth2 v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/term v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
google.golang.org/api v0.126.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
110
go.sum
110
go.sum
|
@ -12,7 +12,6 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP
|
|||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
|
@ -20,10 +19,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
|
|||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds=
|
||||
cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI=
|
||||
cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
|
@ -39,25 +36,17 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
|||
gitea.urkob.com/urko/go-root-dir v0.0.0-20230311113851-2f6d4355888a h1:s73cd3bRR6v0LGiBei841iIolbBJN2tbkUwN54X9vVg=
|
||||
gitea.urkob.com/urko/go-root-dir v0.0.0-20230311113851-2f6d4355888a/go.mod h1:mU9nRHl70tBhJFbgKotpoXMV+s0wx+1uJ988p4oEpSo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE=
|
||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc=
|
||||
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Unknwon/goconfig v1.0.0 h1:9IAu/BYbSLQi8puFjUQApZTxIHqSwrj5d8vpP8vTq4A=
|
||||
github.com/Unknwon/goconfig v1.0.0/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw=
|
||||
github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0=
|
||||
github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
|
@ -66,15 +55,12 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
|||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/aws/aws-sdk-go v1.44.246 h1:iLxPX6JU0bxAci9R6/bp8rX0kL871ByCTx0MZlQWv1U=
|
||||
github.com/aws/aws-sdk-go v1.44.246/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/buengese/sgzip v0.1.1 h1:ry+T8l1mlmiWEsDrH/YHZnCVWD2S3im1KLsyO+8ZmTU=
|
||||
github.com/buengese/sgzip v0.1.1/go.mod h1:i5ZiXGF3fhV7gL1xaRRL1nDnmpNj0X061FQzOS8VMas=
|
||||
github.com/calebcase/tmpfile v1.0.3 h1:BZrOWZ79gJqQ3XbAQlihYZf/YCV0H4KPIdM5K5oMpJo=
|
||||
github.com/calebcase/tmpfile v1.0.3/go.mod h1:UAUc01aHeC+pudPagY/lWvt2qS9ZO5Zzof6/tIUzqeI=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
|
@ -86,7 +72,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
|||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/colinmarc/hdfs/v2 v2.3.0 h1:tMxOjXn6+7iPUlxAyup9Ha2hnmLe3Sv5DM2qqbSQ2VY=
|
||||
github.com/colinmarc/hdfs/v2 v2.3.0/go.mod h1:nsyY1uyQOomU34KVQk9Qb/lDJobN1MQ/9WS6IqcVZno=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
|
@ -94,15 +79,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5 h1:FT+t0UEDykcor4y3dMVKXIiWJETBpRgERYTGlmMd7HU=
|
||||
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5/go.mod h1:rSS3kM9XMzSQ6pw91Qgd6yB5jdt70N4OdtrAf74As5M=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
|
||||
github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
|
||||
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
|
@ -120,18 +102,14 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
|
@ -172,7 +150,6 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
|
@ -184,45 +161,28 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
||||
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
|
||||
github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
|
||||
github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
|
||||
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/iguanesolutions/go-systemd/v5 v5.1.1 h1:Hs0Z16knPGCBFnKECrICPh+RQ89Sgy0xyzcalrHMKdw=
|
||||
github.com/iguanesolutions/go-systemd/v5 v5.1.1/go.mod h1:Quv57scs6S7T0rC6qyLfW20KU/P4p9hrbLPF+ILYrXY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
||||
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
|
||||
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
|
@ -233,7 +193,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
|||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolio/eventkit v0.0.0-20221004135224-074cf276595b h1:tO4MX3k5bvV0Sjv5jYrxStMTJxf1m/TW24XRyHji4aU=
|
||||
github.com/jtolio/eventkit v0.0.0-20221004135224-074cf276595b/go.mod h1:q7yMR8BavTz/gBNtIT/uF487LMgcuEpNGKISLAjNQes=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 h1:G+9t9cEtnC9jFiTxyptEKuNIAbiN5ZCQzX2a74lj3xg=
|
||||
|
@ -242,27 +201,19 @@ github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dv
|
|||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/koofr/go-httpclient v0.0.0-20230225102643-5d51a2e9dea6 h1:uF5FHZ/L5gvZTyBNhhcm55rRorL66DOs4KIeeVXZ8eI=
|
||||
github.com/koofr/go-httpclient v0.0.0-20230225102643-5d51a2e9dea6/go.mod h1:6HAT62hK6QH+ljNtZayJCKpbZy5hJIB12+1Ze1bFS7M=
|
||||
github.com/koofr/go-koofrclient v0.0.0-20221207135200-cbd7fc9ad6a6 h1:FHVoZMOVRA+6/y4yRlbiR3WvsrOcKBd/f64H7YiWR2U=
|
||||
github.com/koofr/go-koofrclient v0.0.0-20221207135200-cbd7fc9ad6a6/go.mod h1:MRAz4Gsxd+OzrZ0owwrUHc0zLESL+1Y5syqK/sJxK2A=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kurin/blazer v0.5.3 h1:SAgYv0TKU0kN/ETfO5ExjNAPyMt2FocO2s/UlCHfjAk=
|
||||
github.com/kurin/blazer v0.5.3/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
|
@ -281,24 +232,16 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
|||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/ncw/go-acd v0.0.0-20201019170801-fe55f33415b1 h1:nAjWYc03awJAjsozNehdGZsm5LP7AhLOvjgbS8zN1tk=
|
||||
github.com/ncw/go-acd v0.0.0-20201019170801-fe55f33415b1/go.mod h1:MLIrzg7gp/kzVBxRE1olT7CWYMCklcUWU+ekoxOD9x0=
|
||||
github.com/ncw/swift/v2 v2.0.1 h1:q1IN8hNViXEv8Zvg3Xdis4a3c4IlIGezkYz09zQL5J0=
|
||||
github.com/ncw/swift/v2 v2.0.1/go.mod h1:z0A9RVdYPjNjXVo2pDOPxZ4eu3oarO1P91fTItcb+Kg=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.34.0 h1:uG1KucBxAbn8cYRgQHxtQKogtl85nOX8LhimZCPfMqw=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.34.0/go.mod h1:MXMLMzHnnd9wlpgadPkdlkZ9YrwQmCOmbX5kjVEJodw=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14 h1:XeOYlK9W1uCmhjJSsY78Mcuh7MVkNjTzmHx1yBzizSU=
|
||||
github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14/go.mod h1:jVblp62SafmidSkvWrXyxAme3gaTfEtWwRPGz5cpvHg=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6 h1:5TvW1dv00Y13njmQ1AWkxSWtPkwE7ZEF6yDuv9q+Als=
|
||||
github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
|
||||
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
|
@ -330,9 +273,7 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
|||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8 h1:Y258uzXU/potCYnQd1r6wlAnoMB68BiCkCcCnKx1SH8=
|
||||
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8/go.mod h1:bSJjRokAHHOhA+XFxplld8w2R/dXLH7Z3BZ532vhFwU=
|
||||
github.com/rclone/ftp v0.0.0-20230327202000-dadc1f64e87d h1:ZyH6ZfA/PzxF4qQS2MgFLXRdw/pWOSNJA7Lq0pkX49Y=
|
||||
github.com/rclone/ftp v0.0.0-20230327202000-dadc1f64e87d/go.mod h1:mWj8othLks994zO7BLHHfh9cpj1eM1n7XqWvX+DM6ic=
|
||||
github.com/rclone/rclone v1.63.1 h1:iITCUNBfAXnguHjRPFq+w/gGIW0L0las78h4H5CH2Ms=
|
||||
github.com/rclone/rclone v1.63.1/go.mod h1:eUQaKsf1wJfHKB0RDoM8RaPAeRB2eI/Qw+Vc9Ho5FGM=
|
||||
github.com/rfjakob/eme v1.1.2 h1:SxziR8msSOElPayZNFfQw4Tjx/Sbaeeh3eRvrHVMUs4=
|
||||
|
@ -351,11 +292,8 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf
|
|||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
|
||||
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spacemonkeygo/monkit/v3 v3.0.19 h1:wqBb9bpD7jXkVi4XwIp8jn1fektaVBQ+cp9SHRXgAdo=
|
||||
github.com/spacemonkeygo/monkit/v3 v3.0.19/go.mod h1:kj1ViJhlyADa7DiA4xVnTuPA46lFKbM7mxQTrXCuJP4=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
|
@ -373,45 +311,36 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca h1:I9rVnNXdIkij4UvMT7OmKhH9sOIvS8iXkxfPdnn9wQA=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca/go.mod h1:suDIky6yrK07NnaBadCB4sS0CqFOvUK91lH7CR+JlDA=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 h1:zMsHhfK9+Wdl1F7sIKLyx3wrOFofpb3rWFbA4HgcK5k=
|
||||
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3/go.mod h1:R0Gbuw7ElaGSLOZUSwBm/GgVwMd30jWxBDdAyMOeTuc=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yunify/qingstor-sdk-go/v3 v3.2.0 h1:9sB2WZMgjwSUNZhrgvaNGazVltoFUUfuS9f0uCWtTr8=
|
||||
github.com/yunify/qingstor-sdk-go/v3 v3.2.0/go.mod h1:KciFNuMu6F4WLk9nGwwK69sCGKLCdd9f97ac/wfumS4=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
|
||||
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -473,8 +402,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
|
|||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -482,8 +411,7 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
|
|||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
|
||||
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
|
||||
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -542,8 +470,8 @@ golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
|||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -551,8 +479,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -618,8 +546,7 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
|||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o=
|
||||
google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw=
|
||||
google.golang.org/api v0.115.0 h1:6FFkVvStt4YqXSx3azKyzj7fXerGnVlLJ/eud01nBDE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
@ -627,7 +554,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
|
|||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
|
@ -657,9 +583,7 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
|
|||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||
google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633 h1:0BOZf6qNozI3pkN3fJLwNubheHJYHhMh91GRFOWWK08=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
@ -672,8 +596,7 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
|||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -692,8 +615,6 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
@ -716,8 +637,5 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
|
|||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
storj.io/common v0.0.0-20221123115229-fed3e6651b63 h1:OuleF/3FvZe3Nnu6NdwVr+FvCXjfD4iNNdgfI2kcs3k=
|
||||
storj.io/common v0.0.0-20221123115229-fed3e6651b63/go.mod h1:+gF7jbVvpjVIVHhK+EJFhfPbudX395lnPq/dKkj/Qys=
|
||||
storj.io/drpc v0.0.32 h1:5p5ZwsK/VOgapaCu+oxaPVwO6UwIs+iwdMiD50+R4PI=
|
||||
storj.io/drpc v0.0.32/go.mod h1:6rcOyR/QQkSTX/9L5ZGtlZaE2PtXTTZl8d+ulSeeYEg=
|
||||
storj.io/uplink v1.10.0 h1:3hS0hszupHSxEoC4DsMpljaRy0uNoijEPVF6siIE28Q=
|
||||
storj.io/uplink v1.10.0/go.mod h1:gJIQumB8T3tBHPRive51AVpbc+v2xe+P/goFNMSRLG4=
|
||||
|
|
|
@ -0,0 +1,437 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
rcloneb2 "github.com/rclone/rclone/backend/b2"
|
||||
|
||||
"github.com/kurin/blazer/b2"
|
||||
"github.com/rclone/rclone/fs/config/configmap"
|
||||
"github.com/rclone/rclone/fs/operations"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
const writers = 10
|
||||
const maxConcurrentWeight = 4
|
||||
const largeFileSize = 500 * 1024 * 1024 // 500 MB
|
||||
|
||||
type BackBalze struct {
|
||||
bucketName string
|
||||
dir string
|
||||
filePath string
|
||||
maxWorkers int
|
||||
bbID string
|
||||
bbKey string
|
||||
}
|
||||
|
||||
func NewBackBlaze(bbID, bbKey string) *BackBalze {
|
||||
log.Println("runtime.NumCPU()", runtime.NumCPU())
|
||||
return &BackBalze{
|
||||
bbID: bbID,
|
||||
bbKey: bbKey,
|
||||
maxWorkers: runtime.NumCPU(),
|
||||
}
|
||||
}
|
||||
func (b *BackBalze) WithBucket(bucketName string) *BackBalze {
|
||||
b.bucketName = bucketName
|
||||
return b
|
||||
}
|
||||
func (b *BackBalze) WithDir(dir string) *BackBalze {
|
||||
b.dir = dir
|
||||
return b
|
||||
}
|
||||
func (b *BackBalze) WithFile(filePath string) *BackBalze {
|
||||
b.filePath = filePath
|
||||
return b
|
||||
}
|
||||
func (b *BackBalze) Sync(ctx context.Context) error {
|
||||
if b.bucketName == "" && (b.filePath == "" || b.dir == "") {
|
||||
return fmt.Errorf("bucket name is %v | filePath is %v | dir is %v", b.bucketName, b.filePath, b.dir)
|
||||
}
|
||||
|
||||
if b.filePath != "" && b.dir != "" {
|
||||
return errors.New("you must select just 1 option, dir or file")
|
||||
}
|
||||
|
||||
b2Client, err := b2.NewClient(ctx, b.bbID, b.bbKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b2.NewClient %w", err)
|
||||
}
|
||||
log.Println("b2Client ok")
|
||||
bc, err := b2Client.Bucket(ctx, b.bucketName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b2Client.Bucket %w", err)
|
||||
}
|
||||
|
||||
if bc == nil {
|
||||
return fmt.Errorf("bucket doesn't exist %s", b.bucketName)
|
||||
}
|
||||
|
||||
log.Println("bucket found:", bc.Name())
|
||||
if b.filePath != "" {
|
||||
log.Println("file:", b.filePath)
|
||||
|
||||
if err := copyFile(ctx, bc, b.filePath); err != nil {
|
||||
return fmt.Errorf("copyFile %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if b.dir != "" {
|
||||
oldFiles, err := bucketFiles(ctx, bc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bucketFiles %w", err)
|
||||
}
|
||||
log.Println(strings.Repeat("*", 40))
|
||||
log.Println("oldFiles to clean:\n\t\t" + strings.Join(oldFiles, "\n\t\t"))
|
||||
log.Println(strings.Repeat("*", 40))
|
||||
|
||||
fileChan := make(chan string)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < b.maxWorkers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for src := range fileChan {
|
||||
log.Println("start copying file", src)
|
||||
if err := copyFile(ctx, bc, src); err != nil {
|
||||
log.Printf("error copying file %s: %v\n", src, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Walk the directory and send files to the channel for uploading
|
||||
err = filepath.WalkDir(b.dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.IsDir() {
|
||||
fileChan <- path
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error walking the directory: %v", err)
|
||||
}
|
||||
|
||||
// Close the channel (no more files to send)
|
||||
close(fileChan)
|
||||
wg.Wait()
|
||||
|
||||
// Cleanup old files after backup is completed
|
||||
if err := cleanBucket(ctx, bc, oldFiles); err != nil {
|
||||
return fmt.Errorf("cleanBucket %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("copied successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BackBalze) OldSync() error {
|
||||
if b.bucketName == "" && (b.filePath == "" || b.dir == "") {
|
||||
return fmt.Errorf("bucket name is %v | filePath is %v | dir is %v", b.bucketName, b.filePath, b.dir)
|
||||
}
|
||||
|
||||
if b.filePath != "" && b.dir != "" {
|
||||
return errors.New("you must select just 1 option, dir or file")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
b2Client, err := b2.NewClient(ctx, b.bbID, b.bbKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b2.NewClient %w", err)
|
||||
}
|
||||
log.Println("b2Client ok")
|
||||
bc, err := b2Client.Bucket(ctx, b.bucketName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b2Client.Bucket %w", err)
|
||||
}
|
||||
|
||||
if bc == nil {
|
||||
return fmt.Errorf("bucket doesn't exist %s", b.bucketName)
|
||||
}
|
||||
|
||||
log.Println("bucket found:", bc.Name())
|
||||
if b.filePath != "" {
|
||||
log.Println("file:", b.filePath)
|
||||
|
||||
if err := copyFile(ctx, bc, b.filePath); err != nil {
|
||||
return fmt.Errorf("copyFile %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if b.dir != "" {
|
||||
oldFiles, err := bucketFiles(ctx, bc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bucketFiles %w", err)
|
||||
}
|
||||
log.Println(strings.Repeat("*", 40))
|
||||
log.Println("oldFiles to clean:\n\t\t" + strings.Join(oldFiles, "\n\t\t"))
|
||||
log.Println(strings.Repeat("*", 40))
|
||||
|
||||
fileChan := make(chan string)
|
||||
uploadSem := semaphore.NewWeighted(maxConcurrentWeight)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < b.maxWorkers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for src := range fileChan {
|
||||
info, err := os.Stat(src)
|
||||
if err != nil {
|
||||
log.Printf("error getting file info %s: %v\n", src, err)
|
||||
continue
|
||||
}
|
||||
weight := int64(1)
|
||||
if info.Size() > largeFileSize {
|
||||
weight = 2
|
||||
}
|
||||
if err := uploadSem.Acquire(ctx, weight); err == nil {
|
||||
log.Println("start copying file", src)
|
||||
if err := copyFile(ctx, bc, src); err != nil {
|
||||
log.Printf("error copying file %s: %v\n", src, err)
|
||||
}
|
||||
uploadSem.Release(weight)
|
||||
} else {
|
||||
log.Printf("error acquiring semaphore: %v\n", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Walk the directory and send files to the channel for uploading
|
||||
err = filepath.WalkDir(b.dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.IsDir() {
|
||||
fileChan <- path
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error walking the directory: %v", err)
|
||||
}
|
||||
|
||||
// Close the channel (no more files to send)
|
||||
close(fileChan)
|
||||
wg.Wait()
|
||||
|
||||
// Cleanup old files after backup is completed
|
||||
if err := cleanBucket(ctx, bc, oldFiles); err != nil {
|
||||
return fmt.Errorf("cleanBucket %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("copied successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(ctx context.Context, bucket *b2.Bucket, src string) error {
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w := bucket.Object(fi.Name()).NewWriter(ctx)
|
||||
w.ConcurrentUploads = writers
|
||||
if _, err := io.Copy(w, f); err != nil {
|
||||
w.Close()
|
||||
return err
|
||||
}
|
||||
return w.Close()
|
||||
}
|
||||
|
||||
func cleanBucket(ctx context.Context, bucket *b2.Bucket, files []string) error {
|
||||
var errorBuilder strings.Builder
|
||||
for _, v := range files {
|
||||
obj := bucket.Object(v)
|
||||
if obj == nil {
|
||||
log.Println("object is nil", v)
|
||||
continue
|
||||
}
|
||||
if err := obj.Delete(ctx); err != nil {
|
||||
errorBuilder.WriteString(fmt.Errorf("error deleting %s : %w", v, err).Error())
|
||||
errorBuilder.WriteString("; ")
|
||||
}
|
||||
}
|
||||
|
||||
if errorBuilder.Len() > 0 {
|
||||
return errors.New(errorBuilder.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func bucketFiles(ctx context.Context, bucket *b2.Bucket) ([]string, error) {
|
||||
bucketIter := bucket.List(ctx)
|
||||
if bucketIter == nil {
|
||||
return nil, fmt.Errorf("bucket list cannot be nil")
|
||||
}
|
||||
var files []string
|
||||
for {
|
||||
if !bucketIter.Next() {
|
||||
if bucketIter.Err() != nil {
|
||||
return nil, fmt.Errorf("bucketIter err %w", bucketIter.Err())
|
||||
}
|
||||
break
|
||||
}
|
||||
if bucketIter.Object() == nil {
|
||||
log.Println("bucketIter Object is nil")
|
||||
continue
|
||||
}
|
||||
files = append(files, bucketIter.Object().Name())
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
type duplicate struct {
|
||||
bucket string
|
||||
file string
|
||||
count int
|
||||
}
|
||||
|
||||
func (d duplicate) dir() string {
|
||||
if !strings.Contains(d.file, "/") {
|
||||
return d.bucket
|
||||
}
|
||||
splitted := strings.Split(d.file, "/")
|
||||
return strings.Join(splitted[:(len(splitted)-1)], "/")
|
||||
}
|
||||
|
||||
func (b *BackBalze) CleanUp(ctx context.Context, cancel context.CancelFunc) error {
|
||||
b2Client, err := b2.NewClient(ctx, b.bbID, b.bbKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b2.NewClient %w", err)
|
||||
}
|
||||
log.Println("b2Client ok")
|
||||
|
||||
dups, err := b.listDuplicates(ctx, cancel, b2Client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b.listDuplicates: %w", err)
|
||||
}
|
||||
|
||||
if len(dups) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, d := range dups {
|
||||
smpl := configmap.Simple{}
|
||||
smpl.Set("account", b.bbID)
|
||||
smpl.Set("key", b.bbKey)
|
||||
smpl.Set("chunk_size", strconv.FormatInt(int64(9600), 10))
|
||||
f, err := rcloneb2.NewFs(ctx, "B2", d.dir(), smpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rclonefs.NewFs %w", err)
|
||||
}
|
||||
if err := operations.CleanUp(ctx, f); err != nil {
|
||||
return fmt.Errorf("operations.CleanUp %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BackBalze) ListDuplicateVersions(ctx context.Context, cancel context.CancelFunc) error {
|
||||
b2Client, err := b2.NewClient(ctx, b.bbID, b.bbKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b2.NewClient %w", err)
|
||||
}
|
||||
log.Println("b2Client ok")
|
||||
|
||||
dups, err := b.listDuplicates(ctx, cancel, b2Client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b.listDuplicates: %w", err)
|
||||
}
|
||||
|
||||
if len(dups) > 0 {
|
||||
var builder strings.Builder
|
||||
for _, dup := range dups {
|
||||
builder.WriteString(fmt.Sprintf("%+v\n", dup))
|
||||
}
|
||||
return fmt.Errorf("found duplicates: %s", builder.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BackBalze) listDuplicates(ctx context.Context, cancel context.CancelFunc, b2Client *b2.Client) ([]duplicate, error) {
|
||||
buckets, err := b2Client.ListBuckets(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("b2Client.Bucket %w", err)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
dups := make([]duplicate, 0)
|
||||
|
||||
log.Println("len(buckets)", len(buckets))
|
||||
sm := semaphore.NewWeighted(int64(b.maxWorkers))
|
||||
wg.Add(len(buckets))
|
||||
for _, bc := range buckets {
|
||||
if err := sm.Acquire(ctx, 1); err != nil {
|
||||
return nil, fmt.Errorf("sm.Acquire %w", err)
|
||||
}
|
||||
|
||||
go func(bc *b2.Bucket) {
|
||||
defer sm.Release(1)
|
||||
defer wg.Done()
|
||||
files := make(map[string]int, 0)
|
||||
|
||||
bucketIter := bc.List(ctx, b2.ListHidden())
|
||||
if bucketIter == nil {
|
||||
log.Println("bucket list cannot be nil")
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
if !bucketIter.Next() {
|
||||
if bucketIter.Err() != nil {
|
||||
log.Println("bucketIter err %w", bucketIter.Err())
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
if bucketIter.Object() == nil {
|
||||
log.Println("bucketIter Object is nil")
|
||||
continue
|
||||
}
|
||||
files[bucketIter.Object().Name()]++
|
||||
}
|
||||
|
||||
// Search duplicates
|
||||
for file, count := range files {
|
||||
if count > 1 {
|
||||
dups = append(dups, duplicate{
|
||||
bucket: bc.Name(),
|
||||
file: file,
|
||||
count: count,
|
||||
})
|
||||
}
|
||||
}
|
||||
}(bc)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return dups, nil
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package backblaze
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/kurin/blazer/b2"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type BackBlaze struct {
|
||||
logger *logrus.Logger
|
||||
b2Client *b2.Client
|
||||
bbID string
|
||||
bbKey string
|
||||
options BackBlazeOptions
|
||||
}
|
||||
|
||||
// NewBackBlaze initializes a new BackBlaze struct with given BackBlaze ID and Key.
|
||||
func NewBackBlaze(ctx context.Context, logger *logrus.Logger, bbID, bbKey string) (*BackBlaze, error) {
|
||||
b2Client, err := b2.NewClient(ctx, bbID, bbKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("b2.NewClient %w", err)
|
||||
}
|
||||
|
||||
return &BackBlaze{
|
||||
logger: logger,
|
||||
b2Client: b2Client,
|
||||
bbID: bbID,
|
||||
bbKey: bbKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type BackBlazeOptions struct {
|
||||
Bucket string
|
||||
Dir string
|
||||
FilePath string
|
||||
MaxWorkers int
|
||||
ConcurrentUploads int
|
||||
}
|
||||
|
||||
func (b *BackBlaze) WithOptions(options BackBlazeOptions) *BackBlaze {
|
||||
b.options = options
|
||||
return b
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
package backblaze
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kurin/blazer/b2"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Path string
|
||||
Size int // file size in bytes
|
||||
ModTime time.Time
|
||||
IsUploaded *bool
|
||||
}
|
||||
|
||||
// localFiles lists the local files in the given backup directory and sends them to a channel.
|
||||
// It closes the channel after all files are listed.
|
||||
func (b *BackBlaze) localFiles(backupDir string, fileChan chan<- File) error {
|
||||
defer close(fileChan)
|
||||
// Walk the directory and send files to the channel
|
||||
err := filepath.WalkDir(backupDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.IsDir() {
|
||||
i, err := d.Info()
|
||||
if err != nil {
|
||||
return fmt.Errorf("d.Info: %w", err)
|
||||
}
|
||||
fileChan <- File{Path: filepath.Base(path), Size: int(i.Size()), ModTime: i.ModTime()}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error walking the directory: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// b2BucketFiles lists the files in the given B2 bucket and sends them to a channel.
|
||||
// It closes the channel after all files are listed.
|
||||
func (b *BackBlaze) b2BucketFiles(ctx context.Context, bucketName string, fileChan chan<- File) error {
|
||||
bucket, err := b.b2Client.Bucket(ctx, bucketName)
|
||||
defer close(fileChan)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b2Client.Bucket %w", err)
|
||||
}
|
||||
|
||||
bucketIter := bucket.List(ctx, b2.ListHidden())
|
||||
if bucketIter == nil {
|
||||
return errors.New("bucket list cannot be nil")
|
||||
}
|
||||
|
||||
for {
|
||||
if !bucketIter.Next() {
|
||||
if bucketIter.Err() != nil {
|
||||
return fmt.Errorf("bucketIter err %w", bucketIter.Err())
|
||||
}
|
||||
break
|
||||
}
|
||||
if bucketIter.Object() == nil {
|
||||
return errors.New("bucketIter Object is nil")
|
||||
}
|
||||
|
||||
fileName := path.Base(bucketIter.Object().Name())
|
||||
attrs, err := bucketIter.Object().Attrs(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bucketIter.Object().Attrs %s err %w", fileName, bucketIter.Err())
|
||||
}
|
||||
isUploaded := attrs.Status != b2.Uploaded
|
||||
fileChan <- File{Path: path.Base(fileName), Size: int(attrs.Size), IsUploaded: &isUploaded, ModTime: attrs.UploadTimestamp}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrLocalNotInCloud error = errors.New("exists locally but not in the cloud")
|
||||
var ErrCloudNotInLocal error = errors.New("exists on B2 but not locally")
|
||||
|
||||
type B2Local struct {
|
||||
File File
|
||||
Err error
|
||||
}
|
||||
|
||||
// CompareConcurrent concurrently fetches the list of local files and cloud files,
|
||||
// then compares them to ensure all local files exist in the cloud.
|
||||
// Errors are sent to a provided error channel. The function will panic if an error occurs while listing files.
|
||||
func (b *BackBlaze) CompareConcurrent(
|
||||
ctx context.Context,
|
||||
backupDir, bucketName string,
|
||||
localChan, b2Chan chan<- B2Local,
|
||||
doneChan chan<- int,
|
||||
) {
|
||||
var wg sync.WaitGroup
|
||||
localFiles := make(map[string]File)
|
||||
cloudFiles := make(map[string]File)
|
||||
localFileChan := make(chan File)
|
||||
b2FileChan := make(chan File)
|
||||
|
||||
// Local listing
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for f := range localFileChan {
|
||||
if _, ok := localFiles[f.Path]; ok {
|
||||
panic(fmt.Errorf("local file already exists in map: %s", f.Path))
|
||||
}
|
||||
b.logger.Debugf("local file %+v\n", f)
|
||||
localFiles[f.Path] = f
|
||||
}
|
||||
}()
|
||||
|
||||
if err := b.localFiles(backupDir, localFileChan); err != nil {
|
||||
panic(fmt.Errorf("b.LocalFilesWithB2: %w", err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Cloud listing
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for f := range b2FileChan {
|
||||
if _, ok := cloudFiles[f.Path]; ok {
|
||||
panic(fmt.Errorf(`cloud file already exists in map: %s\n you should run 'backblazebackup cleanup --bucket "%s"'`, f.Path, b.options.Bucket))
|
||||
}
|
||||
b.logger.Debugf("B2 file %+v\n", f)
|
||||
cloudFiles[f.Path] = f
|
||||
}
|
||||
}()
|
||||
if err := b.b2BucketFiles(ctx, bucketName, b2FileChan); err != nil {
|
||||
panic(fmt.Errorf("b.LocalFilesWithB2: %w", err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for both to complete
|
||||
wg.Wait()
|
||||
|
||||
// Now check local files that are not present in cloud
|
||||
var count atomic.Int64
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for path, localFile := range localFiles {
|
||||
if _, exists := cloudFiles[path]; !exists {
|
||||
localChan <- B2Local{File: localFile, Err: ErrLocalNotInCloud}
|
||||
continue
|
||||
}
|
||||
b.logger.Debugf("localFile %+v\n", localFile)
|
||||
localChan <- B2Local{File: localFile, Err: nil}
|
||||
count.Add(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// Now check cloud files that are not in local
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for path, cloudFile := range cloudFiles {
|
||||
if _, exists := localFiles[path]; !exists {
|
||||
b2Chan <- B2Local{File: cloudFile, Err: ErrCloudNotInLocal}
|
||||
continue
|
||||
}
|
||||
b2Chan <- B2Local{File: cloudFile, Err: nil}
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
close(localChan)
|
||||
close(b2Chan)
|
||||
doneChan <- int(count.Load())
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package backblaze
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/rclone/rclone/backend/b2"
|
||||
"github.com/rclone/rclone/fs/config/configmap"
|
||||
"github.com/rclone/rclone/fs/operations"
|
||||
)
|
||||
|
||||
func (b *BackBlaze) CleanUp(ctx context.Context, cancel context.CancelFunc, bucketName string) error {
|
||||
var dups []duplicate
|
||||
var err error
|
||||
if bucketName != "" {
|
||||
dups, err = b.listDuplicatesFromBucket(ctx, cancel, b.b2Client, bucketName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b.listDuplicatesFromBucket: %w", err)
|
||||
}
|
||||
} else {
|
||||
dups, err = b.listDuplicates(ctx, cancel, b.b2Client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b.listDuplicates: %w", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(dups) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
smpl := configmap.Simple{}
|
||||
smpl.Set("account", b.bbID)
|
||||
smpl.Set("key", b.bbKey)
|
||||
smpl.Set("chunk_size", strconv.FormatInt(int64(9600), 10))
|
||||
b.logger.Infoln("duplicates", len(dups))
|
||||
for _, d := range dups {
|
||||
f, err := b2.NewFs(ctx, "B2", d.dir(), smpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rclonefs.NewFs %w", err)
|
||||
}
|
||||
if err := operations.CleanUp(ctx, f); err != nil {
|
||||
return fmt.Errorf("operations.CleanUp %w", err)
|
||||
}
|
||||
b.logger.Infoln(d.dir(), "cleaned up")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
package backblaze
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/kurin/blazer/b2"
|
||||
"golang.org/x/sync/semaphore"
|
||||
)
|
||||
|
||||
type duplicate struct {
|
||||
bucket string
|
||||
file string
|
||||
count int
|
||||
}
|
||||
|
||||
func (d duplicate) dir() string {
|
||||
if !strings.Contains(d.file, "/") {
|
||||
return d.bucket
|
||||
}
|
||||
splitted := strings.Split(d.file, "/")
|
||||
return strings.Join(splitted[:(len(splitted)-1)], "/")
|
||||
}
|
||||
|
||||
func (b *BackBlaze) ListDuplicateVersions(ctx context.Context, cancel context.CancelFunc) error {
|
||||
b2Client, err := b2.NewClient(ctx, b.bbID, b.bbKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b2.NewClient %w", err)
|
||||
}
|
||||
|
||||
dups, err := b.listDuplicates(ctx, cancel, b2Client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b.listDuplicates: %w", err)
|
||||
}
|
||||
|
||||
if len(dups) > 0 {
|
||||
var builder strings.Builder
|
||||
for _, dup := range dups {
|
||||
builder.WriteString(fmt.Sprintf("%+v\n", dup))
|
||||
}
|
||||
return fmt.Errorf("found duplicates: %s", builder.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BackBlaze) listDuplicates(ctx context.Context, cancel context.CancelFunc, b2Client *b2.Client) ([]duplicate, error) {
|
||||
buckets, err := b2Client.ListBuckets(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("b2Client.Bucket %w", err)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
dups := make([]duplicate, 0)
|
||||
|
||||
sm := semaphore.NewWeighted(int64(b.options.MaxWorkers))
|
||||
wg.Add(len(buckets))
|
||||
for _, bc := range buckets {
|
||||
if err := sm.Acquire(ctx, 1); err != nil {
|
||||
return nil, fmt.Errorf("sm.Acquire %w", err)
|
||||
}
|
||||
|
||||
go func(bc *b2.Bucket) {
|
||||
defer sm.Release(1)
|
||||
defer wg.Done()
|
||||
files := make(map[string]int, 0)
|
||||
|
||||
bucketIter := bc.List(ctx, b2.ListHidden())
|
||||
if bucketIter == nil {
|
||||
b.logger.Errorln("bucket list cannot be nil")
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
if !bucketIter.Next() {
|
||||
if bucketIter.Err() != nil {
|
||||
b.logger.Errorf("bucketIter err %s", bucketIter.Err())
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
if bucketIter.Object() == nil {
|
||||
b.logger.Errorln("bucketIter Object is nil")
|
||||
continue
|
||||
}
|
||||
files[bucketIter.Object().Name()]++
|
||||
}
|
||||
|
||||
// Search duplicates
|
||||
for file, count := range files {
|
||||
if count > 1 {
|
||||
dups = append(dups, duplicate{
|
||||
bucket: bc.Name(),
|
||||
file: file,
|
||||
count: count,
|
||||
})
|
||||
}
|
||||
}
|
||||
}(bc)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return dups, nil
|
||||
}
|
||||
|
||||
func (b *BackBlaze) listDuplicatesFromBucket(ctx context.Context, cancel context.CancelFunc, b2Client *b2.Client, bucketName string) ([]duplicate, error) {
|
||||
bucket, err := b2Client.Bucket(ctx, bucketName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("b2Client.Bucket %w", err)
|
||||
}
|
||||
|
||||
dups := make([]duplicate, 0)
|
||||
files := make(map[string]int, 0)
|
||||
|
||||
bucketIter := bucket.List(ctx, b2.ListHidden())
|
||||
if bucketIter == nil {
|
||||
return nil, errors.New("bucket list cannot be nil")
|
||||
|
||||
}
|
||||
|
||||
for {
|
||||
if !bucketIter.Next() {
|
||||
if bucketIter.Err() != nil {
|
||||
return nil, fmt.Errorf("bucketIter err %w", bucketIter.Err())
|
||||
}
|
||||
break
|
||||
}
|
||||
if bucketIter.Object() == nil {
|
||||
return nil, errors.New("bucketIter Object is nil")
|
||||
}
|
||||
files[bucketIter.Object().Name()]++
|
||||
}
|
||||
|
||||
// Search duplicates
|
||||
for file, count := range files {
|
||||
if count > 1 {
|
||||
dups = append(dups, duplicate{
|
||||
bucket: bucket.Name(),
|
||||
file: file,
|
||||
count: count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return dups, nil
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
package backblaze
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/kurin/blazer/b2"
|
||||
)
|
||||
|
||||
func (b *BackBlaze) Sync(ctx context.Context) error {
|
||||
if b.options.Bucket == "" && (b.options.FilePath == "" || b.options.Dir == "") {
|
||||
return fmt.Errorf("bucket name is %v | filePath is %v | dir is %v", b.options.Bucket, b.options.FilePath, b.options.Dir)
|
||||
}
|
||||
|
||||
if b.options.FilePath != "" && b.options.Dir != "" {
|
||||
return errors.New("you must select just 1 option, dir or file")
|
||||
}
|
||||
|
||||
bc, err := b.b2Client.Bucket(ctx, b.options.Bucket)
|
||||
if err != nil {
|
||||
return fmt.Errorf("b2Client.Bucket %w", err)
|
||||
}
|
||||
|
||||
if bc == nil {
|
||||
return fmt.Errorf("bucket doesn't exist %s", b.options.Bucket)
|
||||
}
|
||||
|
||||
b.logger.Infoln("bucket found:", bc.Name())
|
||||
if b.options.FilePath != "" {
|
||||
// Create a separate context for long-running operations
|
||||
longRunningCtx, cancelLongRunningOps := context.WithCancel(context.Background())
|
||||
defer cancelLongRunningOps()
|
||||
|
||||
b.logger.Infoln("file:", b.options.FilePath)
|
||||
|
||||
if err := b.copyFile(longRunningCtx, bc, b.options.FilePath); err != nil {
|
||||
return fmt.Errorf("copyFile %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if b.options.Dir != "" {
|
||||
// Create a separate context for long-running operations
|
||||
longRunningCtx, cancelLongRunningOps := context.WithCancel(context.Background())
|
||||
defer cancelLongRunningOps()
|
||||
|
||||
oldFiles, err := b.bucketFiles(longRunningCtx, bc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bucketFiles %w", err)
|
||||
}
|
||||
b.logger.Debugln(strings.Repeat("*", 40))
|
||||
b.logger.Debugln("oldFiles to clean:\n\t\t" + strings.Join(oldFiles, "\n\t\t"))
|
||||
b.logger.Debugln(strings.Repeat("*", 40))
|
||||
|
||||
fileChan := make(chan string)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < b.options.MaxWorkers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for src := range fileChan {
|
||||
if err := b.copyFile(longRunningCtx, bc, src); err != nil {
|
||||
b.logger.Errorf("error copying file %s: %v\n", src, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Walk the directory and send files to the channel for uploading
|
||||
err = filepath.WalkDir(b.options.Dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.IsDir() {
|
||||
fileChan <- path
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error walking the directory: %v", err)
|
||||
}
|
||||
|
||||
// Close the channel (no more files to send)
|
||||
close(fileChan)
|
||||
wg.Wait()
|
||||
|
||||
// Cleanup old files after backup is completed
|
||||
if err := b.cleanBucket(longRunningCtx, bc, oldFiles); err != nil {
|
||||
return fmt.Errorf("cleanBucket %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
b.logger.Infoln("copied successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BackBlaze) copyFile(ctx context.Context, bucket *b2.Bucket, src string) error {
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w := bucket.Object(fi.Name()).NewWriter(ctx)
|
||||
w.ConcurrentUploads = b.options.ConcurrentUploads
|
||||
w.UseFileBuffer = true
|
||||
b.logger.Infoln("start copying ", fi.Name())
|
||||
if _, err := io.Copy(w, f); err != nil {
|
||||
w.Close()
|
||||
return err
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.logger.Infoln("end copying ", fi.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BackBlaze) cleanBucket(ctx context.Context, bucket *b2.Bucket, files []string) error {
|
||||
var errorBuilder strings.Builder
|
||||
for _, v := range files {
|
||||
obj := bucket.Object(v)
|
||||
if obj == nil {
|
||||
b.logger.Errorln("bucket.Object is nil", v)
|
||||
continue
|
||||
}
|
||||
if err := obj.Delete(ctx); err != nil {
|
||||
errorBuilder.WriteString(fmt.Errorf("error deleting %s : %w", v, err).Error())
|
||||
errorBuilder.WriteString("; ")
|
||||
}
|
||||
}
|
||||
|
||||
if errorBuilder.Len() > 0 {
|
||||
return errors.New(errorBuilder.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BackBlaze) bucketFiles(ctx context.Context, bucket *b2.Bucket) ([]string, error) {
|
||||
bucketIter := bucket.List(ctx)
|
||||
if bucketIter == nil {
|
||||
return nil, fmt.Errorf("bucket list cannot be nil")
|
||||
}
|
||||
var files []string
|
||||
for {
|
||||
if !bucketIter.Next() {
|
||||
if bucketIter.Err() != nil {
|
||||
return nil, fmt.Errorf("bucketIter err %w", bucketIter.Err())
|
||||
}
|
||||
break
|
||||
}
|
||||
if bucketIter.Object() == nil {
|
||||
b.logger.Errorln("bucketIter Object is nil")
|
||||
continue
|
||||
}
|
||||
files = append(files, bucketIter.Object().Name())
|
||||
}
|
||||
return files, nil
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
mime = "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
|
||||
subjectReport = "B2 vs Local Files"
|
||||
templateSuccess = "success.html"
|
||||
templateConfirm = "confirm.html"
|
||||
templateForgotPassword = "forgot_password.html"
|
||||
templateRegister = "register.html"
|
||||
delimeter = "**=myohmy689407924327"
|
||||
)
|
||||
|
||||
type EmailService struct {
|
||||
auth smtp.Auth
|
||||
host string
|
||||
port string
|
||||
from string
|
||||
tlsconfig *tls.Config
|
||||
}
|
||||
|
||||
type EmailWithAttachments struct {
|
||||
To string
|
||||
Bucket string
|
||||
BackupDir string
|
||||
Count map[string]int
|
||||
Attachments []EmailAttachment
|
||||
}
|
||||
|
||||
type EmailAttachment struct {
|
||||
File io.Reader
|
||||
Title string
|
||||
}
|
||||
|
||||
func (e EmailAttachment) ReadContent() ([]byte, error) {
|
||||
bts, err := io.ReadAll(e.File)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading attachment: %s", err)
|
||||
}
|
||||
return bts, nil
|
||||
}
|
||||
|
||||
type MailServiceConfig struct {
|
||||
Auth smtp.Auth
|
||||
Host string
|
||||
Port string
|
||||
From string // Sender email address
|
||||
}
|
||||
|
||||
func NewMailService(config MailServiceConfig) *EmailService {
|
||||
return &EmailService{
|
||||
auth: config.Auth,
|
||||
host: config.Host,
|
||||
port: config.Port,
|
||||
from: config.From,
|
||||
tlsconfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: config.Host,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EmailService) SendOK(emailData EmailWithAttachments) error {
|
||||
template := strings.Replace(htmlTemplate, "{{bucket}}", emailData.Bucket, -1)
|
||||
template = strings.Replace(template, "{{local_backup_path}}", emailData.BackupDir, -1)
|
||||
for k, v := range emailData.Count {
|
||||
template = strings.Replace(template, "{{"+k+"}}", fmt.Sprint(v), -1)
|
||||
}
|
||||
msg, err := newMessage(e.from, emailData.To, subjectReport).
|
||||
withAttachments(template, emailData.Attachments)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while upload attachments %w", err)
|
||||
}
|
||||
|
||||
return e.send(emailData.To, msg)
|
||||
}
|
||||
|
||||
func (e *EmailService) send(to string, msg []byte) error {
|
||||
c, err := smtp.Dial(e.host + ":" + e.port)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DIAL: %s", err)
|
||||
}
|
||||
|
||||
if err = c.StartTLS(e.tlsconfig); err != nil {
|
||||
return fmt.Errorf("c.StartTLS: %s", err)
|
||||
}
|
||||
|
||||
// Auth
|
||||
if err = c.Auth(e.auth); err != nil {
|
||||
return fmt.Errorf("c.Auth: %s", err)
|
||||
}
|
||||
|
||||
// To && From
|
||||
if err = c.Mail(e.from); err != nil {
|
||||
return fmt.Errorf("c.Mail: %s", err)
|
||||
}
|
||||
|
||||
if err = c.Rcpt(to); err != nil {
|
||||
return fmt.Errorf("c.Rcpt: %s", err)
|
||||
}
|
||||
|
||||
// Data
|
||||
w, err := c.Data()
|
||||
if err != nil {
|
||||
return fmt.Errorf("c.Data: %s", err)
|
||||
}
|
||||
|
||||
written, err := w.Write(msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("w.Write: %s", err)
|
||||
}
|
||||
|
||||
if written <= 0 {
|
||||
return fmt.Errorf("%d bytes written", written)
|
||||
}
|
||||
|
||||
if err = w.Close(); err != nil {
|
||||
return fmt.Errorf("w.Close: %s", err)
|
||||
}
|
||||
|
||||
if err = c.Quit(); err != nil {
|
||||
return fmt.Errorf("w.Quit: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type message struct {
|
||||
from string
|
||||
to string
|
||||
subject string
|
||||
}
|
||||
|
||||
func newMessage(from, to, subject string) message {
|
||||
return message{from: from, to: to, subject: subject}
|
||||
}
|
||||
|
||||
func (m message) withAttachments(body string, attachments []EmailAttachment) ([]byte, error) {
|
||||
headers := make(map[string]string)
|
||||
headers["From"] = m.from
|
||||
headers["To"] = m.to
|
||||
headers["Subject"] = m.subject
|
||||
headers["MIME-Version"] = "1.0"
|
||||
|
||||
var message bytes.Buffer
|
||||
|
||||
for k, v := range headers {
|
||||
message.WriteString(k)
|
||||
message.WriteString(": ")
|
||||
message.WriteString(v)
|
||||
message.WriteString("\r\n")
|
||||
}
|
||||
|
||||
message.WriteString("Content-Type: " + fmt.Sprintf("multipart/mixed; boundary=\"%s\"\r\n", delimeter))
|
||||
message.WriteString("--" + delimeter + "\r\n")
|
||||
message.WriteString("Content-Type: text/html; charset=\"UTF-8\"\r\n\r\n")
|
||||
message.WriteString(body + "\r\n\r\n")
|
||||
|
||||
for _, attachment := range attachments {
|
||||
attachmentRawFile, err := attachment.ReadContent()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
message.WriteString("--" + delimeter + "\r\n")
|
||||
message.WriteString("Content-Disposition: attachment; filename=\"" + attachment.Title + "\"\r\n")
|
||||
message.WriteString("Content-Type: application/octet-stream\r\n")
|
||||
message.WriteString("Content-Transfer-Encoding: base64\r\n\r\n")
|
||||
message.WriteString(base64.StdEncoding.EncodeToString(attachmentRawFile) + "\r\n")
|
||||
}
|
||||
|
||||
message.WriteString("--" + delimeter + "--") // End the message
|
||||
return message.Bytes(), nil
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package email
|
||||
|
||||
import (
|
||||
"net/smtp"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gitea.urkob.com/urko/backblaze-backup/kit"
|
||||
"gitea.urkob.com/urko/backblaze-backup/kit/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_mailService_SendOK(t *testing.T) {
|
||||
cfg := config.NewConfig(kit.RootDir() + "/.env.test")
|
||||
|
||||
mailSrv := NewMailService(MailServiceConfig{
|
||||
Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost),
|
||||
Host: cfg.MailHost,
|
||||
Port: cfg.MailPort,
|
||||
From: cfg.MailFrom,
|
||||
},
|
||||
)
|
||||
reader, err := os.Open("testdata/attachment1.txt")
|
||||
require.NoError(t, err)
|
||||
defer reader.Close()
|
||||
|
||||
reader2, err := os.Open("testdata/attachment2.txt")
|
||||
require.NoError(t, err)
|
||||
defer reader2.Close()
|
||||
|
||||
reader3, err := os.Open("testdata/attachment3.txt")
|
||||
require.NoError(t, err)
|
||||
defer reader3.Close()
|
||||
|
||||
data := EmailWithAttachments{
|
||||
To: cfg.MailTo,
|
||||
Attachments: []EmailAttachment{
|
||||
{
|
||||
Title: "attachment1.txt",
|
||||
File: reader,
|
||||
},
|
||||
{
|
||||
Title: "attachment2.txt",
|
||||
File: reader2,
|
||||
},
|
||||
{
|
||||
Title: "attachment3.txt",
|
||||
File: reader3,
|
||||
},
|
||||
},
|
||||
}
|
||||
err = mailSrv.SendOK(data)
|
||||
require.NoError(t, err)
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package email
|
||||
|
||||
const htmlTemplate = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Report</title>
|
||||
</head>
|
||||
<body style="font-family: Arial, sans-serif; margin: 0; padding: 0;">
|
||||
<div style="background-color: #f4f4f4; padding: 20px;">
|
||||
<div style="max-width: 600px; background-color: #ffffff; margin: 0 auto; padding: 20px; border-radius: 4px; box-shadow: 0 0 10px rgba(0,0,0,0.1);">
|
||||
<h2 style="color: #333333; margin-top: 0;">Bucket Report</h2>
|
||||
<hr style="border: none; border-bottom: 1px solid #ddd;">
|
||||
<p><strong>Local Backup Path:</strong> {{local_backup_path}}</p>
|
||||
<p><strong>Bucket Name:</strong> {{bucket}}</p>
|
||||
<p><strong>Local files:</strong> {{count_local}}</p>
|
||||
<p><strong>B2 files:</strong> {{count_b2}}</p>
|
||||
<p><strong>OK local in B2:</strong> {{count_ok}}</p>
|
||||
<p><strong>B2 files not in local:</strong> {{count_not_in_local}}</p>
|
||||
<p><strong>Local files not in B2:</strong> {{count_not_in_cloud}}</p>
|
||||
<hr style="border: none; border-bottom: 1px solid #ddd;">
|
||||
<p style="text-align: center; color: #666666;">This is an automated report, please do not reply to this email.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
|
@ -1 +0,0 @@
|
|||
this is txt 1 attachment
|
|
@ -1 +0,0 @@
|
|||
this is txt 2 attachment
|
|
@ -1 +0,0 @@
|
|||
this is txt 3 attachment
|
|
@ -1,37 +1,30 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gitea.urkob.com/urko/backblaze-backup/kit"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BbId string `required:"true" split_words:"true"`
|
||||
BbKey string `required:"true" split_words:"true"`
|
||||
LogLevel uint32 `required:"false" split_words:"true" default:"4"`
|
||||
// Mail vars
|
||||
MailTo string `required:"true" split_words:"true"`
|
||||
MailHost string `required:"true" split_words:"true"`
|
||||
MailPort string `required:"true" split_words:"true"`
|
||||
MailUser string `required:"true" split_words:"true"`
|
||||
MailFrom string `required:"true" split_words:"true"`
|
||||
MailPassword string `required:"true" split_words:"true"`
|
||||
BbKey string `required:"false" split_words:"true"`
|
||||
}
|
||||
|
||||
func NewConfig(envFile string) *Config {
|
||||
if envFile != "" {
|
||||
err := godotenv.Load(envFile)
|
||||
err := godotenv.Load(kit.RootDir() + "/" + envFile)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("godotenv.Load: %w", err))
|
||||
log.Fatalln("godotenv.Load:", err)
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &Config{}
|
||||
err := envconfig.Process("", cfg)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("envconfig.Process: %w", err))
|
||||
log.Fatalf("envconfig.Process: %s\n", err)
|
||||
}
|
||||
|
||||
return cfg
|
||||
|
|
12
main.go
12
main.go
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"gitea.urkob.com/urko/backblaze-backup/cmd"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -16,21 +15,14 @@ var rootCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(cmd.Sync, cmd.Versions, cmd.Cleanup, cmd.Check)
|
||||
rootCmd.AddCommand(cmd.Sync, cmd.Versions, cmd.Cleanup)
|
||||
cmd.Sync.PersistentFlags().String("file", "", "absolute path of the file you want to upload to backblaze")
|
||||
cmd.Sync.PersistentFlags().String("dir", "", "absolute path of the directory you want to upload to backblaze")
|
||||
cmd.Sync.PersistentFlags().String("bucket", "", "backblaze bucket name")
|
||||
cmd.Sync.PersistentFlags().Int("workers", runtime.NumCPU(), "The number of worker goroutines that are spawned to handle file processing in parallel. Each worker handles the upload of one file at a time, allowing for efficient use of system resources when dealing with a large number of files. The default value is the number of CPU cores available on the system, enabling the application to automatically scale its parallel processing capabilities based on the available hardware.")
|
||||
cmd.Sync.PersistentFlags().Int("concurrent-uploads", 4, "The number of chunk uploads that can be performed simultaneously for a single file. When a file is uploaded, it might be split into multiple chunks to enable more efficient and reliable data transfer. The concurrent-uploads flag controls how many of these chunks can be uploaded in parallel during a single file upload. This is particularly useful for large files, where parallel chunk uploads can significantly speed up the overall upload time. The default value is 4.")
|
||||
|
||||
cmd.Check.PersistentFlags().String("dir", "", "Specifies the absolute path of the directory containing the backup files to be compared against the Backblaze B2 bucket. This flag is mutually exclusive with the 'file' flag.")
|
||||
cmd.Check.PersistentFlags().String("bucket", "", "Name of the Backblaze B2 bucket against which the local files or directory will be compared.")
|
||||
|
||||
cmd.Cleanup.PersistentFlags().String("bucket", "", "backblaze bucket name")
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
if err := cmd.Sync.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue