2015-06-21 11:02:56 +00:00
package main
import (
2019-03-26 15:14:40 +00:00
"bufio"
2017-03-08 19:12:16 +00:00
"context"
2015-06-21 11:02:56 +00:00
"fmt"
"io"
"os"
2017-11-20 21:08:53 +00:00
"path/filepath"
2016-08-21 20:38:22 +00:00
"runtime"
2022-07-02 21:30:26 +00:00
"strconv"
2015-11-04 21:05:36 +00:00
"strings"
2016-02-13 17:29:26 +00:00
"syscall"
2017-10-14 12:51:00 +00:00
"time"
2015-06-21 11:02:56 +00:00
2017-09-24 18:04:23 +00:00
"github.com/restic/restic/internal/backend"
2017-07-08 13:38:48 +00:00
"github.com/restic/restic/internal/backend/azure"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/backend/b2"
2017-07-08 13:34:23 +00:00
"github.com/restic/restic/internal/backend/gs"
2022-06-12 12:38:19 +00:00
"github.com/restic/restic/internal/backend/limiter"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/backend/local"
"github.com/restic/restic/internal/backend/location"
2018-03-13 21:30:51 +00:00
"github.com/restic/restic/internal/backend/rclone"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/backend/rest"
2022-10-15 14:33:15 +00:00
"github.com/restic/restic/internal/backend/retry"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/backend/s3"
"github.com/restic/restic/internal/backend/sftp"
"github.com/restic/restic/internal/backend/swift"
2017-06-10 11:10:08 +00:00
"github.com/restic/restic/internal/cache"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/debug"
2017-11-20 21:08:53 +00:00
"github.com/restic/restic/internal/fs"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/options"
"github.com/restic/restic/internal/repository"
2017-07-24 15:42:25 +00:00
"github.com/restic/restic/internal/restic"
2018-05-01 12:40:52 +00:00
"github.com/restic/restic/internal/textfile"
2021-03-07 21:50:52 +00:00
"github.com/restic/restic/internal/ui/termstatus"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/errors"
2016-09-01 20:17:37 +00:00
2018-11-18 13:31:00 +00:00
"os/exec"
2019-07-02 08:49:49 +00:00
2022-03-28 20:24:15 +00:00
"golang.org/x/term"
2015-06-21 11:02:56 +00:00
)
2022-08-25 17:55:05 +00:00
var version = "0.14.0-dev (compiled manually)"
2015-06-21 11:02:56 +00:00
2018-08-19 19:31:53 +00:00
// TimeFormat is the format used for all timestamps printed by restic.
const TimeFormat = "2006-01-02 15:04:05"
2020-03-31 17:09:01 +00:00
type backendWrapper func ( r restic . Backend ) ( restic . Backend , error )
2016-09-17 10:36:05 +00:00
// GlobalOptions hold all global options for restic.
2015-06-21 11:02:56 +00:00
type GlobalOptions struct {
2018-11-18 13:31:00 +00:00
Repo string
2020-08-30 21:20:57 +00:00
RepositoryFile string
2018-11-18 13:31:00 +00:00
PasswordFile string
PasswordCommand string
KeyHint string
Quiet bool
Verbose int
NoLock bool
JSON bool
CacheDir string
NoCache bool
CleanupCache bool
2022-04-13 18:34:05 +00:00
Compression repository . CompressionMode
2022-07-02 21:52:02 +00:00
PackSize uint
2015-06-21 11:02:56 +00:00
2022-06-22 16:29:58 +00:00
backend . TransportOptions
limiter . Limits
2017-10-08 18:28:03 +00:00
2015-06-21 11:02:56 +00:00
password string
stdout io . Writer
2015-06-21 13:20:54 +00:00
stderr io . Writer
2017-03-25 14:33:52 +00:00
2021-01-03 16:42:06 +00:00
backendTestHook , backendInnerTestHook backendWrapper
2020-03-31 17:09:01 +00:00
2018-04-21 20:07:14 +00:00
// verbosity is set as follows:
// 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 18:22:14 +00:00
// 3 means: print very detailed debug messages, this is used when --verbose=2 is specified
2018-04-21 20:07:14 +00:00
verbosity uint
2017-03-25 14:33:52 +00:00
Options [ ] string
extended options . Options
2015-06-21 11:02:56 +00:00
}
2016-09-17 10:36:05 +00:00
var globalOptions = GlobalOptions {
stdout : os . Stdout ,
stderr : os . Stderr ,
}
2020-04-12 16:46:22 +00:00
var isReadingPassword bool
2021-10-31 22:08:13 +00:00
var internalGlobalCtx context . Context
2020-04-12 16:46:22 +00:00
2016-02-13 17:29:26 +00:00
func init ( ) {
2017-03-08 19:12:16 +00:00
var cancel context . CancelFunc
2021-10-31 22:08:13 +00:00
internalGlobalCtx , cancel = context . WithCancel ( context . Background ( ) )
2022-08-26 21:04:59 +00:00
AddCleanupHandler ( func ( code int ) ( int , error ) {
2020-08-09 11:54:39 +00:00
// Must be called before the unlock cleanup handler to ensure that the latter is
// not blocked due to limited number of backend connections, see #1434
2017-03-08 19:12:16 +00:00
cancel ( )
2022-08-26 21:04:59 +00:00
return code , nil
2017-03-08 19:12:16 +00:00
} )
2016-09-17 10:36:05 +00:00
f := cmdRoot . PersistentFlags ( )
2022-08-19 18:48:25 +00:00
f . StringVarP ( & globalOptions . Repo , "repo" , "r" , "" , "`repository` to backup to or restore from (default: $RESTIC_REPOSITORY)" )
f . StringVarP ( & globalOptions . RepositoryFile , "repository-file" , "" , "" , "`file` to read the repository location from (default: $RESTIC_REPOSITORY_FILE)" )
f . StringVarP ( & globalOptions . PasswordFile , "password-file" , "p" , "" , "`file` to read the repository password from (default: $RESTIC_PASSWORD_FILE)" )
f . StringVarP ( & globalOptions . KeyHint , "key-hint" , "" , "" , "`key` ID of key to try decrypting first (default: $RESTIC_KEY_HINT)" )
f . StringVarP ( & globalOptions . PasswordCommand , "password-command" , "" , "" , "shell `command` to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)" )
2017-01-18 09:46:04 +00:00
f . BoolVarP ( & globalOptions . Quiet , "quiet" , "q" , false , "do not output comprehensive progress report" )
2020-10-02 18:25:34 +00:00
f . CountVarP ( & globalOptions . Verbose , "verbose" , "v" , "be verbose (specify multiple times or a level using --verbose=`n`, max level/times is 3)" )
2020-10-06 20:38:19 +00:00
f . BoolVar ( & globalOptions . NoLock , "no-lock" , false , "do not lock the repository, this allows some operations on read-only repositories" )
2017-02-12 20:43:39 +00:00
f . BoolVarP ( & globalOptions . JSON , "json" , "" , false , "set output mode to JSON for commands that support it" )
2020-04-03 17:44:33 +00:00
f . StringVar ( & globalOptions . CacheDir , "cache-dir" , "" , "set the cache `directory`. (default: use system default cache directory)" )
2017-06-10 11:10:08 +00:00
f . BoolVar ( & globalOptions . NoCache , "no-cache" , false , "do not use a local cache" )
2022-06-22 16:29:58 +00:00
f . StringSliceVar ( & globalOptions . RootCertFilenames , "cacert" , nil , "`file` to load root certificates from (default: use system certificates)" )
f . StringVar ( & globalOptions . TLSClientCertKeyFilename , "tls-client-cert" , "" , "path to a `file` containing PEM encoded TLS client certificate and private key" )
2022-07-12 18:48:01 +00:00
f . BoolVar ( & globalOptions . InsecureTLS , "insecure-tls" , false , "skip TLS certificate verification when connecting to the repository (insecure)" )
2017-11-20 21:08:53 +00:00
f . BoolVar ( & globalOptions . CleanupCache , "cleanup-cache" , false , "auto remove old cache directories" )
2022-05-07 20:23:59 +00:00
f . Var ( & globalOptions . Compression , "compression" , "compression mode (only available for repository format version 2), one of (auto|off|max)" )
2022-08-26 18:39:38 +00:00
f . IntVar ( & globalOptions . Limits . UploadKb , "limit-upload" , 0 , "limits uploads to a maximum `rate` in KiB/s. (default: unlimited)" )
f . IntVar ( & globalOptions . Limits . DownloadKb , "limit-download" , 0 , "limits downloads to a maximum `rate` in KiB/s. (default: unlimited)" )
f . UintVar ( & globalOptions . PackSize , "pack-size" , 0 , "set target pack `size` in MiB, created pack files may be larger (default: $RESTIC_PACK_SIZE)" )
2017-03-25 14:33:52 +00:00
f . StringSliceVarP ( & globalOptions . Options , "option" , "o" , [ ] string { } , "set extended option (`key=value`, can be specified multiple times)" )
2021-07-10 17:44:18 +00:00
// Use our "generate" command instead of the cobra provided "completion" command
cmdRoot . CompletionOptions . DisableDefaultCmd = true
2017-03-25 14:33:52 +00:00
2022-08-19 18:48:25 +00:00
globalOptions . Repo = os . Getenv ( "RESTIC_REPOSITORY" )
globalOptions . RepositoryFile = os . Getenv ( "RESTIC_REPOSITORY_FILE" )
globalOptions . PasswordFile = os . Getenv ( "RESTIC_PASSWORD_FILE" )
globalOptions . KeyHint = os . Getenv ( "RESTIC_KEY_HINT" )
globalOptions . PasswordCommand = os . Getenv ( "RESTIC_PASSWORD_COMMAND" )
2022-07-30 14:21:53 +00:00
comp := os . Getenv ( "RESTIC_COMPRESSION" )
if comp != "" {
// ignore error as there's no good way to handle it
_ = globalOptions . Compression . Set ( comp )
}
2022-08-19 18:48:25 +00:00
// parse target pack size from env, on error the default value will be used
targetPackSize , _ := strconv . ParseUint ( os . Getenv ( "RESTIC_PACK_SIZE" ) , 10 , 32 )
globalOptions . PackSize = uint ( targetPackSize )
2022-07-30 14:21:53 +00:00
2016-02-13 17:29:26 +00:00
restoreTerminal ( )
}
// checkErrno returns nil when err is set to syscall.Errno(0), since this is no
// error condition.
func checkErrno ( err error ) error {
e , ok := err . ( syscall . Errno )
if ! ok {
return err
}
if e == 0 {
return nil
}
return err
}
2016-08-27 16:31:46 +00:00
func stdinIsTerminal ( ) bool {
2022-03-28 20:24:15 +00:00
return term . IsTerminal ( int ( os . Stdin . Fd ( ) ) )
2016-08-27 16:31:46 +00:00
}
func stdoutIsTerminal ( ) bool {
2021-03-07 21:50:52 +00:00
// mintty on windows can use pipes which behave like a posix terminal,
// but which are not a terminal handle
2022-03-28 20:24:15 +00:00
return term . IsTerminal ( int ( os . Stdout . Fd ( ) ) ) || stdoutCanUpdateStatus ( )
2021-03-07 21:50:52 +00:00
}
func stdoutCanUpdateStatus ( ) bool {
return termstatus . CanUpdateStatus ( os . Stdout . Fd ( ) )
2016-08-27 16:31:46 +00:00
}
2017-03-06 10:23:00 +00:00
func stdoutTerminalWidth ( ) int {
2022-03-28 20:24:15 +00:00
w , _ , err := term . GetSize ( int ( os . Stdout . Fd ( ) ) )
2017-03-06 10:23:00 +00:00
if err != nil {
return 0
}
return w
}
2016-02-13 17:29:26 +00:00
// restoreTerminal installs a cleanup handler that restores the previous
2020-04-12 16:46:22 +00:00
// terminal state on exit. This handler is only intended to restore the
// terminal configuration if restic exits after receiving a signal. A regular
// program execution must revert changes to the terminal configuration itself.
// The terminal configuration is only restored while reading a password.
2016-02-13 17:29:26 +00:00
func restoreTerminal ( ) {
2022-03-28 20:24:15 +00:00
if ! term . IsTerminal ( int ( os . Stdout . Fd ( ) ) ) {
2016-02-13 17:29:26 +00:00
return
}
2016-08-25 20:13:47 +00:00
fd := int ( os . Stdout . Fd ( ) )
2022-03-28 20:24:15 +00:00
state , err := term . GetState ( fd )
2016-02-13 17:29:26 +00:00
if err != nil {
fmt . Fprintf ( os . Stderr , "unable to get terminal state: %v\n" , err )
return
}
2022-08-26 21:04:59 +00:00
AddCleanupHandler ( func ( code int ) ( int , error ) {
2020-04-12 16:46:22 +00:00
// Restoring the terminal configuration while restic runs in the
// background, causes restic to get stopped on unix systems with
// a SIGTTOU signal. Thus only restore the terminal settings if
// they might have been modified, which is the case while reading
// a password.
if ! isReadingPassword {
2022-08-26 21:04:59 +00:00
return code , nil
2020-04-12 16:46:22 +00:00
}
2022-03-28 20:24:15 +00:00
err := checkErrno ( term . Restore ( fd , state ) )
2016-02-13 17:29:26 +00:00
if err != nil {
2020-04-12 16:46:22 +00:00
fmt . Fprintf ( os . Stderr , "unable to restore terminal state: %v\n" , err )
2016-02-13 17:29:26 +00:00
}
2022-08-26 21:04:59 +00:00
return code , err
2016-02-13 17:29:26 +00:00
} )
}
2016-08-21 21:10:28 +00:00
// ClearLine creates a platform dependent string to clear the current
2021-08-29 12:55:33 +00:00
// line, so it can be overwritten.
//
// w should be the terminal width, or 0 to let clearLine figure it out.
func clearLine ( w int ) string {
if runtime . GOOS != "windows" {
return "\x1b[2K"
}
// ANSI sequences are not supported on Windows cmd shell.
if w <= 0 {
if w = stdoutTerminalWidth ( ) ; w <= 0 {
return ""
2016-08-22 15:27:58 +00:00
}
2016-08-21 20:38:22 +00:00
}
2021-08-29 12:55:33 +00:00
return strings . Repeat ( " " , w - 1 ) + "\r"
2016-08-21 20:38:22 +00:00
}
2016-01-17 15:59:03 +00:00
// Printf writes the message to the configured stdout stream.
2016-09-17 10:36:05 +00:00
func Printf ( format string , args ... interface { } ) {
_ , err := fmt . Fprintf ( globalOptions . stdout , format , args ... )
2015-06-21 11:02:56 +00:00
if err != nil {
fmt . Fprintf ( os . Stderr , "unable to write to stdout: %v\n" , err )
}
}
2019-05-26 19:23:53 +00:00
// Print writes the message to the configured stdout stream.
func Print ( args ... interface { } ) {
_ , err := fmt . Fprint ( globalOptions . stdout , args ... )
if err != nil {
fmt . Fprintf ( os . Stderr , "unable to write to stdout: %v\n" , err )
}
}
// Println writes the message to the configured stdout stream.
func Println ( args ... interface { } ) {
_ , err := fmt . Fprintln ( globalOptions . stdout , args ... )
if err != nil {
fmt . Fprintf ( os . Stderr , "unable to write to stdout: %v\n" , err )
}
}
2016-01-17 15:59:03 +00:00
// Verbosef calls Printf to write the message when the verbose flag is set.
2016-09-17 10:36:05 +00:00
func Verbosef ( format string , args ... interface { } ) {
2018-04-21 20:07:14 +00:00
if globalOptions . verbosity >= 1 {
Printf ( format , args ... )
2015-06-21 11:25:26 +00:00
}
2016-08-25 20:13:47 +00:00
}
2020-10-08 21:01:24 +00:00
// Verboseff calls Printf to write the message when the verbosity is >= 2
func Verboseff ( format string , args ... interface { } ) {
if globalOptions . verbosity >= 2 {
Printf ( format , args ... )
}
}
2016-01-17 15:59:03 +00:00
// Warnf writes the message to the configured stderr stream.
2016-09-17 10:36:05 +00:00
func Warnf ( format string , args ... interface { } ) {
_ , err := fmt . Fprintf ( globalOptions . stderr , format , args ... )
2015-06-21 11:02:56 +00:00
if err != nil {
fmt . Fprintf ( os . Stderr , "unable to write to stderr: %v\n" , err )
}
}
2016-12-28 09:53:31 +00:00
// Exitf uses Warnf to write the message and then terminates the process with
// the given exit code.
2016-09-17 10:36:05 +00:00
func Exitf ( exitcode int , format string , args ... interface { } ) {
2020-01-27 16:24:42 +00:00
if ! ( strings . HasSuffix ( format , "\n" ) ) {
2015-06-21 11:02:56 +00:00
format += "\n"
}
2016-09-17 10:36:05 +00:00
Warnf ( format , args ... )
2016-12-28 09:53:31 +00:00
Exit ( exitcode )
2015-06-21 11:02:56 +00:00
}
2017-07-24 21:01:16 +00:00
// resolvePassword determines the password to be used for opening the repository.
2019-08-13 16:27:20 +00:00
func resolvePassword ( opts GlobalOptions , envStr string ) ( string , error ) {
2018-11-18 13:31:00 +00: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
}
return ( strings . TrimSpace ( string ( output ) ) ) , nil
}
2017-07-24 21:01:16 +00:00
if opts . PasswordFile != "" {
2018-05-01 12:40:52 +00:00
s , err := textfile . Read ( opts . PasswordFile )
2022-06-13 18:35:37 +00:00
if errors . Is ( err , os . ErrNotExist ) {
2017-07-27 11:23:08 +00:00
return "" , errors . Fatalf ( "%s does not exist" , opts . PasswordFile )
}
2017-07-24 21:01:16 +00:00
return strings . TrimSpace ( string ( s ) ) , errors . Wrap ( err , "Readfile" )
}
2019-08-13 16:27:20 +00:00
if pwd := os . Getenv ( envStr ) ; pwd != "" {
2017-07-24 21:01:16 +00:00
return pwd , nil
}
return "" , nil
}
2015-11-04 21:05:36 +00:00
// readPassword reads the password from the given reader directly.
func readPassword ( in io . Reader ) ( password string , err error ) {
2019-03-26 15:14:40 +00:00
sc := bufio . NewScanner ( in )
sc . Scan ( )
2015-11-04 21:05:36 +00:00
2019-03-26 15:14:40 +00:00
return sc . Text ( ) , errors . Wrap ( err , "Scan" )
2015-11-04 21:05:36 +00:00
}
// readPasswordTerminal reads the password from the given reader which must be a
// tty. Prompt is printed on the writer out before attempting to read the
// password.
func readPasswordTerminal ( in * os . File , out io . Writer , prompt string ) ( password string , err error ) {
fmt . Fprint ( out , prompt )
2020-04-12 16:46:22 +00:00
isReadingPassword = true
2022-03-28 20:24:15 +00:00
buf , err := term . ReadPassword ( int ( in . Fd ( ) ) )
2020-04-12 16:46:22 +00:00
isReadingPassword = false
2015-11-04 21:05:36 +00:00
fmt . Fprintln ( out )
if err != nil {
2016-09-12 12:08:51 +00:00
return "" , errors . Wrap ( err , "ReadPassword" )
2015-11-04 21:05:36 +00:00
}
password = string ( buf )
return password , nil
}
2016-09-12 12:08:51 +00:00
// ReadPassword reads the password from a password file, the environment
// variable RESTIC_PASSWORD or prompts the user.
2016-09-17 10:36:05 +00:00
func ReadPassword ( opts GlobalOptions , prompt string ) ( string , error ) {
2017-07-24 21:01:16 +00:00
if opts . password != "" {
return opts . password , nil
2016-09-12 12:08:51 +00:00
}
2015-11-04 21:05:36 +00:00
var (
password string
err error
)
2016-08-27 16:31:46 +00:00
if stdinIsTerminal ( ) {
2015-11-04 21:05:36 +00:00
password , err = readPasswordTerminal ( os . Stdin , os . Stderr , prompt )
} else {
password , err = readPassword ( os . Stdin )
2020-09-30 15:25:54 +00:00
Verbosef ( "reading repository password from stdin\n" )
2015-11-04 21:05:36 +00:00
}
2015-06-21 11:02:56 +00:00
if err != nil {
2016-09-12 12:08:51 +00:00
return "" , errors . Wrap ( err , "unable to read password" )
2015-06-21 11:02:56 +00:00
}
2015-11-04 21:05:36 +00:00
if len ( password ) == 0 {
2021-02-14 19:39:28 +00:00
return "" , errors . Fatal ( "an empty password is not a password" )
2015-06-21 13:01:52 +00:00
}
2016-09-12 12:08:51 +00:00
return password , nil
2015-06-21 11:02:56 +00:00
}
2016-01-17 15:59:03 +00:00
// ReadPasswordTwice calls ReadPassword two times and returns an error when the
// passwords don't match.
2016-09-17 10:36:05 +00:00
func ReadPasswordTwice ( gopts GlobalOptions , prompt1 , prompt2 string ) ( string , error ) {
pw1 , err := ReadPassword ( gopts , prompt1 )
2016-09-12 12:08:51 +00:00
if err != nil {
return "" , err
}
2019-03-26 15:14:40 +00:00
if stdinIsTerminal ( ) {
pw2 , err := ReadPassword ( gopts , prompt2 )
if err != nil {
return "" , err
}
2016-09-12 12:08:51 +00:00
2019-03-26 15:14:40 +00:00
if pw1 != pw2 {
return "" , errors . Fatal ( "passwords do not match" )
}
2015-06-21 11:02:56 +00:00
}
2016-09-12 12:08:51 +00:00
return pw1 , nil
2015-06-21 11:02:56 +00:00
}
2020-08-30 21:20:57 +00:00
func ReadRepo ( opts GlobalOptions ) ( string , error ) {
if opts . Repo == "" && opts . RepositoryFile == "" {
return "" , errors . Fatal ( "Please specify repository location (-r or --repository-file)" )
}
repo := opts . Repo
if opts . RepositoryFile != "" {
if repo != "" {
return "" , errors . Fatal ( "Options -r and --repository-file are mutually exclusive, please specify only one" )
}
s , err := textfile . Read ( opts . RepositoryFile )
2022-06-13 18:35:37 +00:00
if errors . Is ( err , os . ErrNotExist ) {
2020-08-30 21:20:57 +00:00
return "" , errors . Fatalf ( "%s does not exist" , opts . RepositoryFile )
}
if err != nil {
return "" , err
}
repo = strings . TrimSpace ( string ( s ) )
}
return repo , nil
}
2016-08-21 11:09:31 +00:00
const maxKeys = 20
2016-01-17 15:59:03 +00:00
// OpenRepository reads the password and opens the repository.
2021-10-31 22:08:13 +00:00
func OpenRepository ( ctx context . Context , opts GlobalOptions ) ( * repository . Repository , error ) {
2020-08-30 21:20:57 +00:00
repo , err := ReadRepo ( opts )
if err != nil {
return nil , err
2015-06-21 11:02:56 +00:00
}
2021-10-31 22:08:13 +00:00
be , err := open ( ctx , repo , opts , opts . extended )
2015-06-21 11:02:56 +00:00
if err != nil {
return nil , err
}
2020-03-21 19:39:01 +00:00
report := func ( msg string , err error , d time . Duration ) {
2017-10-14 12:51:00 +00:00
Warnf ( "%v returned error, retrying after %v: %v\n" , msg , d , err )
2020-03-21 19:39:01 +00:00
}
success := func ( msg string , retries int ) {
Warnf ( "%v operation successful after %d retries\n" , msg , retries )
}
2022-10-15 14:33:15 +00:00
be = retry . New ( be , 10 , report , success )
2017-10-14 12:51:00 +00:00
2020-03-31 17:09:01 +00:00
// wrap backend if a test specified a hook
if opts . backendTestHook != nil {
be , err = opts . backendTestHook ( be )
if err != nil {
return nil , err
}
}
2022-07-02 21:30:26 +00:00
s , err := repository . New ( be , repository . Options {
Compression : opts . Compression ,
2022-07-02 21:52:02 +00:00
PackSize : opts . PackSize * 1024 * 1024 ,
2022-07-02 21:30:26 +00:00
} )
if err != nil {
return nil , err
}
2015-06-21 11:02:56 +00:00
2019-06-12 10:40:05 +00:00
passwordTriesLeft := 1
if stdinIsTerminal ( ) && opts . password == "" {
passwordTriesLeft = 3
2015-06-21 11:02:56 +00:00
}
2019-06-12 10:40:05 +00:00
for ; passwordTriesLeft > 0 ; passwordTriesLeft -- {
opts . password , err = ReadPassword ( opts , "enter password for repository: " )
if err != nil && passwordTriesLeft > 1 {
opts . password = ""
fmt . Printf ( "%s. Try again\n" , err )
}
if err != nil {
continue
}
2021-10-31 22:08:13 +00:00
err = s . SearchKey ( ctx , opts . password , maxKeys , opts . KeyHint )
2019-06-12 10:40:05 +00:00
if err != nil && passwordTriesLeft > 1 {
opts . password = ""
2022-04-20 18:03:21 +00:00
fmt . Fprintf ( os . Stderr , "%s. Try again\n" , err )
2019-06-12 10:40:05 +00:00
}
}
2015-06-21 11:02:56 +00:00
if err != nil {
2019-06-12 10:40:05 +00:00
if errors . IsFatal ( err ) {
return nil , err
}
return nil , errors . Fatalf ( "%s" , err )
2015-06-21 11:02:56 +00:00
}
2018-08-11 05:34:37 +00:00
if stdoutIsTerminal ( ) && ! opts . JSON {
2018-04-29 12:19:10 +00:00
id := s . Config ( ) . ID
if len ( id ) > 8 {
id = id [ : 8 ]
}
2018-08-19 06:18:43 +00:00
if ! opts . JSON {
2022-11-04 21:40:07 +00:00
extra := ""
if s . Config ( ) . Version >= 2 {
extra = ", compression level " + opts . Compression . String ( )
}
Verbosef ( "repository %v opened (version %v%s)\n" , id , s . Config ( ) . Version , extra )
2018-08-19 06:18:43 +00:00
}
2017-10-04 11:45:05 +00:00
}
2017-06-10 11:10:08 +00:00
if opts . NoCache {
return s , nil
}
2017-11-20 20:32:25 +00:00
c , err := cache . New ( s . Config ( ) . ID , opts . CacheDir )
2017-06-10 11:10:08 +00:00
if err != nil {
Warnf ( "unable to open cache: %v\n" , err )
2017-11-20 20:32:25 +00:00
return s , nil
}
2021-03-19 11:32:10 +00:00
if c . Created && ! opts . JSON && stdoutIsTerminal ( ) {
2018-08-28 20:03:47 +00:00
Verbosef ( "created new cache in %v\n" , c . Base )
}
2017-12-03 14:52:57 +00:00
// start using the cache
s . UseCache ( c )
2017-11-20 20:32:25 +00:00
oldCacheDirs , err := cache . Old ( c . Base )
if err != nil {
Warnf ( "unable to find old cache directories: %v" , err )
2017-11-20 21:08:53 +00: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
if opts . CleanupCache {
2021-12-28 23:08:29 +00:00
if stdoutIsTerminal ( ) && ! opts . JSON {
Verbosef ( "removing %d old cache dirs from %v\n" , len ( oldCacheDirs ) , c . Base )
}
2017-11-20 21:08:53 +00:00
for _ , item := range oldCacheDirs {
2018-05-01 14:22:12 +00:00
dir := filepath . Join ( c . Base , item . Name ( ) )
2017-11-20 21:08:53 +00:00
err = fs . RemoveAll ( dir )
if err != nil {
Warnf ( "unable to remove %v: %v\n" , dir , err )
}
}
2017-06-10 11:10:08 +00:00
} else {
2017-12-13 18:57:05 +00:00
if stdoutIsTerminal ( ) {
2019-07-02 08:49:49 +00:00
Verbosef ( "found %d old cache directories in %v, run `restic cache --cleanup` to remove them\n" ,
2017-12-13 18:57:05 +00:00
len ( oldCacheDirs ) , c . Base )
}
2017-06-10 11:10:08 +00:00
}
2015-06-21 11:02:56 +00:00
return s , nil
}
2017-03-25 16:31:59 +00:00
func parseConfig ( loc location . Location , opts options . Options ) ( interface { } , error ) {
// only apply options for a particular backend here
opts = opts . Extract ( loc . Scheme )
2016-09-18 11:24:29 +00:00
2015-12-28 17:23:20 +00:00
switch loc . Scheme {
case "local" :
2017-03-25 16:31:59 +00:00
cfg := loc . Config . ( local . Config )
if err := opts . Apply ( loc . Scheme , & cfg ) ; err != nil {
return nil , err
}
debug . Log ( "opening local repository at %#v" , cfg )
return cfg , nil
2015-12-28 17:23:20 +00:00
case "sftp" :
2017-03-25 16:31:59 +00:00
cfg := loc . Config . ( sftp . Config )
if err := opts . Apply ( loc . Scheme , & cfg ) ; err != nil {
return nil , err
}
debug . Log ( "opening sftp repository at %#v" , cfg )
return cfg , nil
2015-12-28 17:23:20 +00:00
case "s3" :
cfg := loc . Config . ( s3 . Config )
if cfg . KeyID == "" {
cfg . KeyID = os . Getenv ( "AWS_ACCESS_KEY_ID" )
}
2017-03-25 16:31:59 +00:00
2021-08-04 20:56:18 +00:00
if cfg . Secret . String ( ) == "" {
cfg . Secret = options . NewSecretString ( os . Getenv ( "AWS_SECRET_ACCESS_KEY" ) )
2015-12-28 17:23:20 +00:00
}
2015-08-14 13:39:16 +00:00
2021-08-04 20:56:18 +00:00
if cfg . KeyID == "" && cfg . Secret . String ( ) != "" {
2021-09-30 18:45:31 +00:00
return nil , errors . Fatalf ( "unable to open S3 backend: Key ID ($AWS_ACCESS_KEY_ID) is empty" )
2021-08-04 20:56:18 +00:00
} else if cfg . KeyID != "" && cfg . Secret . String ( ) == "" {
2021-09-30 18:45:31 +00:00
return nil , errors . Fatalf ( "unable to open S3 backend: Secret ($AWS_SECRET_ACCESS_KEY) is empty" )
}
2019-07-23 09:14:26 +00:00
if cfg . Region == "" {
2019-11-22 14:24:42 +00:00
cfg . Region = os . Getenv ( "AWS_DEFAULT_REGION" )
2019-07-23 09:14:26 +00:00
}
2017-03-25 16:31:59 +00:00
if err := opts . Apply ( loc . Scheme , & cfg ) ; err != nil {
return nil , err
}
2016-09-27 20:35:08 +00:00
debug . Log ( "opening s3 repository at %#v" , cfg )
2017-03-25 16:31:59 +00:00
return cfg , nil
2017-07-08 13:34:23 +00:00
case "gs" :
cfg := loc . Config . ( gs . Config )
if cfg . ProjectID == "" {
cfg . ProjectID = os . Getenv ( "GOOGLE_PROJECT_ID" )
}
if err := opts . Apply ( loc . Scheme , & cfg ) ; err != nil {
return nil , err
}
debug . Log ( "opening gs repository at %#v" , cfg )
return cfg , nil
2017-07-08 13:38:48 +00:00
case "azure" :
cfg := loc . Config . ( azure . Config )
if cfg . AccountName == "" {
cfg . AccountName = os . Getenv ( "AZURE_ACCOUNT_NAME" )
}
2021-08-04 20:56:18 +00:00
if cfg . AccountKey . String ( ) == "" {
cfg . AccountKey = options . NewSecretString ( os . Getenv ( "AZURE_ACCOUNT_KEY" ) )
2017-07-08 13:38:48 +00:00
}
2022-03-05 18:16:13 +00:00
if cfg . AccountSAS . String ( ) == "" {
cfg . AccountSAS = options . NewSecretString ( os . Getenv ( "AZURE_ACCOUNT_SAS" ) )
}
2017-07-08 13:38:48 +00:00
if err := opts . Apply ( loc . Scheme , & cfg ) ; err != nil {
return nil , err
}
debug . Log ( "opening gs repository at %#v" , cfg )
return cfg , nil
2017-03-29 21:58:25 +00:00
case "swift" :
cfg := loc . Config . ( swift . Config )
2017-05-01 08:13:03 +00:00
if err := swift . ApplyEnvironment ( "" , & cfg ) ; err != nil {
return nil , err
2017-03-29 21:58:25 +00:00
}
if err := opts . Apply ( loc . Scheme , & cfg ) ; err != nil {
return nil , err
}
debug . Log ( "opening swift repository at %#v" , cfg )
return cfg , nil
2017-05-28 08:19:01 +00:00
case "b2" :
cfg := loc . Config . ( b2 . Config )
if cfg . AccountID == "" {
cfg . AccountID = os . Getenv ( "B2_ACCOUNT_ID" )
}
2017-12-19 20:12:38 +00:00
if cfg . AccountID == "" {
return nil , errors . Fatalf ( "unable to open B2 backend: Account ID ($B2_ACCOUNT_ID) is empty" )
}
2021-08-04 20:56:18 +00:00
if cfg . Key . String ( ) == "" {
cfg . Key = options . NewSecretString ( os . Getenv ( "B2_ACCOUNT_KEY" ) )
2017-05-28 08:19:01 +00:00
}
2021-08-04 20:56:18 +00:00
if cfg . Key . String ( ) == "" {
2017-12-19 20:12:38 +00:00
return nil , errors . Fatalf ( "unable to open B2 backend: Key ($B2_ACCOUNT_KEY) is empty" )
}
2017-05-28 08:19:01 +00:00
if err := opts . Apply ( loc . Scheme , & cfg ) ; err != nil {
return nil , err
}
debug . Log ( "opening b2 repository at %#v" , cfg )
return cfg , nil
2016-02-21 14:24:46 +00:00
case "rest" :
2017-03-25 16:31:59 +00:00
cfg := loc . Config . ( rest . Config )
if err := opts . Apply ( loc . Scheme , & cfg ) ; err != nil {
return nil , err
}
2018-03-13 21:30:51 +00:00
debug . Log ( "opening rest repository at %#v" , cfg )
return cfg , nil
case "rclone" :
cfg := loc . Config . ( rclone . Config )
if err := opts . Apply ( loc . Scheme , & cfg ) ; err != nil {
return nil , err
}
2017-03-25 16:31:59 +00:00
debug . Log ( "opening rest repository at %#v" , cfg )
return cfg , nil
}
return nil , errors . Fatalf ( "invalid backend: %q" , loc . Scheme )
}
// Open the backend specified by a location config.
2021-10-31 22:08:13 +00:00
func open ( ctx context . Context , s string , gopts GlobalOptions , opts options . Options ) ( restic . Backend , error ) {
2020-03-20 22:52:27 +00:00
debug . Log ( "parsing location %v" , location . StripPassword ( s ) )
2017-03-25 16:31:59 +00:00
loc , err := location . Parse ( s )
if err != nil {
return nil , errors . Fatalf ( "parsing repository location failed: %v" , err )
}
var be restic . Backend
cfg , err := parseConfig ( loc , opts )
if err != nil {
return nil , err
}
2022-06-22 16:29:58 +00:00
rt , err := backend . Transport ( globalOptions . TransportOptions )
2017-09-24 18:04:23 +00:00
if err != nil {
return nil , err
}
2017-12-29 11:43:49 +00:00
// wrap the transport so that the throughput via HTTP is limited
2022-06-22 16:29:58 +00:00
lim := limiter . NewStaticLimiter ( gopts . Limits )
2018-05-22 18:48:17 +00:00
rt = lim . Transport ( rt )
2017-12-29 11:43:49 +00:00
2017-03-25 16:31:59 +00:00
switch loc . Scheme {
case "local" :
2021-10-31 22:08:13 +00:00
be , err = local . Open ( ctx , cfg . ( local . Config ) )
2017-03-25 16:31:59 +00:00
case "sftp" :
2021-10-31 22:08:13 +00:00
be , err = sftp . Open ( ctx , cfg . ( sftp . Config ) )
2017-03-25 16:31:59 +00:00
case "s3" :
2021-10-31 22:08:13 +00:00
be , err = s3 . Open ( ctx , cfg . ( s3 . Config ) , rt )
2017-07-08 13:34:23 +00:00
case "gs" :
2018-01-27 19:12:34 +00:00
be , err = gs . Open ( cfg . ( gs . Config ) , rt )
2017-07-08 13:38:48 +00:00
case "azure" :
2017-09-24 18:04:23 +00:00
be , err = azure . Open ( cfg . ( azure . Config ) , rt )
2017-03-29 21:58:25 +00:00
case "swift" :
2021-10-31 22:08:13 +00:00
be , err = swift . Open ( ctx , cfg . ( swift . Config ) , rt )
2017-05-28 08:19:01 +00:00
case "b2" :
2021-10-31 22:08:13 +00:00
be , err = b2 . Open ( ctx , cfg . ( b2 . Config ) , rt )
2017-03-25 16:31:59 +00:00
case "rest" :
2017-09-24 18:04:23 +00:00
be , err = rest . Open ( cfg . ( rest . Config ) , rt )
2018-03-13 21:30:51 +00:00
case "rclone" :
2018-05-22 18:48:17 +00:00
be , err = rclone . Open ( cfg . ( rclone . Config ) , lim )
2017-03-25 16:31:59 +00:00
2016-09-18 11:24:29 +00:00
default :
return nil , errors . Fatalf ( "invalid backend: %q" , loc . Scheme )
2015-06-21 11:02:56 +00:00
}
2016-09-18 11:24:29 +00:00
if err != nil {
2022-05-07 20:23:59 +00:00
return nil , errors . Fatalf ( "unable to open repository at %v: %v" , location . StripPassword ( s ) , err )
2016-09-18 11:24:29 +00:00
}
2021-01-03 16:42:06 +00:00
// wrap backend if a test specified an inner hook
if gopts . backendInnerTestHook != nil {
be , err = gopts . backendInnerTestHook ( be )
if err != nil {
return nil , err
}
}
if loc . Scheme == "local" || loc . Scheme == "sftp" {
// wrap the backend in a LimitBackend so that the throughput is limited
be = limiter . LimitBackend ( be , lim )
}
2017-04-19 16:56:01 +00:00
// check if config is there
2021-10-31 22:08:13 +00:00
fi , err := be . Stat ( ctx , restic . Handle { Type : restic . ConfigFile } )
2017-04-19 16:56:01 +00:00
if err != nil {
2020-03-20 22:52:27 +00:00
return nil , errors . Fatalf ( "unable to open config file: %v\nIs there a repository at the following location?\n%v" , err , location . StripPassword ( s ) )
2017-04-19 16:56:01 +00:00
}
if fi . Size == 0 {
return nil , errors . New ( "config file has zero size, invalid repository?" )
}
2016-09-18 11:24:29 +00:00
return be , nil
2015-06-21 11:02:56 +00:00
}
// Create the backend specified by URI.
2021-10-31 22:08:13 +00:00
func create ( ctx context . Context , s string , opts options . Options ) ( restic . Backend , error ) {
2016-09-27 20:35:08 +00:00
debug . Log ( "parsing location %v" , s )
2015-12-28 17:23:20 +00:00
loc , err := location . Parse ( s )
2015-06-21 11:02:56 +00:00
if err != nil {
return nil , err
}
2017-03-25 16:31:59 +00:00
cfg , err := parseConfig ( loc , opts )
if err != nil {
return nil , err
}
2022-06-22 16:29:58 +00:00
rt , err := backend . Transport ( globalOptions . TransportOptions )
2017-09-24 18:04:23 +00:00
if err != nil {
return nil , err
}
2015-12-28 17:23:20 +00:00
switch loc . Scheme {
case "local" :
2021-10-31 22:08:13 +00:00
return local . Create ( ctx , cfg . ( local . Config ) )
2015-12-28 17:23:20 +00:00
case "sftp" :
2021-10-31 22:08:13 +00:00
return sftp . Create ( ctx , cfg . ( sftp . Config ) )
2015-12-28 17:23:20 +00:00
case "s3" :
2021-10-31 22:08:13 +00:00
return s3 . Create ( ctx , cfg . ( s3 . Config ) , rt )
2017-07-08 13:34:23 +00:00
case "gs" :
2018-01-27 19:12:34 +00:00
return gs . Create ( cfg . ( gs . Config ) , rt )
2017-07-08 13:38:48 +00:00
case "azure" :
2017-09-24 18:04:23 +00:00
return azure . Create ( cfg . ( azure . Config ) , rt )
2017-03-29 21:58:25 +00:00
case "swift" :
2021-10-31 22:08:13 +00:00
return swift . Open ( ctx , cfg . ( swift . Config ) , rt )
2017-05-28 08:19:01 +00:00
case "b2" :
2021-10-31 22:08:13 +00:00
return b2 . Create ( ctx , cfg . ( b2 . Config ) , rt )
2016-02-21 14:24:46 +00:00
case "rest" :
2021-10-31 22:08:13 +00:00
return rest . Create ( ctx , cfg . ( rest . Config ) , rt )
2018-03-13 21:30:51 +00:00
case "rclone" :
2021-10-31 22:08:13 +00:00
return rclone . Create ( ctx , cfg . ( rclone . Config ) )
2015-06-21 11:02:56 +00:00
}
2016-09-27 20:35:08 +00:00
debug . Log ( "invalid repository scheme: %v" , s )
2016-09-01 20:17:37 +00:00
return nil , errors . Fatalf ( "invalid scheme %q" , loc . Scheme )
2015-06-21 11:02:56 +00:00
}