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"
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"
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"
"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-10-08 18:28:03 +00:00
"github.com/restic/restic/internal/limiter"
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"
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
"golang.org/x/crypto/ssh/terminal"
2015-06-21 11:02:56 +00:00
)
2021-02-14 10:44:24 +00:00
var version = "0.12.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
CACerts [ ] string
TLSClientCert string
CleanupCache bool
2015-06-21 11:02:56 +00:00
2017-10-08 18:28:03 +00:00
LimitUploadKb int
LimitDownloadKb int
2017-03-08 19:12:16 +00:00
ctx context . Context
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
2016-02-13 17:29:26 +00:00
func init ( ) {
2017-03-08 19:12:16 +00:00
var cancel context . CancelFunc
globalOptions . ctx , cancel = context . WithCancel ( context . Background ( ) )
AddCleanupHandler ( func ( ) error {
cancel ( )
return nil
} )
2016-09-17 10:36:05 +00:00
f := cmdRoot . PersistentFlags ( )
2020-04-03 17:44:33 +00:00
f . StringVarP ( & globalOptions . Repo , "repo" , "r" , os . Getenv ( "RESTIC_REPOSITORY" ) , "`repository` to backup to or restore from (default: $RESTIC_REPOSITORY)" )
2020-08-30 21:20:57 +00:00
f . StringVarP ( & globalOptions . RepositoryFile , "repository-file" , "" , os . Getenv ( "RESTIC_REPOSITORY_FILE" ) , "`file` to read the repository location from (default: $RESTIC_REPOSITORY_FILE)" )
2020-09-19 13:47:32 +00:00
f . StringVarP ( & globalOptions . PasswordFile , "password-file" , "p" , os . Getenv ( "RESTIC_PASSWORD_FILE" ) , "`file` to read the repository password from (default: $RESTIC_PASSWORD_FILE)" )
2020-04-03 17:44:33 +00:00
f . StringVarP ( & globalOptions . KeyHint , "key-hint" , "" , os . Getenv ( "RESTIC_KEY_HINT" ) , "`key` ID of key to try decrypting first (default: $RESTIC_KEY_HINT)" )
2020-09-19 12:23:25 +00:00
f . StringVarP ( & globalOptions . PasswordCommand , "password-command" , "" , os . Getenv ( "RESTIC_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" )
2018-04-29 11:17:33 +00:00
f . StringSliceVar ( & globalOptions . CACerts , "cacert" , nil , "`file` to load root certificates from (default: use system certificates)" )
2020-04-03 17:44:33 +00:00
f . StringVar ( & globalOptions . TLSClientCert , "tls-client-cert" , "" , "path to a `file` containing PEM encoded TLS client certificate and private key" )
2017-11-20 21:08:53 +00:00
f . BoolVar ( & globalOptions . CleanupCache , "cleanup-cache" , false , "auto remove old cache directories" )
2017-10-08 18:28:03 +00:00
f . IntVar ( & globalOptions . LimitUploadKb , "limit-upload" , 0 , "limits uploads to a maximum rate in KiB/s. (default: unlimited)" )
f . IntVar ( & globalOptions . LimitDownloadKb , "limit-download" , 0 , "limits downloads to a maximum rate in KiB/s. (default: unlimited)" )
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)" )
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 {
return terminal . IsTerminal ( int ( os . Stdin . Fd ( ) ) )
}
func stdoutIsTerminal ( ) bool {
return terminal . IsTerminal ( int ( os . Stdout . Fd ( ) ) )
}
2017-03-06 10:23:00 +00:00
func stdoutTerminalWidth ( ) int {
w , _ , err := terminal . GetSize ( int ( os . Stdout . Fd ( ) ) )
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 ( ) {
2016-08-27 16:31:46 +00:00
if ! stdoutIsTerminal ( ) {
2016-02-13 17:29:26 +00:00
return
}
2016-08-25 20:13:47 +00:00
fd := int ( os . Stdout . Fd ( ) )
2016-02-13 17:29:26 +00:00
state , err := terminal . GetState ( fd )
if err != nil {
fmt . Fprintf ( os . Stderr , "unable to get terminal state: %v\n" , err )
return
}
AddCleanupHandler ( func ( ) 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 {
return nil
}
2016-02-13 17:29:26 +00:00
err := checkErrno ( terminal . Restore ( fd , state ) )
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
}
return err
} )
}
2016-08-21 21:10:28 +00:00
// ClearLine creates a platform dependent string to clear the current
// line, so it can be overwritten. ANSI sequences are not supported on
// current windows cmd shell.
2016-08-21 20:38:22 +00:00
func ClearLine ( ) string {
if runtime . GOOS == "windows" {
2017-03-06 10:23:00 +00:00
if w := stdoutTerminalWidth ( ) ; w > 0 {
2016-08-22 15:27:58 +00:00
return strings . Repeat ( " " , w - 1 ) + "\r"
}
return ""
2016-08-21 20:38:22 +00:00
}
2016-08-21 21:10:28 +00:00
return "\x1b[2K"
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-08-25 20:13:47 +00:00
// PrintProgress wraps fmt.Printf to handle the difference in writing progress
// information to terminals and non-terminal stdout
func PrintProgress ( format string , args ... interface { } ) {
var (
message string
carriageControl string
)
message = fmt . Sprintf ( format , args ... )
if ! ( strings . HasSuffix ( message , "\r" ) || strings . HasSuffix ( message , "\n" ) ) {
2016-08-27 16:31:46 +00:00
if stdoutIsTerminal ( ) {
2016-08-25 20:13:47 +00:00
carriageControl = "\r"
} else {
carriageControl = "\n"
}
message = fmt . Sprintf ( "%s%s" , message , carriageControl )
2015-06-21 11:02:56 +00:00
}
2016-08-27 16:31:46 +00:00
if stdoutIsTerminal ( ) {
2016-08-25 20:13:47 +00:00
message = fmt . Sprintf ( "%s%s" , ClearLine ( ) , message )
}
fmt . Print ( message )
2015-06-21 11:02:56 +00:00
}
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 )
if os . IsNotExist ( errors . Cause ( err ) ) {
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
2015-11-04 21:05:36 +00:00
buf , err := terminal . 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 {
2019-06-12 10:40:05 +00:00
return "" , errors . New ( "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 )
if os . IsNotExist ( errors . Cause ( err ) ) {
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.
2016-09-17 10:36:05 +00:00
func OpenRepository ( 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
}
2020-08-30 21:20:57 +00:00
be , err := open ( repo , opts , opts . extended )
2015-06-21 11:02:56 +00:00
if err != nil {
return nil , err
}
2017-10-14 12:51:00 +00:00
be = backend . NewRetryBackend ( be , 10 , func ( msg string , err error , d time . Duration ) {
Warnf ( "%v returned error, retrying after %v: %v\n" , msg , d , err )
} )
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
}
}
2015-06-21 11:02:56 +00:00
s := repository . New ( be )
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
}
err = s . SearchKey ( opts . ctx , opts . password , maxKeys , opts . KeyHint )
if err != nil && passwordTriesLeft > 1 {
opts . password = ""
fmt . Printf ( "%s. Try again\n" , err )
}
}
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 {
Verbosef ( "repository %v opened successfully, password is correct\n" , id )
}
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
}
2018-08-28 20:03:47 +00:00
if c . Created && ! opts . JSON {
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 {
Printf ( "removing %d old cache dirs from %v\n" , len ( oldCacheDirs ) , c . Base )
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
2015-12-28 17:23:20 +00:00
if cfg . Secret == "" {
cfg . Secret = os . Getenv ( "AWS_SECRET_ACCESS_KEY" )
}
2015-08-14 13:39:16 +00:00
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" )
}
if cfg . AccountKey == "" {
cfg . AccountKey = os . Getenv ( "AZURE_ACCOUNT_KEY" )
}
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" )
}
2017-05-28 08:19:01 +00:00
if cfg . Key == "" {
cfg . Key = os . Getenv ( "B2_ACCOUNT_KEY" )
}
2017-12-19 20:12:38 +00:00
if cfg . Key == "" {
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.
2017-12-29 11:43:49 +00:00
func open ( 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
}
2018-01-27 12:57:43 +00:00
tropts := backend . TransportOptions {
RootCertFilenames : globalOptions . CACerts ,
TLSClientCertKeyFilename : globalOptions . TLSClientCert ,
}
rt , err := backend . Transport ( tropts )
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
2018-05-22 18:48:17 +00:00
lim := limiter . NewStaticLimiter ( gopts . LimitUploadKb , gopts . LimitDownloadKb )
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" :
2020-09-19 20:01:32 +00:00
be , err = local . Open ( globalOptions . ctx , cfg . ( local . Config ) )
2017-03-25 16:31:59 +00:00
case "sftp" :
2020-09-19 20:01:32 +00:00
be , err = sftp . Open ( globalOptions . ctx , cfg . ( sftp . Config ) )
2017-03-25 16:31:59 +00:00
case "s3" :
2020-09-19 20:01:32 +00:00
be , err = s3 . Open ( globalOptions . 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" :
2017-09-24 18:04:23 +00:00
be , err = swift . Open ( cfg . ( swift . Config ) , rt )
2017-05-28 08:19:01 +00:00
case "b2" :
2017-11-22 11:27:29 +00:00
be , err = b2 . Open ( globalOptions . 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 {
2020-03-20 22:52:27 +00:00
return nil , errors . Fatalf ( "unable to open repo 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
2017-11-22 11:27:29 +00:00
fi , err := be . Stat ( globalOptions . 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.
2017-03-25 14:33:52 +00:00
func create ( 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
}
2018-01-27 12:57:43 +00:00
tropts := backend . TransportOptions {
RootCertFilenames : globalOptions . CACerts ,
TLSClientCertKeyFilename : globalOptions . TLSClientCert ,
}
rt , err := backend . Transport ( tropts )
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" :
2020-09-19 20:01:32 +00:00
return local . Create ( globalOptions . ctx , cfg . ( local . Config ) )
2015-12-28 17:23:20 +00:00
case "sftp" :
2020-09-19 20:01:32 +00:00
return sftp . Create ( globalOptions . ctx , cfg . ( sftp . Config ) )
2015-12-28 17:23:20 +00:00
case "s3" :
2020-09-19 19:57:02 +00:00
return s3 . Create ( globalOptions . 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" :
2017-09-24 18:04:23 +00:00
return swift . Open ( cfg . ( swift . Config ) , rt )
2017-05-28 08:19:01 +00:00
case "b2" :
2017-11-22 11:27:29 +00:00
return b2 . Create ( globalOptions . ctx , cfg . ( b2 . Config ) , rt )
2016-02-21 14:24:46 +00:00
case "rest" :
2020-04-10 10:08:52 +00:00
return rest . Create ( globalOptions . ctx , cfg . ( rest . Config ) , rt )
2018-03-13 21:30:51 +00:00
case "rclone" :
2020-04-10 10:08:52 +00:00
return rclone . Create ( globalOptions . 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
}