2025-09-28 22:04:48 +02:00
package global
2015-06-21 13:02:56 +02:00
import (
2017-03-08 20:12:16 +01:00
"context"
2015-06-21 13:02:56 +02:00
"fmt"
"os"
2025-02-28 19:30:56 +00:00
"os/exec"
2017-11-20 22:08:53 +01:00
"path/filepath"
2022-07-02 23:30:26 +02:00
"strconv"
2015-11-04 22:05:36 +01:00
"strings"
2017-10-14 14:51:00 +02:00
"time"
2015-06-21 13:02:56 +02:00
2025-09-28 22:21:59 +02:00
"github.com/restic/chunker"
2017-09-24 20:04:23 +02:00
"github.com/restic/restic/internal/backend"
2024-05-24 23:04:06 +02:00
"github.com/restic/restic/internal/backend/cache"
2022-06-12 14:38:19 +02:00
"github.com/restic/restic/internal/backend/limiter"
2017-07-23 14:21:03 +02:00
"github.com/restic/restic/internal/backend/location"
2023-04-07 22:01:30 +02:00
"github.com/restic/restic/internal/backend/logger"
2022-10-15 16:33:15 +02:00
"github.com/restic/restic/internal/backend/retry"
2023-04-07 23:02:35 +02:00
"github.com/restic/restic/internal/backend/sema"
2017-07-23 14:21:03 +02:00
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/options"
"github.com/restic/restic/internal/repository"
2017-07-24 17:42:25 +02:00
"github.com/restic/restic/internal/restic"
2018-05-01 14:40:52 +02:00
"github.com/restic/restic/internal/textfile"
2025-09-14 17:21:30 +02:00
"github.com/restic/restic/internal/ui"
2025-09-14 14:26:29 +02:00
"github.com/restic/restic/internal/ui/progress"
2025-02-07 18:54:26 +01:00
"github.com/spf13/pflag"
2017-07-23 14:21:03 +02:00
"github.com/restic/restic/internal/errors"
2015-06-21 13:02:56 +02:00
)
2025-03-05 20:47:08 -05:00
// ErrNoRepository is used to report if opening a repository failed due
2024-07-10 21:46:26 +02:00
// to a missing backend storage location or config file
var ErrNoRepository = errors . New ( "repository does not exist" )
2025-09-28 22:04:48 +02:00
const Version = "0.18.1-dev (compiled manually)"
2015-06-21 13:02:56 +02:00
2018-08-19 21:31:53 +02:00
// TimeFormat is the format used for all timestamps printed by restic.
const TimeFormat = "2006-01-02 15:04:05"
2025-09-28 21:44:40 +02:00
type BackendWrapper func ( r backend . Backend ) ( backend . Backend , error )
2020-03-31 19:09:01 +02:00
2025-09-28 22:04:48 +02:00
// Options hold all global options for restic.
type Options struct {
2024-05-18 18:59:29 +02:00
Repo string
RepositoryFile string
PasswordFile string
PasswordCommand string
KeyHint string
Quiet bool
Verbose int
NoLock bool
RetryLock time . Duration
JSON bool
CacheDir string
NoCache bool
CleanupCache bool
Compression repository . CompressionMode
PackSize uint
NoExtraVerify bool
InsecureNoPassword bool
2015-06-21 13:02:56 +02:00
2022-06-22 18:29:58 +02:00
backend . TransportOptions
limiter . Limits
2017-10-08 11:28:03 -07:00
2025-09-28 21:44:40 +02:00
Password string
Term ui . Terminal
2017-03-25 15:33:52 +01:00
2025-09-28 21:44:40 +02:00
Backends * location . Registry
BackendTestHook , BackendInnerTestHook BackendWrapper
2020-03-31 19:09:01 +02:00
2025-09-28 21:44:40 +02:00
// Verbosity is set as follows:
2018-04-21 22:07:14 +02:00
// 0 means: don't print any messages except errors, this is used when --quiet is specified
// 1 is the default: print essential messages
// 2 means: print more messages, report minor things, this is used when --verbose is specified
2020-06-23 20:22:14 +02:00
// 3 means: print very detailed debug messages, this is used when --verbose=2 is specified
2025-09-28 21:44:40 +02:00
Verbosity uint
2018-04-21 22:07:14 +02:00
2017-03-25 15:33:52 +01:00
Options [ ] string
2025-09-28 21:44:40 +02:00
Extended options . Options
2015-06-21 13:02:56 +02:00
}
2025-09-28 22:04:48 +02:00
func ( opts * Options ) AddFlags ( f * pflag . FlagSet ) {
2025-02-07 18:54:26 +01:00
f . StringVarP ( & opts . Repo , "repo" , "r" , "" , "`repository` to backup to or restore from (default: $RESTIC_REPOSITORY)" )
f . StringVarP ( & opts . RepositoryFile , "repository-file" , "" , "" , "`file` to read the repository location from (default: $RESTIC_REPOSITORY_FILE)" )
f . StringVarP ( & opts . PasswordFile , "password-file" , "p" , "" , "`file` to read the repository password from (default: $RESTIC_PASSWORD_FILE)" )
f . StringVarP ( & opts . KeyHint , "key-hint" , "" , "" , "`key` ID of key to try decrypting first (default: $RESTIC_KEY_HINT)" )
f . StringVarP ( & opts . PasswordCommand , "password-command" , "" , "" , "shell `command` to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)" )
f . BoolVarP ( & opts . Quiet , "quiet" , "q" , false , "do not output comprehensive progress report" )
// use empty parameter name as `-v, --verbose n` instead of the correct `--verbose=n` is confusing
f . CountVarP ( & opts . Verbose , "verbose" , "v" , "be verbose (specify multiple times or a level using --verbose=n``, max level/times is 2)" )
f . BoolVar ( & opts . NoLock , "no-lock" , false , "do not lock the repository, this allows some operations on read-only repositories" )
f . DurationVar ( & opts . RetryLock , "retry-lock" , 0 , "retry to lock the repository if it is already locked, takes a value like 5m or 2h (default: no retries)" )
f . BoolVarP ( & opts . JSON , "json" , "" , false , "set output mode to JSON for commands that support it" )
f . StringVar ( & opts . CacheDir , "cache-dir" , "" , "set the cache `directory`. (default: use system default cache directory)" )
f . BoolVar ( & opts . NoCache , "no-cache" , false , "do not use a local cache" )
f . StringSliceVar ( & opts . RootCertFilenames , "cacert" , nil , "`file` to load root certificates from (default: use system certificates or $RESTIC_CACERT)" )
f . StringVar ( & opts . TLSClientCertKeyFilename , "tls-client-cert" , "" , "path to a `file` containing PEM encoded TLS client certificate and private key (default: $RESTIC_TLS_CLIENT_CERT)" )
f . BoolVar ( & opts . InsecureNoPassword , "insecure-no-password" , false , "use an empty password for the repository, must be passed to every restic command (insecure)" )
f . BoolVar ( & opts . InsecureTLS , "insecure-tls" , false , "skip TLS certificate verification when connecting to the repository (insecure)" )
f . BoolVar ( & opts . CleanupCache , "cleanup-cache" , false , "auto remove old cache directories" )
2025-04-01 00:51:12 +05:30
f . Var ( & opts . Compression , "compression" , "compression mode (only available for repository format version 2), one of (auto|off|fastest|better|max) (default: $RESTIC_COMPRESSION)" )
2025-02-07 18:54:26 +01:00
f . BoolVar ( & opts . NoExtraVerify , "no-extra-verify" , false , "skip additional verification of data before upload (see documentation)" )
f . IntVar ( & opts . Limits . UploadKb , "limit-upload" , 0 , "limits uploads to a maximum `rate` in KiB/s. (default: unlimited)" )
f . IntVar ( & opts . Limits . DownloadKb , "limit-download" , 0 , "limits downloads to a maximum `rate` in KiB/s. (default: unlimited)" )
f . UintVar ( & opts . PackSize , "pack-size" , 0 , "set target pack `size` in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)" )
f . StringSliceVarP ( & opts . Options , "option" , "o" , [ ] string { } , "set extended option (`key=value`, can be specified multiple times)" )
f . StringVar ( & opts . HTTPUserAgent , "http-user-agent" , "" , "set a http user agent for outgoing http requests" )
f . DurationVar ( & opts . StuckRequestTimeout , "stuck-request-timeout" , 5 * time . Minute , "`duration` after which to retry stuck requests" )
opts . Repo = os . Getenv ( "RESTIC_REPOSITORY" )
opts . RepositoryFile = os . Getenv ( "RESTIC_REPOSITORY_FILE" )
opts . PasswordFile = os . Getenv ( "RESTIC_PASSWORD_FILE" )
opts . KeyHint = os . Getenv ( "RESTIC_KEY_HINT" )
opts . PasswordCommand = os . Getenv ( "RESTIC_PASSWORD_COMMAND" )
if os . Getenv ( "RESTIC_CACERT" ) != "" {
opts . RootCertFilenames = strings . Split ( os . Getenv ( "RESTIC_CACERT" ) , "," )
}
opts . TLSClientCertKeyFilename = os . Getenv ( "RESTIC_TLS_CLIENT_CERT" )
comp := os . Getenv ( "RESTIC_COMPRESSION" )
if comp != "" {
// ignore error as there's no good way to handle it
_ = opts . Compression . Set ( comp )
}
// parse target pack size from env, on error the default value will be used
targetPackSize , _ := strconv . ParseUint ( os . Getenv ( "RESTIC_PACK_SIZE" ) , 10 , 32 )
opts . PackSize = uint ( targetPackSize )
if os . Getenv ( "RESTIC_HTTP_USER_AGENT" ) != "" {
opts . HTTPUserAgent = os . Getenv ( "RESTIC_HTTP_USER_AGENT" )
}
}
2025-09-28 22:04:48 +02:00
func ( opts * Options ) PreRun ( needsPassword bool ) error {
2025-02-07 19:14:55 +01:00
// set verbosity, default is one
2025-09-28 21:44:40 +02:00
opts . Verbosity = 1
2025-02-07 19:14:55 +01:00
if opts . Quiet && opts . Verbose > 0 {
return errors . Fatal ( "--quiet and --verbose cannot be specified at the same time" )
}
switch {
case opts . Verbose >= 2 :
2025-09-28 21:44:40 +02:00
opts . Verbosity = 3
2025-02-07 19:14:55 +01:00
case opts . Verbose > 0 :
2025-09-28 21:44:40 +02:00
opts . Verbosity = 2
2025-02-07 19:14:55 +01:00
case opts . Quiet :
2025-09-28 21:44:40 +02:00
opts . Verbosity = 0
2025-02-07 19:14:55 +01:00
}
// parse extended options
extendedOpts , err := options . Parse ( opts . Options )
if err != nil {
return err
}
2025-09-28 21:44:40 +02:00
opts . Extended = extendedOpts
2025-02-07 19:14:55 +01:00
if ! needsPassword {
return nil
}
pwd , err := resolvePassword ( opts , "RESTIC_PASSWORD" )
if err != nil {
2025-09-24 22:11:54 +02:00
return errors . Fatalf ( "Resolving password failed: %v" , err )
2025-02-07 19:14:55 +01:00
}
2025-09-28 21:44:40 +02:00
opts . Password = pwd
2025-02-07 19:14:55 +01:00
return nil
}
2017-07-24 23:01:16 +02:00
// resolvePassword determines the password to be used for opening the repository.
2025-09-28 22:04:48 +02:00
func resolvePassword ( opts * Options , envStr string ) ( string , error ) {
2018-11-18 14:31:00 +01:00
if opts . PasswordFile != "" && opts . PasswordCommand != "" {
return "" , errors . Fatalf ( "Password file and command are mutually exclusive options" )
}
if opts . PasswordCommand != "" {
args , err := backend . SplitShellStrings ( opts . PasswordCommand )
if err != nil {
return "" , err
}
cmd := exec . Command ( args [ 0 ] , args [ 1 : ] ... )
cmd . Stderr = os . Stderr
output , err := cmd . Output ( )
if err != nil {
return "" , err
}
2025-03-23 10:11:43 +00:00
return strings . TrimSpace ( string ( output ) ) , nil
2018-11-18 14:31:00 +01:00
}
2017-07-24 23:01:16 +02:00
if opts . PasswordFile != "" {
2025-09-28 22:04:48 +02:00
return LoadPasswordFromFile ( opts . PasswordFile )
2017-07-24 23:01:16 +02:00
}
2019-08-13 18:27:20 +02:00
if pwd := os . Getenv ( envStr ) ; pwd != "" {
2017-07-24 23:01:16 +02:00
return pwd , nil
}
return "" , nil
}
2025-09-28 22:04:48 +02:00
// LoadPasswordFromFile loads a password from a file while stripping a BOM and
2024-06-05 22:14:45 +02:00
// converting the password to UTF-8.
2025-09-28 22:04:48 +02:00
func LoadPasswordFromFile ( pwdFile string ) ( string , error ) {
2024-06-05 22:14:45 +02:00
s , err := textfile . Read ( pwdFile )
if errors . Is ( err , os . ErrNotExist ) {
return "" , errors . Fatalf ( "%s does not exist" , pwdFile )
}
return strings . TrimSpace ( string ( s ) ) , errors . Wrap ( err , "Readfile" )
}
2025-09-28 22:22:22 +02:00
// readPassword reads the password from a password file, the environment
2024-03-29 23:52:45 +01:00
// variable RESTIC_PASSWORD or prompts the user. If the context is canceled,
// the function leaks the password reading goroutine.
2025-09-28 22:22:22 +02:00
func readPassword ( ctx context . Context , gopts Options , prompt string ) ( string , error ) {
2025-09-18 22:19:38 +02:00
if gopts . InsecureNoPassword {
2025-09-28 21:44:40 +02:00
if gopts . Password != "" {
2024-05-18 18:59:29 +02:00
return "" , errors . Fatal ( "--insecure-no-password must not be specified together with providing a password via a cli option or environment variable" )
}
return "" , nil
}
2025-09-28 21:44:40 +02:00
if gopts . Password != "" {
return gopts . Password , nil
2016-09-12 14:08:51 +02:00
}
2025-09-28 21:44:40 +02:00
password , err := gopts . Term . ReadPassword ( ctx , prompt )
2015-06-21 13:02:56 +02:00
if err != nil {
2025-09-14 19:21:51 +02:00
return "" , fmt . Errorf ( "unable to read password: %w" , err )
2015-06-21 13:02:56 +02:00
}
2015-11-04 22:05:36 +01:00
if len ( password ) == 0 {
2024-05-18 18:59:29 +02:00
return "" , errors . Fatal ( "an empty password is not allowed by default. Pass the flag `--insecure-no-password` to restic to disable this check" )
2015-06-21 15:01:52 +02:00
}
2016-09-12 14:08:51 +02:00
return password , nil
2015-06-21 13:02:56 +02:00
}
2016-01-17 16:59:03 +01:00
// ReadPasswordTwice calls ReadPassword two times and returns an error when the
2024-03-29 23:52:45 +01:00
// passwords don't match. If the context is canceled, the function leaks the
// password reading goroutine.
2025-09-28 22:04:48 +02:00
func ReadPasswordTwice ( ctx context . Context , gopts Options , prompt1 , prompt2 string ) ( string , error ) {
2025-09-28 22:22:22 +02:00
pw1 , err := readPassword ( ctx , gopts , prompt1 )
2016-09-12 14:08:51 +02:00
if err != nil {
return "" , err
}
2025-09-28 21:44:40 +02:00
if gopts . Term . InputIsTerminal ( ) {
2025-09-28 22:22:22 +02:00
pw2 , err := readPassword ( ctx , gopts , prompt2 )
2019-03-26 16:14:40 +01:00
if err != nil {
return "" , err
}
2016-09-12 14:08:51 +02:00
2019-03-26 16:14:40 +01:00
if pw1 != pw2 {
return "" , errors . Fatal ( "passwords do not match" )
}
2015-06-21 13:02:56 +02:00
}
2016-09-12 14:08:51 +02:00
return pw1 , nil
2015-06-21 13:02:56 +02:00
}
2025-09-28 22:21:59 +02:00
func readRepo ( gopts Options ) ( string , error ) {
2025-09-18 22:19:38 +02:00
if gopts . Repo == "" && gopts . RepositoryFile == "" {
2020-08-30 23:20:57 +02:00
return "" , errors . Fatal ( "Please specify repository location (-r or --repository-file)" )
}
2025-09-18 22:19:38 +02:00
repo := gopts . Repo
if gopts . RepositoryFile != "" {
2020-08-30 23:20:57 +02:00
if repo != "" {
return "" , errors . Fatal ( "Options -r and --repository-file are mutually exclusive, please specify only one" )
}
2025-09-18 22:19:38 +02:00
s , err := textfile . Read ( gopts . RepositoryFile )
2022-06-13 20:35:37 +02:00
if errors . Is ( err , os . ErrNotExist ) {
2025-09-18 22:19:38 +02:00
return "" , errors . Fatalf ( "%s does not exist" , gopts . RepositoryFile )
2020-08-30 23:20:57 +02:00
}
if err != nil {
return "" , err
}
repo = strings . TrimSpace ( string ( s ) )
}
return repo , nil
}
2016-08-21 13:09:31 +02:00
const maxKeys = 20
2016-01-17 16:59:03 +01:00
// OpenRepository reads the password and opens the repository.
2025-09-28 22:04:48 +02:00
func OpenRepository ( ctx context . Context , gopts Options , printer progress . Printer ) ( * repository . Repository , error ) {
2025-09-28 22:21:59 +02:00
repo , err := readRepo ( gopts )
2020-08-30 23:20:57 +02:00
if err != nil {
return nil , err
2015-06-21 13:02:56 +02:00
}
2025-09-28 22:21:59 +02:00
be , err := innerOpenBackend ( ctx , repo , gopts , gopts . Extended , false , printer )
2015-06-21 13:02:56 +02:00
if err != nil {
return nil , err
}
2025-09-28 22:21:59 +02:00
// check if config is there
fi , err := be . Stat ( ctx , backend . Handle { Type : restic . ConfigFile } )
if be . IsNotExist ( err ) {
//nolint:staticcheck // capitalized error string is intentional
return nil , fmt . Errorf ( "Fatal: %w: unable to open config file: %v\nIs there a repository at the following location?\n%v" , ErrNoRepository , err , location . StripPassword ( gopts . Backends , repo ) )
}
if err != nil {
return nil , errors . Fatalf ( "unable to open config file: %v\nIs there a repository at the following location?\n%v" , err , location . StripPassword ( gopts . Backends , repo ) )
}
if fi . Size == 0 {
return nil , errors . New ( "config file has zero size, invalid repository?" )
}
2022-07-02 23:30:26 +02:00
s , err := repository . New ( be , repository . Options {
2025-09-18 22:19:38 +02:00
Compression : gopts . Compression ,
PackSize : gopts . PackSize * 1024 * 1024 ,
NoExtraVerify : gopts . NoExtraVerify ,
2022-07-02 23:30:26 +02:00
} )
if err != nil {
2025-06-03 14:34:12 +05:30
return nil , errors . Fatalf ( "%s" , err )
2022-07-02 23:30:26 +02:00
}
2015-06-21 13:02:56 +02:00
2019-06-12 18:40:05 +08:00
passwordTriesLeft := 1
2025-09-28 21:44:40 +02:00
if gopts . Term . InputIsTerminal ( ) && gopts . Password == "" && ! gopts . InsecureNoPassword {
2019-06-12 18:40:05 +08:00
passwordTriesLeft = 3
2015-06-21 13:02:56 +02:00
}
2019-06-12 18:40:05 +08:00
for ; passwordTriesLeft > 0 ; passwordTriesLeft -- {
2025-09-28 22:22:22 +02:00
gopts . Password , err = readPassword ( ctx , gopts , "enter password for repository: " )
2024-03-29 23:52:45 +01:00
if ctx . Err ( ) != nil {
return nil , ctx . Err ( )
}
2019-06-12 18:40:05 +08:00
if err != nil && passwordTriesLeft > 1 {
2025-09-28 21:44:40 +02:00
gopts . Password = ""
2025-09-14 14:26:29 +02:00
printer . E ( "%s. Try again" , err )
2019-06-12 18:40:05 +08:00
}
if err != nil {
continue
}
2025-09-28 21:44:40 +02:00
err = s . SearchKey ( ctx , gopts . Password , maxKeys , gopts . KeyHint )
2019-06-12 18:40:05 +08:00
if err != nil && passwordTriesLeft > 1 {
2025-09-28 21:44:40 +02:00
gopts . Password = ""
2025-09-14 14:26:29 +02:00
printer . E ( "%s. Try again" , err )
2019-06-12 18:40:05 +08:00
}
}
2015-06-21 13:02:56 +02:00
if err != nil {
2024-07-30 19:06:18 -04:00
if errors . IsFatal ( err ) || errors . Is ( err , repository . ErrNoKeyFound ) {
2019-06-12 18:40:05 +08:00
return nil , err
}
return nil , errors . Fatalf ( "%s" , err )
2015-06-21 13:02:56 +02:00
}
2025-09-14 17:58:52 +02:00
id := s . Config ( ) . ID
if len ( id ) > 8 {
id = id [ : 8 ]
2017-10-04 13:45:05 +02:00
}
2025-09-14 17:58:52 +02:00
extra := ""
if s . Config ( ) . Version >= 2 {
2025-09-18 22:19:38 +02:00
extra = ", compression level " + gopts . Compression . String ( )
2025-09-14 17:58:52 +02:00
}
printer . PT ( "repository %v opened (version %v%s)" , id , s . Config ( ) . Version , extra )
2017-10-04 13:45:05 +02:00
2025-09-18 22:19:38 +02:00
if gopts . NoCache {
2017-06-10 13:10:08 +02:00
return s , nil
}
2025-09-18 22:19:38 +02:00
c , err := cache . New ( s . Config ( ) . ID , gopts . CacheDir )
2017-06-10 13:10:08 +02:00
if err != nil {
2025-09-14 14:26:29 +02:00
printer . E ( "unable to open cache: %v" , err )
2017-11-20 21:32:25 +01:00
return s , nil
}
2025-09-14 17:58:52 +02:00
if c . Created {
printer . PT ( "created new cache in %v" , c . Base )
2018-08-28 22:03:47 +02:00
}
2017-12-03 15:52:57 +01:00
// start using the cache
2025-09-14 16:13:21 +02:00
s . UseCache ( c , printer . E )
2017-12-03 15:52:57 +01:00
2017-11-20 21:32:25 +01:00
oldCacheDirs , err := cache . Old ( c . Base )
if err != nil {
2025-09-14 14:26:29 +02:00
printer . E ( "unable to find old cache directories: %v" , err )
2017-11-20 22:08:53 +01:00
}
// nothing more to do if no old cache dirs could be found
if len ( oldCacheDirs ) == 0 {
return s , nil
}
// cleanup old cache dirs if instructed to do so
2025-09-18 22:19:38 +02:00
if gopts . CleanupCache {
2025-09-14 17:58:52 +02:00
printer . PT ( "removing %d old cache dirs from %v" , len ( oldCacheDirs ) , c . Base )
2017-11-20 22:08:53 +01:00
for _ , item := range oldCacheDirs {
2018-05-01 16:22:12 +02:00
dir := filepath . Join ( c . Base , item . Name ( ) )
2024-07-21 15:22:21 +02:00
err = os . RemoveAll ( dir )
2017-11-20 22:08:53 +01:00
if err != nil {
2025-09-14 14:26:29 +02:00
printer . E ( "unable to remove %v: %v" , dir , err )
2017-11-20 22:08:53 +01:00
}
}
2017-06-10 13:10:08 +02:00
} else {
2025-09-14 17:58:52 +02:00
printer . PT ( "found %d old cache directories in %v, run `restic cache --cleanup` to remove them" ,
len ( oldCacheDirs ) , c . Base )
2017-06-10 13:10:08 +02:00
}
2015-06-21 13:02:56 +02:00
return s , nil
}
2017-03-25 17:31:59 +01:00
func parseConfig ( loc location . Location , opts options . Options ) ( interface { } , error ) {
2023-04-21 21:51:58 +02:00
cfg := loc . Config
2023-10-01 11:40:12 +02:00
if cfg , ok := cfg . ( backend . ApplyEnvironmenter ) ; ok {
2023-06-08 15:28:07 +02:00
cfg . ApplyEnvironment ( "" )
2023-04-21 21:51:58 +02:00
}
2018-03-13 22:30:51 +01:00
2023-04-21 21:51:58 +02:00
// only apply options for a particular backend here
opts = opts . Extract ( loc . Scheme )
if err := opts . Apply ( loc . Scheme , cfg ) ; err != nil {
return nil , err
2017-03-25 17:31:59 +01:00
}
2023-04-21 21:51:58 +02:00
debug . Log ( "opening %v repository at %#v" , loc . Scheme , cfg )
return cfg , nil
2017-03-25 17:31:59 +01:00
}
2025-09-28 22:21:59 +02:00
// CreateRepository a repository with the given version and chunker polynomial.
func CreateRepository ( ctx context . Context , gopts Options , version uint , chunkerPolynomial * chunker . Pol , printer progress . Printer ) ( * repository . Repository , error ) {
if version < restic . MinRepoVersion || version > restic . MaxRepoVersion {
return nil , errors . Fatalf ( "only repository versions between %v and %v are allowed" , restic . MinRepoVersion , restic . MaxRepoVersion )
}
repo , err := readRepo ( gopts )
if err != nil {
return nil , err
}
gopts . Password , err = ReadPasswordTwice ( ctx , gopts ,
"enter password for new repository: " ,
"enter password again: " )
if err != nil {
return nil , err
}
be , err := innerOpenBackend ( ctx , repo , gopts , gopts . Extended , true , printer )
if err != nil {
return nil , errors . Fatalf ( "create repository at %s failed: %v" , location . StripPassword ( gopts . Backends , repo ) , err )
}
s , err := repository . New ( be , repository . Options {
Compression : gopts . Compression ,
PackSize : gopts . PackSize * 1024 * 1024 ,
} )
if err != nil {
return nil , errors . Fatalf ( "%s" , err )
}
err = s . Init ( ctx , version , gopts . Password , chunkerPolynomial )
if err != nil {
return nil , errors . Fatalf ( "create key in repository at %s failed: %v" , location . StripPassword ( gopts . Backends , repo ) , err )
}
return s , nil
}
func innerOpenBackend ( ctx context . Context , s string , gopts Options , opts options . Options , create bool , printer progress . Printer ) ( backend . Backend , error ) {
2025-09-28 21:44:40 +02:00
debug . Log ( "parsing location %v" , location . StripPassword ( gopts . Backends , s ) )
loc , err := location . Parse ( gopts . Backends , s )
2017-03-25 17:31:59 +01:00
if err != nil {
return nil , errors . Fatalf ( "parsing repository location failed: %v" , err )
}
cfg , err := parseConfig ( loc , opts )
if err != nil {
return nil , err
}
2025-09-14 14:52:02 +02:00
rt , err := backend . Transport ( gopts . TransportOptions )
2017-09-24 20:04:23 +02:00
if err != nil {
2025-06-03 14:34:12 +05:30
return nil , errors . Fatalf ( "%s" , err )
2017-09-24 20:04:23 +02:00
}
2017-12-29 12:43:49 +01:00
// wrap the transport so that the throughput via HTTP is limited
2022-06-22 18:29:58 +02:00
lim := limiter . NewStaticLimiter ( gopts . Limits )
2018-05-22 20:48:17 +02:00
rt = lim . Transport ( rt )
2017-12-29 12:43:49 +01:00
2025-09-28 21:44:40 +02:00
factory := gopts . Backends . Lookup ( loc . Scheme )
2023-06-08 13:04:34 +02:00
if factory == nil {
2016-09-18 13:24:29 +02:00
return nil , errors . Fatalf ( "invalid backend: %q" , loc . Scheme )
2015-06-21 13:02:56 +02:00
}
2024-04-19 22:26:14 +02:00
var be backend . Backend
if create {
2025-09-14 16:13:21 +02:00
be , err = factory . Create ( ctx , cfg , rt , lim , printer . E )
2024-04-19 22:26:14 +02:00
} else {
2025-09-14 16:13:21 +02:00
be , err = factory . Open ( ctx , cfg , rt , lim , printer . E )
2024-04-19 22:26:14 +02:00
}
2024-07-10 21:46:26 +02:00
if errors . Is ( err , backend . ErrNoRepository ) {
2025-09-21 21:58:29 +02:00
//nolint:staticcheck // capitalized error string is intentional
2025-09-28 21:44:40 +02:00
return nil , fmt . Errorf ( "Fatal: %w at %v: %v" , ErrNoRepository , location . StripPassword ( gopts . Backends , s ) , err )
2024-07-10 21:46:26 +02:00
}
2016-09-18 13:24:29 +02:00
if err != nil {
2025-09-21 15:58:29 +02:00
if create {
// init already wraps the error message
return nil , err
}
2025-09-28 21:44:40 +02:00
return nil , errors . Fatalf ( "unable to open repository at %v: %v" , location . StripPassword ( gopts . Backends , s ) , err )
2016-09-18 13:24:29 +02:00
}
2023-04-07 23:02:35 +02:00
// wrap with debug logging and connection limiting
2023-04-22 12:32:57 +02:00
be = logger . New ( sema . NewBackend ( be ) )
2023-04-07 22:01:30 +02:00
2021-01-03 17:42:06 +01:00
// wrap backend if a test specified an inner hook
2025-09-28 21:44:40 +02:00
if gopts . BackendInnerTestHook != nil {
be , err = gopts . BackendInnerTestHook ( be )
2021-01-03 17:42:06 +01:00
if err != nil {
return nil , err
}
}
2024-10-17 20:21:54 +02:00
report := func ( msg string , err error , d time . Duration ) {
if d >= 0 {
2025-09-14 14:26:29 +02:00
printer . E ( "%v returned error, retrying after %v: %v" , msg , d , err )
2024-10-17 20:21:54 +02:00
} else {
2025-09-14 14:26:29 +02:00
printer . E ( "%v failed: %v" , msg , err )
2024-10-17 20:21:54 +02:00
}
}
success := func ( msg string , retries int ) {
2025-09-14 14:26:29 +02:00
printer . E ( "%v operation successful after %d retries" , msg , retries )
2024-10-17 20:21:54 +02:00
}
be = retry . New ( be , 15 * time . Minute , report , success )
// wrap backend if a test specified a hook
2025-09-28 21:44:40 +02:00
if gopts . BackendTestHook != nil {
be , err = gopts . BackendTestHook ( be )
2024-10-17 20:21:54 +02:00
if err != nil {
return nil , err
}
}
2024-04-19 22:26:14 +02:00
return be , nil
}