restic/cmd/restic/main.go

221 lines
5.2 KiB
Go
Raw Normal View History

2014-04-28 00:00:15 +02:00
package main
import (
"bufio"
"bytes"
2024-03-29 23:58:48 +01:00
"context"
"encoding/json"
2015-07-12 22:10:01 +02:00
"fmt"
"log"
"os"
2017-06-11 14:17:44 +02:00
"runtime"
godebug "runtime/debug"
2016-09-17 12:36:05 +02:00
"github.com/spf13/cobra"
"go.uber.org/automaxprocs/maxprocs"
2016-09-01 22:17:37 +02:00
"github.com/restic/restic/internal/debug"
2017-07-23 14:21:03 +02:00
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/feature"
"github.com/restic/restic/internal/options"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
2014-04-28 00:00:15 +02:00
)
func init() {
// don't import `go.uber.org/automaxprocs` to disable the log output
_, _ = maxprocs.Set()
}
2024-04-05 22:23:08 +02:00
var ErrOK = errors.New("ok")
2023-07-23 12:09:01 +02:00
// cmdRoot is the base command when no other command has been specified.
var cmdRoot = &cobra.Command{
Use: "restic",
Short: "Backup and restore files",
Long: `
2016-09-17 12:36:05 +02:00
restic is a backup program which allows saving multiple revisions of files and
directories in an encrypted repository stored on different backends.
2023-07-23 12:09:01 +02:00
The full documentation can be found at https://restic.readthedocs.io/ .
`,
SilenceErrors: true,
SilenceUsage: true,
DisableAutoGenTag: true,
2017-01-22 19:10:32 +01:00
2024-02-10 22:58:10 +01:00
PersistentPreRunE: func(c *cobra.Command, _ []string) error {
2018-04-22 11:57:20 +02:00
// set verbosity, default is one
2018-04-21 22:07:14 +02:00
globalOptions.verbosity = 1
if globalOptions.Quiet && globalOptions.Verbose > 0 {
2018-04-22 11:57:20 +02:00
return errors.Fatal("--quiet and --verbose cannot be specified at the same time")
2018-04-21 22:07:14 +02:00
}
switch {
2018-04-22 11:57:20 +02:00
case globalOptions.Verbose >= 2:
globalOptions.verbosity = 3
case globalOptions.Verbose > 0:
globalOptions.verbosity = 2
2018-04-21 22:07:14 +02:00
case globalOptions.Quiet:
globalOptions.verbosity = 0
}
2017-03-25 15:33:52 +01:00
// parse extended options
opts, err := options.Parse(globalOptions.Options)
if err != nil {
return err
}
globalOptions.extended = opts
if !needsPassword(c.Name()) {
return nil
}
pwd, err := resolvePassword(globalOptions, "RESTIC_PASSWORD")
if err != nil {
fmt.Fprintf(os.Stderr, "Resolving password failed: %v\n", err)
Exit(1)
}
globalOptions.password = pwd
2017-03-25 15:33:52 +01:00
// run the debug functions for all subcommands (if build tag "debug" is
// enabled)
return runDebug()
},
PersistentPostRun: func(_ *cobra.Command, _ []string) {
stopDebug()
},
2016-09-17 12:36:05 +02:00
}
var cmdGroupDefault = "default"
var cmdGroupAdvanced = "advanced"
func init() {
cmdRoot.AddGroup(
&cobra.Group{
ID: cmdGroupDefault,
Title: "Available Commands:",
},
&cobra.Group{
ID: cmdGroupAdvanced,
Title: "Advanced Options:",
},
)
}
// Distinguish commands that need the password from those that work without,
// so we don't run $RESTIC_PASSWORD_COMMAND for no reason (it might prompt the
// user for authentication).
func needsPassword(cmd string) bool {
switch cmd {
case "cache", "generate", "help", "options", "self-update", "version", "__complete":
return false
default:
return true
}
}
func tweakGoGC() {
// lower GOGC from 100 to 50, unless it was manually overwritten by the user
oldValue := godebug.SetGCPercent(50)
if oldValue != 100 {
godebug.SetGCPercent(oldValue)
}
}
func printExitError(code int, message string) {
if globalOptions.JSON {
type jsonExitError struct {
MessageType string `json:"message_type"` // exit_error
Code int `json:"code"`
Message string `json:"message"`
}
jsonS := jsonExitError{
MessageType: "exit_error",
Code: code,
Message: message,
}
err := json.NewEncoder(globalOptions.stderr).Encode(jsonS)
if err != nil {
Warnf("JSON encode failed: %v\n", err)
return
}
} else {
fmt.Fprintf(globalOptions.stderr, "%v\n", message)
}
}
func main() {
tweakGoGC()
// install custom global logger into a buffer, if an error occurs
// we can show the logs
2024-03-30 14:28:59 +01:00
logBuffer := bytes.NewBuffer(nil)
log.SetOutput(logBuffer)
err := feature.Flag.Apply(os.Getenv("RESTIC_FEATURES"), func(s string) {
fmt.Fprintln(os.Stderr, s)
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
Exit(1)
}
2016-09-27 22:35:08 +02:00
debug.Log("main %#v", os.Args)
2018-03-21 21:49:03 +01:00
debug.Log("restic %s compiled with %v on %v/%v",
2017-06-11 14:17:44 +02:00
version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
2024-03-29 23:58:48 +01:00
ctx := createGlobalContext()
err = cmdRoot.ExecuteContext(ctx)
if err == nil {
err = ctx.Err()
2024-04-05 22:23:08 +02:00
} else if err == ErrOK {
// ErrOK overwrites context cancelation errors
err = nil
2024-03-29 23:58:48 +01:00
}
var exitMessage string
switch {
case restic.IsAlreadyLocked(err):
exitMessage = fmt.Sprintf("%v\nthe `unlock` command can be used to remove stale locks", err)
case err == ErrInvalidSourceData:
exitMessage = fmt.Sprintf("Warning: %v", err)
case errors.IsFatal(err):
exitMessage = err.Error()
case errors.Is(err, repository.ErrNoKeyFound):
exitMessage = fmt.Sprintf("Fatal: %v", err)
case err != nil:
exitMessage = fmt.Sprintf("%+v", err)
if logBuffer.Len() > 0 {
exitMessage += "also, the following messages were logged by a library:\n"
sc := bufio.NewScanner(logBuffer)
for sc.Scan() {
exitMessage += fmt.Sprintln(sc.Text())
}
}
2016-01-17 16:59:03 +01:00
}
var exitCode int
2024-03-29 23:58:48 +01:00
switch {
case err == nil:
exitCode = 0
2024-03-29 23:58:48 +01:00
case err == ErrInvalidSourceData:
exitCode = 3
case errors.Is(err, ErrNoRepository):
exitCode = 10
case restic.IsAlreadyLocked(err):
exitCode = 11
case errors.Is(err, repository.ErrNoKeyFound):
exitCode = 12
2024-03-29 23:58:48 +01:00
case errors.Is(err, context.Canceled):
exitCode = 130
default:
exitCode = 1
2014-04-28 00:00:15 +02:00
}
if exitCode != 0 {
printExitError(exitCode, exitMessage)
}
Exit(exitCode)
2014-04-28 00:00:15 +02:00
}