mirror of
https://github.com/restic/restic.git
synced 2026-02-23 01:06:23 +00:00
Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd9b2295f1 | ||
|
|
a439cdeb05 | ||
|
|
827f6d7b24 | ||
|
|
77ab10d401 | ||
|
|
3b0ad2e368 | ||
|
|
2996c110f1 | ||
|
|
4609b5c24d | ||
|
|
830511460a | ||
|
|
0dc3648416 | ||
|
|
d71dba3788 | ||
|
|
e482633943 | ||
|
|
900621051a | ||
|
|
1f246c5309 | ||
|
|
e40805b002 | ||
|
|
d65bea1b2a | ||
|
|
3b68acf853 | ||
|
|
82a70643a2 | ||
|
|
0dd805421e | ||
|
|
16b82f4b1d | ||
|
|
7a6bfcd58c | ||
|
|
de54618852 | ||
|
|
98526b8dbe | ||
|
|
0083680d33 | ||
|
|
05222b7343 | ||
|
|
d4ff5b6bf4 | ||
|
|
cf0883e16c | ||
|
|
a35a24b8b4 | ||
|
|
df7f72cdde | ||
|
|
3edc723bf0 | ||
|
|
71891b340c | ||
|
|
6f5c3e57f6 | ||
|
|
56af0ce370 | ||
|
|
c9745cd47e | ||
|
|
2434ab2106 | ||
|
|
1688713400 | ||
|
|
00597284de | ||
|
|
6dc7cca597 | ||
|
|
d32c7c2aba | ||
|
|
09e9b74cbd | ||
|
|
d53595e43c | ||
|
|
0de19cc87f | ||
|
|
2c9ec07d0b | ||
|
|
a7971a3ece | ||
|
|
4ab0022da8 | ||
|
|
4b3c054257 | ||
|
|
7486bfea5b | ||
|
|
c8fc72364a | ||
|
|
987ef2f4a9 | ||
|
|
5b95bb7059 | ||
|
|
8471a359ee | ||
|
|
f9422ff4c7 | ||
|
|
c0572ca15f | ||
|
|
a630d69e0c | ||
|
|
20bcd281a3 | ||
|
|
c012fccd22 | ||
|
|
920727dd34 | ||
|
|
157d365894 | ||
|
|
bfa18ee8ec | ||
|
|
890eebf151 | ||
|
|
9310cd0cd6 | ||
|
|
9f7ce7ce5a | ||
|
|
0b600d6cef | ||
|
|
3ae2a79bdf | ||
|
|
f7c0893f76 | ||
|
|
c3de301fc8 | ||
|
|
944b446ac0 | ||
|
|
b096fc7abf | ||
|
|
d10754e2b4 | ||
|
|
7ac683c360 | ||
|
|
6caa9d38ac | ||
|
|
19fd0f101f | ||
|
|
8c91c51d1b | ||
|
|
7e28bf7e97 | ||
|
|
43d6e426c8 | ||
|
|
26fc60e7cb | ||
|
|
e5d7879622 | ||
|
|
d2ee58f2e9 | ||
|
|
3f25537a06 | ||
|
|
d203ae37f4 | ||
|
|
6eedd66c1a | ||
|
|
e4b39ae553 | ||
|
|
7cbcb6d318 | ||
|
|
c0fca3f50a | ||
|
|
4c2072d875 | ||
|
|
92ecca1808 | ||
|
|
7236635cc1 | ||
|
|
21a3486ebb | ||
|
|
bda8d7722e | ||
|
|
c2bcb764cd | ||
|
|
9e24154ec9 | ||
|
|
9f3ca97ee8 | ||
|
|
32d5ceba87 | ||
|
|
e010f3b884 | ||
|
|
941202c119 | ||
|
|
c021ad2334 | ||
|
|
2b3420820b | ||
|
|
da57302fca | ||
|
|
1869930d95 | ||
|
|
1213d8fef4 | ||
|
|
a432b42c81 | ||
|
|
7d0f2eaf24 | ||
|
|
41a4d67d93 | ||
|
|
afde60e433 | ||
|
|
d7baa67acb | ||
|
|
167397c18c | ||
|
|
9151eec24e | ||
|
|
22475729ce | ||
|
|
04c67d700d |
145
CHANGELOG.md
145
CHANGELOG.md
@@ -1,3 +1,131 @@
|
||||
Changelog for restic 0.9.4 (2019-01-06)
|
||||
=======================================
|
||||
|
||||
The following sections list the changes in restic 0.9.4 relevant to
|
||||
restic users. The changes are ordered by importance.
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
* Fix #1989: Google Cloud Storage: Respect bandwidth limit
|
||||
* Fix #2040: Add host name filter shorthand flag for `stats` command
|
||||
* Fix #2068: Correctly return error loading data
|
||||
* Fix #2095: Consistently use local time for snapshots times
|
||||
* Enh #1605: Concurrent restore
|
||||
* Enh #2089: Increase granularity of the "keep within" retention policy
|
||||
* Enh #2097: Add key hinting
|
||||
* Enh #2017: Mount: Enforce FUSE Unix permissions with allow-other
|
||||
* Enh #2070: Make all commands display timestamps in local time
|
||||
* Enh #2085: Allow --files-from to be specified multiple times
|
||||
* Enh #2094: Run command to get password
|
||||
|
||||
Details
|
||||
-------
|
||||
|
||||
* Bugfix #1989: Google Cloud Storage: Respect bandwidth limit
|
||||
|
||||
The GCS backend did not respect the bandwidth limit configured, a previous commit
|
||||
accidentally removed support for it.
|
||||
|
||||
https://github.com/restic/restic/issues/1989
|
||||
https://github.com/restic/restic/pull/2100
|
||||
|
||||
* Bugfix #2040: Add host name filter shorthand flag for `stats` command
|
||||
|
||||
The default value for `--host` flag was set to 'H' (the shorthand version of the flag), this
|
||||
caused the lookup for the latest snapshot to fail.
|
||||
|
||||
Add shorthand flag `-H` for `--host` (with empty default so if these flags are not specified the
|
||||
latest snapshot will not filter by host name).
|
||||
|
||||
Also add shorthand `-H` for `backup` command.
|
||||
|
||||
https://github.com/restic/restic/issues/2040
|
||||
|
||||
* Bugfix #2068: Correctly return error loading data
|
||||
|
||||
In one case during `prune` and `check`, an error loading data from the backend is not returned
|
||||
properly. This is now corrected.
|
||||
|
||||
https://github.com/restic/restic/issues/1999#issuecomment-433737921
|
||||
https://github.com/restic/restic/pull/2068
|
||||
|
||||
* Bugfix #2095: Consistently use local time for snapshots times
|
||||
|
||||
By default snapshots created with restic backup were set to local time, but when the --time flag
|
||||
was used the provided timestamp was parsed as UTC. With this change all snapshots times are set
|
||||
to local time.
|
||||
|
||||
https://github.com/restic/restic/pull/2095
|
||||
|
||||
* Enhancement #1605: Concurrent restore
|
||||
|
||||
This change significantly improves restore performance, especially when using
|
||||
high-latency remote repositories like B2.
|
||||
|
||||
The implementation now uses several concurrent threads to download and process multiple
|
||||
remote files concurrently. To further reduce restore time, each remote file is downloaded
|
||||
using a single repository request.
|
||||
|
||||
https://github.com/restic/restic/issues/1605
|
||||
https://github.com/restic/restic/pull/1719
|
||||
|
||||
* Enhancement #2089: Increase granularity of the "keep within" retention policy
|
||||
|
||||
The `keep-within` option of the `forget` command now accepts time ranges with an hourly
|
||||
granularity. For example, running `restic forget --keep-within 3d12h` will keep all the
|
||||
snapshots made within three days and twelve hours from the time of the latest snapshot.
|
||||
|
||||
https://github.com/restic/restic/issues/2089
|
||||
https://github.com/restic/restic/pull/2090
|
||||
|
||||
* Enhancement #2097: Add key hinting
|
||||
|
||||
Added a new option `--key-hint` and corresponding environment variable `RESTIC_KEY_HINT`.
|
||||
The key hint is a key ID to try decrypting first, before other keys in the repository.
|
||||
|
||||
This change will benefit repositories with many keys; if the correct key hint is supplied then
|
||||
restic only needs to check one key. If the key hint is incorrect (the key does not exist, or the
|
||||
password is incorrect) then restic will check all keys, as usual.
|
||||
|
||||
https://github.com/restic/restic/issues/2097
|
||||
|
||||
* Enhancement #2017: Mount: Enforce FUSE Unix permissions with allow-other
|
||||
|
||||
The fuse mount (`restic mount`) now lets the kernel check the permissions of the files within
|
||||
snapshots (this is done through the `DefaultPermissions` FUSE option) when the option
|
||||
`--allow-other` is specified.
|
||||
|
||||
To restore the old behavior, we've added the `--no-default-permissions` option. This allows
|
||||
all users that have access to the mount point to access all files within the snapshots.
|
||||
|
||||
https://github.com/restic/restic/pull/2017
|
||||
|
||||
* Enhancement #2070: Make all commands display timestamps in local time
|
||||
|
||||
Restic used to drop the timezone information from displayed timestamps, it now converts
|
||||
timestamps to local time before printing them so the times can be easily compared to.
|
||||
|
||||
https://github.com/restic/restic/pull/2070
|
||||
|
||||
* Enhancement #2085: Allow --files-from to be specified multiple times
|
||||
|
||||
Before, restic took only the last file specified with `--files-from` into account, this is now
|
||||
corrected.
|
||||
|
||||
https://github.com/restic/restic/issues/2085
|
||||
https://github.com/restic/restic/pull/2086
|
||||
|
||||
* Enhancement #2094: Run command to get password
|
||||
|
||||
We've added the `--password-command` option which allows specifying a command that restic
|
||||
runs every time the password for the repository is needed, so it can be integrated with a
|
||||
password manager or keyring. The option can also be set via the environment variable
|
||||
`$RESTIC_PASSWORD_COMMAND`.
|
||||
|
||||
https://github.com/restic/restic/pull/2094
|
||||
|
||||
|
||||
Changelog for restic 0.9.3 (2018-10-13)
|
||||
=======================================
|
||||
|
||||
@@ -8,7 +136,7 @@ Summary
|
||||
-------
|
||||
|
||||
* Fix #1935: Remove truncated files from cache
|
||||
* Fix #1978: Do not return an error when the scanner is faster than backup
|
||||
* Fix #1978: Do not return an error when the scanner is slower than backup
|
||||
* Enh #1766: Restore: suppress lchown errors when not running as root
|
||||
* Enh #1909: Reject files/dirs by name first
|
||||
* Enh #1940: Add directory filter to ls command
|
||||
@@ -20,6 +148,7 @@ Summary
|
||||
* Enh #1920: Vendor dependencies with Go 1.11 Modules
|
||||
* Enh #1949: Add new command `self-update`
|
||||
* Enh #1953: Ls: Add JSON output support for restic ls cmd
|
||||
* Enh #1962: Stream JSON output for ls command
|
||||
|
||||
Details
|
||||
-------
|
||||
@@ -32,7 +161,7 @@ Details
|
||||
|
||||
https://github.com/restic/restic/issues/1935
|
||||
|
||||
* Bugfix #1978: Do not return an error when the scanner is faster than backup
|
||||
* Bugfix #1978: Do not return an error when the scanner is slower than backup
|
||||
|
||||
When restic makes a backup, there's a background task called "scanner" which collects
|
||||
information on how many files and directories are to be saved, in order to display progress
|
||||
@@ -155,6 +284,18 @@ Details
|
||||
|
||||
https://github.com/restic/restic/pull/1953
|
||||
|
||||
* Enhancement #1962: Stream JSON output for ls command
|
||||
|
||||
The `ls` command now supports JSON output with the global `--json` flag, and this change
|
||||
streams out JSON messages one object at a time rather than en entire array buffered in memory
|
||||
before encoding. The advantage is it allows large listings to be handled efficiently.
|
||||
|
||||
Two message types are printed: snapshots and nodes. A snapshot object will precede node
|
||||
objects which belong to that snapshot. The `struct_type` field can be used to determine which
|
||||
kind of message an object is.
|
||||
|
||||
https://github.com/restic/restic/pull/1962
|
||||
|
||||
|
||||
Changelog for restic 0.9.2 (2018-08-06)
|
||||
=======================================
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Introduction
|
||||
------------
|
||||
|
||||
restic is a backup program that is fast, efficient and secure.
|
||||
restic is a backup program that is fast, efficient and secure. It supports the three major operating systems (Linux, macOS, Windows) and a few smaller ones (FreeBSD, OpenBSD).
|
||||
|
||||
For detailed usage and installation instructions check out the `documentation <https://restic.readthedocs.io/en/latest>`__.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Bugfix: Do not return an error when the scanner is faster than backup
|
||||
Bugfix: Do not return an error when the scanner is slower than backup
|
||||
|
||||
When restic makes a backup, there's a background task called "scanner" which
|
||||
collects information on how many files and directories are to be saved, in
|
||||
|
||||
13
changelog/0.9.3_2018-10-13/pull-1962
Normal file
13
changelog/0.9.3_2018-10-13/pull-1962
Normal file
@@ -0,0 +1,13 @@
|
||||
Enhancement: Stream JSON output for ls command
|
||||
|
||||
The `ls` command now supports JSON output with the global `--json`
|
||||
flag, and this change streams out JSON messages one object at a time
|
||||
rather than en entire array buffered in memory before encoding. The
|
||||
advantage is it allows large listings to be handled efficiently.
|
||||
|
||||
Two message types are printed: snapshots and nodes. A snapshot
|
||||
object will precede node objects which belong to that snapshot.
|
||||
The `struct_type` field can be used to determine which kind of
|
||||
message an object is.
|
||||
|
||||
https://github.com/restic/restic/pull/1962
|
||||
11
changelog/0.9.4_2019-01-06/issue-1605
Normal file
11
changelog/0.9.4_2019-01-06/issue-1605
Normal file
@@ -0,0 +1,11 @@
|
||||
Enhancement: Concurrent restore
|
||||
|
||||
This change significantly improves restore performance, especially
|
||||
when using high-latency remote repositories like B2.
|
||||
|
||||
The implementation now uses several concurrent threads to download and process
|
||||
multiple remote files concurrently. To further reduce restore time, each remote
|
||||
file is downloaded using a single repository request.
|
||||
|
||||
https://github.com/restic/restic/issues/1605
|
||||
https://github.com/restic/restic/pull/1719
|
||||
7
changelog/0.9.4_2019-01-06/issue-1989
Normal file
7
changelog/0.9.4_2019-01-06/issue-1989
Normal file
@@ -0,0 +1,7 @@
|
||||
Bugfix: Google Cloud Storage: Respect bandwidth limit
|
||||
|
||||
The GCS backend did not respect the bandwidth limit configured, a previous
|
||||
commit accidentally removed support for it.
|
||||
|
||||
https://github.com/restic/restic/issues/1989
|
||||
https://github.com/restic/restic/pull/2100
|
||||
11
changelog/0.9.4_2019-01-06/issue-2040
Normal file
11
changelog/0.9.4_2019-01-06/issue-2040
Normal file
@@ -0,0 +1,11 @@
|
||||
Bugfix: Add host name filter shorthand flag for `stats` command
|
||||
|
||||
The default value for `--host` flag was set to 'H' (the shorthand version of
|
||||
the flag), this caused the lookup for the latest snapshot to fail.
|
||||
|
||||
Add shorthand flag `-H` for `--host` (with empty default so if these flags
|
||||
are not specified the latest snapshot will not filter by host name).
|
||||
|
||||
Also add shorthand `-H` for `backup` command.
|
||||
|
||||
https://github.com/restic/restic/issues/2040
|
||||
9
changelog/0.9.4_2019-01-06/issue-2089
Normal file
9
changelog/0.9.4_2019-01-06/issue-2089
Normal file
@@ -0,0 +1,9 @@
|
||||
Enhancement: increase granularity of the "keep within" retention policy
|
||||
|
||||
The `keep-within` option of the `forget` command now accepts time ranges with
|
||||
an hourly granularity. For example, running `restic forget --keep-within 3d12h`
|
||||
will keep all the snapshots made within three days and twelve hours from the
|
||||
time of the latest snapshot.
|
||||
|
||||
https://github.com/restic/restic/issues/2089
|
||||
https://github.com/restic/restic/pull/2090
|
||||
12
changelog/0.9.4_2019-01-06/issue-2097
Normal file
12
changelog/0.9.4_2019-01-06/issue-2097
Normal file
@@ -0,0 +1,12 @@
|
||||
Enhancement: Add key hinting
|
||||
|
||||
Added a new option `--key-hint` and corresponding environment variable
|
||||
`RESTIC_KEY_HINT`. The key hint is a key ID to try decrypting first, before
|
||||
other keys in the repository.
|
||||
|
||||
This change will benefit repositories with many keys; if the correct key hint
|
||||
is supplied then restic only needs to check one key. If the key hint is
|
||||
incorrect (the key does not exist, or the password is incorrect) then restic
|
||||
will check all keys, as usual.
|
||||
|
||||
https://github.com/restic/restic/issues/2097
|
||||
11
changelog/0.9.4_2019-01-06/pull-2017
Normal file
11
changelog/0.9.4_2019-01-06/pull-2017
Normal file
@@ -0,0 +1,11 @@
|
||||
Enhancement: mount: Enforce FUSE Unix permissions with allow-other
|
||||
|
||||
The fuse mount (`restic mount`) now lets the kernel check the permissions of
|
||||
the files within snapshots (this is done through the `DefaultPermissions` FUSE
|
||||
option) when the option `--allow-other` is specified.
|
||||
|
||||
To restore the old behavior, we've added the `--no-default-permissions` option.
|
||||
This allows all users that have access to the mount point to access all
|
||||
files within the snapshots.
|
||||
|
||||
https://github.com/restic/restic/pull/2017
|
||||
6
changelog/0.9.4_2019-01-06/pull-2068
Normal file
6
changelog/0.9.4_2019-01-06/pull-2068
Normal file
@@ -0,0 +1,6 @@
|
||||
Bugfix: Correctly return error loading data
|
||||
|
||||
In one case during `prune` and `check`, an error loading data from the backend is not returned properly. This is now corrected.
|
||||
|
||||
https://github.com/restic/restic/pull/2068
|
||||
https://github.com/restic/restic/issues/1999#issuecomment-433737921
|
||||
7
changelog/0.9.4_2019-01-06/pull-2070
Normal file
7
changelog/0.9.4_2019-01-06/pull-2070
Normal file
@@ -0,0 +1,7 @@
|
||||
Enhancement: Make all commands display timestamps in local time
|
||||
|
||||
Restic used to drop the timezone information from displayed timestamps, it now
|
||||
converts timestamps to local time before printing them so the times can be
|
||||
easily compared to.
|
||||
|
||||
https://github.com/restic/restic/pull/2070
|
||||
7
changelog/0.9.4_2019-01-06/pull-2086
Normal file
7
changelog/0.9.4_2019-01-06/pull-2086
Normal file
@@ -0,0 +1,7 @@
|
||||
Enhancement: Allow --files-from to be specified multiple times
|
||||
|
||||
Before, restic took only the last file specified with `--files-from` into
|
||||
account, this is now corrected.
|
||||
|
||||
https://github.com/restic/restic/issues/2085
|
||||
https://github.com/restic/restic/pull/2086
|
||||
8
changelog/0.9.4_2019-01-06/pull-2094
Normal file
8
changelog/0.9.4_2019-01-06/pull-2094
Normal file
@@ -0,0 +1,8 @@
|
||||
Enhancement: Run command to get password
|
||||
|
||||
We've added the `--password-command` option which allows specifying a command
|
||||
that restic runs every time the password for the repository is needed, so it
|
||||
can be integrated with a password manager or keyring. The option can also be
|
||||
set via the environment variable `$RESTIC_PASSWORD_COMMAND`.
|
||||
|
||||
https://github.com/restic/restic/pull/2094
|
||||
7
changelog/0.9.4_2019-01-06/pull-2095
Normal file
7
changelog/0.9.4_2019-01-06/pull-2095
Normal file
@@ -0,0 +1,7 @@
|
||||
Bugfix: consistently use local time for snapshots times
|
||||
|
||||
By default snapshots created with restic backup were set to local time,
|
||||
but when the --time flag was used the provided timestamp was parsed as
|
||||
UTC. With this change all snapshots times are set to local time.
|
||||
|
||||
https://github.com/restic/restic/pull/2095
|
||||
@@ -45,8 +45,12 @@ given as the arguments.
|
||||
},
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if backupOptions.Stdin && backupOptions.FilesFrom == "-" {
|
||||
return errors.Fatal("cannot use both `--stdin` and `--files-from -`")
|
||||
if backupOptions.Stdin {
|
||||
for _, filename := range backupOptions.FilesFrom {
|
||||
if filename == "-" {
|
||||
return errors.Fatal("cannot use both `--stdin` and `--files-from -`")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var t tomb.Tomb
|
||||
@@ -75,7 +79,7 @@ type BackupOptions struct {
|
||||
StdinFilename string
|
||||
Tags []string
|
||||
Host string
|
||||
FilesFrom string
|
||||
FilesFrom []string
|
||||
TimeStamp string
|
||||
WithAtime bool
|
||||
}
|
||||
@@ -97,11 +101,11 @@ func init() {
|
||||
f.StringVar(&backupOptions.StdinFilename, "stdin-filename", "stdin", "file name to use when reading from stdin")
|
||||
f.StringArrayVar(&backupOptions.Tags, "tag", nil, "add a `tag` for the new snapshot (can be specified multiple times)")
|
||||
|
||||
f.StringVar(&backupOptions.Host, "host", "H", "set the `hostname` for the snapshot manually. To prevent an expensive rescan use the \"parent\" flag")
|
||||
f.StringVarP(&backupOptions.Host, "host", "H", "", "set the `hostname` for the snapshot manually. To prevent an expensive rescan use the \"parent\" flag")
|
||||
f.StringVar(&backupOptions.Host, "hostname", "", "set the `hostname` for the snapshot manually")
|
||||
f.MarkDeprecated("hostname", "use --host")
|
||||
|
||||
f.StringVar(&backupOptions.FilesFrom, "files-from", "", "read the files to backup from file (can be combined with file args)")
|
||||
f.StringArrayVar(&backupOptions.FilesFrom, "files-from", nil, "read the files to backup from file (can be combined with file args/can be specified multiple times)")
|
||||
f.StringVar(&backupOptions.TimeStamp, "time", "", "time of the backup (ex. '2012-11-01 22:08:41') (default: now)")
|
||||
f.BoolVar(&backupOptions.WithAtime, "with-atime", false, "store the atime for all files and directories")
|
||||
}
|
||||
@@ -175,12 +179,16 @@ func readLinesFromFile(filename string) ([]string, error) {
|
||||
|
||||
// Check returns an error when an invalid combination of options was set.
|
||||
func (opts BackupOptions) Check(gopts GlobalOptions, args []string) error {
|
||||
if opts.FilesFrom == "-" && gopts.password == "" {
|
||||
return errors.Fatal("unable to read password from stdin when data is to be read from stdin, use --password-file or $RESTIC_PASSWORD")
|
||||
if gopts.password == "" {
|
||||
for _, filename := range opts.FilesFrom {
|
||||
if filename == "-" {
|
||||
return errors.Fatal("unable to read password from stdin when data is to be read from stdin, use --password-file or $RESTIC_PASSWORD")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Stdin {
|
||||
if opts.FilesFrom != "" {
|
||||
if len(opts.FilesFrom) > 0 {
|
||||
return errors.Fatal("--stdin and --files-from cannot be used together")
|
||||
}
|
||||
|
||||
@@ -302,20 +310,25 @@ func collectTargets(opts BackupOptions, args []string) (targets []string, err er
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
fromfile, err := readLinesFromFile(opts.FilesFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// expand wildcards
|
||||
var lines []string
|
||||
for _, line := range fromfile {
|
||||
var expanded []string
|
||||
expanded, err := filepath.Glob(line)
|
||||
for _, file := range opts.FilesFrom {
|
||||
fromfile, err := readLinesFromFile(file)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, fmt.Sprintf("pattern: %s", line))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// expand wildcards
|
||||
for _, line := range fromfile {
|
||||
var expanded []string
|
||||
expanded, err := filepath.Glob(line)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, fmt.Sprintf("pattern: %s", line))
|
||||
}
|
||||
if len(expanded) == 0 {
|
||||
Warnf("pattern %q does not match any files, skipping\n", line)
|
||||
}
|
||||
lines = append(lines, expanded...)
|
||||
}
|
||||
lines = append(lines, expanded...)
|
||||
}
|
||||
|
||||
// merge files from files-from into normal args so we can reuse the normal
|
||||
@@ -374,7 +387,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
|
||||
|
||||
timeStamp := time.Now()
|
||||
if opts.TimeStamp != "" {
|
||||
timeStamp, err = time.Parse(TimeFormat, opts.TimeStamp)
|
||||
timeStamp, err = time.ParseInLocation(TimeFormat, opts.TimeStamp, time.Local)
|
||||
if err != nil {
|
||||
return errors.Fatalf("error in time option: %v\n", err)
|
||||
}
|
||||
@@ -382,6 +395,12 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
|
||||
|
||||
var t tomb.Tomb
|
||||
|
||||
term.Print("open repository\n")
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := ui.NewBackup(term, gopts.verbosity)
|
||||
|
||||
// use the terminal for stdout/stderr
|
||||
@@ -403,12 +422,6 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina
|
||||
|
||||
t.Go(func() error { return p.Run(t.Context(gopts.ctx)) })
|
||||
|
||||
p.V("open repository")
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.V("lock repository")
|
||||
lock, err := lockRepo(repo)
|
||||
defer unlockRepo(lock)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
@@ -47,12 +48,12 @@ func init() {
|
||||
flags.StringArrayVar(&dumpOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"")
|
||||
}
|
||||
|
||||
func splitPath(path string) []string {
|
||||
d, f := filepath.Split(path)
|
||||
func splitPath(p string) []string {
|
||||
d, f := path.Split(p)
|
||||
if d == "" || d == "/" {
|
||||
return []string{f}
|
||||
}
|
||||
s := splitPath(filepath.Clean(d))
|
||||
s := splitPath(path.Clean(d))
|
||||
return append(s, f)
|
||||
}
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ func (s *statefulOutput) PrintObjectNormal(kind, id, nodepath, treeID string, sn
|
||||
} else {
|
||||
Printf(" ... path %s\n", nodepath)
|
||||
}
|
||||
Printf(" ... in snapshot %s (%s)\n", sn.ID().Str(), sn.Time.Format(TimeFormat))
|
||||
Printf(" ... in snapshot %s (%s)\n", sn.ID().Str(), sn.Time.Local().Format(TimeFormat))
|
||||
}
|
||||
|
||||
func (s *statefulOutput) PrintObject(kind, id, nodepath, treeID string, sn *restic.Snapshot) {
|
||||
|
||||
@@ -59,7 +59,7 @@ func init() {
|
||||
f.IntVarP(&forgetOptions.Weekly, "keep-weekly", "w", 0, "keep the last `n` weekly snapshots")
|
||||
f.IntVarP(&forgetOptions.Monthly, "keep-monthly", "m", 0, "keep the last `n` monthly snapshots")
|
||||
f.IntVarP(&forgetOptions.Yearly, "keep-yearly", "y", 0, "keep the last `n` yearly snapshots")
|
||||
f.VarP(&forgetOptions.Within, "keep-within", "", "keep snapshots that are older than `duration` (eg. 1y5m7d) relative to the latest snapshot")
|
||||
f.VarP(&forgetOptions.Within, "keep-within", "", "keep snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot")
|
||||
|
||||
f.Var(&forgetOptions.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)")
|
||||
f.StringVar(&forgetOptions.Host, "host", "", "only consider snapshots with the given `host`")
|
||||
|
||||
@@ -59,7 +59,7 @@ func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions
|
||||
ID: id.Str(),
|
||||
UserName: k.Username,
|
||||
HostName: k.Hostname,
|
||||
Created: k.Created.Format(TimeFormat),
|
||||
Created: k.Created.Local().Format(TimeFormat),
|
||||
}
|
||||
|
||||
keys = append(keys, key)
|
||||
|
||||
@@ -64,10 +64,8 @@ func init() {
|
||||
|
||||
type lsSnapshot struct {
|
||||
*restic.Snapshot
|
||||
|
||||
ID *restic.ID `json:"id"`
|
||||
ShortID string `json:"short_id"`
|
||||
Nodes []lsNode `json:"nodes"`
|
||||
StructType string `json:"struct_type"` // "snapshot"
|
||||
}
|
||||
|
||||
@@ -150,24 +148,22 @@ func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
|
||||
var (
|
||||
printSnapshot func(sn *restic.Snapshot)
|
||||
printNode func(path string, node *restic.Node)
|
||||
printFinish func() error
|
||||
)
|
||||
|
||||
if gopts.JSON {
|
||||
var lssnapshots []lsSnapshot
|
||||
enc := json.NewEncoder(gopts.stdout)
|
||||
|
||||
printSnapshot = func(sn *restic.Snapshot) {
|
||||
lss := lsSnapshot{
|
||||
enc.Encode(lsSnapshot{
|
||||
Snapshot: sn,
|
||||
ID: sn.ID(),
|
||||
ShortID: sn.ID().Str(),
|
||||
StructType: "snapshot",
|
||||
}
|
||||
lssnapshots = append(lssnapshots, lss)
|
||||
})
|
||||
}
|
||||
|
||||
printNode = func(path string, node *restic.Node) {
|
||||
lsn := lsNode{
|
||||
enc.Encode(lsNode{
|
||||
Name: node.Name,
|
||||
Type: node.Type,
|
||||
Path: path,
|
||||
@@ -179,25 +175,15 @@ func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
|
||||
AccessTime: node.AccessTime,
|
||||
ChangeTime: node.ChangeTime,
|
||||
StructType: "node",
|
||||
}
|
||||
s := &lssnapshots[len(lssnapshots)-1]
|
||||
s.Nodes = append(s.Nodes, lsn)
|
||||
}
|
||||
|
||||
printFinish = func() error {
|
||||
return json.NewEncoder(gopts.stdout).Encode(lssnapshots)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// default output methods
|
||||
printSnapshot = func(sn *restic.Snapshot) {
|
||||
Verbosef("snapshot %s of %v filtered by %v at %s):\n", sn.ID().Str(), sn.Paths, dirs, sn.Time)
|
||||
}
|
||||
printNode = func(path string, node *restic.Node) {
|
||||
Printf("%s\n", formatNode(path, node, lsOptions.ListLong))
|
||||
}
|
||||
printFinish = func() error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for sn := range FindFilteredSnapshots(ctx, repo, opts.Host, opts.Tags, opts.Paths, args[:1]) {
|
||||
@@ -240,5 +226,6 @@ func runLs(opts LsOptions, gopts GlobalOptions, args []string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return printFinish()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -53,13 +53,14 @@ For details please see the documentation for time.Format() at:
|
||||
|
||||
// MountOptions collects all options for the mount command.
|
||||
type MountOptions struct {
|
||||
OwnerRoot bool
|
||||
AllowRoot bool
|
||||
AllowOther bool
|
||||
Host string
|
||||
Tags restic.TagLists
|
||||
Paths []string
|
||||
SnapshotTemplate string
|
||||
OwnerRoot bool
|
||||
AllowRoot bool
|
||||
AllowOther bool
|
||||
NoDefaultPermissions bool
|
||||
Host string
|
||||
Tags restic.TagLists
|
||||
Paths []string
|
||||
SnapshotTemplate string
|
||||
}
|
||||
|
||||
var mountOptions MountOptions
|
||||
@@ -71,6 +72,7 @@ func init() {
|
||||
mountFlags.BoolVar(&mountOptions.OwnerRoot, "owner-root", false, "use 'root' as the owner of files and dirs")
|
||||
mountFlags.BoolVar(&mountOptions.AllowRoot, "allow-root", false, "allow root user to access the data in the mounted directory")
|
||||
mountFlags.BoolVar(&mountOptions.AllowOther, "allow-other", false, "allow other users to access the data in the mounted directory")
|
||||
mountFlags.BoolVar(&mountOptions.NoDefaultPermissions, "no-default-permissions", false, "for 'allow-other', ignore Unix permissions and allow users to read all snapshot files")
|
||||
|
||||
mountFlags.StringVarP(&mountOptions.Host, "host", "H", "", `only consider snapshots for this host`)
|
||||
mountFlags.Var(&mountOptions.Tags, "tag", "only consider snapshots which include this `taglist`")
|
||||
@@ -118,6 +120,11 @@ func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error {
|
||||
|
||||
if opts.AllowOther {
|
||||
mountOptions = append(mountOptions, systemFuse.AllowOther())
|
||||
|
||||
// let the kernel check permissions unless it is explicitly disabled
|
||||
if !opts.NoDefaultPermissions {
|
||||
mountOptions = append(mountOptions, systemFuse.DefaultPermissions())
|
||||
}
|
||||
}
|
||||
|
||||
c, err := systemFuse.Mount(mountpoint, mountOptions...)
|
||||
|
||||
148
cmd/restic/cmd_recover.go
Normal file
148
cmd/restic/cmd_recover.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdRecover = &cobra.Command{
|
||||
Use: "recover [flags]",
|
||||
Short: "Recover data from the repository",
|
||||
Long: `
|
||||
The "recover" command build a new snapshot from all directories it can find in
|
||||
the raw data of the repository. It can be used if, for example, a snapshot has
|
||||
been removed by accident with "forget".
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runRecover(globalOptions)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRoot.AddCommand(cmdRecover)
|
||||
}
|
||||
|
||||
func runRecover(gopts GlobalOptions) error {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo, err := OpenRepository(gopts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lock, err := lockRepo(repo)
|
||||
defer unlockRepo(lock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Verbosef("load index files\n")
|
||||
if err = repo.LoadIndex(gopts.ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// trees maps a tree ID to whether or not it is referenced by a different
|
||||
// tree. If it is not referenced, we have a root tree.
|
||||
trees := make(map[restic.ID]bool)
|
||||
|
||||
for blob := range repo.Index().Each(gopts.ctx) {
|
||||
if blob.Blob.Type != restic.TreeBlob {
|
||||
continue
|
||||
}
|
||||
trees[blob.Blob.ID] = false
|
||||
}
|
||||
|
||||
cur := 0
|
||||
max := len(trees)
|
||||
Verbosef("load %d trees\n\n", len(trees))
|
||||
|
||||
for id := range trees {
|
||||
cur++
|
||||
Verbosef("\rtree (%v/%v)", cur, max)
|
||||
|
||||
if !trees[id] {
|
||||
trees[id] = false
|
||||
}
|
||||
|
||||
tree, err := repo.LoadTree(gopts.ctx, id)
|
||||
if err != nil {
|
||||
Warnf("unable to load tree %v: %v\n", id.Str(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, node := range tree.Nodes {
|
||||
if node.Type != "dir" || node.Subtree == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
subtree := *node.Subtree
|
||||
trees[subtree] = true
|
||||
}
|
||||
}
|
||||
Verbosef("\ndone\n")
|
||||
|
||||
roots := restic.NewIDSet()
|
||||
for id, seen := range trees {
|
||||
if seen {
|
||||
continue
|
||||
}
|
||||
|
||||
roots.Insert(id)
|
||||
}
|
||||
|
||||
Verbosef("found %d roots\n", len(roots))
|
||||
|
||||
tree := restic.NewTree()
|
||||
for id := range roots {
|
||||
var subtreeID = id
|
||||
node := restic.Node{
|
||||
Type: "dir",
|
||||
Name: id.Str(),
|
||||
Mode: 0755,
|
||||
Subtree: &subtreeID,
|
||||
AccessTime: time.Now(),
|
||||
ModTime: time.Now(),
|
||||
ChangeTime: time.Now(),
|
||||
}
|
||||
tree.Insert(&node)
|
||||
}
|
||||
|
||||
treeID, err := repo.SaveTree(gopts.ctx, tree)
|
||||
if err != nil {
|
||||
return errors.Fatalf("unable to save new tree to the repo: %v", err)
|
||||
}
|
||||
|
||||
err = repo.Flush(gopts.ctx)
|
||||
if err != nil {
|
||||
return errors.Fatalf("unable to save blobs to the repo: %v", err)
|
||||
}
|
||||
|
||||
err = repo.SaveIndex(gopts.ctx)
|
||||
if err != nil {
|
||||
return errors.Fatalf("unable to save new index to the repo: %v", err)
|
||||
}
|
||||
|
||||
sn, err := restic.NewSnapshot([]string{"/recover"}, []string{}, hostname, time.Now())
|
||||
if err != nil {
|
||||
return errors.Fatalf("unable to save snapshot: %v", err)
|
||||
}
|
||||
|
||||
sn.Tree = &treeID
|
||||
|
||||
id, err := repo.SaveJSONUnpacked(gopts.ctx, restic.SnapshotFile, sn)
|
||||
if err != nil {
|
||||
return errors.Fatalf("unable to save snapshot: %v", err)
|
||||
}
|
||||
|
||||
Printf("saved new snapshot %v\n", id.Str())
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -113,8 +113,8 @@ func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
|
||||
}
|
||||
|
||||
totalErrors := 0
|
||||
res.Error = func(dir string, node *restic.Node, err error) error {
|
||||
Warnf("ignoring error for %s: %s\n", dir, err)
|
||||
res.Error = func(location string, err error) error {
|
||||
Warnf("ignoring error for %s: %s\n", location, err)
|
||||
totalErrors++
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build selfupdate
|
||||
// xbuild selfupdate
|
||||
|
||||
package main
|
||||
|
||||
@@ -14,7 +14,7 @@ var cmdSelfUpdate = &cobra.Command{
|
||||
Use: "self-update [flags]",
|
||||
Short: "Update the restic binary",
|
||||
Long: `
|
||||
The command "update-restic" downloads the latest stable release of restic from
|
||||
The command "self-update" downloads the latest stable release of restic from
|
||||
GitHub and replaces the currently running binary. After download, the
|
||||
authenticity of the binary is verified using the GPG signature on the release
|
||||
files.
|
||||
@@ -36,16 +36,38 @@ func init() {
|
||||
cmdRoot.AddCommand(cmdSelfUpdate)
|
||||
|
||||
flags := cmdSelfUpdate.Flags()
|
||||
flags.StringVar(&selfUpdateOptions.Output, "output", os.Args[0], "Save the downloaded file as `filename`")
|
||||
flags.StringVar(&selfUpdateOptions.Output, "output", "", "Save the downloaded file as `filename` (default: running binary itself)")
|
||||
}
|
||||
|
||||
func runSelfUpdate(opts SelfUpdateOptions, gopts GlobalOptions, args []string) error {
|
||||
v, err := selfupdate.DownloadLatestStableRelease(gopts.ctx, opts.Output, Verbosef)
|
||||
if opts.Output == "" {
|
||||
file, err := os.Executable()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to find executable")
|
||||
}
|
||||
|
||||
opts.Output = file
|
||||
}
|
||||
|
||||
fi, err := os.Lstat(opts.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !fi.Mode().IsRegular() {
|
||||
return errors.Errorf("output file %v is not a normal file, use --output to specify a different file", opts.Output)
|
||||
}
|
||||
|
||||
Printf("writing restic to %v\n", opts.Output)
|
||||
|
||||
v, err := selfupdate.DownloadLatestStableRelease(gopts.ctx, opts.Output, version, Verbosef)
|
||||
if err != nil {
|
||||
return errors.Fatalf("unable to update restic: %v", err)
|
||||
}
|
||||
|
||||
Printf("successfully updated restic to version %v\n", v)
|
||||
if v != version {
|
||||
Printf("successfully updated restic to version %v\n", v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ func PrintSnapshots(stdout io.Writer, list restic.Snapshots, reasons []restic.Ke
|
||||
for _, sn := range list {
|
||||
data := snapshot{
|
||||
ID: sn.ID().Str(),
|
||||
Timestamp: sn.Time.Format(TimeFormat),
|
||||
Timestamp: sn.Time.Local().Format(TimeFormat),
|
||||
Hostname: sn.Hostname,
|
||||
Tags: sn.Tags,
|
||||
Paths: sn.Paths,
|
||||
@@ -195,7 +195,7 @@ func PrintSnapshots(stdout io.Writer, list restic.Snapshots, reasons []restic.Ke
|
||||
data.Reasons = keepReasons[*id].Matches
|
||||
}
|
||||
|
||||
if len(sn.Paths) > 1 {
|
||||
if len(sn.Paths) > 1 && !compact {
|
||||
multiline = true
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ func init() {
|
||||
cmdRoot.AddCommand(cmdStats)
|
||||
f := cmdStats.Flags()
|
||||
f.StringVar(&countMode, "mode", countModeRestoreSize, "counting mode: restore-size (default), files-by-contents, blobs-per-file, or raw-data")
|
||||
f.StringVar(&snapshotByHost, "host", "H", "filter latest snapshot by this hostname")
|
||||
f.StringVarP(&snapshotByHost, "host", "H", "", "filter latest snapshot by this hostname")
|
||||
}
|
||||
|
||||
func runStats(gopts GlobalOptions, args []string) error {
|
||||
@@ -296,7 +296,7 @@ type statsContainer struct {
|
||||
// blobs that have been seen as a part of the file
|
||||
fileBlobs map[string]restic.IDSet
|
||||
|
||||
// blobs and blobsSeen are used to count indiviudal
|
||||
// blobs and blobsSeen are used to count individual
|
||||
// unique blobs, independent of references to files
|
||||
blobs, blobsSeen restic.BlobSet
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func formatBytes(c uint64) string {
|
||||
case c > 1<<10:
|
||||
return fmt.Sprintf("%.3f KiB", b/(1<<10))
|
||||
default:
|
||||
return fmt.Sprintf("%dB", c)
|
||||
return fmt.Sprintf("%d B", c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,6 @@ func formatNode(path string, n *restic.Node, long bool) string {
|
||||
|
||||
return fmt.Sprintf("%s %5d %5d %6d %s %s%s",
|
||||
mode|n.Mode, n.UID, n.GID, n.Size,
|
||||
n.ModTime.Format(TimeFormat), path,
|
||||
n.ModTime.Local().Format(TimeFormat), path,
|
||||
target)
|
||||
}
|
||||
|
||||
@@ -34,26 +34,29 @@ import (
|
||||
"github.com/restic/restic/internal/errors"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
var version = "0.9.3"
|
||||
var version = "0.9.4"
|
||||
|
||||
// TimeFormat is the format used for all timestamps printed by restic.
|
||||
const TimeFormat = "2006-01-02 15:04:05"
|
||||
|
||||
// GlobalOptions hold all global options for restic.
|
||||
type GlobalOptions struct {
|
||||
Repo string
|
||||
PasswordFile string
|
||||
Quiet bool
|
||||
Verbose int
|
||||
NoLock bool
|
||||
JSON bool
|
||||
CacheDir string
|
||||
NoCache bool
|
||||
CACerts []string
|
||||
TLSClientCert string
|
||||
CleanupCache bool
|
||||
Repo string
|
||||
PasswordFile string
|
||||
PasswordCommand string
|
||||
KeyHint string
|
||||
Quiet bool
|
||||
Verbose int
|
||||
NoLock bool
|
||||
JSON bool
|
||||
CacheDir string
|
||||
NoCache bool
|
||||
CACerts []string
|
||||
TLSClientCert string
|
||||
CleanupCache bool
|
||||
|
||||
LimitUploadKb int
|
||||
LimitDownloadKb int
|
||||
@@ -91,6 +94,8 @@ func init() {
|
||||
f := cmdRoot.PersistentFlags()
|
||||
f.StringVarP(&globalOptions.Repo, "repo", "r", os.Getenv("RESTIC_REPOSITORY"), "repository to backup to or restore from (default: $RESTIC_REPOSITORY)")
|
||||
f.StringVarP(&globalOptions.PasswordFile, "password-file", "p", os.Getenv("RESTIC_PASSWORD_FILE"), "read the repository password from a file (default: $RESTIC_PASSWORD_FILE)")
|
||||
f.StringVarP(&globalOptions.KeyHint, "key-hint", "", os.Getenv("RESTIC_KEY_HINT"), "key ID of key to try decrypting first (default: $RESTIC_KEY_HINT)")
|
||||
f.StringVarP(&globalOptions.PasswordCommand, "password-command", "", os.Getenv("RESTIC_PASSWORD_COMMAND"), "specify a shell command to obtain a password (default: $RESTIC_PASSWORD_COMMAND)")
|
||||
f.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "do not output comprehensive progress report")
|
||||
f.CountVarP(&globalOptions.Verbose, "verbose", "v", "be verbose (specify --verbose multiple times or level `n`)")
|
||||
f.BoolVar(&globalOptions.NoLock, "no-lock", false, "do not lock the repo, this allows some operations on read-only repos")
|
||||
@@ -179,7 +184,6 @@ func Printf(format string, args ...interface{}) {
|
||||
_, err := fmt.Fprintf(globalOptions.stdout, format, args...)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to write to stdout: %v\n", err)
|
||||
Exit(100)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +224,6 @@ func Warnf(format string, args ...interface{}) {
|
||||
_, err := fmt.Fprintf(globalOptions.stderr, format, args...)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to write to stderr: %v\n", err)
|
||||
Exit(100)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +239,23 @@ func Exitf(exitcode int, format string, args ...interface{}) {
|
||||
}
|
||||
|
||||
// resolvePassword determines the password to be used for opening the repository.
|
||||
func resolvePassword(opts GlobalOptions, env string) (string, error) {
|
||||
func resolvePassword(opts GlobalOptions) (string, error) {
|
||||
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
|
||||
}
|
||||
if opts.PasswordFile != "" {
|
||||
s, err := textfile.Read(opts.PasswordFile)
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
@@ -245,7 +264,7 @@ func resolvePassword(opts GlobalOptions, env string) (string, error) {
|
||||
return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile")
|
||||
}
|
||||
|
||||
if pwd := os.Getenv(env); pwd != "" {
|
||||
if pwd := os.Getenv("RESTIC_PASSWORD"); pwd != "" {
|
||||
return pwd, nil
|
||||
}
|
||||
|
||||
@@ -353,7 +372,7 @@ func OpenRepository(opts GlobalOptions) (*repository.Repository, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.SearchKey(opts.ctx, opts.password, maxKeys)
|
||||
err = s.SearchKey(opts.ctx, opts.password, maxKeys, opts.KeyHint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -363,7 +382,9 @@ func OpenRepository(opts GlobalOptions) (*repository.Repository, error) {
|
||||
if len(id) > 8 {
|
||||
id = id[:8]
|
||||
}
|
||||
Verbosef("repository %v opened successfully, password is correct\n", id)
|
||||
if !opts.JSON {
|
||||
Verbosef("repository %v opened successfully, password is correct\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.NoCache {
|
||||
|
||||
@@ -54,7 +54,7 @@ directories in an encrypted repository stored on different backends.
|
||||
if c.Name() == "version" {
|
||||
return nil
|
||||
}
|
||||
pwd, err := resolvePassword(globalOptions, "RESTIC_PASSWORD")
|
||||
pwd, err := resolvePassword(globalOptions)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Resolving password failed: %v\n", err)
|
||||
Exit(1)
|
||||
|
||||
@@ -30,12 +30,12 @@ command.
|
||||
Arch Linux
|
||||
==========
|
||||
|
||||
On `Arch Linux <https://www.archlinux.org/>`__, there is a package called ``restic-git``
|
||||
which can be installed from AUR, e.g. with ``pacaur``:
|
||||
On `Arch Linux <https://www.archlinux.org/>`__, there is a package called ``restic``
|
||||
installed from the official community repos, e.g. with ``pacman -S``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pacaur -S restic-git
|
||||
$ pacman -S restic
|
||||
|
||||
Debian
|
||||
======
|
||||
@@ -72,7 +72,7 @@ macOS
|
||||
=====
|
||||
|
||||
If you are using macOS, you can install restic using the
|
||||
`homebrew <http://brew.sh/>`__ package manager:
|
||||
`homebrew <https://brew.sh/>`__ package manager:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
|
||||
@@ -21,6 +21,19 @@ using a local repository; the remaining sections of this chapter cover all the
|
||||
other options. You can skip to the next chapter once you've read the relevant
|
||||
section here.
|
||||
|
||||
For automated backups, restic accepts the repository location in the
|
||||
environment variable ``RESTIC_REPOSITORY``. For the password, several options
|
||||
exist:
|
||||
|
||||
* Setting the environment variable ``RESTIC_PASSWORD``
|
||||
|
||||
* Specifying the path to a file with the password via the option
|
||||
``--password-file`` or the environment variable ``RESTIC_PASSWORD_FILE``
|
||||
|
||||
* Configuring a program to be called when the password is needed via the
|
||||
option ``--password-command`` or the environment variable
|
||||
``RESTIC_PASSWORD_COMMAND``
|
||||
|
||||
Local
|
||||
*****
|
||||
|
||||
@@ -41,11 +54,6 @@ command and enter the same password twice:
|
||||
Remembering your password is important! If you lose it, you won't be
|
||||
able to access data stored in the repository.
|
||||
|
||||
For automated backups, restic accepts the repository location in the
|
||||
environment variable ``RESTIC_REPOSITORY``. The password can be read
|
||||
from a file (via the option ``--password-file`` or the environment variable
|
||||
``RESTIC_PASSWORD_FILE``) or the environment variable ``RESTIC_PASSWORD``.
|
||||
|
||||
SFTP
|
||||
****
|
||||
|
||||
@@ -298,10 +306,10 @@ dashboard in on the "Buckets" page when signed into your B2 account:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ export B2_ACCOUNT_ID=<MY_ACCOUNT_ID>
|
||||
$ export B2_ACCOUNT_ID=<MY_APPLICATION_KEY_ID>
|
||||
$ export B2_ACCOUNT_KEY=<MY_SECRET_ACCOUNT_KEY>
|
||||
|
||||
.. note:: In case you want to use Backblaze Application Keys replace <MY_ACCOUNT_ID> and <MY_SECRET_ACCOUNT_KEY> with <applicationKeyId> and <applicationKey> respectively.
|
||||
.. note:: In case you want to use Backblaze Application Keys replace <MY_APPLICATION_KEY_ID> and <MY_SECRET_ACCOUNT_KEY> with <applicationKeyId> and <applicationKey> respectively.
|
||||
|
||||
You can then initialize a repository stored at Backblaze B2. If the
|
||||
bucket does not exist yet and the credentials you passed to restic have the
|
||||
|
||||
@@ -204,9 +204,12 @@ backup ``/sys`` or ``/dev`` on a Linux system:
|
||||
|
||||
$ restic -r /srv/restic-repo backup --one-file-system /
|
||||
|
||||
.. note:: ``--one-file-system`` is currently unsupported on Windows, and will
|
||||
cause the backup to immediately fail with an error.
|
||||
|
||||
By using the ``--files-from`` option you can read the files you want to
|
||||
backup from a file. This is especially useful if a lot of files have to
|
||||
be backed up that are not in the same folder or are maybe pre-filtered
|
||||
backup from one or more files. This is especially useful if a lot of files have
|
||||
to be backed up that are not in the same folder or are maybe pre-filtered
|
||||
by other software.
|
||||
|
||||
For example maybe you want to backup files which have a name that matches a
|
||||
|
||||
@@ -66,7 +66,7 @@ backup with the intention to make you restore malicious data:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ sudo echo "boom" >> backup/index/d795ffa99a8ab8f8e42cec1f814df4e48b8f49129360fb57613df93739faee97
|
||||
$ echo "boom" >> backup/index/d795ffa99a8ab8f8e42cec1f814df4e48b8f49129360fb57613df93739faee97
|
||||
|
||||
In order to detect these things, it is a good idea to regularly use the
|
||||
``check`` command to test whether everything is alright, your precious
|
||||
|
||||
@@ -168,8 +168,9 @@ The ``forget`` command accepts the following parameters:
|
||||
this option (can be specified multiple times).
|
||||
- ``--keep-within duration`` keep all snapshots which have been made within
|
||||
the duration of the latest snapshot. ``duration`` needs to be a number of
|
||||
years, months, and days, e.g. ``2y5m7d`` will keep all snapshots made in the
|
||||
two years, five months, and seven days before the latest snapshot.
|
||||
years, months, days, and hours, e.g. ``2y5m7d3h`` will keep all snapshots
|
||||
made in the two years, five months, seven days, and three hours before the
|
||||
latest snapshot.
|
||||
|
||||
Multiple policies will be ORed together so as to be as inclusive as possible
|
||||
for keeping snapshots.
|
||||
|
||||
@@ -85,7 +85,7 @@ back end is contained in `Design <https://restic.readthedocs.io/en/latest/design
|
||||
If you'd like to start contributing to restic, but don't know exactly
|
||||
what do to, have a look at this great article by Dave Cheney:
|
||||
`Suggestions for contributing to an Open Source
|
||||
project <http://dave.cheney.net/2016/03/12/suggestions-for-contributing-to-an-open-source-project>`__
|
||||
project <https://dave.cheney.net/2016/03/12/suggestions-for-contributing-to-an-open-source-project>`__
|
||||
A few issues have been tagged with the label ``help wanted``, you can
|
||||
start looking at those:
|
||||
https://github.com/restic/restic/labels/help%20wanted
|
||||
@@ -113,7 +113,7 @@ Compatibility
|
||||
|
||||
Backward compatibility for backups is important so that our users are
|
||||
always able to restore saved data. Therefore restic follows `Semantic
|
||||
Versioning <http://semver.org>`__ to clearly define which versions are
|
||||
Versioning <https://semver.org>`__ to clearly define which versions are
|
||||
compatible. The repository and data structures contained therein are
|
||||
considered the "Public API" in the sense of Semantic Versioning. This
|
||||
goes for all released versions of restic, this may not be the case for
|
||||
@@ -128,7 +128,7 @@ prior versions.
|
||||
Building documentation
|
||||
**********************
|
||||
|
||||
The restic documentation is built with `Sphinx <http://sphinx-doc.org>`__,
|
||||
The restic documentation is built with `Sphinx <https://www.sphinx-doc.org>`__,
|
||||
therefore building it locally requires a recent Python version and requirements listed in ``doc/requirements.txt``.
|
||||
This example will guide you through the process using `virtualenv <https://virtualenv.pypa.io>`__:
|
||||
|
||||
|
||||
@@ -275,6 +275,7 @@ _restic_backup()
|
||||
flags+=("-h")
|
||||
local_nonpersistent_flags+=("--help")
|
||||
flags+=("--host=")
|
||||
two_word_flags+=("-H")
|
||||
local_nonpersistent_flags+=("--host=")
|
||||
flags+=("--one-file-system")
|
||||
flags+=("-x")
|
||||
@@ -295,12 +296,14 @@ _restic_backup()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -343,12 +346,14 @@ _restic_cache()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -385,12 +390,14 @@ _restic_cat()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -435,12 +442,14 @@ _restic_check()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -479,12 +488,14 @@ _restic_diff()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -528,12 +539,14 @@ _restic_dump()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -600,12 +613,14 @@ _restic_find()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -681,12 +696,14 @@ _restic_forget()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -729,12 +746,14 @@ _restic_generate()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -771,12 +790,14 @@ _restic_init()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -815,12 +836,14 @@ _restic_key()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -857,12 +880,14 @@ _restic_list()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -911,12 +936,14 @@ _restic_ls()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -956,12 +983,14 @@ _restic_migrate()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -1001,6 +1030,8 @@ _restic_mount()
|
||||
flags+=("--host=")
|
||||
two_word_flags+=("-H")
|
||||
local_nonpersistent_flags+=("--host=")
|
||||
flags+=("--no-default-permissions")
|
||||
local_nonpersistent_flags+=("--no-default-permissions")
|
||||
flags+=("--owner-root")
|
||||
local_nonpersistent_flags+=("--owner-root")
|
||||
flags+=("--path=")
|
||||
@@ -1013,12 +1044,14 @@ _restic_mount()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -1055,12 +1088,14 @@ _restic_prune()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -1097,12 +1132,58 @@ _restic_rebuild-index()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
flags+=("-q")
|
||||
flags+=("--repo=")
|
||||
two_word_flags+=("-r")
|
||||
flags+=("--tls-client-cert=")
|
||||
flags+=("--verbose")
|
||||
flags+=("-v")
|
||||
|
||||
must_have_one_flag=()
|
||||
must_have_one_noun=()
|
||||
noun_aliases=()
|
||||
}
|
||||
|
||||
_restic_recover()
|
||||
{
|
||||
last_command="restic_recover"
|
||||
|
||||
command_aliases=()
|
||||
|
||||
commands=()
|
||||
|
||||
flags=()
|
||||
two_word_flags=()
|
||||
local_nonpersistent_flags=()
|
||||
flags_with_completion=()
|
||||
flags_completion=()
|
||||
|
||||
flags+=("--help")
|
||||
flags+=("-h")
|
||||
local_nonpersistent_flags+=("--help")
|
||||
flags+=("--cacert=")
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -1157,12 +1238,14 @@ _restic_restore()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -1201,12 +1284,14 @@ _restic_self-update()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -1255,12 +1340,14 @@ _restic_snapshots()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -1294,6 +1381,7 @@ _restic_stats()
|
||||
flags+=("-h")
|
||||
local_nonpersistent_flags+=("--help")
|
||||
flags+=("--host=")
|
||||
two_word_flags+=("-H")
|
||||
local_nonpersistent_flags+=("--host=")
|
||||
flags+=("--mode=")
|
||||
local_nonpersistent_flags+=("--mode=")
|
||||
@@ -1301,12 +1389,14 @@ _restic_stats()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -1356,12 +1446,14 @@ _restic_tag()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -1400,12 +1492,14 @@ _restic_unlock()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -1442,12 +1536,14 @@ _restic_version()
|
||||
flags+=("--cache-dir=")
|
||||
flags+=("--cleanup-cache")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
@@ -1487,6 +1583,7 @@ _restic_root_command()
|
||||
commands+=("mount")
|
||||
commands+=("prune")
|
||||
commands+=("rebuild-index")
|
||||
commands+=("recover")
|
||||
commands+=("restore")
|
||||
commands+=("self-update")
|
||||
commands+=("snapshots")
|
||||
@@ -1508,12 +1605,14 @@ _restic_root_command()
|
||||
flags+=("-h")
|
||||
local_nonpersistent_flags+=("--help")
|
||||
flags+=("--json")
|
||||
flags+=("--key-hint=")
|
||||
flags+=("--limit-download=")
|
||||
flags+=("--limit-upload=")
|
||||
flags+=("--no-cache")
|
||||
flags+=("--no-lock")
|
||||
flags+=("--option=")
|
||||
two_word_flags+=("-o")
|
||||
flags+=("--password-command=")
|
||||
flags+=("--password-file=")
|
||||
two_word_flags+=("-p")
|
||||
flags+=("--quiet")
|
||||
|
||||
@@ -9,7 +9,7 @@ Versions
|
||||
========
|
||||
|
||||
The cache directory is selected according to the `XDG base dir specification
|
||||
<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`__.
|
||||
<https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`__.
|
||||
Each repository has its own cache sub-directory, consisting of the repository ID
|
||||
which is chosen at ``init``. All cache directories for different repos are
|
||||
independent of each other.
|
||||
|
||||
@@ -276,7 +276,7 @@ the IV for counter mode and the nonce for Poly1305. This operation needs
|
||||
three keys: A 32 byte for AES-256 for encryption, a 16 byte AES key and
|
||||
a 16 byte key for Poly1305. For details see the original paper `The
|
||||
Poly1305-AES message-authentication
|
||||
code <http://cr.yp.to/mac/poly1305-20050329.pdf>`__ by Dan Bernstein.
|
||||
code <https://cr.yp.to/mac/poly1305-20050329.pdf>`__ by Dan Bernstein.
|
||||
The data is then encrypted with AES-256 and afterwards a message
|
||||
authentication code (MAC) is computed over the ciphertext, everything is
|
||||
then stored as IV \|\| CIPHERTEXT \|\| MAC.
|
||||
|
||||
@@ -27,7 +27,7 @@ strictly necessary. With high probability this is duplicate data. In
|
||||
order to clean it up, the command ``restic prune`` can be used. The
|
||||
cause of this bug is not yet known.
|
||||
|
||||
I ran a ``restic`` command but it is not working as intented, what do I do now?
|
||||
I ran a ``restic`` command but it is not working as intended, what do I do now?
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
If you are running a restic command and it is not working as you hoped it would,
|
||||
@@ -45,7 +45,7 @@ $ restic backup --exclude "~/documents" ~
|
||||
This command will result in a complete backup of the current logged in user's home directory and it won't exclude the folder ``~/documents/`` - which is not what the user wanted to achieve.
|
||||
The problem is how the path to ``~/documents`` is passed to restic.
|
||||
|
||||
In order to spot an issue like this, you can make use of the following ruby command preceeding your restic command.
|
||||
In order to spot an issue like this, you can make use of the following ruby command preceding your restic command.
|
||||
|
||||
::
|
||||
|
||||
@@ -71,7 +71,7 @@ Restic handles globbing and expansion in the following ways:
|
||||
- Globbing is only expanded for lines read via ``--files-from``
|
||||
- Environment variables are not expanded in the file read via ``--files-from``
|
||||
- ``*`` is expanded for paths read via ``--files-from``
|
||||
- E.g. For backup targets given to restic as arguments on the shell, neither glob expansion nor shell variable replacement is done. If restic is called as ``restic backup '*' '$HOME'``, it will try to backup the literal file(s)/dir(s) ``*`` and ``$HOME``
|
||||
- e.g. For backup targets given to restic as arguments on the shell, neither glob expansion nor shell variable replacement is done. If restic is called as ``restic backup '*' '$HOME'``, it will try to backup the literal file(s)/dir(s) ``*`` and ``$HOME``
|
||||
- Double-asterisk ``**`` only works in exclude patterns as this is a custom extension built into restic; the shell must not expand it
|
||||
|
||||
|
||||
@@ -172,4 +172,4 @@ The following may work:
|
||||
Why does restic perform so poorly on Windows?
|
||||
---------------------------------------------
|
||||
|
||||
In some cases the realtime protection of antivirus software can interfere with restic's operations. If you are experiencing bad performace you can try to temporarily disable your antivirus software to find out if it is the cause for your performance problems.
|
||||
In some cases the real-time protection of antivirus software can interfere with restic's operations. If you are experiencing bad performance you can try to temporarily disable your antivirus software to find out if it is the cause for your performance problems.
|
||||
|
||||
@@ -37,8 +37,8 @@ given as the arguments.
|
||||
takes filename[:header], exclude contents of directories containing filename (except filename itself) if header of that file is as provided (can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-files\-from\fP=""
|
||||
read the files to backup from file (can be combined with file args)
|
||||
\fB\-\-files\-from\fP=[]
|
||||
read the files to backup from file (can be combined with file args/can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-f\fP, \fB\-\-force\fP[=false]
|
||||
@@ -49,7 +49,7 @@ given as the arguments.
|
||||
help for backup
|
||||
|
||||
.PP
|
||||
\fB\-\-host\fP="H"
|
||||
\fB\-H\fP, \fB\-\-host\fP=""
|
||||
set the \fB\fChostname\fR for the snapshot manually. To prevent an expensive rescan use the "parent" flag
|
||||
|
||||
.PP
|
||||
@@ -98,6 +98,10 @@ given as the arguments.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -118,6 +122,10 @@ given as the arguments.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -53,6 +53,10 @@ The "cache" command allows listing and cleaning local cache directories.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -73,6 +77,10 @@ The "cache" command allows listing and cleaning local cache directories.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -41,6 +41,10 @@ The "cat" command is used to print internal objects to stdout.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -61,6 +65,10 @@ The "cat" command is used to print internal objects to stdout.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -62,6 +62,10 @@ repository and not use a local cache.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -82,6 +86,10 @@ repository and not use a local cache.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -61,6 +61,10 @@ T The type was changed, e.g. a file was made a symlink
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -81,6 +85,10 @@ T The type was changed, e.g. a file was made a symlink
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -58,6 +58,10 @@ repository.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -78,6 +82,10 @@ repository.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -91,6 +91,10 @@ It can also be used to search for restic blobs or trees for troubleshooting.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -111,6 +115,10 @@ It can also be used to search for restic blobs or trees for troubleshooting.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -48,7 +48,7 @@ data after 'forget' was run successfully, see the 'prune' command.
|
||||
|
||||
.PP
|
||||
\fB\-\-keep\-within\fP=
|
||||
keep snapshots that are older than \fB\fCduration\fR (eg. 1y5m7d) relative to the latest snapshot
|
||||
keep snapshots that are newer than \fB\fCduration\fR (eg. 1y5m7d2h) relative to the latest snapshot
|
||||
|
||||
.PP
|
||||
\fB\-\-keep\-tag\fP=[]
|
||||
@@ -104,6 +104,10 @@ data after 'forget' was run successfully, see the 'prune' command.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -124,6 +128,10 @@ data after 'forget' was run successfully, see the 'prune' command.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -54,6 +54,10 @@ and the auto\-completion files for bash and zsh).
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -74,6 +78,10 @@ and the auto\-completion files for bash and zsh).
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -41,6 +41,10 @@ The "init" command initializes a new repository.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -61,6 +65,10 @@ The "init" command initializes a new repository.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -45,6 +45,10 @@ The "key" command manages keys (passwords) for accessing the repository.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -65,6 +69,10 @@ The "key" command manages keys (passwords) for accessing the repository.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -41,6 +41,10 @@ The "list" command allows listing objects in the repository based on type.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -61,6 +65,10 @@ The "list" command allows listing objects in the repository based on type.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -76,6 +76,10 @@ a path separator); paths use the forward slash '/' as separator.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -96,6 +100,10 @@ a path separator); paths use the forward slash '/' as separator.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -46,6 +46,10 @@ name is explicitly given, a list of migrations that can be applied is printed.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -66,6 +70,10 @@ name is explicitly given, a list of migrations that can be applied is printed.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -68,6 +68,10 @@ For details please see the documentation for time.Format() at:
|
||||
\fB\-H\fP, \fB\-\-host\fP=""
|
||||
only consider snapshots for this host
|
||||
|
||||
.PP
|
||||
\fB\-\-no\-default\-permissions\fP[=false]
|
||||
for 'allow\-other', ignore Unix permissions and allow users to read all snapshot files
|
||||
|
||||
.PP
|
||||
\fB\-\-owner\-root\fP[=false]
|
||||
use 'root' as the owner of files and dirs
|
||||
@@ -102,6 +106,10 @@ For details please see the documentation for time.Format() at:
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -122,6 +130,10 @@ For details please see the documentation for time.Format() at:
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -42,6 +42,10 @@ referenced and therefore not needed any more.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -62,6 +66,10 @@ referenced and therefore not needed any more.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -42,6 +42,10 @@ repository.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -62,6 +66,10 @@ repository.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
97
doc/man/restic-recover.1
Normal file
97
doc/man/restic-recover.1
Normal file
@@ -0,0 +1,97 @@
|
||||
.TH "restic backup" "1" "Jan 2017" "generated by `restic generate`" ""
|
||||
.nh
|
||||
.ad l
|
||||
|
||||
|
||||
.SH NAME
|
||||
.PP
|
||||
restic\-recover \- Recover data from the repository
|
||||
|
||||
|
||||
.SH SYNOPSIS
|
||||
.PP
|
||||
\fBrestic recover [flags]\fP
|
||||
|
||||
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The "recover" command build a new snapshot from all directories it can find in
|
||||
the raw data of the repository. It can be used if, for example, a snapshot has
|
||||
been removed by accident with "forget".
|
||||
|
||||
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\fB\-h\fP, \fB\-\-help\fP[=false]
|
||||
help for recover
|
||||
|
||||
|
||||
.SH OPTIONS INHERITED FROM PARENT COMMANDS
|
||||
.PP
|
||||
\fB\-\-cacert\fP=[]
|
||||
\fB\fCfile\fR to load root certificates from (default: use system certificates)
|
||||
|
||||
.PP
|
||||
\fB\-\-cache\-dir\fP=""
|
||||
set the cache directory. (default: use system default cache directory)
|
||||
|
||||
.PP
|
||||
\fB\-\-cleanup\-cache\fP[=false]
|
||||
auto remove old cache directories
|
||||
|
||||
.PP
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-upload\fP=0
|
||||
limits uploads to a maximum rate in KiB/s. (default: unlimited)
|
||||
|
||||
.PP
|
||||
\fB\-\-no\-cache\fP[=false]
|
||||
do not use a local cache
|
||||
|
||||
.PP
|
||||
\fB\-\-no\-lock\fP[=false]
|
||||
do not lock the repo, this allows some operations on read\-only repos
|
||||
|
||||
.PP
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
.PP
|
||||
\fB\-q\fP, \fB\-\-quiet\fP[=false]
|
||||
do not output comprehensive progress report
|
||||
|
||||
.PP
|
||||
\fB\-r\fP, \fB\-\-repo\fP=""
|
||||
repository to backup to or restore from (default: $RESTIC\_REPOSITORY)
|
||||
|
||||
.PP
|
||||
\fB\-\-tls\-client\-cert\fP=""
|
||||
path to a file containing PEM encoded TLS client certificate and private key
|
||||
|
||||
.PP
|
||||
\fB\-v\fP, \fB\-\-verbose\fP[=0]
|
||||
be verbose (specify \-\-verbose multiple times or level \fB\fCn\fR)
|
||||
|
||||
|
||||
.SH SEE ALSO
|
||||
.PP
|
||||
\fBrestic(1)\fP
|
||||
@@ -74,6 +74,10 @@ repository.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -94,6 +98,10 @@ repository.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -15,7 +15,7 @@ restic\-self\-update \- Update the restic binary
|
||||
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The command "update\-restic" downloads the latest stable release of restic from
|
||||
The command "self\-update" downloads the latest stable release of restic from
|
||||
GitHub and replaces the currently running binary. After download, the
|
||||
authenticity of the binary is verified using the GPG signature on the release
|
||||
files.
|
||||
@@ -27,8 +27,8 @@ files.
|
||||
help for self\-update
|
||||
|
||||
.PP
|
||||
\fB\-\-output\fP="./restic\-generate.temp"
|
||||
Save the downloaded file as \fB\fCfilename\fR
|
||||
\fB\-\-output\fP=""
|
||||
Save the downloaded file as \fB\fCfilename\fR (default: running binary itself)
|
||||
|
||||
|
||||
.SH OPTIONS INHERITED FROM PARENT COMMANDS
|
||||
@@ -48,6 +48,10 @@ files.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -68,6 +72,10 @@ files.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -61,6 +61,10 @@ The "snapshots" command lists all snapshots stored in the repository.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -81,6 +85,10 @@ The "snapshots" command lists all snapshots stored in the repository.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -52,7 +52,7 @@ Refer to the online manual for more details about each mode.
|
||||
help for stats
|
||||
|
||||
.PP
|
||||
\fB\-\-host\fP="H"
|
||||
\fB\-H\fP, \fB\-\-host\fP=""
|
||||
filter latest snapshot by this hostname
|
||||
|
||||
.PP
|
||||
@@ -77,6 +77,10 @@ Refer to the online manual for more details about each mode.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -97,6 +101,10 @@ Refer to the online manual for more details about each mode.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -72,6 +72,10 @@ When no snapshot\-ID is given, all snapshots matching the host, tag and path fil
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -92,6 +96,10 @@ When no snapshot\-ID is given, all snapshots matching the host, tag and path fil
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -45,6 +45,10 @@ The "unlock" command removes stale locks that have been created by other restic
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -65,6 +69,10 @@ The "unlock" command removes stale locks that have been created by other restic
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -42,6 +42,10 @@ and the version of this software.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -62,6 +66,10 @@ and the version of this software.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
|
||||
@@ -40,6 +40,10 @@ directories in an encrypted repository stored on different backends.
|
||||
\fB\-\-json\fP[=false]
|
||||
set output mode to JSON for commands that support it
|
||||
|
||||
.PP
|
||||
\fB\-\-key\-hint\fP=""
|
||||
key ID of key to try decrypting first (default: $RESTIC\_KEY\_HINT)
|
||||
|
||||
.PP
|
||||
\fB\-\-limit\-download\fP=0
|
||||
limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
@@ -60,6 +64,10 @@ directories in an encrypted repository stored on different backends.
|
||||
\fB\-o\fP, \fB\-\-option\fP=[]
|
||||
set extended option (\fB\fCkey=value\fR, can be specified multiple times)
|
||||
|
||||
.PP
|
||||
\fB\-\-password\-command\fP=""
|
||||
specify a shell command to obtain a password (default: $RESTIC\_PASSWORD\_COMMAND)
|
||||
|
||||
.PP
|
||||
\fB\-p\fP, \fB\-\-password\-file\fP=""
|
||||
read the repository password from a file (default: $RESTIC\_PASSWORD\_FILE)
|
||||
@@ -83,4 +91,4 @@ directories in an encrypted repository stored on different backends.
|
||||
|
||||
.SH SEE ALSO
|
||||
.PP
|
||||
\fBrestic\-backup(1)\fP, \fBrestic\-cache(1)\fP, \fBrestic\-cat(1)\fP, \fBrestic\-check(1)\fP, \fBrestic\-diff(1)\fP, \fBrestic\-dump(1)\fP, \fBrestic\-find(1)\fP, \fBrestic\-forget(1)\fP, \fBrestic\-generate(1)\fP, \fBrestic\-init(1)\fP, \fBrestic\-key(1)\fP, \fBrestic\-list(1)\fP, \fBrestic\-ls(1)\fP, \fBrestic\-migrate(1)\fP, \fBrestic\-mount(1)\fP, \fBrestic\-prune(1)\fP, \fBrestic\-rebuild\-index(1)\fP, \fBrestic\-restore(1)\fP, \fBrestic\-self\-update(1)\fP, \fBrestic\-snapshots(1)\fP, \fBrestic\-stats(1)\fP, \fBrestic\-tag(1)\fP, \fBrestic\-unlock(1)\fP, \fBrestic\-version(1)\fP
|
||||
\fBrestic\-backup(1)\fP, \fBrestic\-cache(1)\fP, \fBrestic\-cat(1)\fP, \fBrestic\-check(1)\fP, \fBrestic\-diff(1)\fP, \fBrestic\-dump(1)\fP, \fBrestic\-find(1)\fP, \fBrestic\-forget(1)\fP, \fBrestic\-generate(1)\fP, \fBrestic\-init(1)\fP, \fBrestic\-key(1)\fP, \fBrestic\-list(1)\fP, \fBrestic\-ls(1)\fP, \fBrestic\-migrate(1)\fP, \fBrestic\-mount(1)\fP, \fBrestic\-prune(1)\fP, \fBrestic\-rebuild\-index(1)\fP, \fBrestic\-recover(1)\fP, \fBrestic\-restore(1)\fP, \fBrestic\-self\-update(1)\fP, \fBrestic\-snapshots(1)\fP, \fBrestic\-stats(1)\fP, \fBrestic\-tag(1)\fP, \fBrestic\-unlock(1)\fP, \fBrestic\-version(1)\fP
|
||||
|
||||
@@ -47,6 +47,7 @@ Usage help is available:
|
||||
--cleanup-cache auto remove old cache directories
|
||||
-h, --help help for restic
|
||||
--json set output mode to JSON for commands that support it
|
||||
--key-hint string key ID of key to try decrypting first (default: $RESTIC_KEY_HINT)
|
||||
--limit-download int limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
--limit-upload int limits uploads to a maximum rate in KiB/s. (default: unlimited)
|
||||
--no-cache do not use a local cache
|
||||
@@ -80,7 +81,7 @@ command:
|
||||
--exclude-caches excludes cache directories that are marked with a CACHEDIR.TAG file
|
||||
--exclude-file file read exclude patterns from a file (can be specified multiple times)
|
||||
--exclude-if-present stringArray takes filename[:header], exclude contents of directories containing filename (except filename itself) if header of that file is as provided (can be specified multiple times)
|
||||
--files-from string read the files to backup from file (can be combined with file args)
|
||||
--files-from string read the files to backup from file (can be combined with file args/can be specified multiple times)
|
||||
-f, --force force re-reading the target files/directories (overrides the "parent" flag)
|
||||
-h, --help help for backup
|
||||
--hostname hostname set the hostname for the snapshot manually. To prevent an expensive rescan use the "parent" flag
|
||||
@@ -97,6 +98,7 @@ command:
|
||||
--cache-dir string set the cache directory. (default: use system default cache directory)
|
||||
--cleanup-cache auto remove old cache directories
|
||||
--json set output mode to JSON for commands that support it
|
||||
--key-hint string key ID of key to try decrypting first (default: $RESTIC_KEY_HINT)
|
||||
--limit-download int limits downloads to a maximum rate in KiB/s. (default: unlimited)
|
||||
--limit-upload int limits uploads to a maximum rate in KiB/s. (default: unlimited)
|
||||
--no-cache do not use a local cache
|
||||
@@ -179,7 +181,7 @@ locks with the following command:
|
||||
d369ccc7d126594950bf74f0a348d5d98d9e99f3215082eb69bf02dc9b3e464c
|
||||
|
||||
The ``find`` command searches for a given
|
||||
`pattern <http://golang.org/pkg/path/filepath/#Match>`__ in the
|
||||
`pattern <https://golang.org/pkg/path/filepath/#Match>`__ in the
|
||||
repository.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
@@ -7,7 +7,7 @@ case $state in
|
||||
level1)
|
||||
case $words[1] in
|
||||
restic)
|
||||
_arguments '1: :(backup cache cat check diff dump find forget generate help init key list ls migrate mount options prune rebuild-index restore self-update snapshots stats tag unlock version)'
|
||||
_arguments '1: :(backup cache cat check diff dump find forget generate help init key list ls migrate mount options prune rebuild-index recover restore self-update snapshots stats tag unlock version)'
|
||||
;;
|
||||
*)
|
||||
_arguments '*: :_files'
|
||||
|
||||
@@ -2,6 +2,6 @@ FROM alpine:latest
|
||||
|
||||
COPY restic /usr/bin
|
||||
|
||||
RUN apk add --update --no-cache ca-certificates fuse
|
||||
RUN apk add --update --no-cache ca-certificates fuse openssh-client
|
||||
|
||||
ENTRYPOINT ["/usr/bin/restic"]
|
||||
|
||||
1
go.mod
1
go.mod
@@ -15,6 +15,7 @@ require (
|
||||
github.com/golang/protobuf v1.2.0 // indirect
|
||||
github.com/google/go-cmp v0.2.0
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.0
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/juju/ratelimit v1.0.1
|
||||
|
||||
2
go.sum
2
go.sum
@@ -26,6 +26,8 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
|
||||
@@ -398,6 +398,7 @@ func updateDocker(outputDir, version string) {
|
||||
cmd := fmt.Sprintf("bzcat %s/restic_%s_linux_amd64.bz2 > restic", outputDir, version)
|
||||
run("sh", "-c", cmd)
|
||||
run("chmod", "+x", "restic")
|
||||
run("docker", "pull", "alpine:latest")
|
||||
run("docker", "build", "--rm", "--tag", "restic/restic:latest", "-f", "docker/Dockerfile", ".")
|
||||
run("docker", "tag", "restic/restic:latest", "restic/restic:"+version)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/api/googleapi"
|
||||
storage "google.golang.org/api/storage/v1"
|
||||
@@ -40,8 +41,17 @@ type Backend struct {
|
||||
// Ensure that *Backend implements restic.Backend.
|
||||
var _ restic.Backend = &Backend{}
|
||||
|
||||
func getStorageService() (*storage.Service, error) {
|
||||
client, err := google.DefaultClient(context.TODO(), storage.DevstorageReadWriteScope)
|
||||
func getStorageService(rt http.RoundTripper) (*storage.Service, error) {
|
||||
// create a new HTTP client
|
||||
httpClient := &http.Client{
|
||||
Transport: rt,
|
||||
}
|
||||
|
||||
// create a now context with the HTTP client stored at the oauth2.HTTPClient key
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
|
||||
|
||||
// use this context
|
||||
client, err := google.DefaultClient(ctx, storage.DevstorageReadWriteScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -59,7 +69,7 @@ const defaultListMaxItems = 1000
|
||||
func open(cfg Config, rt http.RoundTripper) (*Backend, error) {
|
||||
debug.Log("open, config %#v", cfg)
|
||||
|
||||
service, err := getStorageService()
|
||||
service, err := getStorageService(rt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getStorageService")
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ func New(cfg Config, lim limiter.Limiter) (*Backend, error) {
|
||||
|
||||
// send an HTTP request to the base URL, see if the server is there
|
||||
client := &http.Client{
|
||||
Transport: tr,
|
||||
Transport: debug.RoundTripper(tr),
|
||||
Timeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ func Open(cfg Config, lim limiter.Limiter) (*Backend, error) {
|
||||
URL: url,
|
||||
}
|
||||
|
||||
restBackend, err := rest.Open(restConfig, be.tr)
|
||||
restBackend, err := rest.Open(restConfig, debug.RoundTripper(be.tr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -283,7 +283,7 @@ func Create(cfg Config) (*Backend, error) {
|
||||
URL: url,
|
||||
}
|
||||
|
||||
restBackend, err := rest.Create(restConfig, be.tr)
|
||||
restBackend, err := rest.Create(restConfig, debug.RoundTripper(be.tr))
|
||||
if err != nil {
|
||||
_ = be.Close()
|
||||
return nil, err
|
||||
|
||||
2
internal/cache/dir.go
vendored
2
internal/cache/dir.go
vendored
@@ -32,7 +32,7 @@ func xdgCacheDir() (string, error) {
|
||||
func windowsCacheDir() (string, error) {
|
||||
appdata := os.Getenv("LOCALAPPDATA")
|
||||
if appdata == "" {
|
||||
return "", errors.New("unable to locate cache directory (APPDATA unset)")
|
||||
return "", errors.New("unable to locate cache directory (LOCALAPPDATA unset)")
|
||||
}
|
||||
return filepath.Join(appdata, "restic"), nil
|
||||
}
|
||||
|
||||
3
internal/cache/file.go
vendored
3
internal/cache/file.go
vendored
@@ -127,7 +127,8 @@ func (c *Cache) Save(h restic.Handle, rd io.Reader) error {
|
||||
if n <= crypto.Extension {
|
||||
_ = f.Close()
|
||||
_ = c.Remove(h)
|
||||
return errors.Errorf("trying to cache truncated file %v", h)
|
||||
debug.Log("trying to cache truncated file %v, removing", h)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = f.Close(); err != nil {
|
||||
|
||||
@@ -7,13 +7,12 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/pack"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// Checker runs various checks on a repository. It is advisable to create an
|
||||
@@ -652,7 +651,7 @@ func checkPack(ctx context.Context, r restic.Repository, id restic.ID) error {
|
||||
debug.Log("checking pack %v", id)
|
||||
h := restic.Handle{Type: restic.DataFile, Name: id.String()}
|
||||
|
||||
packfile, hash, size, err := repository.DownloadAndHash(ctx, r, h)
|
||||
packfile, hash, size, err := repository.DownloadAndHash(ctx, r.Backend(), h)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "checkPack")
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ func TestCheckerModifiedData(t *testing.T) {
|
||||
|
||||
beError := &errorBackend{Backend: repo.Backend()}
|
||||
checkRepo := repository.New(beError)
|
||||
test.OK(t, checkRepo.SearchKey(context.TODO(), test.TestPassword, 5))
|
||||
test.OK(t, checkRepo.SearchKey(context.TODO(), test.TestPassword, 5, ""))
|
||||
|
||||
chkr := checker.New(checkRepo)
|
||||
|
||||
|
||||
@@ -5,14 +5,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/list"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/pack"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/worker"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// Pack contains information about the contents of a pack.
|
||||
@@ -35,38 +34,118 @@ func newIndex() *Index {
|
||||
}
|
||||
}
|
||||
|
||||
const listPackWorkers = 10
|
||||
|
||||
// Lister lists files and their contents
|
||||
type Lister interface {
|
||||
// List runs fn for all files of type t in the repo.
|
||||
List(ctx context.Context, t restic.FileType, fn func(restic.ID, int64) error) error
|
||||
|
||||
// ListPack returns the list of blobs saved in the pack id and the length
|
||||
// of the file as stored in the backend.
|
||||
ListPack(ctx context.Context, id restic.ID, size int64) ([]restic.Blob, int64, error)
|
||||
}
|
||||
|
||||
// New creates a new index for repo from scratch. InvalidFiles contains all IDs
|
||||
// of files that cannot be listed successfully.
|
||||
func New(ctx context.Context, repo restic.Repository, ignorePacks restic.IDSet, p *restic.Progress) (idx *Index, invalidFiles restic.IDs, err error) {
|
||||
func New(ctx context.Context, repo Lister, ignorePacks restic.IDSet, p *restic.Progress) (idx *Index, invalidFiles restic.IDs, err error) {
|
||||
p.Start()
|
||||
defer p.Done()
|
||||
|
||||
ch := make(chan worker.Job)
|
||||
go list.AllPacks(ctx, repo, ignorePacks, ch)
|
||||
type Job struct {
|
||||
PackID restic.ID
|
||||
Size int64
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Error error
|
||||
PackID restic.ID
|
||||
Size int64
|
||||
Entries []restic.Blob
|
||||
}
|
||||
|
||||
inputCh := make(chan Job)
|
||||
outputCh := make(chan Result)
|
||||
wg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
// list the files in the repo, send to inputCh
|
||||
wg.Go(func() error {
|
||||
defer close(inputCh)
|
||||
return repo.List(ctx, restic.DataFile, func(id restic.ID, size int64) error {
|
||||
if ignorePacks.Has(id) {
|
||||
return nil
|
||||
}
|
||||
|
||||
job := Job{
|
||||
PackID: id,
|
||||
Size: size,
|
||||
}
|
||||
|
||||
select {
|
||||
case inputCh <- job:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
// run the workers listing the files, read from inputCh, send to outputCh
|
||||
var workers sync.WaitGroup
|
||||
for i := 0; i < listPackWorkers; i++ {
|
||||
workers.Add(1)
|
||||
go func() {
|
||||
defer workers.Done()
|
||||
for job := range inputCh {
|
||||
res := Result{PackID: job.PackID}
|
||||
res.Entries, res.Size, res.Error = repo.ListPack(ctx, job.PackID, job.Size)
|
||||
|
||||
select {
|
||||
case outputCh <- res:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// wait until all the workers are done, then close outputCh
|
||||
wg.Go(func() error {
|
||||
workers.Wait()
|
||||
close(outputCh)
|
||||
return nil
|
||||
})
|
||||
|
||||
idx = newIndex()
|
||||
|
||||
for job := range ch {
|
||||
for res := range outputCh {
|
||||
p.Report(restic.Stat{Blobs: 1})
|
||||
|
||||
j := job.Result.(list.Result)
|
||||
if job.Error != nil {
|
||||
cause := errors.Cause(job.Error)
|
||||
if res.Error != nil {
|
||||
cause := errors.Cause(res.Error)
|
||||
if _, ok := cause.(pack.InvalidFileError); ok {
|
||||
invalidFiles = append(invalidFiles, j.PackID())
|
||||
invalidFiles = append(invalidFiles, res.PackID)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "pack file cannot be listed %v: %v\n", j.PackID(), job.Error)
|
||||
fmt.Fprintf(os.Stderr, "pack file cannot be listed %v: %v\n", res.PackID, res.Error)
|
||||
continue
|
||||
}
|
||||
|
||||
debug.Log("pack %v contains %d blobs", j.PackID(), len(j.Entries()))
|
||||
debug.Log("pack %v contains %d blobs", res.PackID, len(res.Entries))
|
||||
|
||||
err := idx.AddPack(j.PackID(), j.Size(), j.Entries())
|
||||
err := idx.AddPack(res.PackID, res.Size, res.Entries)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done(): // an error occurred
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
err = wg.Wait()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return idx, invalidFiles, nil
|
||||
@@ -89,7 +168,13 @@ type indexJSON struct {
|
||||
Packs []packJSON `json:"packs"`
|
||||
}
|
||||
|
||||
func loadIndexJSON(ctx context.Context, repo restic.Repository, id restic.ID) (*indexJSON, error) {
|
||||
// ListLoader allows listing files and their content, in addition to loading and unmarshaling JSON files.
|
||||
type ListLoader interface {
|
||||
Lister
|
||||
LoadJSONUnpacked(context.Context, restic.FileType, restic.ID, interface{}) error
|
||||
}
|
||||
|
||||
func loadIndexJSON(ctx context.Context, repo ListLoader, id restic.ID) (*indexJSON, error) {
|
||||
debug.Log("process index %v\n", id)
|
||||
|
||||
var idx indexJSON
|
||||
@@ -102,7 +187,7 @@ func loadIndexJSON(ctx context.Context, repo restic.Repository, id restic.ID) (*
|
||||
}
|
||||
|
||||
// Load creates an index by loading all index files from the repo.
|
||||
func Load(ctx context.Context, repo restic.Repository, p *restic.Progress) (*Index, error) {
|
||||
func Load(ctx context.Context, repo ListLoader, p *restic.Progress) (*Index, error) {
|
||||
debug.Log("loading indexes")
|
||||
|
||||
p.Start()
|
||||
@@ -259,8 +344,13 @@ func (idx *Index) FindBlob(h restic.BlobHandle) (result []Location, err error) {
|
||||
|
||||
const maxEntries = 3000
|
||||
|
||||
// Saver saves structures as JSON.
|
||||
type Saver interface {
|
||||
SaveJSONUnpacked(ctx context.Context, t restic.FileType, item interface{}) (restic.ID, error)
|
||||
}
|
||||
|
||||
// Save writes the complete index to the repo.
|
||||
func (idx *Index) Save(ctx context.Context, repo restic.Repository, supersedes restic.IDs) (restic.IDs, error) {
|
||||
func (idx *Index) Save(ctx context.Context, repo Saver, supersedes restic.IDs) (restic.IDs, error) {
|
||||
debug.Log("pack files: %d\n", len(idx.Packs))
|
||||
|
||||
var indexIDs []restic.ID
|
||||
|
||||
@@ -2,10 +2,12 @@ package index
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/checker"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/test"
|
||||
@@ -48,7 +50,7 @@ func TestIndexNew(t *testing.T) {
|
||||
repo, cleanup := createFilledRepo(t, 3, 0)
|
||||
defer cleanup()
|
||||
|
||||
idx, _, err := New(context.TODO(), repo, restic.NewIDSet(), nil)
|
||||
idx, invalid, err := New(context.TODO(), repo, restic.NewIDSet(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("New() returned error %v", err)
|
||||
}
|
||||
@@ -57,9 +59,102 @@ func TestIndexNew(t *testing.T) {
|
||||
t.Fatalf("New() returned nil index")
|
||||
}
|
||||
|
||||
if len(invalid) > 0 {
|
||||
t.Fatalf("New() returned invalid files: %v", invalid)
|
||||
}
|
||||
|
||||
validateIndex(t, repo, idx)
|
||||
}
|
||||
|
||||
type ErrorRepo struct {
|
||||
restic.Repository
|
||||
MaxListFiles int
|
||||
|
||||
MaxPacks int
|
||||
MaxPacksMutex sync.Mutex
|
||||
}
|
||||
|
||||
// List returns an error after repo.MaxListFiles files.
|
||||
func (repo *ErrorRepo) List(ctx context.Context, t restic.FileType, fn func(restic.ID, int64) error) error {
|
||||
if repo.MaxListFiles == 0 {
|
||||
return errors.New("test error, max is zero")
|
||||
}
|
||||
|
||||
max := repo.MaxListFiles
|
||||
return repo.Repository.List(ctx, t, func(id restic.ID, size int64) error {
|
||||
if max == 0 {
|
||||
return errors.New("test error, max reached zero")
|
||||
}
|
||||
|
||||
max--
|
||||
return fn(id, size)
|
||||
})
|
||||
}
|
||||
|
||||
// ListPack returns an error after repo.MaxPacks files.
|
||||
func (repo *ErrorRepo) ListPack(ctx context.Context, id restic.ID, size int64) ([]restic.Blob, int64, error) {
|
||||
repo.MaxPacksMutex.Lock()
|
||||
max := repo.MaxPacks
|
||||
if max > 0 {
|
||||
repo.MaxPacks--
|
||||
}
|
||||
repo.MaxPacksMutex.Unlock()
|
||||
|
||||
if max == 0 {
|
||||
return nil, 0, errors.New("test list pack error")
|
||||
}
|
||||
|
||||
return repo.Repository.ListPack(ctx, id, size)
|
||||
}
|
||||
|
||||
func TestIndexNewListErrors(t *testing.T) {
|
||||
repo, cleanup := createFilledRepo(t, 3, 0)
|
||||
defer cleanup()
|
||||
|
||||
for _, max := range []int{0, 3, 5} {
|
||||
errRepo := &ErrorRepo{
|
||||
Repository: repo,
|
||||
MaxListFiles: max,
|
||||
}
|
||||
idx, invalid, err := New(context.TODO(), errRepo, restic.NewIDSet(), nil)
|
||||
if err == nil {
|
||||
t.Errorf("expected error not found, got nil")
|
||||
}
|
||||
|
||||
if idx != nil {
|
||||
t.Errorf("expected nil index, got %v", idx)
|
||||
}
|
||||
|
||||
if len(invalid) != 0 {
|
||||
t.Errorf("expected empty invalid list, got %v", invalid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexNewPackErrors(t *testing.T) {
|
||||
repo, cleanup := createFilledRepo(t, 3, 0)
|
||||
defer cleanup()
|
||||
|
||||
for _, max := range []int{0, 3, 5} {
|
||||
errRepo := &ErrorRepo{
|
||||
Repository: repo,
|
||||
MaxPacks: max,
|
||||
}
|
||||
idx, invalid, err := New(context.TODO(), errRepo, restic.NewIDSet(), nil)
|
||||
if err == nil {
|
||||
t.Errorf("expected error not found, got nil")
|
||||
}
|
||||
|
||||
if idx != nil {
|
||||
t.Errorf("expected nil index, got %v", idx)
|
||||
}
|
||||
|
||||
if len(invalid) != 0 {
|
||||
t.Errorf("expected empty invalid list, got %v", invalid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexLoad(t *testing.T) {
|
||||
repo, cleanup := createFilledRepo(t, 3, 0)
|
||||
defer cleanup()
|
||||
@@ -186,7 +281,7 @@ func BenchmarkIndexSave(b *testing.B) {
|
||||
}
|
||||
|
||||
func TestIndexDuplicateBlobs(t *testing.T) {
|
||||
repo, cleanup := createFilledRepo(t, 3, 0.01)
|
||||
repo, cleanup := createFilledRepo(t, 3, 0.05)
|
||||
defer cleanup()
|
||||
|
||||
idx, _, err := New(context.TODO(), repo, restic.NewIDSet(), nil)
|
||||
@@ -252,6 +347,7 @@ func TestIndexSave(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
|
||||
errCh := make(chan error)
|
||||
go checker.Structure(ctx, errCh)
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/worker"
|
||||
)
|
||||
|
||||
const listPackWorkers = 10
|
||||
|
||||
// Lister combines lists packs in a repo and blobs in a pack.
|
||||
type Lister interface {
|
||||
List(context.Context, restic.FileType, func(restic.ID, int64) error) error
|
||||
ListPack(context.Context, restic.ID, int64) ([]restic.Blob, int64, error)
|
||||
}
|
||||
|
||||
// Result is returned in the channel from LoadBlobsFromAllPacks.
|
||||
type Result struct {
|
||||
packID restic.ID
|
||||
size int64
|
||||
entries []restic.Blob
|
||||
}
|
||||
|
||||
// PackID returns the pack ID of this result.
|
||||
func (l Result) PackID() restic.ID {
|
||||
return l.packID
|
||||
}
|
||||
|
||||
// Size returns the size of the pack.
|
||||
func (l Result) Size() int64 {
|
||||
return l.size
|
||||
}
|
||||
|
||||
// Entries returns a list of all blobs saved in the pack.
|
||||
func (l Result) Entries() []restic.Blob {
|
||||
return l.entries
|
||||
}
|
||||
|
||||
// AllPacks sends the contents of all packs to ch.
|
||||
func AllPacks(ctx context.Context, repo Lister, ignorePacks restic.IDSet, ch chan<- worker.Job) {
|
||||
type fileInfo struct {
|
||||
id restic.ID
|
||||
size int64
|
||||
}
|
||||
|
||||
f := func(ctx context.Context, job worker.Job) (interface{}, error) {
|
||||
packInfo := job.Data.(fileInfo)
|
||||
entries, size, err := repo.ListPack(ctx, packInfo.id, packInfo.size)
|
||||
|
||||
return Result{
|
||||
packID: packInfo.id,
|
||||
size: size,
|
||||
entries: entries,
|
||||
}, err
|
||||
}
|
||||
|
||||
jobCh := make(chan worker.Job)
|
||||
wp := worker.New(ctx, listPackWorkers, f, jobCh, ch)
|
||||
|
||||
go func() {
|
||||
defer close(jobCh)
|
||||
|
||||
_ = repo.List(ctx, restic.DataFile, func(id restic.ID, size int64) error {
|
||||
if ignorePacks.Has(id) {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case jobCh <- worker.Job{Data: fileInfo{id: id, size: size}, Result: Result{packID: id}}:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
|
||||
wp.Wait()
|
||||
}
|
||||
@@ -112,9 +112,26 @@ func OpenKey(ctx context.Context, s *Repository, name string, password string) (
|
||||
// given password. If none could be found, ErrNoKeyFound is returned. When
|
||||
// maxKeys is reached, ErrMaxKeysReached is returned. When setting maxKeys to
|
||||
// zero, all keys in the repo are checked.
|
||||
func SearchKey(ctx context.Context, s *Repository, password string, maxKeys int) (k *Key, err error) {
|
||||
func SearchKey(ctx context.Context, s *Repository, password string, maxKeys int, keyHint string) (k *Key, err error) {
|
||||
checked := 0
|
||||
|
||||
if len(keyHint) > 0 {
|
||||
id, err := restic.Find(s.Backend(), restic.KeyFile, keyHint)
|
||||
|
||||
if err == nil {
|
||||
key, err := OpenKey(ctx, s, id, password)
|
||||
|
||||
if err == nil {
|
||||
debug.Log("successfully opened hinted key %v", id)
|
||||
return key, nil
|
||||
}
|
||||
|
||||
debug.Log("could not open hinted key %v", id)
|
||||
} else {
|
||||
debug.Log("Could not find hinted key %v", keyHint)
|
||||
}
|
||||
}
|
||||
|
||||
listCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
||||
@@ -92,6 +92,9 @@ func fillPacks(t testing.TB, rnd *randReader, be Saver, pm *packerManager, buf [
|
||||
}
|
||||
|
||||
n, err := packer.Add(restic.DataBlob, id, buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != l {
|
||||
t.Errorf("Add() returned invalid number of bytes: want %v, got %v", n, l)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,10 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/pack"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
)
|
||||
|
||||
// Repack takes a list of packs together with a list of blobs contained in
|
||||
@@ -24,7 +23,7 @@ func Repack(ctx context.Context, repo restic.Repository, packs restic.IDSet, kee
|
||||
// load the complete pack into a temp file
|
||||
h := restic.Handle{Type: restic.DataFile, Name: packID.String()}
|
||||
|
||||
tempfile, hash, packLength, err := DownloadAndHash(ctx, repo, h)
|
||||
tempfile, hash, packLength, err := DownloadAndHash(ctx, repo.Backend(), h)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Repack")
|
||||
}
|
||||
|
||||
@@ -9,16 +9,15 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/cache"
|
||||
"github.com/restic/restic/internal/crypto"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/hashing"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
"github.com/restic/restic/internal/crypto"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/pack"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
// Repository is used to access a repository in a backend.
|
||||
@@ -511,8 +510,8 @@ func LoadIndex(ctx context.Context, repo restic.Repository, id restic.ID) (*Inde
|
||||
|
||||
// SearchKey finds a key with the supplied password, afterwards the config is
|
||||
// read and parsed. It tries at most maxKeys key files in the repo.
|
||||
func (r *Repository) SearchKey(ctx context.Context, password string, maxKeys int) error {
|
||||
key, err := SearchKey(ctx, r, password, maxKeys)
|
||||
func (r *Repository) SearchKey(ctx context.Context, password string, maxKeys int, keyHint string) error {
|
||||
key, err := SearchKey(ctx, r, password, maxKeys, keyHint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -694,16 +693,21 @@ func (r *Repository) SaveTree(ctx context.Context, t *restic.Tree) (restic.ID, e
|
||||
return id, err
|
||||
}
|
||||
|
||||
// Loader allows loading data from a backend.
|
||||
type Loader interface {
|
||||
Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error
|
||||
}
|
||||
|
||||
// DownloadAndHash is all-in-one helper to download content of the file at h to a temporary filesystem location
|
||||
// and calculate ID of the contents. Returned (temporary) file is positioned at the beginning of the file;
|
||||
// it is reponsibility of the caller to close and delete the file.
|
||||
func DownloadAndHash(ctx context.Context, repo restic.Repository, h restic.Handle) (tmpfile *os.File, hash restic.ID, size int64, err error) {
|
||||
func DownloadAndHash(ctx context.Context, be Loader, h restic.Handle) (tmpfile *os.File, hash restic.ID, size int64, err error) {
|
||||
tmpfile, err = fs.TempFile("", "restic-temp-")
|
||||
if err != nil {
|
||||
return nil, restic.ID{}, -1, errors.Wrap(err, "TempFile")
|
||||
}
|
||||
|
||||
err = repo.Backend().Load(ctx, h, 0, 0, func(rd io.Reader) (ierr error) {
|
||||
err = be.Load(ctx, h, 0, 0, func(rd io.Reader) (ierr error) {
|
||||
_, ierr = tmpfile.Seek(0, io.SeekStart)
|
||||
if ierr == nil {
|
||||
ierr = tmpfile.Truncate(0)
|
||||
@@ -716,6 +720,11 @@ func DownloadAndHash(ctx context.Context, repo restic.Repository, h restic.Handl
|
||||
hash = restic.IDFromHash(hrd.Sum(nil))
|
||||
return ierr
|
||||
})
|
||||
if err != nil {
|
||||
tmpfile.Close()
|
||||
os.Remove(tmpfile.Name())
|
||||
return nil, restic.ID{}, -1, errors.Wrap(err, "Load")
|
||||
}
|
||||
|
||||
_, err = tmpfile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/archiver"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/fs"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
@@ -392,3 +394,107 @@ func TestRepositoryIncrementalIndex(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
rd io.Reader
|
||||
}
|
||||
|
||||
func (be backend) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
return fn(be.rd)
|
||||
}
|
||||
|
||||
type retryBackend struct {
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (be retryBackend) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error {
|
||||
err := fn(bytes.NewReader(be.buf[:len(be.buf)/2]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fn(bytes.NewReader(be.buf))
|
||||
}
|
||||
|
||||
func TestDownloadAndHash(t *testing.T) {
|
||||
buf := make([]byte, 5*1024*1024+881)
|
||||
_, err := io.ReadFull(rnd, buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
be repository.Loader
|
||||
want []byte
|
||||
}{
|
||||
{
|
||||
be: backend{rd: bytes.NewReader(buf)},
|
||||
want: buf,
|
||||
},
|
||||
{
|
||||
be: retryBackend{buf: buf},
|
||||
want: buf,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
f, id, size, err := repository.DownloadAndHash(context.TODO(), test.be, restic.Handle{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
want := restic.Hash(test.want)
|
||||
if !want.Equal(id) {
|
||||
t.Errorf("wrong hash returned, want %v, got %v", want.Str(), id.Str())
|
||||
}
|
||||
|
||||
if size != int64(len(test.want)) {
|
||||
t.Errorf("wrong size returned, want %v, got %v", test.want, size)
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = fs.RemoveIfExists(f.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type errorReader struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (er errorReader) Read(p []byte) (n int, err error) {
|
||||
return 0, er.err
|
||||
}
|
||||
|
||||
func TestDownloadAndHashErrors(t *testing.T) {
|
||||
var tests = []struct {
|
||||
be repository.Loader
|
||||
err string
|
||||
}{
|
||||
{
|
||||
be: backend{rd: errorReader{errors.New("test error 1")}},
|
||||
err: "test error 1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
_, _, _, err := repository.DownloadAndHash(context.TODO(), test.be, restic.Handle{})
|
||||
if err == nil {
|
||||
t.Fatalf("wanted error %q, got nil", test.err)
|
||||
}
|
||||
|
||||
if errors.Cause(err).Error() != test.err {
|
||||
t.Fatalf("wanted error %q, got %q", test.err, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func TestOpenLocal(t testing.TB, dir string) (r restic.Repository) {
|
||||
}
|
||||
|
||||
repo := New(be)
|
||||
err = repo.SearchKey(context.TODO(), test.TestPassword, 10)
|
||||
err = repo.SearchKey(context.TODO(), test.TestPassword, 10, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
)
|
||||
|
||||
// Duration is similar to time.Duration, except it only supports larger ranges
|
||||
// like days, months, and years.
|
||||
// like hours, days, months, and years.
|
||||
type Duration struct {
|
||||
Days, Months, Years int
|
||||
Hours, Days, Months, Years int
|
||||
}
|
||||
|
||||
func (d Duration) String() string {
|
||||
@@ -29,6 +29,10 @@ func (d Duration) String() string {
|
||||
s += fmt.Sprintf("%dd", d.Days)
|
||||
}
|
||||
|
||||
if d.Hours != 0 {
|
||||
s += fmt.Sprintf("%dh", d.Hours)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -73,7 +77,7 @@ func nextNumber(input string) (num int, rest string, err error) {
|
||||
}
|
||||
|
||||
// ParseDuration parses a duration from a string. The format is:
|
||||
// 6y5m234d
|
||||
// 6y5m234d37h
|
||||
func ParseDuration(s string) (Duration, error) {
|
||||
var (
|
||||
d Duration
|
||||
@@ -100,6 +104,8 @@ func ParseDuration(s string) (Duration, error) {
|
||||
d.Months = num
|
||||
case 'd':
|
||||
d.Days = num
|
||||
case 'h':
|
||||
d.Hours = num
|
||||
}
|
||||
|
||||
s = s[1:]
|
||||
@@ -127,5 +133,5 @@ func (d Duration) Type() string {
|
||||
|
||||
// Zero returns true if the duration is empty (all values are set to zero).
|
||||
func (d Duration) Zero() bool {
|
||||
return d.Years == 0 && d.Months == 0 && d.Days == 0
|
||||
return d.Years == 0 && d.Months == 0 && d.Days == 0 && d.Hours == 0
|
||||
}
|
||||
|
||||
@@ -13,15 +13,24 @@ func TestNextNumber(t *testing.T) {
|
||||
rest string
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
input: "12h", num: 12, rest: "h",
|
||||
},
|
||||
{
|
||||
input: "3d", num: 3, rest: "d",
|
||||
},
|
||||
{
|
||||
input: "4d9h", num: 4, rest: "d9h",
|
||||
},
|
||||
{
|
||||
input: "7m5d", num: 7, rest: "m5d",
|
||||
},
|
||||
{
|
||||
input: "-23y7m5d", num: -23, rest: "y7m5d",
|
||||
},
|
||||
{
|
||||
input: "-13y5m11d12h", num: -13, rest: "y5m11d12h",
|
||||
},
|
||||
{
|
||||
input: " 5d", num: 0, rest: " 5d", err: true,
|
||||
},
|
||||
@@ -55,10 +64,15 @@ func TestParseDuration(t *testing.T) {
|
||||
d Duration
|
||||
output string
|
||||
}{
|
||||
{"9h", Duration{Hours: 9}, "9h"},
|
||||
{"3d", Duration{Days: 3}, "3d"},
|
||||
{"4d2h", Duration{Days: 4, Hours: 2}, "4d2h"},
|
||||
{"7m5d", Duration{Months: 7, Days: 5}, "7m5d"},
|
||||
{"6m4d8h", Duration{Months: 6, Days: 4, Hours: 8}, "6m4d8h"},
|
||||
{"5d7m", Duration{Months: 7, Days: 5}, "7m5d"},
|
||||
{"4h3d9m", Duration{Months: 9, Days: 3, Hours: 4}, "9m3d4h"},
|
||||
{"-7m5d", Duration{Months: -7, Days: 5}, "-7m5d"},
|
||||
{"1y4m-5d-3h", Duration{Years: 1, Months: 4, Days: -5, Hours: -3}, "1y4m-5d-3h"},
|
||||
{"2y7m-5d", Duration{Years: 2, Months: 7, Days: -5}, "2y7m-5d"},
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ func (node Node) GetExtendedAttribute(a string) []byte {
|
||||
}
|
||||
|
||||
// CreateAt creates the node at the given path but does NOT restore node meta data.
|
||||
func (node *Node) CreateAt(ctx context.Context, path string, repo Repository, idx *HardlinkIndex) error {
|
||||
func (node *Node) CreateAt(ctx context.Context, path string, repo Repository) error {
|
||||
debug.Log("create node %v at %v", node.Name, path)
|
||||
|
||||
switch node.Type {
|
||||
@@ -144,7 +144,7 @@ func (node *Node) CreateAt(ctx context.Context, path string, repo Repository, id
|
||||
return err
|
||||
}
|
||||
case "file":
|
||||
if err := node.createFileAt(ctx, path, repo, idx); err != nil {
|
||||
if err := node.createFileAt(ctx, path, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
case "symlink":
|
||||
@@ -259,18 +259,7 @@ func (node Node) createDirAt(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node Node) createFileAt(ctx context.Context, path string, repo Repository, idx *HardlinkIndex) error {
|
||||
if node.Links > 1 && idx.Has(node.Inode, node.DeviceID) {
|
||||
if err := fs.Remove(path); !os.IsNotExist(err) {
|
||||
return errors.Wrap(err, "RemoveCreateHardlink")
|
||||
}
|
||||
err := fs.Link(idx.GetFilename(node.Inode, node.DeviceID), path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "CreateHardlink")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node Node) createFileAt(ctx context.Context, path string, repo Repository) error {
|
||||
f, err := fs.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "OpenFile")
|
||||
@@ -287,10 +276,6 @@ func (node Node) createFileAt(ctx context.Context, path string, repo Repository,
|
||||
return errors.Wrap(closeErr, "Close")
|
||||
}
|
||||
|
||||
if node.Links > 1 {
|
||||
idx.Add(node.Inode, node.DeviceID, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -177,11 +177,9 @@ func TestNodeRestoreAt(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
idx := restic.NewHardlinkIndex()
|
||||
|
||||
for _, test := range nodeTests {
|
||||
nodePath := filepath.Join(tempdir, test.Name)
|
||||
rtest.OK(t, test.CreateAt(context.TODO(), nodePath, nil, idx))
|
||||
rtest.OK(t, test.CreateAt(context.TODO(), nodePath, nil))
|
||||
rtest.OK(t, test.RestoreMetadata(nodePath))
|
||||
|
||||
if test.Type == "symlink" && runtime.GOOS == "windows" {
|
||||
|
||||
@@ -196,7 +196,7 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots, reason
|
||||
|
||||
// If the timestamp of the snapshot is within the range, then keep it.
|
||||
if !p.Within.Zero() {
|
||||
t := latest.AddDate(-p.Within.Years, -p.Within.Months, -p.Within.Days)
|
||||
t := latest.AddDate(-p.Within.Years, -p.Within.Months, -p.Within.Days).Add(time.Hour * time.Duration(-p.Within.Hours))
|
||||
if cur.Time.After(t) {
|
||||
keepSnap = true
|
||||
keepSnapReasons = append(keepSnapReasons, fmt.Sprintf("within %v", p.Within))
|
||||
|
||||
@@ -225,6 +225,9 @@ func TestApplyPolicy(t *testing.T) {
|
||||
{Within: parseDuration("1m")},
|
||||
{Within: parseDuration("1m14d")},
|
||||
{Within: parseDuration("1y1d1m")},
|
||||
{Within: parseDuration("13d23h")},
|
||||
{Within: parseDuration("2m2h")},
|
||||
{Within: parseDuration("1y2m3d3h")},
|
||||
}
|
||||
|
||||
for i, p := range tests {
|
||||
|
||||
150
internal/restic/testdata/policy_keep_snapshots_27
vendored
Normal file
150
internal/restic/testdata/policy_keep_snapshots_27
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
{
|
||||
"keep": [
|
||||
{
|
||||
"time": "2016-01-18T12:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-12T21:08:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-12T21:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-09T21:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-08T20:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-07T10:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-06T08:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-05T09:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-04T16:23:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
}
|
||||
],
|
||||
"reasons": [
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-18T12:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 13d23h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-12T21:08:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 13d23h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-12T21:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 13d23h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-09T21:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 13d23h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-08T20:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 13d23h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-07T10:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 13d23h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-06T08:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 13d23h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-05T09:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 13d23h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-04T16:23:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 13d23h"
|
||||
],
|
||||
"counters": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
374
internal/restic/testdata/policy_keep_snapshots_28
vendored
Normal file
374
internal/restic/testdata/policy_keep_snapshots_28
vendored
Normal file
@@ -0,0 +1,374 @@
|
||||
{
|
||||
"keep": [
|
||||
{
|
||||
"time": "2016-01-18T12:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-12T21:08:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-12T21:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-09T21:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-08T20:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-07T10:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-06T08:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-05T09:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-04T16:23:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-04T12:30:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-04T12:28:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-04T12:24:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-04T12:23:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-04T11:23:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-04T10:23:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-03T07:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-01T07:08:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-01T01:03:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2016-01-01T01:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2015-11-22T10:20:30Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2015-11-21T10:20:30Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2015-11-20T10:20:30Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
{
|
||||
"time": "2015-11-18T10:20:30Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
}
|
||||
],
|
||||
"reasons": [
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-18T12:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-12T21:08:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-12T21:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-09T21:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-08T20:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-07T10:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-06T08:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-05T09:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-04T16:23:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-04T12:30:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-04T12:28:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-04T12:24:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-04T12:23:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-04T11:23:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-04T10:23:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-03T07:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-01T07:08:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-01T01:03:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2016-01-01T01:02:03Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2015-11-22T10:20:30Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2015-11-21T10:20:30Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2015-11-20T10:20:30Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
},
|
||||
{
|
||||
"snapshot": {
|
||||
"time": "2015-11-18T10:20:30Z",
|
||||
"tree": null,
|
||||
"paths": null
|
||||
},
|
||||
"matches": [
|
||||
"within 2m2h"
|
||||
],
|
||||
"counters": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
1132
internal/restic/testdata/policy_keep_snapshots_29
vendored
Normal file
1132
internal/restic/testdata/policy_keep_snapshots_29
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,7 @@ type fakeFileSystem struct {
|
||||
duplication float32
|
||||
buf []byte
|
||||
chunker *chunker.Chunker
|
||||
rand *rand.Rand
|
||||
}
|
||||
|
||||
// saveFile reads from rd and saves the blobs in the repository. The list of
|
||||
@@ -87,7 +88,7 @@ func (fs *fakeFileSystem) treeIsKnown(tree *Tree) (bool, []byte, ID) {
|
||||
}
|
||||
|
||||
func (fs *fakeFileSystem) blobIsKnown(id ID, t BlobType) bool {
|
||||
if rand.Float32() < fs.duplication {
|
||||
if fs.rand.Float32() < fs.duplication {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -175,6 +176,7 @@ func TestCreateSnapshot(t testing.TB, repo Repository, at time.Time, depth int,
|
||||
repo: repo,
|
||||
knownBlobs: NewIDSet(),
|
||||
duplication: duplication,
|
||||
rand: rand.New(rand.NewSource(seed)),
|
||||
}
|
||||
|
||||
treeID := fs.saveTree(context.TODO(), seed, depth)
|
||||
|
||||
33
internal/restorer/doc.go
Normal file
33
internal/restorer/doc.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Package restorer contains code to restore data from a repository.
|
||||
//
|
||||
// The Restorer tries to keep the number of backend requests minimal. It does
|
||||
// this by downloading all required blobs of a pack file with a single backend
|
||||
// request and avoiding repeated downloads of the same pack. In addition,
|
||||
// several pack files are fetched concurrently.
|
||||
//
|
||||
// Here is high-level pseudo-code of the how the Restorer attempts to achieve
|
||||
// these goals:
|
||||
//
|
||||
// while there are packs to process
|
||||
// choose a pack to process [1]
|
||||
// get the pack from the backend or cache [2]
|
||||
// write pack blobs to the files that need them [3]
|
||||
// if not all pack blobs were used
|
||||
// cache the pack for future use [4]
|
||||
//
|
||||
// Pack download and processing (steps [2] - [4]) runs on multiple concurrent
|
||||
// Goroutines. The Restorer runs all steps [2]-[4] sequentially on the same
|
||||
// Goroutine.
|
||||
//
|
||||
// Before a pack is downloaded (step [2]), the required space is "reserved" in
|
||||
// the pack cache. Actual download uses single backend request to get all
|
||||
// required pack blobs. This may download blobs that are not needed, but we
|
||||
// assume it'll still be faster than getting individual blobs.
|
||||
//
|
||||
// Target files are written (step [3]) in the "right" order, first file blob
|
||||
// first, then second, then third and so on. Blob write order implies that some
|
||||
// pack blobs may not be immediately used, i.e. they are "out of order" for
|
||||
// their respective target files. Packs with unused blobs are cached (step
|
||||
// [4]). The cache has capacity limit and may purge packs before they are fully
|
||||
// used, in which case the purged packs will need to be re-downloaded.
|
||||
package restorer
|
||||
52
internal/restorer/filepacktraverser.go
Normal file
52
internal/restorer/filepacktraverser.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package restorer
|
||||
|
||||
import (
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
type filePackTraverser struct {
|
||||
lookup func(restic.ID, restic.BlobType) ([]restic.PackedBlob, bool)
|
||||
}
|
||||
|
||||
// iterates over all remaining packs of the file
|
||||
func (t *filePackTraverser) forEachFilePack(file *fileInfo, fn func(packIdx int, packID restic.ID, packBlobs []restic.Blob) bool) error {
|
||||
if len(file.blobs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
getBlobPack := func(blobID restic.ID) (restic.PackedBlob, error) {
|
||||
packs, found := t.lookup(blobID, restic.DataBlob)
|
||||
if !found {
|
||||
return restic.PackedBlob{}, errors.Errorf("Unknown blob %s", blobID.String())
|
||||
}
|
||||
// TODO which pack to use if multiple packs have the blob?
|
||||
// MUST return the same pack for the same blob during the same execution
|
||||
return packs[0], nil
|
||||
}
|
||||
|
||||
var prevPackID restic.ID
|
||||
var prevPackBlobs []restic.Blob
|
||||
packIdx := 0
|
||||
for _, blobID := range file.blobs {
|
||||
packedBlob, err := getBlobPack(blobID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !prevPackID.IsNull() && prevPackID != packedBlob.PackID {
|
||||
if !fn(packIdx, prevPackID, prevPackBlobs) {
|
||||
return nil
|
||||
}
|
||||
packIdx++
|
||||
}
|
||||
if prevPackID != packedBlob.PackID {
|
||||
prevPackID = packedBlob.PackID
|
||||
prevPackBlobs = make([]restic.Blob, 0)
|
||||
}
|
||||
prevPackBlobs = append(prevPackBlobs, packedBlob.Blob)
|
||||
}
|
||||
if len(prevPackBlobs) > 0 {
|
||||
fn(packIdx, prevPackID, prevPackBlobs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user