2015-06-21 11:02:56 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2017-03-08 19:12:16 +00:00
|
|
|
"context"
|
2015-06-21 11:02:56 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2016-09-12 12:08:51 +00:00
|
|
|
"io/ioutil"
|
2015-06-21 11:02:56 +00:00
|
|
|
"os"
|
2016-08-28 20:19:48 +00:00
|
|
|
"restic"
|
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"
|
2015-06-21 11:02:56 +00:00
|
|
|
|
2016-02-14 14:29:28 +00:00
|
|
|
"restic/backend/local"
|
2017-03-25 14:25:58 +00:00
|
|
|
"restic/backend/location"
|
2016-02-21 14:24:46 +00:00
|
|
|
"restic/backend/rest"
|
2016-02-14 14:29:28 +00:00
|
|
|
"restic/backend/s3"
|
|
|
|
"restic/backend/sftp"
|
|
|
|
"restic/debug"
|
2017-03-25 14:33:52 +00:00
|
|
|
"restic/options"
|
2016-02-14 14:29:28 +00:00
|
|
|
"restic/repository"
|
2016-02-21 14:24:46 +00:00
|
|
|
|
2016-09-01 20:17:37 +00:00
|
|
|
"restic/errors"
|
|
|
|
|
2016-02-21 14:24:46 +00:00
|
|
|
"golang.org/x/crypto/ssh/terminal"
|
2015-06-21 11:02:56 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var version = "compiled manually"
|
|
|
|
|
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 {
|
2016-09-17 10:36:05 +00:00
|
|
|
Repo string
|
|
|
|
PasswordFile string
|
|
|
|
Quiet bool
|
|
|
|
NoLock bool
|
2017-02-12 20:43:39 +00:00
|
|
|
JSON bool
|
2015-06-21 11:02:56 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2016-02-13 17:29:26 +00:00
|
|
|
func init() {
|
2017-01-02 19:21:30 +00:00
|
|
|
pw := os.Getenv("RESTIC_PASSWORD")
|
|
|
|
if pw != "" {
|
|
|
|
globalOptions.password = pw
|
|
|
|
}
|
|
|
|
|
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()
|
2017-01-02 19:21:30 +00:00
|
|
|
f.StringVarP(&globalOptions.Repo, "repo", "r", os.Getenv("RESTIC_REPOSITORY"), "repository to backup to or restore from (default: $RESTIC_REPOSITORY)")
|
2016-09-17 10:36:05 +00:00
|
|
|
f.StringVarP(&globalOptions.PasswordFile, "password-file", "p", "", "read the repository password from a file")
|
2017-01-18 09:46:04 +00:00
|
|
|
f.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "do not output comprehensive progress report")
|
2016-09-17 10:36:05 +00:00
|
|
|
f.BoolVar(&globalOptions.NoLock, "no-lock", false, "do not lock the repo, this allows some operations on read-only repos")
|
2017-02-12 20:43:39 +00:00
|
|
|
f.BoolVarP(&globalOptions.JSON, "json", "", false, "set output mode to JSON for commands that support it")
|
2016-09-17 10:36:05 +00:00
|
|
|
|
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
|
|
|
|
// terminal state on exit.
|
|
|
|
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 {
|
|
|
|
err := checkErrno(terminal.Restore(fd, state))
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "unable to get restore terminal state: %#+v\n", err)
|
|
|
|
}
|
|
|
|
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)
|
2016-12-28 09:53:31 +00:00
|
|
|
Exit(100)
|
2015-06-21 11:02:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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{}) {
|
|
|
|
if globalOptions.Quiet {
|
2015-06-21 11:25:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
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
|
|
|
Exit(100)
|
2015-06-21 11:02:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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{}) {
|
2015-06-21 11:02:56 +00:00
|
|
|
if format[len(format)-1] != '\n' {
|
|
|
|
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
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
buf := make([]byte, 1000)
|
|
|
|
n, err := io.ReadFull(in, buf)
|
|
|
|
buf = buf[:n]
|
|
|
|
|
2016-08-29 17:18:57 +00:00
|
|
|
if err != nil && errors.Cause(err) != io.ErrUnexpectedEOF {
|
2016-09-12 12:08:51 +00:00
|
|
|
return "", errors.Wrap(err, "ReadFull")
|
2015-11-04 21:05:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return strings.TrimRight(string(buf), "\r\n"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
buf, err := terminal.ReadPassword(int(in.Fd()))
|
|
|
|
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) {
|
|
|
|
if opts.PasswordFile != "" {
|
|
|
|
s, err := ioutil.ReadFile(opts.PasswordFile)
|
2016-09-12 12:08:51 +00:00
|
|
|
return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile")
|
|
|
|
}
|
|
|
|
|
|
|
|
if pwd := os.Getenv("RESTIC_PASSWORD"); pwd != "" {
|
|
|
|
return pwd, nil
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
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 {
|
2016-09-12 12:08:51 +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
|
|
|
|
}
|
2016-09-17 10:36:05 +00:00
|
|
|
pw2, err := ReadPassword(gopts, prompt2)
|
2016-09-12 12:08:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2015-06-21 11:02:56 +00:00
|
|
|
if pw1 != pw2 {
|
2016-09-12 12:08:51 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
if opts.Repo == "" {
|
2016-09-01 20:17:37 +00:00
|
|
|
return nil, errors.Fatal("Please specify repository location (-r)")
|
2015-06-21 11:02:56 +00:00
|
|
|
}
|
|
|
|
|
2017-03-25 14:33:52 +00:00
|
|
|
be, err := open(opts.Repo, opts.extended)
|
2015-06-21 11:02:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
s := repository.New(be)
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
if opts.password == "" {
|
|
|
|
opts.password, err = ReadPassword(opts, "enter password for repository: ")
|
2016-09-12 12:08:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-06-21 11:02:56 +00:00
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
err = s.SearchKey(opts.password, maxKeys)
|
2015-06-21 11:02:56 +00:00
|
|
|
if err != nil {
|
2016-09-01 20:17:37 +00:00
|
|
|
return nil, errors.Fatalf("unable to open repo: %v", err)
|
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
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
func open(s string, opts options.Options) (restic.Backend, error) {
|
|
|
|
debug.Log("parsing location %v", s)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
switch loc.Scheme {
|
|
|
|
case "local":
|
|
|
|
be, err = local.Open(cfg.(local.Config))
|
|
|
|
case "sftp":
|
2017-04-10 20:39:13 +00:00
|
|
|
be, err = sftp.Open(cfg.(sftp.Config))
|
2017-03-25 16:31:59 +00:00
|
|
|
case "s3":
|
|
|
|
be, err = s3.Open(cfg.(s3.Config))
|
|
|
|
case "rest":
|
|
|
|
be, err = rest.Open(cfg.(rest.Config))
|
|
|
|
|
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 {
|
|
|
|
return nil, errors.Fatalf("unable to open repo at %v: %v", s, err)
|
|
|
|
}
|
|
|
|
|
2017-04-19 16:56:01 +00:00
|
|
|
// check if config is there
|
|
|
|
fi, err := be.Stat(restic.Handle{Type: restic.ConfigFile})
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Fatalf("unable to open config file: %v\nIs there a repository at the following location?\n%v", err, s)
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-12-28 17:23:20 +00:00
|
|
|
switch loc.Scheme {
|
|
|
|
case "local":
|
2017-03-25 16:31:59 +00:00
|
|
|
return local.Create(cfg.(local.Config))
|
2015-12-28 17:23:20 +00:00
|
|
|
case "sftp":
|
2017-04-10 20:39:13 +00:00
|
|
|
return sftp.Create(cfg.(sftp.Config))
|
2015-12-28 17:23:20 +00:00
|
|
|
case "s3":
|
2017-03-25 16:31:59 +00:00
|
|
|
return s3.Open(cfg.(s3.Config))
|
2016-02-21 14:24:46 +00:00
|
|
|
case "rest":
|
2017-03-25 16:31:59 +00:00
|
|
|
return rest.Create(cfg.(rest.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
|
|
|
}
|